请教一个 C++性能问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wisefree
V2EX    C++

请教一个 C++性能问题

  •  
  •   wisefree 335 天前 2914 次点击
    这是一个创建于 335 天前的主题,其中的信息可能已经有所发展或是发生改变。

    对于两个转置运算,两种的性能明显不一样,是为什么呢?

    我电脑的输出是:

    0.0473308

    0.0265206

     #include <iostream> #include <chrono> int main(void) { int I = 100; int J = 200; int K = 300; int idealI = 105; // i j k int* arr = new int[I * J * K]; for (int i = 0; i < I; i++) { for int j = 0; j < J; j++) { for (int k = 0; k < K; k++) { arr[i * (K * J) + j * K + k] = k; } } } // k j i float* transArr = new float[idealI * J * K]; auto startTime = std::chrono::steady_clock::now(); for (int i = 0; i < I; i++) { for (int j = 0; j < J; j++) { for (int k = 0; k < K; k++) { transArr[k * (J * I) + j * I + i] = arr[i * (K * J) + j * K + k] * 0.1f; } } } auto endTime = std::chrono::steady_clock::now(); std::chrono::duration<double> diffTime = endTime - startTime; std::cout << diffTime.count() << std::endl; startTime = std::chrono::steady_clock::now(); for (int i = 0; i < I; i++) { for (int j = 0; j < J; j++) { for (int k = 0; k < K; k++) { transArr[k * (J * idealI) + j * idealI + i] = arr[i * (K * J) + j * K + k] * 0.1f; } } } endTime = std::chrono::steady_clock::now(); diffTime = endTime - startTime; std::cout << diffTime.count() << std::endl; delete[] arr; delete[] transArr; return 0; } 
    第 1 条附言    334 天前
    根据大家的解释,我重新写了一个例子,由于 V2EX 附言的长度有限制,所以把 func1 和 fun2 分开在两个附言种

    func1 比 func2 快了 3 倍左右


    ``` c++
    #include <iostream>
    #include <chrono>


    int I = 360;
    int J = 280;
    int K = 3;

    int idealI = 512;

    void func1(int* arr, float* transArr)
    {
    auto startTime = std::chrono::steady_clock::now();

    for (int i = 0; i < I; i++) {
    for (int j = 0; j < J; j++) {
    for (int k = 0; k < K; k++) {

    int realIdx = (i * (K * J) + j * K + k) * 2;
    int imagIdx = realIdx + 1;

    int transRealIdx = (k * (J * I) + j * I + i) * 2;
    int transImagIdx = transRealIdx + 1;

    transArr[transRealIdx] = arr[realIdx] * 0.1f;
    transArr[transImagIdx] = arr[imagIdx] * 0.1f;
    }
    }
    }

    auto endTime = std::chrono::steady_clock::now();

    std::chrono::duration<double> diffTime = endTime - startTime;

    std::cout << diffTime.count() << std::endl;
    }

    int main(void)
    {

    // i j k
    int* arr = new int[I * J * K * 2];
    for (int i = 0; i < I; i++) {
    for (int j = 0; j < J; j++) {
    for (int k = 0; k < K; k++) {
    int realIdx = (i * (K * J) + j * K + k) * 2;
    int imagIdx = realIdx + 1;

    arr[realIdx] = k;
    arr[imagIdx] = k;
    }
    }
    }

    // k j i
    float* transArr = new float[idealI * J * K * 2];

    for (int i = 0; i < idealI; i++) {
    for (int j = 0; j < J; j++) {
    for (int k = 0; k < K; k++) {
    int realIdx = (i * (K * J) + j * K + k) * 2;
    int imagIdx = realIdx + 1;

    transArr[realIdx] = 0;
    transArr[imagIdx] = 0;
    }
    }
    }

    func1(arr, transArr);


    delete[] arr;
    delete[] transArr;

    return 0;
    }

    ```
    第 2 条附言    334 天前

    #include <iostream> #include <chrono>

    int I = 360; int J = 280; int K = 3;

    int idealI = 512;

    void func2(int* arr, float* transArr) { auto startTime = std::chrono::steady_clock::now();

    for (int i = 0; i < I; i++) { for (int j = 0; j < J; j++) { for (int k = 0; k < K; k++) { int realIdx = (i * (K * J) + j * K + k) * 2; int imagIdx = realIdx + 1; int transRealIdx = (k * (J * idealI) + j * idealI + i) * 2; int transImagIdx = transRealIdx + 1; transArr[transRealIdx] = arr[realIdx] * 0.1f; transArr[transImagIdx] = arr[imagIdx] * 0.1f; } } } auto endTime = std::chrono::steady_clock::now(); std::chrono::duration<double> diffTime = endTime - startTime; std::cout << diffTime.count() << std::endl; 

    }

    int main(void) {

    // i j k int* arr = new int[I * J * K * 2]; for (int i = 0; i < I; i++) { for (int j = 0; j < J; j++) { for (int k = 0; k < K; k++) { int realIdx = (i * (K * J) + j * K + k) * 2; int imagIdx = realIdx + 1; arr[realIdx] = k; arr[imagIdx] = k; } } } // k j i float* transArr = new float[idealI * J * K * 2]; for (int i = 0; i < idealI; i++) { for (int j = 0; j < J; j++) { for (int k = 0; k < K; k++) { int realIdx = (i * (K * J) + j * K + k) * 2; int imagIdx = realIdx + 1; transArr[realIdx] = 0; transArr[imagIdx] = 0; } } } func2(arr, transArr); delete[] arr; delete[] transArr; return 0; 

    }

    20 条回复    2025-04-08 17:15:07 +08:00
    bfjm
        1
    bfjm  
       335 天前 via iPhone   1
    不能这样一起测吧 cpu cache 命中率不一样
    awenxjtu
        2
    awenxjtu  
       335 天前 via Android   1
    cpu 有缓存,缓存的访问速度比内存的访问速度快的多,另外缓存会一大块一大块的和内存交换数据以提高内存的访问速度。第一个 for 循环写法基本上是连续访问内存地址,内存地址基本上会命中缓存中的数据;而第二种写法访问的内存中的地址一会儿这里一会儿那里距离比较远就很可能访问的数据不在缓存中,这样就要等待从内存中读取数据,所以就比第一种写法慢了。
    jark006
        3
    jark006  
       335 天前   1
    简单试了一下,结论就是:第一个三重 for 跑的时候,数据还没完全载入 CPU 缓存,到第二次三重 for 的时候,此时数据都在 CPU 高速缓存里了,数据命中率高,自然就快了。

    你互换一下这俩三重 for ,结果也是第一次的就是慢点。或者再拿个 for 把他俩包起来跑 10 次就发现,开始几次就是慢点,后面都一样快了
    neocanable
        4
    neocanable  
       335 天前   1
    把这段代码编译出来,拿 IDA 反编译一下
    第一个循环:
    ```
    for ( m = 0; m < v29; ++m )
    {
    for ( n = 0; n < v28; ++n )
    {
    for ( ii = 0; ii < v27; ++ii )
    v21[m + v29 * n + v29 * v28 * ii] = (float)(int)v25[ii + v27 * n + v28 * v27 * m] * 0.1;
    }
    }
    ```

    第二个循环:
    ```
    for ( jj = 0; jj < v29; ++jj )
    {
    for ( kk = 0; kk < v28; ++kk )
    {
    for ( mm = 0; mm < v27; ++mm )
    v21[jj + v26 * kk + v26 * v28 * mm] = (float)(int)v25[mm + v27 * kk + v28 * v27 * jj] * 0.1;
    }
    }
    ```


    没啥不一样,只能是 cpu cache 的问题
    ty29022
        5
    ty29022  
       335 天前   1
    >> There are only two hard things in Computer Science: cache invalidation and naming things.
    ty29022
        6
    ty29022  
       335 天前   1
    但我有些疑惑的是这个数据量以现代 cpu 的缓存大小来说应该没多大区别才是
    bfjm
        7
    bfjm  
       335 天前 via iPhone   1
    lzoje
        8
    lzoje  
       335 天前 via Android   1
    new 的时候并没有实际分配到内存,所以第一次访问的时候都是未命中,比较耗时
    pf94
        9
    pf94  
       335 天前   1
    @ty29022 100*200*300*4b=22.88MB “现代 CPU”的 L1 缓存您认为是多少?
    lzoje
        10
    lzoje  
       335 天前 via Android   1
    可以试下 new 之后访问一下,然后再测
    mightybruce
        11
    mightybruce  
       335 天前   3
    cpu 缓存 和 提高计算/访存比 是对大型矩阵计算是有非常大的影响的,对于大多数人不做 HPC 高性能计算,很多优化比如循环拆分和向量化是看不到的,很多时候大家都是借助库比如 openblas 和 openmp 来解决的。

    我提供几篇文章给大家参考参考
    https://renzibei.com/2021/06/30/optimize-gemm/
    https://lzzmm.github.io/2021/09/10/GEMM/
    CedarChen
        12
    CedarChen  
       335 天前   1
    这个我记得是 csapp 封面上的问题哦,op 可以拿过看下
    wisefree
        13
    wisefree  
    OP
       334 天前
    ``` c++

    #include <iostream>
    #include <chrono>


    int I = 360;
    int J = 280;
    int K = 3;

    int idealI = 512;

    void func1(int* arr, float* transArr)
    {
    auto startTime = std::chrono::steady_clock::now();

    for (int i = 0; i < I; i++) {
    for (int j = 0; j < J; j++) {
    for (int k = 0; k < K; k++) {

    int realIdx = (i * (K * J) + j * K + k) * 2;
    int imagIdx = realIdx + 1;

    int transRealIdx = (k * (J * I) + j * I + i) * 2;
    int transImagIdx = transRealIdx + 1;

    transArr[transRealIdx] = arr[realIdx] * 0.1f;
    transArr[transImagIdx] = arr[imagIdx] * 0.1f;
    }
    }
    }

    auto endTime = std::chrono::steady_clock::now();

    std::chrono::duration<double> diffTime = endTime - startTime;

    std::cout << diffTime.count() << std::endl;
    }


    void func2(int* arr, float* transArr)
    {
    auto startTime = std::chrono::steady_clock::now();

    for (int i = 0; i < I; i++) {
    for (int j = 0; j < J; j++) {
    for (int k = 0; k < K; k++) {
    int realIdx = (i * (K * J) + j * K + k) * 2;
    int imagIdx = realIdx + 1;

    int transRealIdx = (k * (J * idealI) + j * idealI + i) *2;
    int transImagIdx = transRealIdx + 1;

    transArr[transRealIdx] = arr[realIdx] * 0.1f;
    transArr[transImagIdx] = arr[imagIdx] * 0.1f;
    }
    }
    }

    auto endTime = std::chrono::steady_clock::now();

    std::chrono::duration<double> diffTime = endTime - startTime;

    std::cout << diffTime.count() << std::endl;
    }

    int main(void)
    {

    // i j k
    int* arr = new int[I * J * K * 2];
    for (int i = 0; i < I; i++) {
    for (int j = 0; j < J; j++) {
    for (int k = 0; k < K; k++) {
    int realIdx = (i * (K * J) + j * K + k) * 2;
    int imagIdx = realIdx + 1;

    arr[realIdx] = k;
    arr[imagIdx] = k;
    }
    }
    }

    // k j i
    float* transArr = new float[idealI * J * K * 2];

    for (int i = 0; i < idealI; i++) {
    for (int j = 0; j < J; j++) {
    for (int k = 0; k < K; k++) {
    int realIdx = (i * (K * J) + j * K + k) * 2;
    int imagIdx = realIdx + 1;

    transArr[realIdx] = 0;
    transArr[imagIdx] = 0;
    }
    }
    }

    func1(arr, transArr);
    func2(arr, transArr);


    delete[] arr;
    delete[] transArr;

    return 0;
    }
    ```
    wisefree
        14
    wisefree  
    OP
       334 天前
    @awenxjtu 重新写了一个例子,应该就可以用你的说法解释了,附言没有发送没有用 markdown 语法,格式有点混乱
    wisefree
        15
    wisefree  
    OP
       334 天前
    @jark006 是的,重新写了一个例子,发现应该是缓存起作用
    wisefree
        16
    wisefree  
    OP
       334 天前
    @neocanable 同意,我重新写了一个例子,也应该是缓存导致速度差异
    wisefree
        17
    wisefree  
    OP
       334 天前
    @lzoje 多谢,是这样的,我调整了用例,new 之后全部赋值
    wisefree
        18
    wisefree  
    OP
       334 天前
    @CedarChen 好的,多谢
    wisefree
        19
    wisefree  
    OP
       334 天前
    @mightybruce 多谢啦,我调整了一下,发现自己对高性能运算,确实没有了解太多
    stinkytoofool
        20
    stinkytoofool  
       184 天前
    空间局部性
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     862 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 21:42 PVG 05:42 LAX 14:42 JFK 17:42
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86