Performance leaq有那么慢吗?还是有另一个原因使较小的程序集列表比较长的程序集列表慢?
我不知道任何真正的程序集,但可以读取GCCPerformance leaq有那么慢吗?还是有另一个原因使较小的程序集列表比较长的程序集列表慢?,performance,assembly,x86,intel,Performance,Assembly,X86,Intel,我不知道任何真正的程序集,但可以读取GCC-S输出,以评估给定C代码的实际成本 这个问题不是关于评测和基准的,而是教育性的。我需要有人向我解释为什么[1]代码段不比第二个快 好吧,过去的想法是:“是的,像MUL这样的一些操作非常昂贵,但是如果一个组件比另一个组件大X倍,它应该会慢一些。” 在我遇到这两个人之前,这是完全正确的: unsigned char bytes[4] = {0, 0, 0, 5}; // 1 int32_t val = *((int32_t*)bytes);
-S
输出,以评估给定C代码的实际成本
这个问题不是关于评测和基准的,而是教育性的。我需要有人向我解释为什么[1]代码段不比第二个快
好吧,过去的想法是:“是的,像MUL这样的一些操作非常昂贵,但是如果一个组件比另一个组件大X倍,它应该会慢一些。”
在我遇到这两个人之前,这是完全正确的:
unsigned char bytes[4] = {0, 0, 0, 5};
// 1
int32_t val = *((int32_t*)bytes);
/* produces:
leaq -16(%rbp), %rax
movl (%rax), %eax
movl %eax, -4(%rbp)
movl $0, %eax
*/
// 2
val = bytes[3] |
(bytes[2] << 8) |
(bytes[1] << 16) |
(bytes[0] << 24);
/* produces:
movzbl -13(%rbp), %eax
movzbl %al, %eax
movzbl -14(%rbp), %edx
movzbl %dl, %edx
sall $8, %edx
orl %eax, %edx
movzbl -15(%rbp), %eax
movzbl %al, %eax
sall $16, %eax
orl %eax, %edx
movzbl -16(%rbp), %eax
movzbl %al, %eax
sall $24, %eax
orl %edx, %eax
movl %eax, -4(%rbp)
movl $0, %eax
*/
更新:
事实证明,结果是特定于硬件的,因此,虽然[1]在我的笔记本电脑(x64 i5-4260U)上速度较慢,但在我的电脑上速度较快(但只有很小的一部分,如5%)。更新:额外的加载/存储可以减少Sandybridge系列上的存储转发延迟,因此,CODE2中的额外工作可能加快了两个循环所依赖的循环承载依赖链的速度。(因为
-O0
将循环计数器保留在内存中)
这个效果是,包括我在写这个答案时的沙桥,OP的哈斯韦尔,年的布罗德韦尔,年的天湖
你应该做什么 如果您对代码如何编译为asm感到好奇,请将代码放在无法优化的函数中。在上,您可以看到gcc 5.1及更高版本(位于
-O3-march=native-mtune=native
)如何看穿字节的ORing,并在加载时动态使用movbe
(move big-endian)来加载bwap。icc、clang和较旧的gcc发出指令,加载单个字节并将它们移动到位
令我失望的是,编译器没有看透字节的ORing,即使我颠倒了顺序,只进行了少量的endian加载,而不是大端ian加载。(请参阅godbolt上的本机、big和little endian函数。)即使将类型更改为uint32_t和uint8_t,对gcc>=5.1以外的编译器也没有帮助
显然,随着优化的进行,编译器会丢弃那些只设置了一个未使用变量的循环。他们只需调用clock
两次,printf,然后将答案立即移动到eax
。如果您希望实际对某个对象进行基准测试,请将其与使用常量数据调用它的函数分开编译。这样,您就可以有简单的代码,将其工作作为函数参数,并且它不能内联到传递常量数据的调用程序中
此外,gcc将main
视为一个“冷”函数,并没有像普通函数那样对其进行大量优化。在大多数程序中,main
不包含内部循环
为什么
-O0
asm这么慢?
显然,从-O0
,代码非常可怕,存储到内存中,甚至增加内存中的循环计数器。弄清楚为什么它的运行速度比我预期的还要慢仍然有点有趣,代码1每时钟不到一个insn
两段代码都没有显示整个循环。可能移除循环体仍会留下一个缓慢的循环。我认为循环本身就是问题所在,而且效率太低,以至于CPU有时间在代码2中执行所有额外的指令,而不会看到任何减速
TL;DR:这两个循环都受到
add$1,-0x24(%rbp)
的限制,这会增加内存中的循环计数器。循环携带的依赖链中的6个周期延迟解释了为什么两个循环的速度相同
我不知道为什么CODE2中的额外指令会以某种方式帮助接近理论上的最大每次迭代6个周期,但这并不是任何人编写的代码中都会出现的瓶颈。将循环计数器保存在寄存器中,不要将相同地址的读-修改-写指令放入循环携带的依赖链中。()
有关我对代码所做的更改,请参见。(迭代次数增加了100倍,因此运行时控制了启动开销的噪音。)为了前3个函数的好处,该链接启用了优化
GooBead没有C模式,只有C++,而且GGC-4.92比GOODBET显示了一个不坏的循环。(g++实现了一个
for(){}
循环,它与所写的完全一样,顶部是cmp/jcc,底部是jmp。即使在-O0
中,gcc也使用do{}while(count++
结构,底部只有一个cmp/jle
我不知道任何真正的汇编程序,但可以读取GCC-S的输出
评估给定C代码的实际成本
好吧,过去常这样想:“是的,像MUL这样的一些业务很不错
价格昂贵,但如果一个组件比另一个组件大X倍,那么
应该慢一点”
首先要寻找的是吞吐量和延迟瓶颈。在Intel上,这意味着每个时钟的吞吐量为4 UOP,如果长的依赖链是一个限制,则小于4 UOP。然后是每个执行端口的瓶颈。例如,每个时钟有两个内存操作,其中最多一个是存储。每个时钟最多一个mul
,因为只有一个E3 ALU端口有一个整数乘法器
有关优化指南、微体系结构文档以及它们可以运行的指令延迟/吞吐量/UOP/端口表,请参阅
在SandyBridge(我的系统)和Haswell(你的系统)上,由于将循环计数器保留在内存中,您的循环严重受限,Agner Fog的表的延迟为add
,内存目标为6个时钟。它的运行速度不可能超过每6个时钟每次迭代一次。循环中有6条指令,即每周期1 insn
实际上,我得到的吞吐量比这要低。可能作为add
读修改写操作一部分的存储有时会被循环中的其他加载/存储延迟。IDK为什么CODE2稍快,这很奇怪。可能是
#include <stdio.h>
#include <time.h>
#include <stdint.h>
#define REPETITIONS 32
#define ITERATIONS 90000
#define CODE1 \
for (int i = 0; i < ITERATIONS; ++i) { \
val = *((int32_t*)bytes); \
}
#define CODE2 \
for (int i = 0; i < ITERATIONS; ++i) { \
val = bytes[3] | \
(bytes[2] << 8) | \
(bytes[1] << 16) | \
(bytes[0] << 24); \
}
int main(void) {
clock_t minTs = 999999999, ts;
unsigned char bytes[4] = {0, 0, 0, 5};
int32_t val;
for (int i = 0; i < REPETITIONS; ++i) {
ts = clock();
CODE1; // Or CODE2
ts = clock() - ts;
if (ts < minTs) minTs = ts;
}
printf("ts: %ld\n", minTs);
return val;
}
###### CODE1 inner loop, profiling on cycles
13.97 │400680: lea -0x10(%rbp),%rax
│400684: mov (%rax),%eax
│400686: mov %eax,-0x2c(%rbp)
│400689: addl $0x1,-0x24(%rbp)
13.84 │40068d: cmpl $0x89543f,-0x24(%rbp)
72.19 │400694: ↑ jle 400680 <code1+0x4a>
## end of the loop
400696: callq 4004e0 <clock@plt>
40069b: sub -0x18(%rbp),%rax
#CODE2
15.08 │400738: movzbl -0xd(%rbp),%eax
0.88 │40073c: movzbl %al,%eax
0.05 │40073f: movzbl -0xe(%rbp),%edx
│400743: movzbl %dl,%edx
13.91 │400746: shl $0x8,%edx
0.70 │400749: or %eax,%edx
0.05 │40074b: movzbl -0xf(%rbp),%eax
│40074f: movzbl %al,%eax
12.70 │400752: shl $0x10,%eax
0.60 │400755: or %eax,%edx
0.09 │400757: movzbl -0x10(%rbp),%eax
0.05 │40075b: movzbl %al,%eax
13.03 │40075e: shl $0x18,%eax
0.70 │400761: or %edx,%eax
0.14 │400763: mov %eax,-0x2c(%rbp)
0.09 │400766: addl $0x1,-0x24(%rbp)
13.63 │40076a: cmpl $0x89543f,-0x24(%rbp)
28.29 │400771: ↑ jle 400738 <code2+0x4a>
## end of the loop
400773: → callq 4004e0 <clock@plt>
400778: sub -0x18(%rbp),%rax
## CODE1 ##
Performance counter stats for './a.out 1' (4 runs):
589.117019 task-clock (msec) # 0.999 CPUs utilized ( +- 1.30% )
2,068,118,847 cycles # 3.511 GHz ( +- 0.48% )
1,729,505,746 instructions # 0.84 insns per cycle
# 0.86 stalled cycles per insn ( +- 0.01% )
2,018,533,639 uops_issued_any # 3426.371 M/sec ( +- 0.02% )
5,648,003,370 uops_dispatched_thread # 9587.235 M/sec ( +- 2.51% )
3,170,497,726 uops_retired_all # 5381.779 M/sec ( +- 0.01% )
2,018,126,488 uops_retired_retire_slots # 3425.680 M/sec ( +- 0.01% )
1,491,267,343 stalled-cycles-frontend # 72.11% frontend cycles idle ( +- 0.66% )
27,192,780 stalled-cycles-backend # 1.31% backend cycles idle ( +- 68.75% )
0.589651097 seconds time elapsed ( +- 1.32% )
## CODE2 ##
peter@tesla:~/src/SO$ ocperf.py stat -r4 -e task-clock,cycles,instructions,uops_issued.any,uops_dispatched.thread,uops_retired.all,uops_retired.retire_slots,stalled-cycles-frontend,stalled-cycles-backend ./a.out 2
perf stat -r4 -e task-clock,cycles,instructions,cpu/event=0xe,umask=0x1,name=uops_issued_any/,cpu/event=0xb1,umask=0x1,name=uops_dispatched_thread/,cpu/event=0xc2,umask=0x1,name=uops_retired_all/,cpu/event=0xc2,umask=0x2,name=uops_retired_retire_slots/,stalled-cycles-frontend,stalled-cycles-backend ./a.out 2
CODE2: ts: 16499
CODE2: ts: 16535
CODE2: ts: 16517
CODE2: ts: 16529
Performance counter stats for './a.out 2' (4 runs):
543.886795 task-clock (msec) # 0.999 CPUs utilized ( +- 1.01% )
2,007,623,288 cycles # 3.691 GHz ( +- 0.01% )
5,185,222,133 instructions # 2.58 insns per cycle
# 0.11 stalled cycles per insn ( +- 0.00% )
5,474,164,892 uops_issued_any # 10064.898 M/sec ( +- 0.00% )
7,586,159,182 uops_dispatched_thread # 13948.048 M/sec ( +- 0.00% )
6,626,044,316 uops_retired_all # 12182.764 M/sec ( +- 0.00% )
5,473,742,754 uops_retired_retire_slots # 10064.121 M/sec ( +- 0.00% )
566,873,826 stalled-cycles-frontend # 28.24% frontend cycles idle ( +- 0.03% )
3,744,569 stalled-cycles-backend # 0.19% backend cycles idle ( +- 2.32% )
0.544190971 seconds time elapsed ( +- 1.01% )