请查看以下代码:
以及生成的汇编代码:
varint_bench_test.go:14 0x10f07ee 488b4c2430 MOVQ 0x30(SP), CX
varint_bench_test.go:14 0x10f07f3 488b5118 MOVQ 0x18(CX), DX
...
varint_bench_test.go:14 0x10f0807 ffd2 CALL DX
TEXT command-line-arguments.WriteUvarint(SB) /Users/robertengels/gotest/gotest/varint_bench_test.go
varint_bench_test.go:12 0x10f07b0 65488b0c2530000000 MOVQ GS:0x30, CX
varint_bench_test.go:12 0x10f07b9 483b6110 CMPQ 0x10(CX), SP
varint_bench_test.go:12 0x10f07bd 0f869f000000 JBE 0x10f0862
varint_bench_test.go:12 0x10f07c3 4883ec28 SUBQ $0x28, SP
varint_bench_test.go:12 0x10f07c7 48896c2420 MOVQ BP, 0x20(SP)
varint_bench_test.go:12 0x10f07cc 488d6c2420 LEAQ 0x20(SP), BP
varint_bench_test.go:13 0x10f07d1 488b442440 MOVQ 0x40(SP), AX
varint_bench_test.go:13 0x10f07d6 eb09 JMP 0x10f07e1
varint_bench_test.go:18 0x10f07d8 488b442440 MOVQ 0x40(SP), AX
varint_bench_test.go:18 0x10f07dd 48c1e807 SHRQ $0x7, AX
varint_bench_test.go:13 0x10f07e1 483d80000000 CMPQ $0x80, AX
varint_bench_test.go:13 0x10f07e7 7243 JB 0x10f082c
varint_bench_test.go:13 0x10f07e9 4889442440 MOVQ AX, 0x40(SP)
varint_bench_test.go:14 0x10f07ee 488b4c2430 MOVQ 0x30(SP), CX
varint_bench_test.go:14 0x10f07f3 488b5118 MOVQ 0x18(CX), DX
varint_bench_test.go:14 0x10f07f7 83c880 ORL $-0x80, AX
varint_bench_test.go:14 0x10f07fa 88442408 MOVB AL, 0x8(SP)
varint_bench_test.go:14 0x10f07fe 488b442438 MOVQ 0x38(SP), AX
varint_bench_test.go:14 0x10f0803 48890424 MOVQ AX, 0(SP)
varint_bench_test.go:14 0x10f0807 ffd2 CALL DX
varint_bench_test.go:14 0x10f0809 488b442418 MOVQ 0x18(SP), AX
varint_bench_test.go:14 0x10f080e 488b4c2410 MOVQ 0x10(SP), CX
varint_bench_test.go:15 0x10f0813 4885c9 TESTQ CX, CX
varint_bench_test.go:15 0x10f0816 74c0 JE 0x10f07d8
varint_bench_test.go:16 0x10f0818 48894c2448 MOVQ CX, 0x48(SP)
varint_bench_test.go:16 0x10f081d 4889442450 MOVQ AX, 0x50(SP)
varint_bench_test.go:16 0x10f0822 488b6c2420 MOVQ 0x20(SP), BP
varint_bench_test.go:16 0x10f0827 4883c428 ADDQ $0x28, SP
varint_bench_test.go:16 0x10f082b c3 RET
varint_bench_test.go:20 0x10f082c 488b4c2430 MOVQ 0x30(SP), CX
varint_bench_test.go:20 0x10f0831 488b4918 MOVQ 0x18(CX), CX
varint_bench_test.go:20 0x10f0835 88442408 MOVB AL, 0x8(SP)
varint_bench_test.go:20 0x10f0839 488b442438 MOVQ 0x38(SP), AX
varint_bench_test.go:20 0x10f083e 48890424 MOVQ AX, 0(SP)
varint_bench_test.go:20 0x10f0842 ffd1 CALL CX
varint_bench_test.go:20 0x10f0844 488b442418 MOVQ 0x18(SP), AX
varint_bench_test.go:20 0x10f0849 488b4c2410 MOVQ 0x10(SP), CX
varint_bench_test.go:20 0x10f084e 48894c2448 MOVQ CX, 0x48(SP)
varint_bench_test.go:20 0x10f0853 4889442450 MOVQ AX, 0x50(SP)
varint_bench_test.go:20 0x10f0858 488b6c2420 MOVQ 0x20(SP), BP
varint_bench_test.go:20 0x10f085d 4883c428 ADDQ $0x28, SP
varint_bench_test.go:20 0x10f0861 c3 RET
varint_bench_test.go:12 0x10f0862 e89948f6ff CALL runtime.morestack_noctxt(SB)
varint_bench_test.go:12 0x10f0867 e944ffffff JMP command-line-arguments.WriteUvarint(SB)
:-1 0x10f086c cc INT $0x3
:-1 0x10f086d cc INT $0x3
:-1 0x10f086e cc INT $0x3
:-1 0x10f086f cc INT $0x3
生成的代码在每次循环调用和每次调用(第14行和第20行)中使用双重间接寻址加载接口地址。编译器可以轻松生成优化后的代码,其中DX一次加载并用于每个接口调用,因为方法中的w是常量。
我认为像这样的循环在典型的Go代码中非常常见,值得更多的优化关注。例如,问题#29010具体提到了由于它们的低效性而不使用接口作为调用站点。
至少可以将调用地址放在堆栈局部变量中,避免一个间接寻址。
更高级的变化可能是为热点接口调用地址和对象引用(r10/r11)预留几个通用寄存器,并在这些使用优化的例程的入口/出口处压入/弹出r10/r11。
问题#18597与此有些重叠。
9条答案
按热度按时间vxf3dgd41#
I can see why it’s problematic - due to the GC and stack growth, but only the dedicated struct pointer would need fix up. The interface fn pointer doesn’t need it.
pbpqsu0x2#
编译器可以轻松生成优化后的代码,其中DX只需加载一次并用于每个接口调用,因为方法中的w是常量。
请注意,我认为当前DX至少需要在每次迭代时溢出和重新装入,因为使用接口调用执行的函数可能会覆盖DX。
ar7v8xwq3#
这可能是因为只有64位和保留一个rN寄存器,并在被调用时使调用者保留。我认为大多数接口调用最终不会调用另一个接口,通常是在初始化之后进入具体实现,所以保存将是轻微的开销,因为它在循环之外...。
2018年12月15日,上午8:59,Martin Möhrmann ***@***.***>写道:编译器可以轻松生成优化代码,其中DX一次加载并用于每个接口调用,因为w在方法中是常量。请注意,我认为DX目前需要至少溢出并在每次迭代时重新装入,因为使用接口调用执行的函数可能会破坏DX。—您收到此邮件是因为您编写了线程。直接回复此邮件,查看GitHub上的内容,或静音线程。
2ic8powd4#
在and64上,寄存器是宝贵的。如果我们要为单一目的专门分配一个寄存器,那可能就是当前的g寄存器。
更改ABI(例如支持callee-save寄存器)是一项重大任务...也是我们正在积极进行的工作。
对于callee-save寄存器,很有可能一个优秀的寄存器分配器已经为其选择了一个callee-save寄存器,但最好确认一下(一旦这个假设成为现实)。
weylhg0b5#
让编译器知道不需要从itab的插槽重新加载是很好的 - 没有东西会写(已经初始化的)itab。
我不确定它实际上会带来多少性能损失,至少在这个例子中是这样。分支预测器将正确预测调用,所以实际上没有什么在等待这些加载的结果。只要L1有足够的带宽来处理这些加载,就不会降低任何东西的速度。
找到一个可以看到改进的基准测试是很好的。不确定那会是什么样子,或者如果不实现优化,我们如何知道。也许使用性能计数器来找到一个具有这些指令停滞的基准测试?
apeeds0o6#
我正在进行一些手组装,以测试性能差异。
bvuwiixz7#
让编译器知道不需要从itab的槽位重新加载是很好的 - 没有东西会写(已经初始化的)itab。我也在想这个问题,但我不知道实现会是什么样子。你有什么建议吗?
j2cgzkjk8#
初始测试显示,当每次调用时需要保存/重新加载寄存器时,性能没有提高。似乎唯一可行的解决方案是为接口调度专用寄存器并使它们成为被调用者保存 - 一旦发生GC/安全点,所有寄存器似乎都被清空了 - 在大多数情况下(除了GC/运行时之外),它们不需要保存/恢复。
我认为这将大大改善Go的性能,同时鼓励基于接口的设计。
n3schb8v9#
我也在想这个问题,但我不知道实现会是什么样子。你有什么建议吗?
没有。如果我们要做任何类型的别名分析以便更积极地进行存储转发,那么itab的特殊情况将很容易纳入。不过不确定如何做前者。