golang的JIT的一个精彩应用是bytedance开源的sonic库,从测试数据来看,应该是golang圈子里最快的JSON解析库 。
怎么做到的呢?
例如有这样一个json:
{"a":123, "b":"abc"}要把它解析到结构体:
type Data struct{ A int64 B string}一般来说,这个过程需要很多的判断:源字段名是什么?源字段什么类型?目的字段名的反射对象在哪里?目的对象的内存指针在哪里?如果想要让解析过程变快,最好是直接去掉这些判断:遇到"a",在目的内存的偏移位置0,写入8字节整形值……
但是上面的做法又没有通用性 。如何直接的解析一个类型,又满足通用性?JIT就是个好办法 。
针对类型Data
,通过JIT产生一段最直接最高效的解析代码,并且以后都通过这段代码来解析 。进而推演到每个类型都有专门的解析代码 。如此:针对特定结构,有特定的最优解析代码 。这样的做法绝对是最优的,无法被别的方法超越 。
就像ClickHouse一样,相信未来会有越来越多的系统应用会添置JIT的能力 。
CGO关于cgo的性能,我认为主要是golang runtime中的物理线程(GMP模型中的M),与运行CGO的物理线程之间的通讯造成了远高于直接函数调用的损耗 。
内部显示 如果是单纯的 emtpy call,使用 cgo 耗时 55.9 ns/op,纯 go 耗时 0.29 ns/op,相差了 192 倍 。golang为了保障runtime的协程调度不被阻塞,就需要所有被调度的协程函数都是不阻塞的 。一旦加入CGO,就无法保障函数不阻塞了,因此只有额外开辟物理线程来执行CGO的函数 。
而实际上我们在使用 cgo 的时候不太可能进行空调用,一般来说会把性能影响较大,计算耗时较长的计算放在 cgo 中,如果是这种情况,每次条用额外 55.9 ns 的额外耗时应该是可以接受的访问 。
——CGO 和 CGO 性能之谜
这里特别需要注意的一个坑是:调用CGO的次数越多,时间越长,golang runtime开启的物理线程就越多 。我曾在VictoriaNetrics中的vm-storage中发现,因为大量调用ZSTD压缩库,导致物理线程数是允许核数的10倍 。并且,在目前的golang版本中,这些物理线程没有明确的销毁机制 。远多余可用核数的物理线程,会导致大量CPU时间消耗在无意义的线程切换上 。建议运营中加上runtime的metric上报,一旦发现物理线程过多,定期重启来减少这种损耗 。其他的不高级主题panic不要用panic来反馈异常,不要用recover()来接收异常 。
除了程序初始化的错误,不要在业务的任何地方使用panic 。
对于错误,存在可预见的error,和不可预见的panic 。绝大多数情况都要通过error来针对性的识别并管理错误 。recover()仅仅用于维护框架稳定的非预期的错误捕获 。
目前还未测试过使用recover()是否会导致性能受损 。就我阅读VictoriaMetrics的源码看来,他们一个recover()都没用——也就是说,他们自信的认为组件只会产生可预见的error 。如果我们处处都想着加上recover()来捕获panic,是否意味着设计和测试上存在问题?for循环避免拷贝VictoriaMetrics中,几乎所有的for循环都是一种风格:
var slice []int64for i := range slice{ item := &slice[i]}我想这就是为了避免for循环中的第二个变量产生拷贝 。就如同写C/C++的人,for循环中的循环变量要求写成
++i
而不是 i++
。规范好写法,避免在细节之处有不必要的损耗 。内存对齐golang中声明的每个变量默认都是字节对齐的,这点很好 。
经验总结扩展阅读
- 持续集成指南:GitLab 的 CI/CD 工具配置与使用
- 信用卡怎么查消费明细
- 消费贷款申请产生的费用高吗
- oppo账号的姓名怎么修改
- 租房可以换锁芯吗合法么 租房换锁费用谁来承担
- 特斯拉可以用家用电充电吗
- 拆线多久可以用祛疤膏?
- 卫生间和厨房用什么瓷砖?
- 肉苁蓉副作用是什么
- 计米器怎么设置参数