使用一个循环与两个循环

Sai*_*alp 4 c++ performance

我正在阅读此博客:- https://developerinsider.co/why-is-one-loop-so-much-slower-than-two-loops/。我决定使用 C++ 和 Xcode 来检查一下。因此,我编写了一个简单的程序,如下所示,当我执行它时,我对结果感到惊讶。实际上,与文章中所述相反,第二个函数比第一个函数慢。谁能帮我弄清楚为什么会出现这种情况吗?

#include <iostream>
#include <向量>
#include <计时>
    
使用命名空间 std::chrono;
    
无效函数1() {
    常量 int n=100000;
            
    int a1[n], b1[n], c1[n], d1[n];
            
    for(int j=0;j<n;j++){
        a1[j] = 0;
        b1[j] = 0;
        c1[j] = 0;
        d1[j] = 0;
    }
            
    自动启动= high_resolution_clock::now();
        
    for(int j=0;j<n;j++){
        a1[j] += b1[j];
        c1[j] += d1[j];
    }
            
    自动停止= high_resolution_clock::now();
    自动持续时间=duration_cast<微秒>(停止-开始);
        
    std::cout << period.count() << " 微秒。" << std::endl;  
}
    
无效函数2() {
    常量 int n=100000;
            
    int a1[n], b1[n], c1[n], d1[n];
            
    for(int j=0; j<n; j++){
        a1[j] = 0;
        b1[j] = 0;
        c1[j] = 0;
        d1[j] = 0;
    }
            
    自动启动= high_resolution_clock::now();
            
    for(int j=0; j<n; j++){
        a1[j] += b1[j];
    }
    
    for(int j=0;j<n;j++){
        c1[j] += d1[j];
    }
            
    自动停止= high_resolution_clock::now();
    自动持续时间=duration_cast<微秒>(停止-开始);
        
    std::cout << period.count() << " 微秒。" << std::endl;
}
        
int main(int argc, const char * argv[]) {
    函数1();
    函数2();
    
    返回0;
}

yzt*_*yzt 9

TL;DR:循环基本相同,如果您看到差异,那么您的测量是错误的。性能测量,更重要的是,性能推理需要大量的计算机知识、一定的科学严谨性和工程敏锐度。现在是长版本...


不幸的是,您链接的文章以及此处的答案和一些评论中有一些非常不准确的信息。

我们先从文章开始吧。不会有任何磁盘缓存对这些功能的性能产生任何影响。确实,当物理内存需求超过可用内存时,虚拟内存会分页到磁盘,但这对于涉及 1.6MB 内存 (4 * 4 * 100K) 的程序来说不是必须考虑的因素。

如果分页发挥作用,性能差异也不会很微妙。如果这些阵列被分页到磁盘并返回,对于最快的磁盘,性能差异将是 1000 倍,而不是 10% 或 100%。

分页和页面错误及其对性能的影响既不简单也不直观。你需要阅读它,并认真地尝试它。该文章所包含的信息很少,完全不准确,甚至达到了误导的程度。

第二个是您的分析策略和微基准本身。显然,通过对数据进行如此简单的操作(添加),瓶颈将是内存带宽本身(可能是指令退出限制或类似的简单循环。)并且由于您仅线性读取内存,并使用您读取的所有内容,无论是 4 个交错流还是 2 个交错流,您都可以利用所有可用的带宽。

但是,如果您在循环中function1调用or ,您将根据 N 测量内存层次结构不同部分的带宽,从 L1 一直到 L3 和主内存。(你应该知道你的机器上各级缓存的大小,以及它们如何工作。)如果你知道 CPU 缓存如何工作,这一点是显而易见的,否则真的很神秘。您是否想知道当阵列冷时第一次执行此操作时的速度有多快,或者您想测量热访问?function2

您的实际用例是否一遍又一遍地复制相同的中型数组?

如果不是,那是什么?你在衡量什么?您是在尝试测量某些东西还是只是进行实验?

难道您不应该测量循环中最快的运行速度,而不是平均值,因为它可能会受到(基本上是随机的)上下文切换或中断的巨大影响?

您是否确定使用了正确的编译器开关?您是否查看过生成的汇编代码以确保编译器没有添加调试检查等,并且没有优化不应该的东西(毕竟,您只是执行无用的循环,而优化编译器不需要任何东西)不仅仅是为了避免生成不需要的代码)。

您是否查看过硬件的理论内存/缓存带宽数?您的特定 CPU 和 RAM 组合将有理论上的限制。无论是 5、50 还是 500 GiB/s,它都会为您提供可移动和使用的数据量的上限。执行单元的数量、IPC 或 CPU 以及其他几十个会影响此类微基准测试性能的数字也是如此。

如果您正在读取 4 个整数(每个 4 个字节,来自 a、b、c 和 d),然后进行两次加法并将两个结果写回,并执行 100'000 次,那么您粗略地看到的是 2.4 MB 内存读写。如果您在 300 微秒内执行 10 次,那么您的程序的内存(好吧,存储缓冲区/L1)吞吐量约为 80 GB/s。这么低吗?有那么高吗?你知道吗?(你应该有一个大概的想法。)

让我告诉您,在撰写本文时,其他两个答案(即thisthis)没有意义。我无法弄清楚第一个,而第二个几乎完全错误(100'000 次for循环中的条件分支很糟糕?分配额外的迭代器变量成本高昂?冷访问堆栈上的数组 vs . 在堆上有“严重的性能影响?”

最后,正如所写,这两个函数具有非常相似的性能。将两者分开确实很困难,除非您可以在实际用例中衡量真正的差异,否则我会说写出让您更高兴的那个。

如果您真的想要它们之间的理论上的差异,我会说具有两个单独循环的循环要好一些,因为交错访问不相关的数据通常不是一个好主意。