Is "len" evaluated each iteration in for i :=0; i < len(x); i ++ { ... } ?

xuanbao · · 404 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Given the following for-loop:</p> <p>for i :=0; i &lt; len(x); i ++ { ... }</p> <p>Is len(x) evaluated every iteration, or just once? Does it depend on whether &#34;x&#34; is modified in the loop?</p> <hr/>**评论:**<br/><br/>Sythe2o0: <pre><p>If you&#39;re thinking about pulling out the length as a variable for efficiency&#39;s sake, benchmark it first:</p> <pre><code>var ( iters = 10000 k int l = make([]int, iters) ) func BenchmarkLoopLen(b *testing.B) { for i := 0; i &lt; b.N; i++ { for j := 0; j &lt; len(l); j++ { k = (k + 2) % 9 } } } func BenchmarkLoopStored(b *testing.B) { for i := 0; i &lt; b.N; i++ { for j := 0; j &lt; iters; j++ { k = (k + 2) % 9 } } } BenchmarkLoopLen-8 30000 50569 ns/op BenchmarkLoopStored-8 30000 51602 ns/op </code></pre> <p>(these results fluctuate by around 1000 ns/op)</p></pre>0xjnml: <pre><p>Yes. No.</p></pre>dinkumator: <pre><p>Technically yes it is evaluated often. You can probably look at the assembly output to confirm.</p> <p>Realistically, no it&#39;s probably not worth worrying about. <code>len(x)</code> is stored at the head of the slice so it is already a pretty fast reference. More details here: <a href="https://blog.golang.org/go-slices-usage-and-internals" rel="nofollow">https://blog.golang.org/go-slices-usage-and-internals</a></p></pre>dinkumator: <pre><p>OK I was curious enough to dogfood my own suggestion. Pretty neat stuff the SSA compiler backend is doing behind the scenes here:</p> <p>len.go:</p> <pre><code>package main func main() { x := make([]int, 42) for i := 0; i &lt; len(x); i++ { x[i] = i } } </code></pre> <p><code>go tool compile -S -S len.go</code></p> <pre><code>&#34;&#34;.main STEXT size=124 args=0x0 locals=0x158 0x0000 00000 (len.go:3) TEXT &#34;&#34;.main(SB), $344-0 0x0000 00000 (len.go:3) MOVQ (TLS), CX 0x0009 00009 (len.go:3) LEAQ -216(SP), AX 0x0011 00017 (len.go:3) CMPQ AX, 16(CX) 0x0015 00021 (len.go:3) JLS 117 0x0017 00023 (len.go:3) SUBQ $344, SP 0x001e 00030 (len.go:3) MOVQ BP, 336(SP) &lt;-- this is exactly the space needed, it&#39;s an array not a slice! 0x0026 00038 (len.go:3) LEAQ 336(SP), BP 0x002e 00046 (len.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x002e 00046 (len.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x002e 00046 (len.go:4) LEAQ &#34;&#34;..autotmp_4(SP), DI 0x0032 00050 (len.go:4) XORPS X0, X0 0x0035 00053 (len.go:4) ADDQ $-48, DI 0x0039 00057 (len.go:4) DUFFZERO $201 0x004c 00076 (len.go:4) MOVL $0, AX 0x004e 00078 (len.go:5) JMP 95 0x0050 00080 (len.go:5) MOVQ AX, CX 0x0053 00083 (len.go:6) SHLQ $3, AX 0x0057 00087 (len.go:6) MOVQ CX, &#34;&#34;..autotmp_4(SP)(AX*1) 0x005b 00091 (len.go:5) LEAQ 1(CX), AX 0x005f 00095 (len.go:5) CMPQ AX, $42 &lt;-- SSA knows it wasn&#39;t modified, len hardcoded! 0x0063 00099 (len.go:5) JLT 80 0x0065 00101 (len.go:8) MOVQ 336(SP), BP 0x006d 00109 (len.go:8) ADDQ $344, SP ... </code></pre> <p>len2.go:</p> <pre><code> package main func main() { x := make([]int, 42) for i := 0; i &lt; len(x); i++ { x = append(x, i) } } </code></pre> <p><code>go tool compile -S -S len2.go</code></p> <pre><code>&#34;&#34;.main STEXT size=225 args=0x0 locals=0x1a8 0x0000 00000 (len2.go:3) TEXT &#34;&#34;.main(SB), $424-0 0x0000 00000 (len2.go:3) MOVQ (TLS), CX 0x0009 00009 (len2.go:3) LEAQ -296(SP), AX 0x0011 00017 (len2.go:3) CMPQ AX, 16(CX) 0x0015 00021 (len2.go:3) JLS 215 0x001b 00027 (len2.go:3) SUBQ $424, SP 0x0022 00034 (len2.go:3) MOVQ BP, 416(SP) &lt;-- extra space here for slice overhead 0x002a 00042 (len2.go:3) LEAQ 416(SP), BP 0x0032 00050 (len2.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0032 00050 (len2.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0032 00050 (len2.go:4) LEAQ &#34;&#34;..autotmp_4+80(SP), DI 0x0037 00055 (len2.go:4) XORPS X0, X0 0x003a 00058 (len2.go:4) ADDQ $-48, DI 0x003e 00062 (len2.go:4) DUFFZERO $201 0x0051 00081 (len2.go:4) MOVL $0, AX 0x0053 00083 (len2.go:4) MOVL $42, CX 0x0058 00088 (len2.go:4) MOVQ CX, DX 0x005b 00091 (len2.go:4) LEAQ &#34;&#34;..autotmp_4+80(SP), BX 0x0060 00096 (len2.go:5) JMP 108 0x0062 00098 (len2.go:6) MOVQ AX, (BX)(DX*8) 0x0066 00102 (len2.go:5) INCQ AX 0x0069 00105 (len2.go:5) MOVQ SI, DX 0x006c 00108 (len2.go:5) CMPQ AX, DX 0x006f 00111 (len2.go:5) JGE 199 0x0071 00113 (len2.go:6) LEAQ 1(DX), SI 0x0075 00117 (len2.go:6) CMPQ SI, CX 0x0078 00120 (len2.go:6) JLE 98 0x007a 00122 (len2.go:6) MOVQ AX, &#34;&#34;.i+72(SP) 0x007f 00127 (len2.go:6) MOVQ DX, &#34;&#34;.x.len+64(SP) &lt;-- reading/comparing to slice.len every time 0x0084 00132 (len2.go:6) LEAQ type.int(SB), AX 0x008b 00139 (len2.go:6) MOVQ AX, (SP) 0x008f 00143 (len2.go:6) MOVQ BX, 8(SP) 0x0094 00148 (len2.go:6) MOVQ DX, 16(SP) 0x0099 00153 (len2.go:6) MOVQ CX, 24(SP) 0x009e 00158 (len2.go:6) MOVQ SI, 32(SP) 0x00a3 00163 (len2.go:6) PCDATA $0, $0 0x00a3 00163 (len2.go:6) CALL runtime.growslice(SB) 0x00a8 00168 (len2.go:6) MOVQ 40(SP), BX 0x00ad 00173 (len2.go:6) MOVQ 48(SP), AX 0x00b2 00178 (len2.go:6) MOVQ 56(SP), CX 0x00b7 00183 (len2.go:6) LEAQ 1(AX), SI 0x00bb 00187 (len2.go:6) MOVQ &#34;&#34;.i+72(SP), AX 0x00c0 00192 (len2.go:6) MOVQ &#34;&#34;.x.len+64(SP), DX 0x00c5 00197 (len2.go:6) JMP 98 0x00c7 00199 (len2.go:8) MOVQ 416(SP), BP 0x00cf 00207 (len2.go:8) ADDQ $424, SP 0x00d6 00214 (len2.go:8) RET </code></pre></pre>0xjnml: <pre><p>To put it in a different perspective:</p> <p>Assuming <code>x</code> is a local vaiable, <code>len(x)</code> is just reading of an address at fixed offset in the stack frame. Accessing the cached value in another local variable is again just reading of an address at fixed offset in the stack frame, so no savings are to be expected.</p> <p>(Ignoring that the compiler can cache some values in registers, then the superfluos variable can actually hurt performance.)</p></pre>random314: <pre><p>Yes but it&#39;s not literally counting the objects. Its just accessing a variable that gets reset only when you alter the iterable.</p></pre>itsmontoya: <pre><p>Generally, using len is faster than storing the value. The compiler optimizes the former better</p></pre>iroflmaowtf: <pre><p>whenever you loop without a <strong>for...range</strong>, simply cache it in a var, you&#39;ll probably do something like <code>cnt := len(x); fmt.Printf(&#34;iteration %v/%v&#34;, i+1, cnt)</code></p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

404 次点击  
加入收藏 微博
0 回复
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传