请教一个 C++性能问题

wisefree · 2024-11-8 21:02:19 · 170 次点击

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

我电脑的输出是:

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;
}
举报· 170 次点击
登录 注册 站外分享
9 条回复  
bfjm 小成 2024-11-8 21:11:59
不能这样一起测吧 cpu cache 命中率不一样
awenxjtu 初学 2024-11-8 21:14:51
cpu 有缓存,缓存的访问速度比内存的访问速度快的多,另外缓存会一大块一大块的和内存交换数据以提高内存的访问速度。第一个 for 循环写法基本上是连续访问内存地址,内存地址基本上会命中缓存中的数据;而第二种写法访问的内存中的地址一会儿这里一会儿那里距离比较远就很可能访问的数据不在缓存中,这样就要等待从内存中读取数据,所以就比第一种写法慢了。
jark006 初学 2024-11-8 21:21:24
简单试了一下,结论就是:第一个三重 for 跑的时候,数据还没完全载入 CPU 缓存,到第二次三重 for 的时候,此时数据都在 CPU 高速缓存里了,数据命中率高,自然就快了。 你互换一下这俩三重 for ,结果也是第一次的就是慢点。或者再拿个 for 把他俩包起来跑 10 次就发现,开始几次就是慢点,后面都一样快了
neocanable 小成 2024-11-8 21:23:10
把这段代码编译出来,拿 IDA 反编译一下 第一个循环: ``` for ( m = 0; m < f9; ++m ) { for ( n = 0; n < f8; ++n ) { for ( ii = 0; ii < f7; ++ii ) f1[m + f9 * n + f9 * f8 * ii] = (float)(int)f5[ii + f7 * n + f8 * f7 * m] * 0.1; } } ``` 第二个循环: ``` for ( jj = 0; jj < f9; ++jj ) { for ( kk = 0; kk < f8; ++kk ) { for ( mm = 0; mm < f7; ++mm ) f1[jj + f6 * kk + f6 * f8 * mm] = (float)(int)f5[mm + f7 * kk + f8 * f7 * jj] * 0.1; } } ``` 没啥不一样,只能是 cpu cache 的问题
ty29022 小成 2024-11-8 21:40:15
>> There are only two hard things in Computer Science: cache invalidation and naming things.
lzoje 初学 2024-11-8 21:51:56
new 的时候并没有实际分配到内存,所以第一次访问的时候都是未命中,比较耗时
pf94 初学 2024-11-8 21:53:28
@ty29022 100*200*300*4b=22.88MB “现代 CPU”的 L1 缓存您认为是多少?
mightybruce 小成 2024-11-8 22:21:05
cpu 缓存 和 提高计算/访存比 是对大型矩阵计算是有非常大的影响的,对于大多数人不做 HPC 高性能计算,很多优化比如循环拆分和向量化是看不到的,很多时候大家都是借助库比如 openblas 和 openmp 来解决的。 我提供几篇文章给大家参考参考 https://renzibei.com/2021/06/30/optimize-gemm/ https://lzzmm.github.io/2021/09/10/GEMM/
CedarChen 初学 2024-11-8 23:43:53
这个我记得是 csapp 封面上的问题哦,op 可以拿过看下
返回顶部