c++ 内存访问比移位慢吗?

3pmvbmvn  于 11个月前  发布在  其他
关注(0)|答案(3)|浏览(106)

下面两个代码片段中哪一个通常运行得更快(没有编译器优化)?这段代码只是一个例子-我知道有更快的方法来做同样的计算。

// arr points to the following array: [1,2,4,8,16,32,64]
// assume that it has already been created, so that the
// array creation does not cause a time penalty
int result = 0;
for (int i = 0; i < 7; i++) {
    result += arr[i];
}

个字符
我很确定内存访问会更慢,但我想确认一下。
编辑:为了澄清这个问题,我特别对这段代码的未优化版本感兴趣。不是因为我真的打算在生产中使用这段代码,而是因为我对内存访问或算术是否更快的概念感兴趣。也许我应该用汇编语言而不是C/C++编写代码,以进一步澄清我对代码的编译器优化不感兴趣。
一些回复说,在大多数情况下,内存访问速度较慢,而另一些回复说,我应该进行基准测试,这取决于我的处理器和系统。
感兴趣的架构是x86或x86-64 -你可以在2021年的现代笔记本电脑或台式电脑中找到的东西。一旦我在这段代码的未优化和优化版本上运行了一些基准测试,我将再次编辑这个问题。感谢到目前为止做出回应的每个人。
编辑2:我对上面两段代码的一个变体运行了gprof,发现平均而言(经过数十亿次运行),使用内存访问的计算版本大约需要2.4纳秒,而使用算术的版本大约需要1.1纳秒,这是在64位Linux计算机上。
运行是未优化的(-O 0),并使用GCC版本10.2.0。我还尝试使用clang版本10.0.1,结果如下:内存版本平均为2.45纳秒(与GCC无显著差异),算术版本为1.63纳秒(明显比GCC差,尽管这种差异可能有其他原因...我的基准并不严格,因为它只是为了给出给予一个粗略的估计)。
我用于基准测试的变体用一系列7行重复的代码(result += arr[0]; ...result += (1 << 0); ...)取代了循环,因为我发现循环本身花费的时间远远超过了这两种情况下的计算时间。
我没有去优化运行,因为我知道算术版本将被优化为一个常数(127或0x 7 F),这实际上只是测试编译器是否足够智能来优化内存版本。

v7pvogib

v7pvogib1#

我建议您看看Agner Fog的优秀手册,尤其是one related to C++
当CPU访问和内存访问是关键的时间消耗者时,优化速度是相关的。
在大多数现代架构中,移位(ALU指令)将比内存访问快得多,引用手册:

移位操作(1个时钟周期)

在大多数微处理器上,移位操作只需要一个时钟周期

内存访问(2到4个时钟周期,或更糟)

从RAM存储器中读取数据与对数据进行计算所需的时间相比可能需要相当长的时间。这就是为什么所有现代计算机都有内存缓存的原因。通常,有一个8 - 64 Kbytes的1级数据缓存和一个256 Kbytes到2 Mbytes的2级缓存。通常,还有一个几Mbytes的3级缓存。
程序中所有数据的组合大小大于2级高速缓存并且数据在存储器中分散或以非顺序方式访问,则存储器访问很可能是程序中最大的时间消耗。如果存储器中的变量被高速缓存则对该变量的阅读或写入仅花费2-4个时钟周期,但是如果没有缓存,则需要几百个时钟周期。参见第25页的数据存储和第89页的内存缓存。
所以在你的具体例子中,你应该选择移位。

evrscar2

evrscar22#

由于你的问题似乎是关于从内存中加载一个单词的相对速度 * 与执行算术运算的相对速度 *,你似乎真的是在要求一个比较,更像是对

*p

字符串

  • vs. * 的
p + 1


,其中p是指向int的指针,其值本身不需要从内存中获取(因为它已经在CPU寄存器中)。一般来说,现代CPU的算术单元运行速度至少比附加的内存子系统快几倍,即使对于其内容当前可从CPU最快的缓存获得的内存位置,所以人们通常会认为后者更快。
但是以这种粒度来看待性能问题真的没有意义,尤其是不去问未优化的代码。包含这些操作的整个程序的性能很大程度上受到操作上下文的影响,如果你正在寻求最佳性能,那么你当然会编译 * 有 * 优化,而不是没有。此外,如果在任何给定情况下产生的代码与在启用优化的情况下产生的代码完全不同的话,那么通常完全不清楚在不启用优化的情况下产生的代码将如何不同。
总的来说,这个问题有一种强烈的过早优化的味道--也就是说,程序员的微优化,而不是编译器的优化--这通常会适得其反。为了获得最佳性能,使用最好的算法来解决这个问题,并编写干净,自然的代码。这通常会帮助编译器在优化方面做得更好,如果生成的程序不够快,那么就分析它,确定最大的瓶颈在哪里,然后在这些地方工作。

5m1hhzi4

5m1hhzi43#

Duff's Device包括在比较中也会很有趣,它使用了本文中称为loop unrolling的东西,通过减少循环的数量来减少复制所需的指令。

  • “循环展开的基本思想是,通过减少循环测试的数量,有时减少循环中花费的时间,可以减少循环中执行的指令的数量。例如,在块代码中只有单个指令的循环的情况下,通常将针对循环的每次迭代执行循环测试,即每次执行指令。如果,相反,将相同指令的八个副本放置在循环中,则测试将仅每八次迭代执行一次,并且这可以通过避免七次测试来获得时间。然而,这仅处理八次迭代的倍数,需要其他东西来处理任何剩余的迭代。"*

Duff's Device的示例代码片段:

void copy_duff(register short *to, register short *from, register  count)
{
    register n=(count+7)/8;
    switch(count%8) {
        case 0:    do {    *to = *from++;
        case 7:        *to = *from++;
        case 6:        *to = *from++;
        case 5:        *to = *from++;
        case 4:        *to = *from++;
        case 3:        *to = *from++;
        case 2:        *to = *from++;
        case 1:        *to = *from++;
        } while(--n>0);
    }
}

字符串

相关问题