在SO上,有很多关于性能分析的问题,但我似乎没有找到整体情况。涉及的问题很多,而且大多数Q&一次忽略除了少数几个,或者不为其提议辩护。
我想知道什么。如果我有两个功能做同样的事情,我很好奇速度的差异,没有外部工具,定时器测试这个是否有意义,或者在测试中编译会影响结果吗?
我问这个是因为如果它是明智的,作为一个C ++程序员,我想知道它应该如何做到最好,因为它们比使用外部工具简单得多。如果它有意义,让我们继续讨论所有可能的陷阱:
考虑这个例子。以下代码显示了执行相同操作的两种方法:
#include <algorithm>
#include <ctime>
#include <iostream>
typedef unsigned char byte;
inline
void
swapBytes( void* in, size_t n )
{
for( size_t lo=0, hi=n-1; hi>lo; ++lo, --hi )
in[lo] ^= in[hi]
, in[hi] ^= in[lo]
, in[lo] ^= in[hi] ;
}
int
main()
{
byte arr[9] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' };
const int iterations = 100000000;
clock_t begin = clock();
for( int i=iterations; i!=0; --i )
swapBytes( arr, 8 );
clock_t middle = clock();
for( int i=iterations; i!=0; --i )
std::reverse( arr, arr+8 );
clock_t end = clock();
double secSwap = (double) ( middle-begin ) / CLOCKS_PER_SEC;
double secReve = (double) ( end-middle ) / CLOCKS_PER_SEC;
std::cout << "swapBytes, for: " << iterations << " times takes: " << middle-begin
<< " clock ticks, which is: " << secSwap << "sec." << std::endl;
std::cout << "std::reverse, for: " << iterations << " times takes: " << end-middle
<< " clock ticks, which is: " << secReve << "sec." << std::endl;
std::cin.get();
return 0;
}
// Output:
// Release:
// swapBytes, for: 100000000 times takes: 3000 clock ticks, which is: 3sec.
// std::reverse, for: 100000000 times takes: 1437 clock ticks, which is: 1.437sec.
// Debug:
// swapBytes, for: 10000000 times takes: 1781 clock ticks, which is: 1.781sec.
// std::reverse, for: 10000000 times takes: 12781 clock ticks, which is: 12.781sec.
问题:
一些设想:
似乎提出了两个高精度计时器:clock()和QueryPerformanceCounter(在Windows上)。显然我们想测量代码的cpu时间而不是实时,但据我所知,这些函数没有提供该功能,因此系统上的其他进程会干扰测量。 gnu c库上的This page似乎与此相矛盾,但是当我在vc ++中设置断点时,调试过程会获得大量时钟滴答,即使它被挂起(我没有在gnu下测试过)。我是否遗漏了替代计数器,或者我们是否至少需要特殊的库或类?如果没有,在这个例子中时钟是否足够好或者是否有理由使用QueryPerformanceCounter?
如果没有调试,反汇编和分析工具,我们可以确定什么?有什么事实发生吗?函数调用是否内联?在检查调试器时,字节实际上是交换的,但我宁愿从理论上知道为什么,而不是从测试。
感谢您的任何指示。
更新
感谢来自hint的tojas,swapBytes函数现在运行速度与std :: reverse一样快。我没有意识到在一个字节的情况下临时副本必须只是一个寄存器,因此非常快。优雅可以使你失明。
inline
void
swapBytes( byte* in, size_t n )
{
byte t;
for( int i=0; i<7-i; ++i )
{
t = in[i];
in[i] = in[7-i];
in[7-i] = t;
}
}
感谢来自tip的ChrisW我发现在Windows上,您可以通过Windows Management Instrumentation获取(读取:您的)流程消耗的实际CPU时间。这绝对看起来比高精度计数器更有趣。
答案 0 :(得分:4)
显然我们想测量代码的cpu时间而不是实时,但据我所知,这些函数没有提供该功能,因此系统上的其他进程会干扰测量。
我做了两件事,以确保挂钟时间和CPU时间大致相同:
测试相当长的时间,即几秒钟(例如通过测试数千次迭代的循环)
测试机器是否或多或少相对闲置,除了我正在测试的任何东西。
或者,如果您只想测量/更准确地测量每个线程的CPU时间,那么它可用作性能计数器(参见例如perfmon.exe
)。
如果没有调试,反汇编和分析工具,我们可以确定什么?
几乎没有(除了I / O往往相对较慢)。
答案 1 :(得分:2)
为了回答你的主要问题,它“反向”算法只是从数组中交换元素而不是对数组元素进行操作。
答案 2 :(得分:2)
说你问两个问题是否安全?
哪一个更快,多少?
为什么它更快?
首先,您不需要高精度计时器。您需要做的就是“足够长时间”运行它们并使用低精度计时器进行测量。 (我很老式,我的手表具有秒表功能,而且完全足够好。)
对于第二个,当然可以在调试器下运行代码并在指令级单步执行。由于基本操作非常简单,因此您可以轻松查看基本周期所需的指令大致。
想想简单。表演不是一个难题。通常,人们会尝试发现问题,this is a simple approach。
答案 3 :(得分:2)
如果您需要高分辨率计时,请在Windows上使用QueryPerformanceCounter。计数器精度取决于CPU,但它可以达到每个时钟脉冲。但是,在现实世界中进行剖析总是一个更好的主意。
答案 4 :(得分:2)
(此答案特定于Windows XP和32位VC ++编译器。)
计算小位代码的最简单方法是CPU的时间戳计数器。这是一个64位值,是到目前为止运行的CPU周期数的计数,这大约是你要获得的精确分辨率。你得到的实际数字并不是特别有用,但如果你平均了几次各种竞争方法的运行,那么你就可以用这种方式进行比较。结果有点吵,但仍然有效用于比较目的。
要阅读时间戳计数器,请使用以下代码:
LARGE_INTEGER tsc;
__asm {
cpuid
rdtsc
mov tsc.LowPart,eax
mov tsc.HighPart,edx
}
(cpuid
指令用于确保没有任何不完整的指令等待完成。)
这种方法有四点值得注意。
首先,由于内联汇编语言,它在MS的x64编译器上无法正常工作。 (你必须创建一个带有函数的.ASM文件。读者练习;我不知道细节。)
其次,为了避免循环计数器在不同的核心/线程/你有什么不同步的问题,您可能会发现有必要设置进程的亲和性,以便它只在一个特定的执行单元上运行。 (然后......你可能不会。)
第三,您肯定要检查生成的汇编语言,以确保编译器大致生成您期望的代码。注意要删除的代码,内联函数,等等。
最后,结果相当嘈杂。循环计数器计算在所有事情上花费的周期,包括等待缓存,运行其他进程所花费的时间,在操作系统本身花费的时间等等。不幸的是,不可能(在Windows下,至少)只为您的进程计时。所以,我建议运行测试中的代码很多次(几万)并计算出平均值。这不是很狡猾,但无论如何它似乎都给我带来了有用的结果。
答案 5 :(得分:1)
你有什么东西可以对付个人资料吗?他们帮助了很多。既然您使用的是WinXP,那么您应该尝试一下vtune的试用版。尝试调用图采样测试并查看被调用函数的自身时间和总时间。没有更好的方法来调整你的程序,这样它就是最快的,而不是一个汇编天才(而且是一个真正特别的)。
有些人似乎对剖析师过敏。我曾经是其中之一,并且认为我最了解我的热点所在。对于明显的算法效率低下,我经常是正确的,但对于更多的微优化案例,实际上总是不正确的。只需在不改变任何逻辑的情况下重写函数(例如:重新排序,将特殊情况代码放在单独的非内联函数中等)可以使函数快十几倍,即使最好的反汇编专家通常也无法预测没有探查器。
至于仅仅依靠简单的时序测试,它们是非常有问题的。当前的测试并不是那么糟糕,但是编写时序测试是一个非常常见的错误,优化器将优化掉死代码并最终测试基本上完成nop甚至什么都不做的时间。你应该有一些知识来解释反汇编,以确保编译器没有这样做。
这样的时序测试也有可能显着偏向结果,因为很多只是涉及在同一个循环中反复运行代码,这往往只是测试代码在所有内存中的影响。缓存,所有分支预测都能完美地运行。它通常只是向您展示最佳案例场景,而不向您展示平均的真实案例。
根据现实世界的时间测试稍微好一些;更接近你的应用程序将在高层做的事情。它不会为您提供有关花费多少时间的具体信息,但这正是分析器的目的。
答案 6 :(得分:1)
我认为,任何有能力回答你所有问题的人都会非常忙于回答你的所有问题。在实践中,提出一个明确定义的问题可能更有效。通过这种方式,您可能希望得到明确的答案,您可以收集这些答案并开始走向智慧。
所以,无论如何,也许我可以回答你关于在Windows上使用哪个时钟的问题。
clock()不被视为高精度时钟。如果查看CLOCKS_PER_SEC的值,您会看到它的分辨率为1毫秒。只有在计时非常长的例程或具有10000次迭代的循环时,这才适用。正如您所指出的,如果您尝试重复一个简单的方法10000次,以便获得可以使用clock()测量的时间,编译器可能会介入并优化整个事情。
所以,实际上,唯一使用的时钟是QueryPerformanceCounter()
答案 7 :(得分:-3)
什?如何在没有分析器的情况下测量速度? 测量速度的行为是分析!问题相当于“我怎样才能编写自己的探查器?”答案显然是“不要”。
此外,你应该首先使用std::swap
,这样就完全无效了。
-1表示没有意义。