由于
string串联,您的解决方案使用了太多分配。
我们将创建一些替代的,更快的和/或更优雅的解决方案。请注意,以下解决方案不检查节点值是否包含引号
"字符。如果愿意,则必须以某种方式对其进行转义(否则结果将是无效的查询字符串)。
完整的可运行代码可以在Go
Playground上找到。完整的测试/基准测试代码也可以在Go
Playground上找到,但是它不可运行,将它们保存到Go工作区(例如
$GOPATH/src/query/query.go和
$GOPATH/src/query/query_test.go)中,然后使用运行
gotest -bench .。
另外,请务必检查以下相关问题:如何在Go中有效地串联字符串?
备择方案
创世记
您的逻辑可以通过以下功能捕获:
func buildOriginal(nodes []string) string { var query string for _, n := range nodes { query += fmt.Sprintf(""node_name":"%s",", n) } query = strings.TrimRight(query, ",") return fmt.Sprintf("where={%s}", query)}使用 bytes.Buffer
更好的方法是使用单个缓冲区,例如
bytes.Buffer,在其中构建查询,然后将其转换为
string:
func buildBuffer(nodes []string) string { buf := &bytes.Buffer{} buf.WriteString("where={") for i, v := range nodes { if i > 0 { buf.WriteByte(',') } buf.WriteString(`"node_name":"`) buf.WriteString(v) buf.WriteByte('"') } buf.WriteByte('}') return buf.String()}使用它:
nodes := []string{"node1", "node2"}fmt.Println(buildBuffer(nodes))输出:
where={"node_name":"node1","node_name":"node2"}bytes.Buffer
改善的
bytes.Buffer尽管比原始解决方案要少得多,但仍会进行一些重新分配。
但是,如果在创建
bytes.Bufferusing 时传递了足够大的字节片,则仍可以将分配减少为1
bytes.NewBuffer()。我们可以事先计算所需的大小:
func buildBuffer2(nodes []string) string { size := 8 + len(nodes)*15 for _, v := range nodes { size += len(v) } buf := bytes.NewBuffer(make([]byte, 0, size)) buf.WriteString("where={") for i, v := range nodes { if i > 0 { buf.WriteByte(',') } buf.WriteString(`"node_name":"`) buf.WriteString(v) buf.WriteByte('"') } buf.WriteByte('}') return buf.String()}请注意,在
size计算
8是字符串的大小
where={}和15是字符串的大小
"node_name":"",。
使用 text/template
我们还可以创建一个文本模板,并使用该
text/template程序包执行它,从而有效地生成结果:
var t = template.Must(template.New("").Parse(templ))func buildTemplate(nodes []string) string { size := 8 + len(nodes)*15 for _, v := range nodes { size += len(v) } buf := bytes.NewBuffer(make([]byte, 0, size)) if err := t.Execute(buf, nodes); err != nil { log.Fatal(err) // Handle error } return buf.String()}const templ = `where={{{- range $idx, $n := . -}} {{if ne $idx 0}},{{end}}"node_name":"{{$n}}"{{- end -}}}`使用 strings.Join()
由于其简单性,该解决方案很有趣。我们可以使用适当的前缀和后缀之间
strings.Join()的静态文本来连接节点
","node_name":"。
需要注意的重要事项:
strings.Join()将内置
copy()函数与单个预分配
[]byte缓冲区一起使用,因此非常快!
“作为一种特殊情况,它(该
copy()函数)还将把字节从字符串复制到字节的一部分。”
func buildJoin(nodes []string) string { if len(nodes) == 0 { return "where={}" } return `where={"node_name":"` + strings.Join(nodes, `","node_name":"`) + `"}`}基准结果
我们将使用以下
nodes值进行基准测试:
var nodes = []string{"n1", "node2", "nodethree", "fourthNode", "n1", "node2", "nodethree", "fourthNode", "n1", "node2", "nodethree", "fourthNode", "n1", "node2", "nodethree", "fourthNode", "n1", "node2", "nodethree", "fourthNode",}基准测试代码如下所示:
func BenchmarkOriginal(b *testing.B) { for i := 0; i < b.N; i++ { buildOriginal(nodes) }}func BenchmarkBuffer(b *testing.B) { for i := 0; i < b.N; i++ { buildBuffer(nodes) }}// ... All the other benchmarking functions look the same现在的结果是:
BenchmarkOriginal-4 200000 10572 ns/opBenchmarkBuffer-4 500000 2914 ns/opBenchmarkBuffer2-4 1000000 2024 ns/opBenchmarkBufferTemplate-4 30000 77634 ns/opBenchmarkJoin-4 2000000 830 ns/op
有些令人吃惊的事实:
buildBuffer()是 3.6倍
的速度比
buildOriginal(),和
buildBuffer2()(同预先计算的大小)约 30%
的速度比
buildBuffer(),因为它并不需要重新分配(并复制)的内部缓冲区。
一些令人惊讶的事实:
buildJoin()非常快,甚至击败
buildBuffer2()由 2.4倍
(因为只使用到
[]byte和
copy())。
buildTemplate()另一方面证明速度很慢:比慢
7倍
buildOriginal()。这样做的主要原因是因为它在引擎盖下使用(必须使用)反射。



