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 #111
1 parent 9518d4c commit 27b4c60

25 files changed

+226
-0
lines changed

.gitignore

+10
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

+9
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

+27
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

+18
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

+5
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

+54
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

+31
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+
}
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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr(1, 0, 1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"hello"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 0 , 10)
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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 1.2, 5)
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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", "foo", 5)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 10, 5)
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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 0, -1)
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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 0, 1.2)
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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 0, "foo")

testdata/builtinSubstr.golden

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"hello"

testdata/builtinSubstr.jsonnet

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.substr("hello", 0, 5)

0 commit comments

Comments
 (0)