feat: improve std.base64Decode performance 97%+ (#346)

feat: improve std.base64Decode performance 97%+

Provides a Go-native implementation of std.base64Decode and std.base64DecodeBytes

benchmark                                  old ns/op       new ns/op     delta
Benchmark_Builtin_base64Decode-16          10946388307     25004135      -99.77%
Benchmark_Builtin_base64DecodeBytes-16     6420742757      181513016     -97.17%

related to #111
This commit is contained in:
Wes McNamee 2020-01-27 05:31:46 -08:00 committed by Stanisław Barzowski
parent e8bd3f4ff8
commit 399a61ca31
20 changed files with 142 additions and 0 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1032,6 +1032,56 @@ func builtinStrReplace(i *interpreter, trace traceElement, strv, fromv, tov valu
return makeValueString(strings.Replace(sStr, sFrom, sTo, -1)), nil
}
func base64DecodeGoBytes(i *interpreter, trace traceElement, str string) ([]byte, error) {
strLen := len(str)
if strLen%4 != 0 {
msg := fmt.Sprintf("input string appears not to be a base64 encoded string. Wrong length found (%d)", strLen)
return nil, makeRuntimeError(msg, i.getCurrentStackTrace(trace))
}
decodedBytes, err := base64.StdEncoding.DecodeString(str)
if err != nil {
return nil, i.Error(fmt.Sprintf("failed to decode: %s", err), trace)
}
return decodedBytes, nil
}
func builtinBase64DecodeBytes(i *interpreter, trace traceElement, input value) (value, error) {
vStr, err := i.getString(input, trace)
if err != nil {
msg := fmt.Sprintf("base64DecodeBytes requires a string, got %s", input.getType().name)
return nil, makeRuntimeError(msg, i.getCurrentStackTrace(trace))
}
decodedBytes, err := base64DecodeGoBytes(i, trace, vStr.getGoString())
if err != nil {
return nil, err
}
res := make([]*cachedThunk, len(decodedBytes))
for i := range decodedBytes {
res[i] = readyThunk(makeValueNumber(float64(int(decodedBytes[i]))))
}
return makeValueArray(res), nil
}
func builtinBase64Decode(i *interpreter, trace traceElement, input value) (value, error) {
vStr, err := i.getString(input, trace)
if err != nil {
msg := fmt.Sprintf("base64DecodeBytes requires a string, got %s", input.getType().name)
return nil, makeRuntimeError(msg, i.getCurrentStackTrace(trace))
}
decodedBytes, err := base64DecodeGoBytes(i, trace, vStr.getGoString())
if err != nil {
return nil, err
}
return makeValueString(string(decodedBytes)), nil
}
func builtinUglyObjectFlatMerge(i *interpreter, trace traceElement, x value) (value, error) {
// TODO(sbarzowski) consider keeping comprehensions in AST
// It will probably be way less hacky, with better error messages and better performance
@ -1381,6 +1431,8 @@ var funcBuiltins = buildBuiltinMap([]builtin{
&ternaryBuiltin{name: "substr", function: builtinSubstr, parameters: ast.Identifiers{"str", "from", "len"}},
&ternaryBuiltin{name: "splitLimit", function: builtinSplitLimit, parameters: ast.Identifiers{"str", "c", "maxsplits"}},
&ternaryBuiltin{name: "strReplace", function: builtinStrReplace, parameters: ast.Identifiers{"str", "from", "to"}},
&unaryBuiltin{name: "base64Decode", function: builtinBase64Decode, parameters: ast.Identifiers{"str"}},
&unaryBuiltin{name: "base64DecodeBytes", function: builtinBase64DecodeBytes, parameters: ast.Identifiers{"str"}},
&unaryBuiltin{name: "parseJson", function: builtinParseJSON, parameters: ast.Identifiers{"str"}},
&unaryBuiltin{name: "base64", function: builtinBase64, parameters: ast.Identifiers{"input"}},
&unaryBuiltin{name: "encodeUTF8", function: builtinEncodeUTF8, parameters: ast.Identifiers{"str"}},

View File

@ -39,6 +39,14 @@ func Benchmark_Builtin_reverse(b *testing.B) {
RunBenchmark(b, "reverse")
}
func Benchmark_Builtin_base64Decode(b *testing.B) {
RunBenchmark(b, "base64Decode")
}
func Benchmark_Builtin_base64DecodeBytes(b *testing.B) {
RunBenchmark(b, "base64DecodeBytes")
}
func Benchmark_Builtin_base64(b *testing.B) {
RunBenchmark(b, "base64")
}

1
testdata/builtinBase64Decode.golden vendored Normal file
View File

@ -0,0 +1 @@
"a"

1
testdata/builtinBase64Decode.jsonnet vendored Normal file
View File

@ -0,0 +1 @@
std.base64Decode("YQ==")

View File

@ -0,0 +1,3 @@
[
97
]

View File

@ -0,0 +1 @@
std.base64DecodeBytes("YQ==")

View File

@ -0,0 +1,10 @@
RUNTIME ERROR: failed to decode: illegal base64 data at input byte 0
-------------------------------------------------
testdata/builtinBase64DecodeBytes_high_codepoint:1:1-30 builtin function <base64DecodeBytes>
std.base64DecodeBytes("ĀQ=")
-------------------------------------------------
During evaluation

View File

@ -0,0 +1 @@
std.base64DecodeBytes("ĀQ=")

View File

@ -0,0 +1,10 @@
RUNTIME ERROR: input string appears not to be a base64 encoded string. Wrong length found (5)
-------------------------------------------------
testdata/builtinBase64DecodeBytes_invalid_base64_data:1:1-31 builtin function <base64DecodeBytes>
std.base64DecodeBytes("wrong")
-------------------------------------------------
During evaluation

View File

@ -0,0 +1 @@
std.base64DecodeBytes("wrong")

View File

@ -0,0 +1,10 @@
RUNTIME ERROR: base64DecodeBytes requires a string, got number
-------------------------------------------------
testdata/builtinBase64DecodeBytes_wrong_type:1:1-25 builtin function <base64DecodeBytes>
std.base64DecodeBytes(1)
-------------------------------------------------
During evaluation

View File

@ -0,0 +1 @@
std.base64DecodeBytes(1)

View File

@ -0,0 +1,10 @@
RUNTIME ERROR: failed to decode: illegal base64 data at input byte 0
-------------------------------------------------
testdata/builtinBase64Decode_high_codepoint:1:1-25 builtin function <base64Decode>
std.base64Decode("ĀQ=")
-------------------------------------------------
During evaluation

View File

@ -0,0 +1 @@
std.base64Decode("ĀQ=")

View File

@ -0,0 +1,10 @@
RUNTIME ERROR: input string appears not to be a base64 encoded string. Wrong length found (5)
-------------------------------------------------
testdata/builtinBase64Decode_invalid_base64_data:1:1-26 builtin function <base64Decode>
std.base64Decode("wrong")
-------------------------------------------------
During evaluation

View File

@ -0,0 +1 @@
std.base64Decode("wrong")

View File

@ -0,0 +1,10 @@
RUNTIME ERROR: base64DecodeBytes requires a string, got number
-------------------------------------------------
testdata/builtinBase64Decode_wrong_type:1:1-20 builtin function <base64Decode>
std.base64Decode(1)
-------------------------------------------------
During evaluation

View File

@ -0,0 +1 @@
std.base64Decode(1)