【问题标题】:How to compare performance of two pieces of codes如何比较两段代码的性能
【发布时间】:2012-09-02 16:16:50
【问题描述】:

我与编程领域的几个人进行了一场友好的比赛,最近我们对编写高效的代码非常感兴趣。我们的挑战是不惜一切代价(可读性、可重用性等)尝试优化代码(在 CPU 时间和复杂性方面)。

问题是,现在我们需要比较我们的代码,看看哪种方法比其他方法更好,但我们不知道有任何工具可用于此目的。

我的问题是,是否有一些(任何!)工具需要一段代码 作为输入并计算触发器或 cpu 指令的数量 运行它有必要吗?是否有任何工具可以衡量优化 代码?

附:目标语言是 c++,但很高兴知道这些工具是否也适用于 java。

【问题讨论】:

标签: c++ optimization flops


【解决方案1】:

这是一个我喜欢在需要计时时推出的小型 C++11 秒表:

#include <chrono>
#include <ctime>

template <typename T> class basic_stopwatch
{
    typedef T clock;
    typename clock::time_point p;
    typename clock::duration   d;

public:
    void tick()  { p  = clock::now();            }
    void tock()  { d += clock::now() - p;        }
    void reset() { d  = clock::duration::zero(); }

    template <typename S> unsigned long long int report() const
    {
        return std::chrono::duration_cast<S>(d).count();
    }

    unsigned long long int report_ms() const
    {
        return report<std::chrono::milliseconds>();
    }

    basic_stopwatch() : p(), d() { }
};

struct c_clock
{
    typedef std::clock_t time_point;
    typedef std::clock_t duration;
    static time_point now() { return std::clock(); }
};

template <> unsigned long long int basic_stopwatch<c_clock>::report_ms() const
{
  return 1000. * double(d) / double(CLOCKS_PER_SEC);
}

typedef basic_stopwatch<std::chrono::high_resolution_clock> stopwatch;
typedef basic_stopwatch<c_clock> cstopwatch;

用法:

stopwatch sw;
sw.tick();

run_long_code();

sw.tock();
std::cout << "This took " << sw.report_ms() << "ms.\n";

在任何体面的实现中,默认的high_resolution_clock 应该提供非常准确的时间信息。

【讨论】:

  • 在 Visual Studio 上 high_resolution_clock 很糟糕,在这种情况下使用 boost 库版本。
  • @ronag:谢谢你的建议!
  • +1:在确定哪段代码更快时,计时通常比分析更好。分析通常会遗漏缓存效果和类似效果,这会对性能产生巨大影响。
  • 对于高分辨率时序,你应该直接使用适当的系统调用,例如linux上的clock_gettime(),它提供了实际的纳秒分辨率。 Chrono 呼叫惩罚可能会导致过多的抖动/其他负面影响。 clock_gettime() 系统调用需要数百个 CPU 滴答来运行,但它的持续时间似乎相当恒定。如果您希望完美时序,请禁用操作系统的 CPU 频率调节器,并使用处理器的汇编指令(例如 rdtsc)计算实际的 CPU 滴答声。
  • @mic_e:TSC 的问题在于它不能很好地处理多核。 chronoclock_gettime 提供的稳定时钟克服了这一点。
【解决方案2】:

&lt;ctime&gt; 中的 std::clock() 函数返回在当前进程上花费了多少 CPU 时间(这意味着它不计算程序空闲的时间,因为 CPU 正在执行其他任务)。该功能可用于准确测量算法的执行时间。使用常量std::CLOCKS_PER_SEC(也来自&lt;ctime&gt;)将返回值转换为秒。

【讨论】:

    【解决方案3】:

    从内联汇编中,您可以使用 rdtsc 指令将 32 位(最低有效部分)计数器放入 eax 并将 32 位(最高有效部分)放入 edx。如果您的代码太小,您可以仅使用 eax 寄存器检查总的适当 cpu 周期。如果计数大于最大值。 32 位值,edx 在每个最大 32 位值周期递增。

    int cpu_clk1a=0;
    int cpu_clk1b=0;
    int cpu_clk2a=0;
    int cpu_clk2b=0;
    int max=0;
    std::cin>>max; //loop limit
    
    __asm
    {
        push eax
        push edx
        rdtsc    //gets current cpu-clock-counter into eax&edx
        mov [cpu_clk1a],eax
        mov [cpu_clk1b],edx
        pop edx
        pop eax
    
    }
    
    long temp=0;
    for(int i=0;i<max;i++)
    {
    
        temp+=clock();//needed to defy optimization to  actually measure something
                              //even the smartest compiler cannot know what 
                              //the clock would be
    }
    
    __asm
    {
        push eax
        push edx
        rdtsc     //gets current cpu-clock-counter into aex&edx
        mov [cpu_clk2a],eax
        mov [cpu_clk2b],edx
        pop edx
        pop eax
    
    }
    std::cout<<(cpu_clk2a-cpu_clk1a)<<std::endl;
       //if your loop takes more than ~2billions of cpu-clocks, use cpu_clk1b and 2b
    getchar();
    getchar();
    

    输出:在我的机器上,1000 次迭代的 74000 个 CPU 周期和 10000 次迭代的 800000 个 CPU 周期。因为clock()比较耗时。

    我机器上的 CPU 周期分辨率:~1000 个周期。是的,您需要数千个加法/减法(快速指令)才能相对正确地测量它。

    假设 cpu 工作频率恒定,对于 1GHz cpu,1000 个 cpu 周期几乎等于 1 微秒。在执行此操作之前,您应该预热您的 CPU。

    【讨论】:

      【解决方案4】:

      从一段代码中计算出详细的cpu时间是相当困难的。 执行此操作的正常方法是将较差/平均/最佳输入数据设计为测试用例。并根据您的真实代码使用这些测试用例进行时序分析。如果没有详细的输入测试数据和条件,没有任何工具可以告诉您失败的情况。

      【讨论】:

        【解决方案5】:

        有一些名为profilers 的软件完全可以满足您的需求。

        Windows 的示例是 AMD code analyser 和 POSIX 的 gprof

        【讨论】:

        • 使用 profiling 来判断性能比赛的问题是 profiling 本身会减慢程序的执行速度。一些算法可能比其他算法受到的影响更大,这会扭曲比赛的结果。此外,使用分析支持进行编译可以防止一些编译器优化,这些优化可以用来挤出最后一点额外的速度。不要误会我的意思——分析器是发现性能瓶颈的重要工具。但你不应该盲目相信他们。
        • @Philipp 我不盲目相信分析器,我不是那种数学怪胎。我认为提及它们是个好主意,因为在我看来 OP 根本不知道它们。
        • @Philipp 我也不明白为什么这个答案应该被否决 - 它在技术上是正确的......
        • 再一次,如果您以精确的推理投反对票,请发表评论!
        【解决方案6】:

        测量 CPU 指令的数量毫无用处。

        性能与瓶颈有关,取决于手头的问题,瓶颈可能是网络、磁盘 IO、内存或 CPU。

        如果只是一场友谊赛,我建议时间安排。当然,这意味着提供足够大的测试用例以进行有意义的测量。

        在 Unix 上,您可以使用 gettimeofday 进行相对精确的测量。

        【讨论】:

          【解决方案7】:

          最适合您的目的是valgrind/callgrind

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-12-14
            • 1970-01-01
            • 2016-04-27
            • 2010-12-02
            • 2022-11-27
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多