각 테스트마다 정확히 동일한 시간을 산출해야하는 스레드 스케줄링에 대한 완벽한 솔루션은 프로그램을 OS 독립적으로 컴파일하고 OS가없는 환경에서 프로그램을 실행하기 위해 컴퓨터를 부팅하는 것입니다. 그러나 이것은 대체로 비실용적이며 기껏해야 어려울 것입니다.
OS를 사용하지 않는 좋은 대안은 현재 스레드의 선호도를 1 코어로 설정하고 우선 순위를 가장 높게 설정하는 것입니다. 이 대안은 일관된 충분한 결과를 제공해야합니다.
또한 디버깅을 방해하는 최적화를 꺼야합니다. g ++ 또는 gcc의 경우 테스트중인 코드가 최적화되지 않도록 명령 줄에 추가 -Og
하는 것을 의미 합니다. 이 -O0
플래그는 타이밍 결과에 포함될 불필요한 추가 오버 헤드를 도입하여 코드의 시간 제한 속도를 왜곡하므로 사용해서는 안됩니다.
반대로 최종 프로덕션 빌드 -Ofast
에서 사용 (또는 최소한 -O3
) 한다고 가정하고 "죽은"코드 제거 문제를 무시하면 다음 -Og
과 비교하여 매우 적은 최적화를 수행합니다 -Ofast
. 따라서 -Og
최종 제품에서 코드의 실제 속도를 잘못 나타낼 수 있습니다.
또한 모든 속도 테스트는 (어느 정도까지) 위증이됩니다.로 컴파일 된 최종 프로덕션 제품 -Ofast
에서 코드의 각 스 니펫 / 섹션 / 기능은 격리되지 않습니다. 오히려 각 코드 조각이 계속해서 다음 코드로 흘러가므로 컴파일러가 모든 곳에서 코드 조각을 결합, 병합 및 최적화 할 수 있습니다.
동시에를 많이 사용하는 코드 조각을 벤치마킹하는 경우 realloc()
메모리 조각화가 충분히 높은 프로덕션 제품에서 코드 조각이 느리게 실행될 수 있습니다. 따라서 최종 프로덕션 빌드의 코드가 속도 테스트중인 개별 스 니펫보다 눈에 띄게 더 빠르거나 느리게 실행될 수 있기 때문에 "전체가 부분의 합보다 크다"라는 표현이이 상황에 적용됩니다.
불일치를 줄일 수있는 부분적인 솔루션은 데드 코드 / 루프 제거를 방지하기 위해 테스트에 포함 된 변수를 -Ofast
추가 하여 속도 테스트에 사용 하는 것입니다 asm volatile("" :: "r"(var))
.
다음은 Windows 컴퓨터에서 제곱근 함수를 벤치마킹하는 방법의 예입니다.
// set USE_ASM_TO_PREVENT_ELIMINATION to 0 to prevent `asm volatile("" :: "r"(var))`
// set USE_ASM_TO_PREVENT_ELIMINATION to 1 to enforce `asm volatile("" :: "r"(var))`
#define USE_ASM_TO_PREVENT_ELIMINATION 1
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <chrono>
#include <cmath>
#include <windows.h>
#include <intrin.h>
#pragma intrinsic(__rdtsc)
#include <cstdint>
class Timer {
public:
Timer() : beg_(clock_::now()) {}
void reset() { beg_ = clock_::now(); }
double elapsed() const {
return std::chrono::duration_cast<second_>
(clock_::now() - beg_).count(); }
private:
typedef std::chrono::high_resolution_clock clock_;
typedef std::chrono::duration<double, std::ratio<1> > second_;
std::chrono::time_point<clock_> beg_;
};
unsigned int guess_sqrt32(register unsigned int n) {
register unsigned int g = 0x8000;
if(g*g > n) {
g ^= 0x8000;
}
g |= 0x4000;
if(g*g > n) {
g ^= 0x4000;
}
g |= 0x2000;
if(g*g > n) {
g ^= 0x2000;
}
g |= 0x1000;
if(g*g > n) {
g ^= 0x1000;
}
g |= 0x0800;
if(g*g > n) {
g ^= 0x0800;
}
g |= 0x0400;
if(g*g > n) {
g ^= 0x0400;
}
g |= 0x0200;
if(g*g > n) {
g ^= 0x0200;
}
g |= 0x0100;
if(g*g > n) {
g ^= 0x0100;
}
g |= 0x0080;
if(g*g > n) {
g ^= 0x0080;
}
g |= 0x0040;
if(g*g > n) {
g ^= 0x0040;
}
g |= 0x0020;
if(g*g > n) {
g ^= 0x0020;
}
g |= 0x0010;
if(g*g > n) {
g ^= 0x0010;
}
g |= 0x0008;
if(g*g > n) {
g ^= 0x0008;
}
g |= 0x0004;
if(g*g > n) {
g ^= 0x0004;
}
g |= 0x0002;
if(g*g > n) {
g ^= 0x0002;
}
g |= 0x0001;
if(g*g > n) {
g ^= 0x0001;
}
return g;
}
unsigned int empty_function( unsigned int _input ) {
return _input;
}
unsigned long long empty_ticks=0;
double empty_seconds=0;
Timer my_time;
template<unsigned int benchmark_repetitions>
void benchmark( char* function_name, auto (*function_to_do)( auto ) ) {
register unsigned int i=benchmark_repetitions;
register unsigned long long start=0;
my_time.reset();
start=__rdtsc();
while ( i-- ) {
auto result = (*function_to_do)( i << 7 );
#if USE_ASM_TO_PREVENT_ELIMINATION == 1
asm volatile("" :: "r"(
// There is no data type in C++ that is smaller than a char, so it will
// not throw a segmentation fault error to reinterpret any arbitrary
// data type as a char. Although, the compiler might not like it.
result
));
#endif
}
if ( function_name == nullptr ) {
empty_ticks = (__rdtsc()-start);
empty_seconds = my_time.elapsed();
std::cout<< "Empty:\n" << empty_ticks
<< " ticks\n" << benchmark_repetitions << " repetitions\n"
<< std::setprecision(15) << empty_seconds
<< " seconds\n\n";
} else {
std::cout<< function_name<<":\n" << (__rdtsc()-start-empty_ticks)
<< " ticks\n" << benchmark_repetitions << " repetitions\n"
<< std::setprecision(15) << (my_time.elapsed()-empty_seconds)
<< " seconds\n\n";
}
}
int main( void ) {
void* Cur_Thread= GetCurrentThread();
void* Cur_Process= GetCurrentProcess();
unsigned long long Current_Affinity;
unsigned long long System_Affinity;
unsigned long long furthest_affinity;
unsigned long long nearest_affinity;
if( ! SetThreadPriority(Cur_Thread,THREAD_PRIORITY_TIME_CRITICAL) ) {
SetThreadPriority( Cur_Thread, THREAD_PRIORITY_HIGHEST );
}
if( ! SetPriorityClass(Cur_Process,REALTIME_PRIORITY_CLASS) ) {
SetPriorityClass( Cur_Process, HIGH_PRIORITY_CLASS );
}
GetProcessAffinityMask( Cur_Process, &Current_Affinity, &System_Affinity );
furthest_affinity = 0x8000000000000000ULL>>__builtin_clzll(Current_Affinity);
nearest_affinity = 0x0000000000000001ULL<<__builtin_ctzll(Current_Affinity);
SetProcessAffinityMask( Cur_Process, furthest_affinity );
SetThreadAffinityMask( Cur_Thread, furthest_affinity );
const int repetitions=524288;
benchmark<repetitions>( nullptr, empty_function );
benchmark<repetitions>( "Standard Square Root", standard_sqrt );
benchmark<repetitions>( "Original Guess Square Root", original_guess_sqrt32 );
benchmark<repetitions>( "New Guess Square Root", new_guess_sqrt32 );
SetThreadPriority( Cur_Thread, THREAD_PRIORITY_IDLE );
SetPriorityClass( Cur_Process, IDLE_PRIORITY_CLASS );
SetProcessAffinityMask( Cur_Process, nearest_affinity );
SetThreadAffinityMask( Cur_Thread, nearest_affinity );
for (;;) { getchar(); }
return 0;
}
또한 Timer에 대한 Mike Jarvis의 공로를 인정합니다.
더 큰 코드 조각을 실행하려는 경우 컴퓨터가 중단되는 것을 방지하기 위해 반복 횟수를 줄여야한다는 점에 유의하십시오 (매우 중요합니다).