【问题标题】:How to measure the elapsead time below nanosecond for x86?如何测量 x86 纳秒以下的经过时间?
【发布时间】:2017-09-20 15:08:58
【问题描述】:

我已经搜索并使用了许多方法来测量经过的时间。为此目的有很多问题。例如,this 问题非常好,但是当您需要准确的时间记录器时,我找不到好的方法。为此,我想在这里分享我的方法,以供使用并在有问题时予以纠正。

更新&注意:这个问题是针对基准测试的,不到一纳秒。这与使用clock_gettime(CLOCK_MONOTONIC,&start); 完全不同,它记录的时间超过一纳秒。

更新:衡量加速的一种常用方法是重复程序中应该进行基准测试的部分。但是,正如评论中提到的,当研究人员依赖自动矢量化时,它可能会显示出不同的优化。

注意 在一次重复中测量经过的时间不够准确。在某些情况下,我的结果表明该部分必须重复超过 1K 或 1M 才能获得最短时间。

建议:我不熟悉 shell 编程(只知道一些基本命令...)但是,可以测量最小时间而无需在程序内部重复。 p>

我当前的解决方案为了防止出现分支,我使用宏 #define REP_CODE(X) X X X... X X 重复 ode 部分,其中 X 是我要进行基准测试的代码部分,如下所示:

//numbers
#define FMAX1 MAX1*MAX1
#define COEFF 8 
int __attribute__(( aligned(32))) input[FMAX1+COEFF];           //= {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17};
int __attribute__(( aligned(32))) output[FMAX1];
int __attribute__(( aligned(32))) coeff[COEFF] = {1,2,3,4,5,6,7,8};//= {1,1,1,1,1,1,1,1};//;            //= {1,2,1,2,1,2,1,2,2,1};

int main()
{
    REP_CODE(
        t1_rdtsc=_rdtsc();
        //Code
        for(i = 0; i < FMAX1; i++){
            for(j = 0; j < COEFF; j++){//IACA_START
                output[i] += coeff[j] * input[i+j]; 

            }//IACA_END
        }
        t2_rdtsc=_rdtsc();
        ttotal_rdtsc[ii++]=t2_rdtsc-t1_rdtsc;
        )
    // The smallest element in `ttotal_rdtsc` is the answer
}

这不影响优化,但在某些情况下还受到代码大小和编译时间过多的限制。

有什么建议和更正吗?

提前致谢。

【问题讨论】:

  • “这个问题是针对基准测试的,不到一纳秒。”,为什么,
  • @rubenvb,你是对的。但是,两者都需要。如果您正在努力优化一个真实的应用程序,那很好。
  • 如果没有专用的计时硬件,您根本无法以如此小的间隔准确测量时间。 rdtsc 对此不可靠。如果您想对快速操作进行基准测试,请像其他 10,000 次基准测试一样:运行该操作数千或数百万次,然后除以迭代次数。这不是火箭科学。
  • @FackedDeveloper,简单的 rdtsc 无法准确测量 3-4 条指令(它们在 3-4 GHz CPU 上总共为 1 纳秒),因为有:命令的乱序执行(并且 rdtsc 没有序列化,并且序列化 > 10 滴答声)和长 CPU 流水线(> 12 个阶段,或 > 3-4 ns)。只有“简单”的时间来执行某些命令是不可能的,您不应该比较编译器,而是比较具有良好微体系结构知识的编译器生成的机器代码,使用模拟器(intel IACA)和性能计数器(pmu-tools - ocperf .py)。
  • 是的,平均运行 1000 次迭代(针对实际工作负载)是最好的。

标签: c performance x86 intrinsics


【解决方案1】:

如果您对自动矢量化器有疑问并想限制它,只需在您的begin_rdtsc 之后添加一个asm("#somthing");,它将分隔do-while 循环。我刚刚检查过,它对您发布的代码进行了矢量化,而自动矢量化器无法对其进行矢量化。 我改变了你的宏,你可以使用它....

