12

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

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

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

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

4

7 回答 7

12

这是一个小的 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都应该提供非常准确的时间信息。

于 2012-09-09T16:40:14.897 回答
3

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

于 2012-09-09T16:40:23.687 回答
1

从内联汇编中,您可以使用 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。

于 2012-09-09T18:45:13.083 回答
0

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

于 2012-09-09T16:39:39.570 回答
0

有一些称为分析器的软件可以完全满足您的需求。

Windows 的一个例子是AMD 代码分析器和POSIX 的gprof

于 2012-09-09T16:41:15.323 回答
0

最适合您的目的是valgrind/callgrind

于 2012-09-09T17:23:02.083 回答
0

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

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

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

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

于 2012-09-09T18:31:19.683 回答