如何执行更多的指令来加快执行速度

4
当我运行以下函数时,我得到了一些意外的结果。
在我的机器上,下面的代码一致需要约6秒才能运行。然而,如果我取消注释“;dec [variable + 24]”这行代码,则执行更多的代码后,它需要约4.5秒才能运行。为什么呢?
.DATA
variable dq 0 dup(4)
.CODE             

runAssemblyCode PROC
    mov rax, 2330 * 1000 * 1000
start:
    dec [variable]
    dec [variable + 8]
    dec [variable + 16]
    ;dec [variable + 24]
    dec rax
    jnz start
    ret 
runAssemblyCode ENDP 
END

我注意到Stack Overflow上已经有类似的问题了,但他们的代码示例不如这个简单,而且我没有找到对这个问题的简洁答案。
我尝试用nop指令填充代码,以查看是否存在对齐问题,并将亲和力设置为单个处理器。但两者都没有产生任何影响。

你尝试在开始使用nop填充来调整缓存行之前吗?这样做可能只是平衡了解码和执行路径上的任务分配,也许找到了一种加剧它们的序列,然后通过额外的指令提供了一种解决方法。 - old_timer
愚蠢的问题:我该如何组装这个?这是英特尔语法,对吗?我可以使用gcc/gas吗? - old_timer
你想要递减字节、字或双字吗? - old_timer
3个回答

3
简单的答案是因为现代CPU非常复杂。在观察者看来,底层有很多不可预测或随机的事情发生。
插入额外的指令可能会导致它以不同的顺序安排指令,在这样一个紧密循环中可能会产生影响。但这只是一个猜测。
就我所看到的,它触及与前一个指令相同的高速缓存行,所以它似乎不是一种预取。我真的想不出一个逻辑上的解释,但是CPU利用了许多未经记录的启发式和猜测,以尽可能快地执行代码,有时候,这意味着奇怪的边缘情况,其中它们失败了,代码变得比你预期的要慢。
您是否在不同的CPU型号上进行过测试?如果其他x86 CPU也表现出同样的问题,那将是有趣的。

1

bob.s

.data
variable:
    .word 0,0,0,0
    .word 0,0,0,0
    .word 0,0,0,0
    .word 0,0,0,0
    .word 0,0,0,0
    .word 0,0,0,0

.text
.globl runAssemblyCode
runAssemblyCode:
  mov    $0xFFFFFFFF,%eax

start_loop:
  decl variable+0
  decl variable+8
  decl variable+16
  ;decl variable+24
  dec    %eax
  jne    start_loop
  retq

ted.c

#include <stdio.h>
#include <time.h>

void runAssemblyCode ( void );

int main ( void )
{
    volatile unsigned int ra,rb;

    ra=(unsigned int)time(NULL);
    runAssemblyCode();
    rb=(unsigned int)time(NULL);
    printf("%u\n",rb-ra);
    return(0);
}

gcc -O2 ted.c bob.s -o ted

这是使用额外指令的结果:

00000000004005d4 <runAssemblyCode>:
  4005d4:   b8 ff ff ff ff          mov    $0xffffffff,%eax

00000000004005d9 <start_loop>:
  4005d9:   ff 0c 25 28 10 60 00    decl   0x601028
  4005e0:   ff 0c 25 30 10 60 00    decl   0x601030
  4005e7:   ff 0c 25 38 10 60 00    decl   0x601038
  4005ee:   ff 0c 25 40 10 60 00    decl   0x601040 
  4005f5:   ff c8                   dec    %eax
  4005f7:   75 e0                   jne    4005d9 <start_loop>
  4005f9:   c3                      retq   
  4005fa:   90                      nop

我看不出有什么区别,也许你可以纠正我的代码或者其他人可以在他们的系统上尝试看看他们看到了什么...

如果你正在做除字节为基础的内存递减之外的其他事情,那么这是一条极其痛苦的指令,而且如果不对缓存行以及核心数量等进行敏感处理,那么它将对内存系统造成痛苦。所以这个例程应该对缓存行以及核心数量等进行敏感处理。

无论是否有额外的指令,都需要大约13秒钟。

AMD Phenom 9950四核处理器

Intel(R) Core(TM)2 CPU 6300

无论是否有额外的指令,都需要大约9-10秒钟。

双处理器: Intel(R) Xeon(TM) CPU

无论是否有额外的指令,都需要大约13秒钟。

在这个上面: Intel(R) Core(TM)2 Duo CPU T7500

无论是否有额外的指令,都需要8秒钟。

所有机器都运行Ubuntu 64位10.04或10.10,可能还有11.04。

更多的机器,64位,ubuntu

Intel(R) Xeon(R) CPU X5450 (8核)

无论是否有额外的指令,都需要大约6秒钟。

Intel(R) Xeon(R) CPU E5405(8核)

有或没有都是9秒。

您的系统中DDR/DRAM的速度是多少?运行的处理器类型是什么(如果在Linux上,请使用cat /proc/cpuinfo命令)。

Intel(R) Xeon(R) CPU E5440(8核)

有或没有都是6秒。

啊,找到了一个单核心的,不过是Xeon: Intel(R) Xeon(TM) CPU

有或没有额外指令都是15秒。


我认为应该是 decq,但我不知道这会有多大的区别。 - user786653
我只在一台机器上尝试了decq指令,有或没有该指令都没有任何区别。我并不想以任何方式否定原始问题,希望找到重复它的方法或地方。 - old_timer

0

其实也不算太糟糕。平均而言,完整的循环需要2.6纳秒才能执行,而另一个则需要1.9纳秒。假设使用2GHz的CPU,其周期为0.5纳秒,则差异大约为每个循环(2.6-1.9)/0.5=1个时钟周期,这并不奇怪。
然而,时间差异变得如此明显,是由于您请求的循环次数:0.5纳秒*2330000000=1.2秒,这就是您观察到的差异。


有什么不寻常的吗?为什么执行一条额外的指令会减少循环迭代的执行时间,这并不令人惊讶?OP问的不是它会产生多大的差异,而是为什么差异存在。发生了什么使较短的版本变得更慢 - jalf
@jalf:哦,等等,我误解了问题。我以为较短的版本是更快的那个 ;) - BlackBear
啊,你的回答现在更有意义了。 :) - jalf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接