From 8d50f5104debef584d494c52b8c8d5e7a61875a8 Mon Sep 17 00:00:00 2001 From: Long Hoang Date: Sun, 26 Feb 2017 17:32:35 +0800 Subject: [PATCH 1/2] Introduce build tags to disable unsafe package usage Package `unsafe` is used in jlexer to prevent copying when casting from []byte to string, speeding up the marshalling process to a large extent. However, `unsafe` is not allowed in environments such as Google Appengine. Therefore two build tags are introduced, which either of them can disable the use of unsafe package, falling back to conventional copy-based []byte to string conversion. The build tags are `easyjson_nounsafe` and `appengine` (which is set when building for Google Appengine) --- benchmark/data_codec.go | 5 ++++- jlexer/bytestostr.go | 20 ++++++++++++++++++++ jlexer/bytestostr_nounsafe.go | 10 ++++++++++ jlexer/lexer.go | 13 ------------- 4 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 jlexer/bytestostr.go create mode 100644 jlexer/bytestostr_nounsafe.go diff --git a/benchmark/data_codec.go b/benchmark/data_codec.go index 0bb8c4c..d2d83fa 100644 --- a/benchmark/data_codec.go +++ b/benchmark/data_codec.go @@ -1,4 +1,6 @@ //+build use_codec +//+build !easyjson_nounsafe +//+build !appengine // ************************************************************ // DO NOT EDIT. @@ -10,10 +12,11 @@ package benchmark import ( "errors" "fmt" - codec1978 "github.com/ugorji/go/codec" "reflect" "runtime" "unsafe" + + codec1978 "github.com/ugorji/go/codec" ) const ( diff --git a/jlexer/bytestostr.go b/jlexer/bytestostr.go new file mode 100644 index 0000000..516b193 --- /dev/null +++ b/jlexer/bytestostr.go @@ -0,0 +1,20 @@ +//+build !easyjson_nounsafe +//+build !appengine + +package jlexer + +import ( + "reflect" + "unsafe" +) + +// bytesToStr creates a string pointing at the slice to avoid copying. +// +// Warning: the string returned by the function should be used with care, as the whole input data +// chunk may be either blocked from being freed by GC because of a single string or the buffer.Data +// may be garbage-collected even when the string exists. +func bytesToStr(data []byte) string { + h := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + shdr := reflect.StringHeader{h.Data, h.Len} + return *(*string)(unsafe.Pointer(&shdr)) +} diff --git a/jlexer/bytestostr_nounsafe.go b/jlexer/bytestostr_nounsafe.go new file mode 100644 index 0000000..f6d6ef3 --- /dev/null +++ b/jlexer/bytestostr_nounsafe.go @@ -0,0 +1,10 @@ +//+build easyjson_nounsafe appengine + +package jlexer + +// bytesToStr creates a string normally from []byte +// +// Note that this method is roughly 1.5x slower than using the 'unsafe' method. +func bytesToStr(data []byte) string { + return string(data) +} diff --git a/jlexer/lexer.go b/jlexer/lexer.go index c8a5d07..ae8344e 100644 --- a/jlexer/lexer.go +++ b/jlexer/lexer.go @@ -9,12 +9,10 @@ import ( "errors" "fmt" "io" - "reflect" "strconv" "unicode" "unicode/utf16" "unicode/utf8" - "unsafe" ) // tokenKind determines type of a token. @@ -205,17 +203,6 @@ func (r *Lexer) fetchFalse() { } } -// bytesToStr creates a string pointing at the slice to avoid copying. -// -// Warning: the string returned by the function should be used with care, as the whole input data -// chunk may be either blocked from being freed by GC because of a single string or the buffer.Data -// may be garbage-collected even when the string exists. -func bytesToStr(data []byte) string { - h := (*reflect.SliceHeader)(unsafe.Pointer(&data)) - shdr := reflect.StringHeader{h.Data, h.Len} - return *(*string)(unsafe.Pointer(&shdr)) -} - // fetchNumber scans a number literal token. func (r *Lexer) fetchNumber() { hasE := false From 5d608ccfff4bf40c244150ff479c865779073608 Mon Sep 17 00:00:00 2001 From: Long Hoang Date: Sun, 26 Feb 2017 21:18:53 +0800 Subject: [PATCH 2/2] Add documentation related to previously added build tags to disable unsafe --- README.md | 23 +++++++++++++++++------ jlexer/bytestostr.go | 4 ++++ jlexer/bytestostr_nounsafe.go | 3 +++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 84dd237..384f72c 100644 --- a/README.md +++ b/README.md @@ -141,11 +141,18 @@ for more information. when doing case-insensitive key matching. In the future, case-insensitive object key matching may be provided via an option to the generator. -* easyjson makes use of `unsafe`. While a "safe" version of easyjson could - be written, `unsafe` simplifies the code and provides significant performance - benefits by allowing no-copy conversion from `[]byte` to `string`. That said, - `unsafe` is used only when unmarshaling and parsing JSON, and any `unsafe` - operations / memory allocations done will be safely deallocated by easyjson. +* easyjson makes use of `unsafe`, which simplifies the code and + provides significant performance benefits by allowing no-copy + conversion from `[]byte` to `string`. That said, `unsafe` is used + only when unmarshaling and parsing JSON, and any `unsafe` operations + / memory allocations done will be safely deallocated by + easyjson. Set the build tag `easyjson_nounsafe` to compile it + without `unsafe`. + +* easyjson is compatible with Google App Engine. The `appengine` build + tag (set by App Engine's environment) will automatically disable the + use of `unsafe`, which is not allowed in App Engine's Standard + Environment. Note that the use with App Engine is still experimental. * Floats are formatted using the default precision from Go's `strconv` package. As such, easyjson will not correctly handle high precision floats when @@ -165,7 +172,8 @@ for more information. ## Benchmarks -Most benchmarks were done using the example [13kB example JSON](https://dev.twitter.com/rest/reference/get/search/tweets) +Most benchmarks were done using the example +[13kB example JSON](https://dev.twitter.com/rest/reference/get/search/tweets) (9k after eliminating whitespace). This example is similar to real-world data, is well-structured, and contains a healthy variety of different types, making it ideal for JSON serialization benchmarks. @@ -178,6 +186,9 @@ Note: * For large request marshaling benchmarks, a struct containing 50 regular samples was used, making a ~500kB output JSON. +* Benchmarks are showing the results of easyjson's default behaviour, + which makes use of `unsafe`. + Benchmarks are available in the repository and can be run by invoking `make`. ### easyjson vs. encoding/json diff --git a/jlexer/bytestostr.go b/jlexer/bytestostr.go index 516b193..2ed8042 100644 --- a/jlexer/bytestostr.go +++ b/jlexer/bytestostr.go @@ -1,3 +1,7 @@ +// This file will only be included to the build if neither +// easyjson_nounsafe nor appengine build tag is set. See README notes +// for more details. + //+build !easyjson_nounsafe //+build !appengine diff --git a/jlexer/bytestostr_nounsafe.go b/jlexer/bytestostr_nounsafe.go index f6d6ef3..864d1be 100644 --- a/jlexer/bytestostr_nounsafe.go +++ b/jlexer/bytestostr_nounsafe.go @@ -1,3 +1,6 @@ +// This file is included to the build if any of the buildtags below +// are defined. Refer to README notes for more details. + //+build easyjson_nounsafe appengine package jlexer