go ``` cmd/compile: 提高接口调度性能 ```

liwlm1x9  于 6个月前  发布在  Go
关注(0)|答案(9)|浏览(54)

请查看以下代码:

以及生成的汇编代码:

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与此有些重叠。

vxf3dgd4

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.

pbpqsu0x

pbpqsu0x2#

编译器可以轻松生成优化后的代码,其中DX只需加载一次并用于每个接口调用,因为方法中的w是常量。
请注意,我认为当前DX至少需要在每次迭代时溢出和重新装入,因为使用接口调用执行的函数可能会覆盖DX。

ar7v8xwq

ar7v8xwq3#

这可能是因为只有64位和保留一个rN寄存器,并在被调用时使调用者保留。我认为大多数接口调用最终不会调用另一个接口,通常是在初始化之后进入具体实现,所以保存将是轻微的开销,因为它在循环之外...。
2018年12月15日,上午8:59,Martin Möhrmann ***@***.***>写道:编译器可以轻松生成优化代码,其中DX一次加载并用于每个接口调用,因为w在方法中是常量。请注意,我认为DX目前需要至少溢出并在每次迭代时重新装入,因为使用接口调用执行的函数可能会破坏DX。—您收到此邮件是因为您编写了线程。直接回复此邮件,查看GitHub上的内容,或静音线程。

2ic8powd

2ic8powd4#

在and64上,寄存器是宝贵的。如果我们要为单一目的专门分配一个寄存器,那可能就是当前的g寄存器。
更改ABI(例如支持callee-save寄存器)是一项重大任务...也是我们正在积极进行的工作。
对于callee-save寄存器,很有可能一个优秀的寄存器分配器已经为其选择了一个callee-save寄存器,但最好确认一下(一旦这个假设成为现实)。

weylhg0b

weylhg0b5#

让编译器知道不需要从itab的插槽重新加载是很好的 - 没有东西会写(已经初始化的)itab。
我不确定它实际上会带来多少性能损失,至少在这个例子中是这样。分支预测器将正确预测调用,所以实际上没有什么在等待这些加载的结果。只要L1有足够的带宽来处理这些加载,就不会降低任何东西的速度。
找到一个可以看到改进的基准测试是很好的。不确定那会是什么样子,或者如果不实现优化,我们如何知道。也许使用性能计数器来找到一个具有这些指令停滞的基准测试?

apeeds0o

apeeds0o6#

我正在进行一些手组装,以测试性能差异。

bvuwiixz

bvuwiixz7#

让编译器知道不需要从itab的槽位重新加载是很好的 - 没有东西会写(已经初始化的)itab。我也在想这个问题,但我不知道实现会是什么样子。你有什么建议吗?

j2cgzkjk

j2cgzkjk8#

初始测试显示,当每次调用时需要保存/重新加载寄存器时,性能没有提高。似乎唯一可行的解决方案是为接口调度专用寄存器并使它们成为被调用者保存 - 一旦发生GC/安全点,所有寄存器似乎都被清空了 - 在大多数情况下(除了GC/运行时之外),它们不需要保存/恢复。
我认为这将大大改善Go的性能,同时鼓励基于接口的设计。

n3schb8v

n3schb8v9#

我也在想这个问题,但我不知道实现会是什么样子。你有什么建议吗?
没有。如果我们要做任何类型的别名分析以便更积极地进行存储转发,那么itab的特殊情况将很容易纳入。不过不确定如何做前者。

相关问题