Use strings.Replacer to escape string field

benchmark                                        old ns/op     new ns/op     delta
BenchmarkEscapeStringField_Plain-4               167           65.3          -60.90%
BenchmarkEscapeString_Quotes-4                   167           165           -1.20%
BenchmarkEscapeString_Backslashes-4              211           184           -12.80%
BenchmarkEscapeString_QuotesAndBackslashes-4     413           397           -3.87%

BenchmarkExportTSMStrings_100s_250vps-4     33833611      27381442           -19.07%
BenchmarkExportWALStrings_100s_250vps-4     34977761      29222717           -16.45%

benchmark                                        old allocs     new allocs     delta
BenchmarkEscapeStringField_Plain-4               4              1              -75.00%
BenchmarkEscapeString_Quotes-4                   4              3              -25.00%
BenchmarkEscapeString_Backslashes-4              5              3              -40.00%
BenchmarkEscapeString_QuotesAndBackslashes-4     9              5              -44.44%

BenchmarkExportTSMStrings_100s_250vps-4     201605          76938              -61.84%
BenchmarkExportWALStrings_100s_250vps-4     225371         100728              -55.31%

benchmark                                        old bytes     new bytes     delta
BenchmarkEscapeStringField_Plain-4               56            16            -71.43%
BenchmarkEscapeString_Quotes-4                   56            48            -14.29%
BenchmarkEscapeString_Backslashes-4              104           80            -23.08%
BenchmarkEscapeString_QuotesAndBackslashes-4     208           160           -23.08%

BenchmarkExportTSMStrings_100s_250vps-4     10872629       6062048           -44.24%
BenchmarkExportWALStrings_100s_250vps-4     10094933       5269980           -47.80%
pull/7741/head
Mark Rushakoff 2016-12-17 23:37:25 -08:00
parent 9effe6f364
commit 15ba7958f8
2 changed files with 68 additions and 25 deletions

View File

@ -1095,34 +1095,17 @@ func unescapeTag(in []byte) []byte {
return in return in
} }
// escapeStringFieldReplacer replaces double quotes and backslashes
// with the same character preceded by a backslash.
// As of Go 1.7 this benchmarked better in allocations and CPU time
// compared to iterating through a string byte-by-byte and appending to a new byte slice,
// calling strings.Replace twice, and better than (*Regex).ReplaceAllString.
var escapeStringFieldReplacer = strings.NewReplacer(`"`, `\"`, `\`, `\\`)
// EscapeStringField returns a copy of in with any double quotes or // EscapeStringField returns a copy of in with any double quotes or
// backslashes with escaped values // backslashes with escaped values
func EscapeStringField(in string) string { func EscapeStringField(in string) string {
var out []byte return escapeStringFieldReplacer.Replace(in)
i := 0
for {
if i >= len(in) {
break
}
// escape double-quotes
if in[i] == '\\' {
out = append(out, '\\')
out = append(out, '\\')
i++
continue
}
// escape double-quotes
if in[i] == '"' {
out = append(out, '\\')
out = append(out, '"')
i++
continue
}
out = append(out, in[i])
i++
}
return string(out)
} }
// unescapeStringField returns a copy of in with any escaped double-quotes // unescapeStringField returns a copy of in with any escaped double-quotes

View File

@ -26,6 +26,8 @@ var (
} }
maxFloat64 = strconv.FormatFloat(math.MaxFloat64, 'f', 1, 64) maxFloat64 = strconv.FormatFloat(math.MaxFloat64, 'f', 1, 64)
minFloat64 = strconv.FormatFloat(-math.MaxFloat64, 'f', 1, 64) minFloat64 = strconv.FormatFloat(-math.MaxFloat64, 'f', 1, 64)
sink interface{}
) )
func TestMarshal(t *testing.T) { func TestMarshal(t *testing.T) {
@ -2168,3 +2170,61 @@ func TestPoint_FieldIterator_Delete_Twice(t *testing.T) {
t.Fatalf("Delete failed, got %#v, exp %#v", got, exp) t.Fatalf("Delete failed, got %#v, exp %#v", got, exp)
} }
} }
func TestEscapeStringField(t *testing.T) {
cases := []struct {
in string
expOut string
}{
{in: "abcdefg", expOut: "abcdefg"},
{in: `one double quote " .`, expOut: `one double quote \" .`},
{in: `quote " then backslash \ .`, expOut: `quote \" then backslash \\ .`},
{in: `backslash \ then quote " .`, expOut: `backslash \\ then quote \" .`},
}
for _, c := range cases {
// Unescapes as expected.
got := models.EscapeStringField(c.in)
if got != c.expOut {
t.Errorf("unexpected result from EscapeStringField(%s)\ngot [%s]\nexp [%s]\n", c.in, got, c.expOut)
continue
}
pointLine := fmt.Sprintf(`t s="%s"`, got)
test(t, pointLine, NewTestPoint(
"t",
models.NewTags(nil),
models.Fields{"s": c.in},
time.Unix(0, 0),
))
}
}
func BenchmarkEscapeStringField_Plain(b *testing.B) {
s := "nothing special"
for i := 0; i < b.N; i++ {
sink = models.EscapeStringField(s)
}
}
func BenchmarkEscapeString_Quotes(b *testing.B) {
s := `Hello, "world"`
for i := 0; i < b.N; i++ {
sink = models.EscapeStringField(s)
}
}
func BenchmarkEscapeString_Backslashes(b *testing.B) {
s := `C:\windows\system32`
for i := 0; i < b.N; i++ {
sink = models.EscapeStringField(s)
}
}
func BenchmarkEscapeString_QuotesAndBackslashes(b *testing.B) {
s1 := `a quote " then backslash \ .`
s2 := `a backslash \ then quote " .`
for i := 0; i < b.N; i++ {
sink = [...]string{models.EscapeStringField(s1), models.EscapeStringField(s2)}
}
}