Go:Stringer 命令,通过代码生成提高效率
ℹ️ 这个文章基于 Go 1.13.
stringer命令旨在自动创建满足[fmt.Stringer](https://golang.org/pkg/fmt/#Stringer)
String()
,并将它们描述为字符串。
#案例
命令文档 提供了一个示例,供我研究该命令使用。就是这种情况:
这里是输出:
1
用常量值生成日志可能会有些混乱。
让我们用命令stringer -type=Pill
:生成 String()
方法:
一个新的函数String()
已经生成。这是运行先前代码时的新输出:
Aspirin
现在,该类型将自身描述为字符串,而不是其内部值。
stringer
还可与go generate
命令完美配合,使其功能更强大。只需在代码中添加以下指令即可:
然后,运行“ go generate”命令将自动为所有类型生成新函数。
#效率
stringer
生成一个字符串,其中包含值列表作为字符串,以及一个数组,其中包含每个字符串的索引。在我们的示例中,读取Aspirin
将包括从索引7到13读取字符串
但是它有多快和高效?让我们与其他两个解决方案进行比较:
-使用硬编码值生成String()
函数:
这是一个包含二十个值的列表的基准:
name time/op
Stringer-4 4.16ns ± 2%
StringerWithSwitch-4 3.81ns ± 1%
这是具有一百个值的相同基准:
name time/op
Stringer-4 4.96ns ± 0%
StringerWithSwitch-4 4.99ns ± 1%
您拥有的常数越多,效率越高。这实际上是有道理的。与通过一些跳转指令(表示“ if”条件的汇编指令)相比,从内存中加载值的开销更大。
但是,开关越大,跳转指令的数量就越大。从某个角度来看,从内存中加载将变得更加有效。
String()
函数生成了一个 map:
下面是一个包含20个常量的基准:
name time/op
Stringer-4 4.16ns ± 2%
StringerWithMap-4 28.60ns ± 2%
使用map要慢得多,因为它必须进行函数调用,而且bucket中的查找并不像访问切片的索引那么简单。
有关地图及其内部结构的更多信息,我建议您阅读我的文章 “Go: Map Design by Code.”
#自检程序
在生成的指令中,有些纯粹是出于验证目的而创建的。以下是这些说明:
stringer
将常量的名称与该值一起写在每一行上。在此示例中,“阿司匹林”的值为“ 2”。更新常量名称或其值将产生错误:
- 更新名称而不重新生成
String()
函数:
./pill_string.go: 12: 8: undefined: Aspirin
- 更新值而不重新生成
String()
函数:
./pill_string.go:12 :7 : invalid array index Aspirin - 1 (out of bounds for 1-element array)./pill_string.go: 13 :7 : invalid array index Ibuprofen - 2 (index must be non-negative
但是,如果我们添加新常量 (此处,下一个键是数字”3”),并且不更新生成的文件,”stringer”具有默认值:
Pill(3)
添加此自检不会产生任何影响,因为它在编译时已被删除。可以通过查看生成的[asm]来确认(https://golang.org/doc/asm)程序代码:
➜ go tool compile -S main.go pill_string.go | grep “"".Pill.[^\s]* STEXT”“”.Pill.String STEXT size=275 args=0x18 locals=0x50
二进制文件中只有String()
函数导出。检查对性能或二进制大小没有开销。
常数越多,效率就越高。这实际上是有道理的。从内存加载值比通过某些跳转指令(表示”if”条件的程序集指令)更广泛。
但是,交换机越大,跳转指令的数量就越多。从某个点开始,从内存加载将变得更加有效。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。