jerkzhang,stay hungry, stay foolish
今天在思考一件事情,为何不选择一种比JSON更高效的序列化规则?
在使用 golang 写自定义日志记录;最初的方案是,把要记录的内容,序列化成JSON格式的二进制数据存入数据库中。
首先,明确使用struct进行构造数据而不是map,因为对struct数据进行序列化,性能会更高。(别人对这事做的测试结论:对方测试指定的某个任务,使用 encoding/json 库序列化 struct 对象耗时约 0.018 秒,而使用 json-iterator 库耗时约 0.017 秒;对于 map,使用 encoding/json 库耗时约 0.270 秒,使用 json-iterator 库耗时约 0.223 秒)
因此,首选struct对象这是已经确定的事情。
然后这里就考虑了使用jsoniter代替标准库的"encoding/json",这样速度可以大大提升。
后来,我在权衡,是否此处一定要使用JSON格式进行序列化,性能的瓶颈可能就是JSON格式本身,如果自定义一个特殊的连接符,例如"-s_-^$",以及我自身能确定我要记录的数据中不会出现这个特殊字符串,那我就可以选择使用这个连接符,以及我对key名没有要求,通过约定数据的顺序,再以后需要使用的时候,基于顺序进行还原数据。其实就是利用Join进行序列化,利用Split进行反序列化,以约定的特殊连接符字符串进行连接,这样的性能,我认为会是高效的,而且省略了key的内容,占用空间大小甚至可能下降。
为了验证想法,写下面一个golang程序,作为验证以支持我的想法:
package main import ( "encoding/json" "fmt" "github.com/json-iterator/go" "strings" "time" ) // 模拟数据 var testData = map[string]string{ "a": "sfdsadddddddddddddddddddddddddadfdasfadfa", "c": "lsdfdafdadfldsalal91202", } // 测量并打印执行时间的函数 func measureDuration(fn func()) time.Duration { start := time.Now() fn() return time.Since(start) } // 使用 jsoniter 序列化 func serializeWithJsoniter() ([]byte, error) { return jsoniter.Marshal(testData) } // 使用标准库序列化 func serializeWithStdlib() ([]byte, error) { return json.Marshal(testData) } // 手动构造字符串 func manualConcatenation() []byte { separator := "-s_-^$" return []byte(strings.Join([]string{testData["a"], separator, testData["c"]}, "")) } func main() { // 测量 jsoniter 序列化性能 durationJsoniter := measureDuration(func() { for i := 0; i < 1000; i++ { _, _ = serializeWithJsoniter() } }) fmt.Printf("jsoniter serialization took: %v\n", durationJsoniter) // 测量标准库序列化性能 durationStdlib := measureDuration(func() { for i := 0; i < 1000; i++ { _, _ = serializeWithStdlib() } }) fmt.Printf("stdlib serialization took: %v\n", durationStdlib) // 测量手动构造字符串性能 durationManual := measureDuration(func() { for i := 0; i < 1000; i++ { _ = manualConcatenation() } }) fmt.Printf("manual concatenation took: %v\n", durationManual) }
上述的测试结果是:
jsoniter serialization took: 265.875µs stdlib serialization took: 441.041µs manual concatenation took: 68.25µs
"manual concatenation took"就是自定义基于连接符的序列化方案,明显是优越与JSON格式的序列化。不过上面的方法没有使用struct对象,改成struct对象,如下:
package main import ( "encoding/json" "fmt" "github.com/json-iterator/go" "strings" "time" ) // 定义一个用于测试的 struct type TestStruct struct { A string `json:"a"` C string `json:"c"` } // 测试数据 var testStructInstance = TestStruct{ A: "sfdsadddddddddddddddddddddddddadfdasfadfa", C: "lsdfdafdadfldsalal91202", } // 测量并打印执行时间的函数 func measureDuration(fn func(), description string) { start := time.Now() fn() duration := time.Since(start) fmt.Printf("%s took: %v\n", description, duration) } // 使用 jsoniter 序列化 TestStruct func serializeWithJsoniter() ([]byte, error) { return jsoniter.Marshal(testStructInstance) } // 使用标准库序列化 TestStruct func serializeWithStdlib() ([]byte, error) { return json.Marshal(testStructInstance) } // 手动构造字符串 func manualConcatenation() []byte { separator := "-s_-^$" return []byte(strings.Join([]string{testStructInstance.A, separator, testStructInstance.C}, "")) } func main() { // 测量 jsoniter 序列化性能 measureDuration(func() { for i := 0; i < 1000; i++ { _, _ = serializeWithJsoniter() } }, "jsoniter serialization") // 测量标准库序列化性能 measureDuration(func() { for i := 0; i < 1000; i++ { _, _ = serializeWithStdlib() } }, "stdlib serialization") // 测量手动构造字符串性能 measureDuration(func() { for i := 0; i < 1000; i++ { _ = manualConcatenation() } }, "manual concatenation") }
测试结果如下:
jsoniter serialization took: 233.042µs stdlib serialization took: 258.791µs manual concatenation took: 59.083µs
依然可见基于特殊连接符字符串去进行序列化的思路是可行的,而且性能提高4-8倍 。(其实是因为上述只是简单示例,随着复杂度提高,性能提高数十倍甚至百倍都是可能的)
之后我有进一步修改了测试数据,让测试数据更复杂一些,测试代码如下:
package main import ( "encoding/json" "fmt" "github.com/json-iterator/go" "strings" "time" ) // 定义一个具有15个字段的复杂 struct type TestStruct struct { Field1 string `json:"field1"` Field2 string `json:"field2"` Field3 string `json:"field3"` Field4 string `json:"field4"` Field5 string `json:"field5"` Field6 string `json:"field6"` Field7 string `json:"field7"` Field8 string `json:"field8"` Field9 string `json:"field9"` Field10 string `json:"field10"` Field11 string `json:"field11"` Field12 string `json:"field12"` Field13 string `json:"field13"` Field14 string `json:"field14"` Field15 string `json:"field15"` } // 测试数据,包含15个字段的复杂结构体实例 var testStructInstance = TestStruct{ Field1: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", Field2: "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", Field3: "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.", Field4: "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum.", Field5: "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia.", Field6: "Deserunt mollit anim id est laborum.", Field7: "Maecenas sed diam eget risus varius blandit sit amet non magna.", Field8: "Curabitur blandit tempus porttitor.", Field9: "Vestibulum id ligula porta felis euismod semper.", Field10: "Integer posuere erat a ante venenatis dapibus posuere velit aliquet.", Field11: "Donec id elit non mi porta gravida at eget metus.", Field12: "Maecenas faucibus mollis interdum.", Field13: "Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh.", Field14: "Pellentesque sed dolor. Aliquam congue fermentum nisl, non tincidunt.", Field15: "Fusce pellentesque suscipit sagittis.", } // 测量并打印执行时间的函数 func measureDuration(fn func(), description string) { start := time.Now() fn() duration := time.Since(start) fmt.Printf("%s took: %v\n", description, duration) } // 使用 jsoniter 序列化 TestStruct func serializeWithJsoniter() ([]byte, error) { return jsoniter.Marshal(testStructInstance) } // 使用标准库序列化 TestStruct func serializeWithStdlib() ([]byte, error) { return json.Marshal(testStructInstance) } // 手动构造字符串 func manualConcatenation() []byte { // 手动连接所有字段,使用特定的分隔符 separator := "-s_-^$" fields := []string{ testStructInstance.Field1, // ... 到 Field15 // ... 省略中间字段以节省空间 testStructInstance.Field15, } return []byte(strings.Join(fields, separator)) } func main() { // 测量 jsoniter 序列化性能 measureDuration(func() { for i := 0; i < 1000; i++ { _, _ = serializeWithJsoniter() } }, "jsoniter serialization") // 测量标准库序列化性能 measureDuration(func() { for i := 0; i < 1000; i++ { _, _ = serializeWithStdlib() } }, "stdlib serialization") // 测量手动构造字符串性能 measureDuration(func() { for i := 0; i < 1000; i++ { _ = manualConcatenation() } }, "manual concatenation") }
测试结果如下:
jsoniter serialization took: 1.396708ms stdlib serialization took: 1.359458ms manual concatenation took: 62.875µs
综上可见,json序列化的性能会随着复杂度的提升而大幅受限;但是基于连接符的序列化方案基本上性能变化不大,其性能承载力很高。所以,在一些场景中,基于连接符的序列化方案是原优于json这类序列化方案。