• go 的结构体极速序列化


    序列化有很多种方式,最简单的就是gob,但是最慢,如果我们使用bigCache之类的库,则需要自己做序列化以及反序列化

    高并发环境下还是很重要的,各种对比参考:

    我这里要使用就是最直接的序列化,按照小端直接写到字节流,网上很多的例子都是这样的:

    1. type Counter struct {
    2. Count uint64
    3. LastCount uint64
    4. }
    5. func Counter2Buf(c Counter) []byte {
    6. buf := bytes.NewBuffer([]byte{})
    7. buf.Grow(16)
    8. binary.Write(buf, binary.LittleEndian, c.Count)
    9. binary.Write(buf, binary.LittleEndian, c.LastCount)
    10. return buf.Bytes()
    11. }
    12. func Buf2Counter(b []byte) (*Counter, error) {
    13. if len(b) < 16 {
    14. return nil, fmt.Errorf("len is too short")
    15. }
    16. c := Counter{0, 0}
    17. bytesBuffer := bytes.NewBuffer(b)
    18. binary.Read(bytesBuffer, binary.LittleEndian, &c.Count)
    19. binary.Read(bytesBuffer, binary.LittleEndian, &c.LastCount)
    20. return &c, nil
    21. }

    而实际上性能依然很差,比protobuf还要差,比如我需要做100万次操作,结果如下:

    编码:106 ms

    解码:86 ms

    对应的1单次操作平均:116ms和86ms,protobuf压缩也不过如此,

    其实这里还可以更快,直接操作[]byte,

    1. // 这里按照系统库的方式抄一下,直接添加一个偏移就可以了
    2. func putUint64Little(b []byte, v uint64, off int) {
    3. b[0+off] = byte(v)
    4. b[1+off] = byte(v >> 8)
    5. b[2+off] = byte(v >> 16)
    6. b[3+off] = byte(v >> 24)
    7. b[4+off] = byte(v >> 32)
    8. b[5+off] = byte(v >> 40)
    9. b[6+off] = byte(v >> 48)
    10. b[7+off] = byte(v >> 56)
    11. }
    12. func Couter2Bytes(c Counter) []byte {
    13. buff := make([]byte, 16)
    14. binary.LittleEndian.PutUint64(buff, c.Count)
    15. putUint64Little(buff, c.LastCount, 8)
    16. return buff
    17. }
    18. ///
    19. // 这里按照系统库的方式抄一下,直接添加一个偏移就可以了
    20. func Uint64Litter(b []byte, off int) uint64 {
    21. _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
    22. return uint64(b[0+off]) | uint64(b[1+off])<<8 | uint64(b[2+off])<<16 | uint64(b[3+off])<<24 |
    23. uint64(b[4+off])<<32 | uint64(b[5+off])<<40 | uint64(b[6+off])<<48 | uint64(b[7+off])<<56
    24. }
    25. // 字节转换成整形
    26. func BytesToCounter(b []byte) (*Counter, error) {
    27. if len(b) < 16 {
    28. return nil, fmt.Errorf("len is too short")
    29. }
    30. c := Counter{binary.LittleEndian.Uint64(b),
    31. Uint64Litter(b, 8)}
    32. return &c, nil
    33. }

    这时候,我们执行测试100万次编码测试,

    编码:19 ms

    解码: 18ms

    平均单次耗时:编码19ns,解码18ns

    整体性能提高了5倍;

    后记:有人做过相关对比测试:go语言序列化json/gob/msgp/protobuf性能对比 - 知乎

    对比帖子中的性能测试,我们直接做一个写测试:

    1. type Doc struct {
    2. Doc_id uint32 // 4B
    3. School_level uint32
    4. Active uint32
    5. Work_age uint32
    6. Vip bool // 1B
    7. Chat bool
    8. Position string // 4B + bytes
    9. Company string
    10. City string
    11. }
    12. func Doc2Bytes(doc *Doc) []byte {
    13. length := 16 + 2 + 12 + len(doc.Position) + len(doc.Company) + len(doc.City)
    14. buf := make([]byte, length)
    15. putUint32Little(buf, 0, doc.Doc_id)
    16. putUint32Little(buf, 4, doc.School_level)
    17. putUint32Little(buf, 8, doc.Active)
    18. putUint32Little(buf, 12, doc.Work_age)
    19. putBool(buf, 16, doc.Vip)
    20. putBool(buf, 17, doc.Chat)
    21. off := putString(buf, 18, doc.Position)
    22. off = putString(buf, off, doc.Company)
    23. putString(buf, off, doc.City)
    24. return buf
    25. }
    26. func DocFromByte(b []byte) *Doc {
    27. doc := Doc{
    28. Doc_id: 0,
    29. School_level: 0,
    30. Active: 0,
    31. Work_age: 0,
    32. Vip: false,
    33. Chat: false,
    34. Position: "",
    35. Company: "",
    36. City: "",
    37. }
    38. doc.Doc_id = Uint32Little(b, 0)
    39. doc.School_level = Uint32Little(b, 4)
    40. doc.Active = Uint32Little(b, 8)
    41. doc.Work_age = Uint32Little(b, 12)
    42. doc.Vip = BoolLitter(b, 16)
    43. doc.Chat = BoolLitter(b, 17)
    44. var off int = 0
    45. doc.Position, off = StringLittle(b, 18)
    46. doc.Company, off = StringLittle(b, off)
    47. doc.City, off = StringLittle(b, off)
    48. return &doc
    49. }
    50. func putUint32Little(b []byte, off int, v uint32) {
    51. // 这样比指针更快些,
    52. b[0+off] = byte(v)
    53. b[1+off] = byte(v >> 8)
    54. b[2+off] = byte(v >> 16)
    55. b[3+off] = byte(v >> 24)
    56. //pInt := unsafe.Pointer(&v)
    57. //bInt := (*[4]byte)(pInt)
    58. //copy(b[off:], (*bInt)[0:])
    59. }
    60. func putBool(b []byte, off int, v bool) {
    61. if v {
    62. b[0+off] = byte(1)
    63. } else {
    64. b[0+off] = byte(0)
    65. }
    66. }
    67. func str2bytes(s string) []byte {
    68. x := (*[2]uintptr)(unsafe.Pointer(&s))
    69. h := [3]uintptr{x[0], x[1], x[1]}
    70. return *(*[]byte)(unsafe.Pointer(&h))
    71. }
    72. func bytes2str(b []byte) string {
    73. return *(*string)(unsafe.Pointer(&b))
    74. }
    75. func putString(b []byte, off int, v string) int {
    76. length := uint32(len(v))
    77. putUint32Little(b, off, length)
    78. off = off + 4
    79. // TODO: 优化这里
    80. //buf := []byte(v)
    81. //for i, v := range buf {
    82. // b[off+4+i] = v
    83. //}
    84. //return off + 4 + len(v)
    85. // test it
    86. x := (*[2]uintptr)(unsafe.Pointer(&v))
    87. h := [3]uintptr{x[0], x[1], x[1]}
    88. bStr := *(*[]byte)(unsafe.Pointer(&h))
    89. //bStr := str2bytes(v)
    90. copy(b[off:], bStr[0:])
    91. return off + len(v)
    92. }
    93. func Uint32Little(b []byte, off int) uint32 {
    94. return uint32(b[0+off]) | uint32(b[1+off])<<8 | uint32(b[2+off])<<16 | uint32(b[3+off])<<24
    95. }
    96. func BoolLitter(b []byte, off int) bool {
    97. a := int8(b[0+off])
    98. if a != 0 {
    99. return true
    100. }
    101. return false
    102. }
    103. func StringLittle(b []byte, off int) (string, int) {
    104. length := Uint32Little(b, off)
    105. bPart := b[off+4 : off+4+int(length)]
    106. str := bytes2str(bPart)
    107. return str, off + 4 + int(length)
    108. }
    109. func testSeri() {
    110. doc := Doc{1, 2, 3, 4,
    111. false, true,
    112. "abcdefg",
    113. "XXXX",
    114. "zz",
    115. }
    116. b := Doc2Bytes(&doc)
    117. fmt.Println(b)
    118. doc1 := DocFromByte(b)
    119. fmt.Println(doc1)
    120. }

    测试结果是平均42ns,比protobuf还要快一点点;

  • 相关阅读:
    独孤思维:副业再苦再累,请学会善待自己
    基于卷积神经网络的图像识别技术研究与实践
    QT绘图项目 - 汽车表盘
    【Unity设计模式】✨使用 MVC 和 MVP 编程模式
    如何从字符串中提取相关内容
    JDBC事务隔离简介说明
    介绍一个博客图片上传辅助工具
    Servlet
    毕设(5)—笛卡尔空间轨迹规划(直线、圆弧)
    容器化 | 一文搞定镜像构建方式选型
  • 原文地址:https://blog.csdn.net/robinfoxnan/article/details/127733100