c++ 是否有一种编程方式可以估计CPU执行fp操作所需的时间?

nfeuvbwi  于 2022-11-19  发布在  其他
关注(0)|答案(5)|浏览(117)

“fp操作”我指的是“浮点操作”。我正在处理一个Linux系统。是否有一个系统调用将此值作为静态指标返回,或者您是否可以使用C/C++/其他语言中的算法来测试它?

edit:我应该提到这不是为了测试我的代码的效率。在一次采访中,有人问我一个理论上的算法需要运行多长时间。我必须计算出需要执行多少次FLOPS,然后乘以每个操作所需的时间来得出一个粗略的估计。我只是觉得这是一个有趣的问题。

xjreopfe

xjreopfe1#

这几乎肯定不是一个有用的度量标准。还有很多很多其他的东西与代码效率有关--尤其是缓存命中/未命中。
话虽如此,本主题中有一个ServerFault thread链接到您可以使用的Intel benchmarking suite,但您应该知道,您可能看不到系统的最大FLOPS与应用性能之间的任何关联。

wbgh16ku

wbgh16ku2#

你要使用的是AgnerFog的软件“Testprogramsformeasuring clock cycle and performancemonitoring”。他用这个软件来测量指令的延迟和吞吐量,以生成他著名的指令表。它有很好的文档记录,包括设备驱动程序(带有如何安装它们的说明),您可以在自己的代码中使用它们。这一点特别有用,因为为了测量某些量(如真实的CPU频率),您需要访问model specific registers (MSR),而用户级代码通常无法访问该model specific registers (MSR)
编辑:根据您对面试问题的编辑,估计运行浮点密集型操作所需的时间,您可以使用以下公式:

time = efficiency * number_of_floating_point_operations / peak_flops.

许多处理器的每个内核的峰值flops可通过此链接flops-per-cycle-for-sandy-bridge-and-haswell-sse2-avx-avx2找到
这些量中的每一个都可能难以计算/估计,但效率是最困难的,因为它可能取决于许多因素,例如:
1.算法是计算还是内存受限?
1.算法使用SIMD(例如SSE/AVX/FMA)的能力如何?
1.算法使用MIMD(例如多核)的能力如何?
1.您的实施对不同缓存级别的使用情况如何?
为了更清楚地说明这一点,让我们考虑两种算法:矩阵乘法和点积。这两种算法的浮点运算次数很容易计算。矩阵乘法的浮点运算次数是2*n*n*n。点积的浮点运算次数是2*n。
如果正确执行矩阵乘法,那么SIMD和MIMD可以充分利用它的计算能力。对于小n,它的效率开始较低,对于大n,它的效率将保持稳定。我自己的实现可以达到75%。英特尔® MKL可以达到95%以上(但使用FMA时不到90%)。
因此,对于较大的n,矩阵乘法的时间的包络线后估计是假设100%的效率,给出**time = 2*n^3/peak_flops。**
然而,在一些情况下,对于点积,效率在小n时开始很高,在大n时下降到一个平台。这是因为它受内存限制。因此,对于大n,效率取决于内存的读取速度。对于现代机器,大约为10 GB/s。由于具有四核的现代台式机的峰值flops超过100 GLOPS,浮点数为4或8字节,因此我估计大型n的效率接近1%给出**time = 0.01*n/peak_flops**。我继续进行测试(见下面的代码)。我的系统上得到大约2.2 GLOPS,峰值为236 GFLOPS,大约是峰值的1%。我的系统带宽大约为11 GB/s。
大多数算法受内存限制,因此了解系统读取内存(DDR3、DDR4...)的速度是估计时间的最有用指标之一。
因此,一般来说,如果您知道某个算法的浮点运算数和处理器的峰值触发次数,首先应该问的是,对于较大的n,该算法是受计算限制还是受内存限制,然后对于时间的粗略估计,我会假设计算限制的效率为100%,而对于内存限制,我会查找带宽来估计效率。
此代码通过点积估计数据速率和GFLOPS。

#include <time.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdint.h>

    float dot(float *x, float *y, int n) {
        float sum = 0;
        for(int i=0; i<n; i++) {
            sum += x[i]*y[i];
        }
        return sum;
    }

    int main(){
        const int LEN = 1 << 28;
        float *x = new float[LEN];
        float *y = new float[LEN];
        for(int i=0; i<LEN; i++) { x[i] = 1.0*rand()/RAND_MAX - 0.5; y[i] = 1.0*rand()/RAND_MAX - 0.5;}

        uint32_t size = 2*sizeof(float)*LEN;

        clock_t time0 = clock();
        float sum = dot(x,y,LEN);
        clock_t time1 = clock();
        double dtime = (double)(time1 - time0) / CLOCKS_PER_SEC;
        double rate = 1.0*size/dtime*1E-9;
        double flops = 2.0*LEN/dtime*1E-9;
        printf("sum %f, dtime %f, rate %f, flops %f\n", sum, dtime, rate,flops);
    }
tvokkenx

tvokkenx3#

试图确定“在真空中”的FLOP所花费的时间没有多大意义,因为有很多其他因素影响它(操作数是否在存储器/高速缓冲存储器/寄存器中,它实际上是什么类型的操作,编译器是否发出x87/SSE/SSE 2/...指令,它是否涉及“奇怪的”IEEE 754值,处理器流水线是否被有效地使用,如果代码是分支预测器友好的,......)。
相反,您应该在算法的实际代码上使用分析器,以查看 * 真实的的 * 瓶颈是什么,以及 * 在您的特定代码 * 中 * 在这些瓶颈上实际花费了多少时间。

fcipmucu

fcipmucu4#

有一个快速的方法,通过在做一些操作之前和之后获取时间戳,然后减去它们来查看消耗了多少时间。但是,这不是很准确。

7ivaypg9

7ivaypg95#

下面提供了一个关于可变性的想法,来自我的一个基准测试,它使用了长序列的相同汇编代码指令,试图填充流水线,不同的测试使用了可变数量的寄存器。

Intel(R) Core(TM) i7-4820K CPU running at near 3.9 GHz

 Speeds adding to     1 Register  2 Registers  3 Registers  4 Registers

 32 bit Integer MIPS     4303         8553        11997        12294
 32 bit Float MFLOPS     1304         2608         3866         3866 SP
 64 bit Float MFLOPS     1304         2608         3866         3865 DP
 32 bit MMX Int MIPS     7824        14902        14936        14902
 32 bit SSE MFLOPS       5215        10431        15464        15463 SP
 64 bit SSE2 MFLOPS      2608         5216         7732         7731 DP

 32 bit SSE2 Int MIPS   15647        29803        29872        29803
 64 bit SSE2 Int MIPS    7823        14902        14936        14902

相关问题