- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我将某些程序集与某些c链接起来,以测试函数调用的成本,并使用以下程序集和c源代码(分别使用fasm和gcc)
部件:
format ELF
public no_call as "_no_call"
public normal_call as "_normal_call"
section '.text' executable
iter equ 100000000
no_call:
mov ecx, iter
@@:
push ecx
pop ecx
dec ecx
cmp ecx, 0
jne @b
ret
normal_function:
ret
normal_call:
mov ecx, iter
@@:
push ecx
call normal_function
pop ecx
dec ecx
cmp ecx, 0
jne @b
ret
#include <stdio.h>
#include <time.h>
extern int no_call();
extern int normal_call();
int main()
{
clock_t ct1, ct2;
ct1 = clock();
no_call();
ct2 = clock();
printf("\n\n%d\n", ct2 - ct1);
ct1 = clock();
normal_call();
ct2 = clock();
printf("%d\n", ct2 - ct1);
return 0;
}
gcc intern.o extern.o
,则典型输出为
162
181
gcc extern.o intern.o
链接,我得到的输出更像:
162
130
162.168
和
131.578
秒
format ELF
public no_call as "_no_call"
public normal_call as "_normal_call"
section '.text' executable
iter equ 100000000
offset equ 23 ; this is the number I am changing
times offset nop
times 16 nop
no_call:
mov ecx, iter
no_call.loop_start:
push ecx
pop ecx
dec ecx
cmp ecx, 0
jne no_call.loop_start
ret
times 55 nop
normal_function:
ret
times 58 nop
normal_call:
mov ecx, iter
normal_call.loop_start:
push ecx
call normal_function
pop ecx
dec ecx
cmp ecx, 0
jne normal_call.loop_start
ret
offset
字节来抵消程序,这是我发现的。
if (20 <= offset mod 128 <= 31) then we get an output of (approximately):
162
131
else
162 (+/- 10)
162 (+/- 10)
push ecx
和
pop ecx
,输出将变为
30
125
最佳答案
更新: Skylake存储/重新加载延迟低至3c ,但前提是时机合适。自然地间隔3个或更多周期的存储转发依赖链中涉及的连续负载将经历更快的延迟(例如,循环中有4个imul eax,eax
,mov [rdi], eax
/ mov eax, [rdi]
仅使周期数从每次迭代的12到15个周期递增。),但是如果允许负载执行得更密集,则将发生某种类型的争用,并且每次迭代可获得大约4.5个周期。非整数的平均吞吐量也很明显地表明存在异常。
我看到了32B vector 的效果相同(最好的情况是6.0c,背靠背是6.2到6.9c),但是128b vector 始终在5.0c左右。参见details on Agner Fog's forum。
Update2:Adding a redundant assignment speeds up code when compiled without optimization和2013 blog post表示,这种影响存在于所有Sandybridge系列CPU 上。
Skylake上的背对背(最坏情况)存储转发延迟比以前的uarch上好1个周期,但是负载无法立即执行时的可变性相似。
通过正确(错误)对齐,循环中额外的call
实际上可以帮助Skylake观察从推送到弹出的更低的存储转发延迟。我可以使用YASM在性能计数器(Linux perf stat -r4
)上重现此内容。 (我听说在Windows上使用perf计数器较不方便,而且我也没有Windows开发机。幸运的是,该操作系统与答案并没有真正的关系;任何人都应该能够再现我的perf-counter结果在装有VTune的Windows上。)
我看到在问题中指定位置的align 128
之后,在offset = 0..10、37、63-74、101和127处的速度更快。 L1I缓存行是64B,而uop-cache关心的是32B边界。看起来相对于64B边界的对齐很重要。
无调用循环始终是稳定的5个周期,但是call
循环每次迭代可以从通常的几乎5个周期降低到4c。在偏移量= 38(每次迭代5.68±8.3%周期)时,我看到了比平常慢的性能。根据perf stat -r4
(执行4次并取平均值),在其他点上存在小故障,例如5.17c +-3.3%。
这似乎是前端之间未进行排队的交互,从而导致后端从推送到弹出的存储转发延迟较低。
如果IDK重复使用相同的地址进行存储转发,则IDK会变慢(已在对应的存储数据uop之前执行了多个存储地址ucp),或者发生了什么。
测试代码:bash
shell循环以使用每个不同的偏移量来构建和分析asm:
(set -x; for off in {0..127};do
asm-link -m32 -d call-tight-loop.asm -DFUNC=normal_call -DOFFSET=$off &&
ocperf.py stat -etask-clock,context-switches,cpu-migrations,page-faults:u,cycles,instructions,uops_issued.any,uops_executed.thread,idq.mite_uops,dsb2mite_switches.penalty_cycles -r4 ./call-tight-loop;
done ) |& tee -a call-tight-loop.call.offset-log
(set -x)
是在重定向到日志文件时记录命令及其输出的便捷方法。
asm-link
是运行
yasm -felf32 -Worphan-labels -gdwarf2 call-tight-loop.asm "$@" && ld -melf_i386 -o call-tight-loop call-tight-loop.o
,然后在结果上运行
objdumps -drwC -Mintel
的脚本。
CPU p6 ; YASM directive. For NASM, %use smartalign.
section .text
iter equ 100000000
%ifndef OFFSET
%define OFFSET 0
%endif
align 128
;;offset equ 23 ; this is the number I am changing
times OFFSET nop
times 16 nop
no_call:
mov ecx, iter
.loop:
push ecx
pop ecx
dec ecx
cmp ecx, 0
jne .loop
ret
times 55 nop
normal_function:
ret
times 58 nop
normal_call:
mov ecx, iter
.loop:
push ecx
call normal_function
pop ecx
dec ecx
cmp ecx, 0
jne .loop
ret
%ifndef FUNC
%define FUNC no_call
%endif
align 64
global _start
_start:
call FUNC
mov eax,1 ; __NR_exit from /usr/include/asm/unistd_32.h
xor ebx,ebx
int 0x80 ; sys_exit(0), 32-bit ABI
call
运行的示例输出:
+ asm-link -m32 -d call-tight-loop.asm -DFUNC=normal_call -DOFFSET=3
...
080480d8 <normal_function>:
80480d8: c3 ret
...
08048113 <normal_call>:
8048113: b9 00 e1 f5 05 mov ecx,0x5f5e100
08048118 <normal_call.loop>:
8048118: 51 push ecx
8048119: e8 ba ff ff ff call 80480d8 <normal_function>
804811e: 59 pop ecx
804811f: 49 dec ecx
8048120: 83 f9 00 cmp ecx,0x0
8048123: 75 f3 jne 8048118 <normal_call.loop>
8048125: c3 ret
...
Performance counter stats for './call-tight-loop' (4 runs):
100.646932 task-clock (msec) # 0.998 CPUs utilized ( +- 0.97% )
0 context-switches # 0.002 K/sec ( +-100.00% )
0 cpu-migrations # 0.000 K/sec
1 page-faults:u # 0.010 K/sec
414,143,323 cycles # 4.115 GHz ( +- 0.56% )
700,193,469 instructions # 1.69 insn per cycle ( +- 0.00% )
700,293,232 uops_issued_any # 6957.919 M/sec ( +- 0.00% )
1,000,299,201 uops_executed_thread # 9938.695 M/sec ( +- 0.00% )
83,212,779 idq_mite_uops # 826.779 M/sec ( +- 17.02% )
5,792 dsb2mite_switches_penalty_cycles # 0.058 M/sec ( +- 33.07% )
0.100805233 seconds time elapsed ( +- 0.96% )
call
和
ret
指令(以及
cmp
/
jcc
)以外的所有内容都是涉及循环计数器的关键路径循环依赖项链的一部分。
pop
必须等待
call
/
ret
对堆栈指针的更新,但要等待
the stack engine handles those updates with zero latency。 (据
Agner Fog's microarch pdf称,英特尔自Pentium-M以来,AMD自K10以来,因此我假设您的CPU有一个,即使您未对运行测试的CPU微体系结构说什么也没说。)
call
/
ret
仍然需要执行,但是乱序执行可以使关键路径指令以其最大吞吐量运行。由于这包括
dec
从push / pop + 1个周期的存储->负载转发的等待时间,因此这在任何CPU上都不是很高的吞吐量,并且令人惊讶的是,前端可能成为任何对齐方式的瓶颈。
push
->
pop
延迟为5个周期,因此,在uarch上,循环最多只能每6个周期运行一次迭代。
call
和
ret
指令。 Agner列出
call
的最大吞吐量为每3个周期之一,而
ret
的最大吞吐量为每1个周期之一。或在AMD Bulldozer的2和2上。他的表未列出有关
call
/
ret
对的吞吐量的任何信息,因此IDK是否可以重叠。在AMD Bulldozer上,带有
mov
的存储/重新加载延迟为8个周期。我认为与push / pop差不多。
no_call.loop_start:
)导致了前端瓶颈。
call
版本每次迭代有3个分支:调用,ret和循环分支。请注意,
ret
的分支目标是
call
之后的指令。这些都有可能破坏前端。由于您实际上看到的是实际的速度下降,因此我们必须看到每个分支有1个以上的循环延迟。或者对于no_call版本,单个获取/解码气泡差于大约6个周期,导致在向内核的乱序部分发出uops时导致实际浪费的周期。那真是怪了。
push
/
pop
阻止了它从循环流检测器发出,并且每次都必须从uop缓存中重新获取。
Intel's optimization manual说,对于Sandybridge,循环内不匹配的push / pop阻止了它使用LSD。这意味着它可以将LSD用于具有平衡推入/弹出操作的循环。在我的测试中,在Skylake(使用
lsd.uops
性能计数器)上不是这种情况,但是我没有看到有关这是否是更改或SnB实际上也是如此的任何提及。
normal_function:
与
call
和
jne
放在同一自然对齐的32B机器代码大块中,则代码块可能不适合uop缓存。 (只有3个uop缓存行可以为x86代码的单个32B块缓存解码的uops)。但这不能解释no_call循环出现问题的可能性,因此您可能未在Intel SnB系列微体系结构上运行。
idq.mite_uops
)运行,但通常不是排他性的。
dsb2mite_switches.penalty_cycles
通常约为8k,并且可能仅在定时器中断时发生。
call
循环运行得更快的运行似乎与更低的运行相关。
idq.mite_uops
,但对于offset = 37的情况(仍然是100M迭代需要401M个周期),仍然是34M +-63%。)
push
/
pop
进行注册,则可能会看到不同的结果。这会将push / pop与循环计数器分开,因此会有2个独立的依赖链。它应该同时加快call和no_call版本的速度,但可能不尽相同。它只会使前端瓶颈更加明显。
push edx
而不是
pop eax
,应该会看到巨大的加速,因此push / pop指令不会形成循环承载的依赖链。然后,额外的
call
/
ret
肯定是瓶颈。
dec ecx
已经按照您想要的方式设置了ZF,因此您可以只使用
dec ecx / jnz
。另外,
cmp ecx,0
is less efficient than test ecx,ecx
(较大的代码大小,不能在多个CPU上进行宏熔断)。无论如何,这与两个循环的相对性能完全无关。 (您在函数之间缺少
ALIGN
指令意味着更改第一个将更改第二个循环分支的对齐方式,但是您已经探索了不同的对齐方式。)
关于c - 使用函数调用循环比空循环更快,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45442458/
我是 PHP 新手。我一直在脚本中使用 for 循环、while 循环、foreach 循环。我想知道 哪个性能更好? 选择循环的标准是什么? 当我们在另一个循环中循环时应该使用哪个? 我一直想知道要
我在高中的编程课上,我的作业是制作一个基本的小计和顶级计算器,但我在一家餐馆工作,所以制作一个只能让你在一种食物中读到。因此,我尝试让它能够接收多种食品并将它们添加到一个价格变量中。抱歉,如果某些代码
这是我正在学习的一本教科书。 var ingredients = ["eggs", "milk", "flour", "sugar", "baking soda", "baking powder",
我正在从字符串中提取数字并将其传递给函数。我想给它加 1,然后返回字符串,同时保留前导零。我可以使用 while 循环来完成此操作,但不能使用 for 循环。 for 循环只是跳过零。 var add
编辑:我已经在程序的输出中进行了编辑。 该程序要求估计给定值 mu。用户给出一个值 mu,同时还提供了四个不等于 1 的不同数字(称为 w、x、y、z)。然后,程序尝试使用 de Jaeger 公式找
我正在编写一个算法,该算法对一个整数数组从末尾到开头执行一个大循环,其中包含一个 if 条件。第一次条件为假时,循环可以终止。 因此,对于 for 循环,如果条件为假,它会继续迭代并进行简单的变量更改
现在我已经习惯了在内存非常有限的情况下进行编程,但我没有答案的一个问题是:哪个内存效率更高;- for(;;) 或 while() ?还是它们可以平等互换?如果有的话,还要对效率问题发表评论! 最佳答
这个问题已经有答案了: How do I compare strings in Java? (23 个回答) 已关闭 8 年前。 我正在尝试创建一个小程序,我可以在其中读取该程序的单词。如果单词有 6
这个问题在这里已经有了答案: python : list index out of range error while iteratively popping elements (12 个答案) 关
我正在尝试向用户请求 4 到 10 之间的整数。如果他们回答超出该范围,它将进入循环。当用户第一次正确输入数字时,它不会中断并继续执行 else 语句。如果用户在 else 语句中正确输入数字,它将正
我尝试创建一个带有嵌套 foreach 循环的列表。第一个循环是循环一些数字,第二个循环是循环日期。我想给一个日期写一个数字。所以还有另一个功能来检查它。但结果是数字多次写入日期。 Out 是这样的:
我想要做的事情是使用循环创建一个数组,然后在另一个类中调用该数组,这不会做,也可能永远不会做。解决这个问题最好的方法是什么?我已经寻找了所有解决方案,但它们无法编译。感谢您的帮助。 import ja
我尝试创建一个带有嵌套 foreach 循环的列表。第一个循环是循环一些数字,第二个循环是循环日期。我想给一个日期写一个数字。所以还有另一个功能来检查它。但结果是数字多次写入日期。 Out 是这样的:
我正在模拟一家快餐店三个多小时。这三个小时分为 18 个间隔,每个间隔 600 秒。每个间隔都会输出有关这 600 秒内发生的情况的统计信息。 我原来的结构是这样的: int i; for (i=0;
这个问题已经有答案了: IE8 for...in enumerator (3 个回答) How do I check if an object has a specific property in J
哪个对性能更好?这可能与其他编程语言不一致,所以如果它们不同,或者如果你能用你对特定语言的知识回答我的问题,请解释。 我将使用 c++ 作为示例,但我想知道它在 java、c 或任何其他主流语言中的工
这个问题不太可能帮助任何 future 的访问者;它只与一个小的地理区域、一个特定的时间点或一个非常狭窄的情况有关,这些情况并不普遍适用于互联网的全局受众。为了帮助使这个问题更广泛地适用,visit
我是 C 编程和编写代码的新手,以确定 M 测试用例的质因数分解。如果我一次只扫描一次,该功能本身就可以工作,但是当我尝试执行 M 次时却惨遭失败。 我不知道为什么 scanf() 循环有问题。 in
这个问题已经有答案了: JavaScript by reference vs. by value [duplicate] (4 个回答) 已关闭 3 年前。 我在使用 TSlint 时遇到问题,并且理
我尝试在下面的代码中添加 foreach 或 for 循环,以便为 Charts.js 创建多个数据集。这将允许我在此折线图上创建多条线。 我有一个 PHP 对象,我可以对其进行编码以稍后填充变量,但
我是一名优秀的程序员,十分优秀!