Skip to content

Commit 27b4c60

Browse files
ghostsquadsbarzowski
authored andcommitted
feat: Improve performance of substr by 84.44%
This pulls in the implementation of substr into native Go instead of interpretted Jsonnet. benchmark old ns/op new ns/op delta Benchmark_Builtin_substr-16 97121527 15115905 -84.44% part of google#111
1 parent 9518d4c commit 27b4c60

25 files changed

+226
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ gojsonnet.egg-info/
1414
/bazel-out
1515
/bazel-testlogs
1616
/dumpstdlibast
17+
18+
# built binaries
1719
/jsonnet
20+
/jsonnet.exe
21+
22+
# "old" built binaries (for doing benchmark comparisons)
23+
/jsonnet-old
24+
/jsonnet-old.exe
25+
1826
/linter/jsonnet-lint/jsonnet-lint
1927
/tests_path.source
28+
29+
/builtin-benchmark-results

Makefile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,19 @@ build:
4242
go build ./cmd/jsonnet
4343
.PHONY: build
4444

45+
build.old:
46+
go build ./cmd/jsonnet -o jsonnet-old
47+
.PHONY: build.old
48+
4549
test:
4650
./tests.sh
4751
.PHONY: test
4852

53+
benchmark : FILTER="Builtin"
54+
benchmark: build
55+
./benchmark.sh ${FILTER}
56+
.PHONY: benchmark
57+
4958
generate:
5059
go generate
5160
.PHONY: generate

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,33 @@ Additionally if any files were moved around, see the section [Keeping the Bazel
5555
./tests.sh # Also runs `go test ./...`
5656
```
5757

58+
## Running Benchmarks
59+
60+
Setup
61+
62+
```bash
63+
go get golang.org/x/tools/cmd/benchcmp
64+
```
65+
66+
1. Make sure you build a jsonnet binary _prior_ to making changes.
67+
68+
```bash
69+
go build ./cmd/jsonnet -o jsonnet-old
70+
```
71+
72+
2. Make changes (iterate as needed), and rebuild new binary
73+
74+
```bash
75+
go build ./cmd/jsonnet
76+
```
77+
78+
3. Run benchmark:
79+
80+
```bash
81+
# e.g. ./benchmark.sh Builtin
82+
./benchmark.sh <TestNameFilter>
83+
```
84+
5885
## Implementation Notes
5986

6087
We are generating some helper classes on types by using http://clipperhouse.github.io/gen/. Do the following to regenerate these if necessary:

benchmark.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
FILTER=${1:-Builtin}
6+
7+
root_dir="$( cd "$( dirname "$0" )" && pwd )"
8+
9+
(
10+
cd "${root_dir}"
11+
mkdir -p ./builtin-benchmark-results
12+
rm -f ./builtin-benchmark-results/*
13+
echo "Running Before Test... (10s)"
14+
go test -bench="${FILTER}" -run=^$ -v -benchtime=10s -jsonnetPath=./jsonnet-old > ./builtin-benchmark-results/before.txt
15+
echo "Running After Test... (10s)"
16+
go test -bench="${FILTER}" -run=^$ -v -benchtime=10s -jsonnetPath=./jsonnet > ./builtin-benchmark-results/after.txt
17+
benchcmp ./builtin-benchmark-results/before.txt ./builtin-benchmark-results/after.txt
18+
)

builtin-benchmarks/substr.jsonnet

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
foo: [
3+
std.substr("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed turpis tincidunt id aliquet risus. Eget mauris pharetra et ultrices neque ornare aenean euismod. Diam quis enim lobortis scelerisque fermentum. Varius duis at consectetur lorem donec massa sapien. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Lectus urna duis convallis convallis tellus. Nibh ipsum consequat nisl vel pretium lectus quam id leo. Feugiat in ante metus dictum at tempor commodo. Velit dignissim sodales ut eu sem integer. Dictum sit amet justo donec. Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed turpis tincidunt id aliquet risus. Eget mauris pharetra et ultrices neque ornare aenean euismod. Diam quis enim lobortis scelerisque fermentum. Varius duis at consectetur lorem donec massa sapien. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Lectus urna duis convallis convallis tellus. Nibh ipsum consequat nisl vel pretium lectus quam id leo. Feugiat in ante metus dictum at tempor commodo. Velit dignissim sodales ut eu sem integer. Dictum sit amet justo donec. Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed turpis tincidunt id aliquet risus. Eget mauris pharetra et ultrices neque ornare aenean euismod. Diam quis enim lobortis scelerisque fermentum. Varius duis at consectetur lorem donec massa sapien. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Lectus urna duis convallis convallis tellus. Nibh ipsum consequat nisl vel pretium lectus quam id leo. Feugiat in ante metus dictum at tempor commodo. Velit dignissim sodales ut eu sem integer. Dictum sit amet justo donec. Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed turpis tincidunt id aliquet risus. Eget mauris pharetra et ultrices neque ornare aenean euismod. Diam quis enim lobortis scelerisque fermentum. Varius duis at consectetur lorem donec massa sapien. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Lectus urna duis convallis convallis tellus. Nibh ipsum consequat nisl vel pretium lectus quam id leo. Feugiat in ante metus dictum at tempor commodo. Velit dignissim sodales ut eu sem integer. Dictum sit amet justo donec. Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed turpis tincidunt id aliquet risus. Eget mauris pharetra et ultrices neque ornare aenean euismod. Diam quis enim lobortis scelerisque fermentum. Varius duis at consectetur lorem donec massa sapien. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Lectus urna duis convallis convallis tellus. Nibh ipsum consequat nisl vel pretium lectus quam id leo. Feugiat in ante metus dictum at tempor commodo. Velit dignissim sodales ut eu sem integer. Dictum sit amet justo donec. Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. ", i, 800-i) for i in std.range(0,100)
4+
],
5+
}

builtins.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,59 @@ func builtinPow(i *interpreter, trace traceElement, basev value, expv value) (va
839839
return makeDoubleCheck(i, trace, math.Pow(base.value, exp.value))
840840
}
841841

842+
func builtinSubstr(i *interpreter, trace traceElement, inputStr, inputFrom, inputLen value) (value, error) {
843+
strV, err := i.getString(inputStr, trace)
844+
if err != nil {
845+
msg := fmt.Sprintf("substr first parameter should be a string, got %s", inputStr.getType().name)
846+
return nil, makeRuntimeError(msg, i.getCurrentStackTrace(trace))
847+
}
848+
849+
fromV, err := i.getNumber(inputFrom, trace)
850+
if err != nil {
851+
msg := fmt.Sprintf("substr second parameter should be a number, got %s", inputFrom.getType().name)
852+
return nil, makeRuntimeError(msg, i.getCurrentStackTrace(trace))
853+
}
854+
855+
if math.Mod(fromV.value, 1) != 0 {
856+
msg := fmt.Sprintf("substr second parameter should be an integer, got %f", fromV.value)
857+
return nil, makeRuntimeError(msg, i.getCurrentStackTrace(trace))
858+
}
859+
860+
lenV, err := i.getNumber(inputLen, trace)
861+
if err != nil {
862+
msg := fmt.Sprintf("substr third parameter should be a number, got %s", inputLen.getType().name)
863+
return nil, makeRuntimeError(msg, i.getCurrentStackTrace(trace))
864+
}
865+
866+
lenInt, err := i.getInt(lenV, trace)
867+
868+
if err != nil {
869+
msg := fmt.Sprintf("substr third parameter should be an integer, got %f", lenV.value)
870+
return nil, makeRuntimeError(msg, i.getCurrentStackTrace(trace))
871+
}
872+
873+
if lenInt < 0 {
874+
msg := fmt.Sprintf("substr third parameter should be greater than zero, got %d", lenInt)
875+
return nil, makeRuntimeError(msg, i.getCurrentStackTrace(trace))
876+
}
877+
878+
fromInt := int(fromV.value)
879+
strStr := strV.getGoString()
880+
881+
endIndex := fromInt + lenInt
882+
883+
if endIndex > len(strStr) {
884+
endIndex = len(strStr)
885+
}
886+
887+
if fromInt > len(strStr) {
888+
return makeValueString(""), nil
889+
}
890+
891+
runes := []rune(strStr)
892+
return makeValueString(string(runes[fromInt:endIndex])), nil
893+
}
894+
842895
func builtinSplitLimit(i *interpreter, trace traceElement, strv, cv, maxSplitsV value) (value, error) {
843896
str, err := i.getString(strv, trace)
844897
if err != nil {
@@ -1243,6 +1296,7 @@ var funcBuiltins = buildBuiltinMap([]builtin{
12431296
&binaryBuiltin{name: "pow", function: builtinPow, parameters: ast.Identifiers{"base", "exp"}},
12441297
&binaryBuiltin{name: "modulo", function: builtinModulo, parameters: ast.Identifiers{"x", "y"}},
12451298
&unaryBuiltin{name: "md5", function: builtinMd5, parameters: ast.Identifiers{"x"}},
1299+
&ternaryBuiltin{name: "substr", function: builtinSubstr, parameters: ast.Identifiers{"str", "from", "len"}},
12461300
&ternaryBuiltin{name: "splitLimit", function: builtinSplitLimit, parameters: ast.Identifiers{"str", "c", "maxsplits"}},
12471301
&ternaryBuiltin{name: "strReplace", function: builtinStrReplace, parameters: ast.Identifiers{"str", "from", "to"}},
12481302
&unaryBuiltin{name: "parseJson", function: builtinParseJSON, parameters: ast.Identifiers{"str"}},

builtins_benchmark_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package jsonnet
2+
3+
import (
4+
"flag"
5+
"os"
6+
"os/exec"
7+
"testing"
8+
)
9+
10+
var jsonnetPath string
11+
var outputPassthru bool
12+
13+
func init() {
14+
flag.StringVar(&jsonnetPath, "jsonnetPath", "./jsonnet", "Path to jsonnet binary")
15+
flag.BoolVar(&outputPassthru, "outputPassthru", false, "Pass stdout/err from jsonnet")
16+
}
17+
18+
func Benchmark_Builtin_substr(b *testing.B) {
19+
for n := 0; n < b.N; n++ {
20+
cmd := exec.Command(jsonnetPath, "./builtin-benchmarks/substr.jsonnet")
21+
if outputPassthru {
22+
cmd.Stdout = os.Stdout
23+
cmd.Stderr = os.Stderr
24+
}
25+
26+
err := cmd.Run()
27+
if err != nil {
28+
b.Fail()
29+
}
30+
}
31+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
RUNTIME ERROR: substr first parameter should be a string, got number
2+
-------------------------------------------------
3+
testdata/builtinSubStr_first_param_not_string:1:1-20 builtin function <substr>
4+
5+
std.substr(1, 0, 1)
6+
7+
-------------------------------------------------
8+
During evaluation
9+
10+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr(1, 0, 1)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"hello"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 0 , 10)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
RUNTIME ERROR: substr second parameter should be an integer, got 1.200000
2+
-------------------------------------------------
3+
testdata/builtinSubStr_second_parameter_not_integer:1:1-28 builtin function <substr>
4+
5+
std.substr("hello", 1.2, 5)
6+
7+
-------------------------------------------------
8+
During evaluation
9+
10+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 1.2, 5)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
RUNTIME ERROR: substr second parameter should be a number, got string
2+
-------------------------------------------------
3+
testdata/builtinSubStr_second_parameter_not_number:1:1-30 builtin function <substr>
4+
5+
std.substr("hello", "foo", 5)
6+
7+
-------------------------------------------------
8+
During evaluation
9+
10+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", "foo", 5)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 10, 5)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
RUNTIME ERROR: substr third parameter should be greater than zero, got -1
2+
-------------------------------------------------
3+
testdata/builtinSubStr_third_parameter_less_then_zero:1:1-27 builtin function <substr>
4+
5+
std.substr("hello", 0, -1)
6+
7+
-------------------------------------------------
8+
During evaluation
9+
10+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 0, -1)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
RUNTIME ERROR: substr third parameter should be an integer, got 1.200000
2+
-------------------------------------------------
3+
testdata/builtinSubStr_third_parameter_not_integer:1:1-28 builtin function <substr>
4+
5+
std.substr("hello", 0, 1.2)
6+
7+
-------------------------------------------------
8+
During evaluation
9+
10+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 0, 1.2)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
RUNTIME ERROR: substr third parameter should be a number, got string
2+
-------------------------------------------------
3+
testdata/builtinSubStr_third_parameter_not_number:1:1-30 builtin function <substr>
4+
5+
std.substr("hello", 0, "foo")
6+
7+
-------------------------------------------------
8+
During evaluation
9+
10+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 0, "foo")

testdata/builtinSubstr.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"hello"

testdata/builtinSubstr.jsonnet

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 0, 5)

0 commit comments

Comments
 (0)