long long t1_rdtsc, t2_rdtsc, ttotal_rdtsc[do_while], ttbest_rdtsc = 99999999999999999, elapsed,  elapsed_rdtsc=do_while, overal_time = OVERAL_TIME, ttime=0;
int ii=0;
    #define begin_rdtsc\
                    do{\
                        asm("#mmmmmmmmmmm");\
                        t1_rdtsc=_rdtsc();

    #define end_rdtsc\
                        t2_rdtsc=_rdtsc();\
                        asm("#mmmmmmmmmmm");\
                        ttotal_rdtsc[ii]=t2_rdtsc-t1_rdtsc;\
                    }while (ii++<do_while);\    
                    for(ii=0; ii<do_while; ii++){\
                        if (ttotal_rdtsc[ii]<ttbest_rdtsc){\
                            ttbest_rdtsc = ttotal_rdtsc[ii];}}\             
                    printf("\nthe best is %lld in %lld iteration\n", ttbest_rdtsc, elapsed_rdtsc);

【讨论】:

    【解决方案2】:

    我已经开发了我的第一个答案并得到了这个解决方案。但是,我仍然想要一个解决方案。因为准确地测量时间并且影响最小是非常重要的。我将此部分放在头文件中,并将其包含在主程序文件中。

    //Header file header.h
    #define count 1000 // number of repetition 
    long long t1_rdtsc, t2_rdtsc, ttotal_rdtsc[count], ttbest_rdtsc = 99999999999999999, elapsed,  elapsed_rdtsc=count, overal_time = OVERAL_TIME, ttime=0;
    int ii=0;
    #define begin_rdtsc\
                        do{\
                            t1_rdtsc=_rdtsc();
    
    #define end_rdtsc\
                            t2_rdtsc=_rdtsc();\
                            ttotal_rdtsc[ii]=t2_rdtsc-t1_rdtsc;\
                        }while (ii++<count);\   
                        for(ii=0; ii<do_while; ii++){\
                            if (ttotal_rdtsc[ii]<ttbest_rdtsc){\
                                ttbest_rdtsc = ttotal_rdtsc[ii];}}\             
                        printf("\nthe best is %lld in %lldth iteration \n", ttbest_rdtsc, elapsed_rdtsc);
    
    //Main program
    #include "header.h"
    .
    .
    .
    int main()
    {
        //before the section
        begin_rdtsc
           //put your code here to measure the clocks.
        end_rdtsc
        return 0
    }
    

    【讨论】:

    • 打开自动矢量时,此解决方案不是一个好的解决方案。它限制了自动向量。即使是一个简单的循环也会使自动向量混乱
    • do_while 是我见过的最糟糕的变量名称之一。称它为reps 或count 之类的。而且您的示例main 没有定义您的宏所依赖的任何变量。与main()中的重复循环相比,将这些东西放在宏中似乎没有任何好处@
    • 嘿@PeterCordes。你和保罗教会了我很多观点。他们阻止我在 SO 中提问,我已经创建了这个帐户。你可能会理解我并再次帮助我。如果您知道任何测量时间的方法,请告诉我。我认为最小的时间比平均值或平均值要准确得多。为此,我应该重复该部分,问题是当我将代码放入循环时,它会限制自动矢量化器
    • 我刚刚更改了主要问题中的循环,您可以看到围绕程序的简单循环如何限制 icc 和 clang 中的自动矢量化器。 gcc 工作正常。
    • 我刚刚检查并放入一个单独的函数没有帮助,但__attribute__((noinline)) 有帮助并且不会限制自动矢量化器对其进行矢量化。
    【解决方案3】:

    我建议将这种方法用于 x86 微架构。

    注意:

    1. NUM_LOOP 应该是一个有助于提高准确性的数字 重复你的代码来记录最佳时间
    2. ttbest_rdtsc 必须 大于我建议最大化它的最坏时间。

    3. 我使用(你可能不想要它)OVERAL_TIME 作为另一个检查规则,因为我在许多内核中都使用了它,并且在某些情况下NUM_LOOP 非常大,我不想更改它。我计划OVERAL_TIME 限制迭代并在特定时间后停止。

    更新:整个程序是这样的:

    #include <stdio.h>
    #include <x86intrin.h>
    
    #define NUM_LOOP 100 //executes your code NUM_LOOP times to get the smalest time to avoid overheads such as cache misses, etc.
    
    int main()
    {
        long long t1_rdtsc, t2_rdtsc, ttotal_rdtsc, ttbest_rdtsc = 99999999999999999;
        int do_while = 0;
        do{
    
            t1_rdtsc = _rdtsc();
                //put your code here
            t2_rdtsc = _rdtsc();
    
            ttotal_rdtsc = t2_rdtsc - t1_rdtsc;
    
            //store the smalest time:
            if (ttotal_rdtsc<ttbest_rdtsc)
                ttbest_rdtsc = ttotal_rdtsc;
    
        }while (do_while++ < NUM_LOOP); 
    
        printf("\nthe best is %lld in %d repetitions\n", ttbest_rdtsc, NUM_LOOP );
    
        return 0;
    }
    

    我已经更改为这个并添加到我自己的标题中,然后我可以在我的程序中简单地使用它。

    #include <x86intrin.h>
    #define do_while NUM_LOOP
    #define OVERAL_TIME 999999999
    long long t1_rdtsc, t2_rdtsc, ttotal_rdtsc, ttbest_rdtsc = 99999999999999999, elapsed, elapsed_rdtsc=do_while, overal_time = OVERAL_TIME, ttime=0;
    #define begin_rdtsc\
                    do{\
                        t1_rdtsc=_rdtsc();
    
    #define end_rdtsc\
                        t2_rdtsc=_rdtsc();\
                        ttotal_rdtsc=t2_rdtsc-t1_rdtsc;\
                        if (ttotal_rdtsc<ttbest_rdtsc){\
                            ttbest_rdtsc = ttotal_rdtsc;\
                            elapsed=(do_while-elapsed_rdtsc);}\
                        ttime+=ttotal_rdtsc;\
                    }while (elapsed_rdtsc-- && (ttime<overal_time));\
                    printf("\nthe best is %lld in %lldth iteration and %lld repetitions\n", ttbest_rdtsc, elapsed, (do_while-elapsed_rdtsc));
    

    如何使用这个方法?嗯,很简单!

    int main()
    {
        //before the section
        begin_rdtsc
           //put your code here to measure the clocks.
        end_rdtsc
        return 0
    }
    

    要有创意,你可以改变它来衡量你程序的加速等等。 输出的一个例子是:

    the best is 9600 in 384751th iteration and 569179 repetitions
    

    我的测试代码得到了9600时钟,最好的记录在384751enditeration中,我的代码测试了569179次

    我已经在 GCC 和 Clang 上对它们进行了测试。

    【讨论】:

    • 但是您的基准测试代码包含分支...另外,您是否有任何理由不使用内联函数?至少使用 \ 来格式化凌乱的宏...
    • your benchmarking code contains branches 是什么意思?树枝没问题,我想。因为它只是一个简化版。它可以正确测量我检查的时钟,它适用于有关使用 \ 我尝试使用但我不能依赖它的功能、循环等。因为这个实现完全把语句放在了代码中。我认为宏可能不合适。顺便说一句,如果您认为您可以开发此解决方案,请根据我的解决方案提供另一个答案。我会接受并感谢它
    • 不,分支是不行的,因为它们在基准测试代码和要计时的代码之间建立了紧密的耦合。根据代码的其余部分,您的基准测试代码可能会得到不同的优化,或者会影响要计时的代码的优化。因此,基准测试代码本身会导致非线性、不确定的执行时间开销。关于\,没有理由不能使用它。它只是将一条线一分为二。
    • 这段代码完全不可读,因为它有 10 行长,塞进了一行。但是即使没有阅读它,很明显你已经把这个方式弄得太复杂了。在一开始,在您想要计时的代码之前调用RDTSC,并保存该值。然后,在您计时的代码序列结束时,再次调用RDTSC。减去这两个值,你就有了结果。不需要循环或分支,这意味着您的时间不会被打乱。并且代码的可读性大大提高。请注意,RDTSC 需要 Pentium 或更高版本,因此不适用于 8088。
    • @Lundin,谢谢。我应该知道优化。关于\,它缺乏我对\的知识,我正在研究它。
    猜你喜欢
    • 1970-01-01
    • 2017-06-28
    • 2019-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-13
    相关资源
    最近更新 更多