jerkzhang
今天在思考一件事情,为何不选择一种比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这类序列化方案。