Kompletnym niezawodnym rozwiązaniem planowania wątków, które powinno dawać dokładnie takie same czasy w każdym teście, jest skompilowanie programu, aby był niezależny od systemu operacyjnego i uruchomienie komputera w celu uruchomienia programu w środowisku wolnym od systemu operacyjnego. Jest to jednak w dużej mierze niepraktyczne i co najwyżej trudne.
Dobrym substytutem przejścia bez systemu operacyjnego jest po prostu ustawienie koligacji bieżącego wątku na 1 rdzeń i priorytet na najwyższy. Ta alternatywa powinna zapewnić wystarczająco spójne wyniki.
Powinieneś także wyłączyć optymalizacje, które kolidowałyby z debugowaniem, co w przypadku g ++ lub gcc oznacza dodanie -Og
do wiersza poleceń , aby zapobiec optymalizacji testowanego kodu. -O0
Flaga nie powinna być stosowana, ponieważ wprowadza dodatkowe niepotrzebne obciążenie, które zostaną uwzględnione w wynikach pomiaru czasu, a tym samym pochylanie się czasowe szybkości kodu.
Wręcz przeciwnie, zarówno zakładając, że używasz -Ofast
(lub przynajmniej -O3
) w końcowej kompilacji produkcyjnej, jak i ignorując kwestię eliminacji „martwego” kodu, -Og
przeprowadza bardzo niewiele optymalizacji w porównaniu z -Ofast
; w ten sposób -Og
może fałszywie przedstawić rzeczywistą prędkość kodu w produkcie końcowym.
Co więcej, wszystkie testy szybkości (do pewnego stopnia) krzywoprzysięstwa: w skompilowanym produkcie końcowym -Ofast
każdy fragment / sekcja / funkcja kodu nie jest izolowana; raczej każdy fragment kodu nieustannie przepływa do następnego, umożliwiając w ten sposób kompilatorowi potencjalne łączenie, scalanie i optymalizowanie razem fragmentów kodu z każdego miejsca.
W tym samym czasie, jeśli porównujesz fragment kodu, który jest intensywnie używany realloc()
, fragment kodu może działać wolniej w produkcie produkcyjnym z wystarczająco dużą fragmentacją pamięci. Stąd wyrażenie „całość to więcej niż suma jej części” odnosi się do tej sytuacji, ponieważ kod w końcowej wersji produkcyjnej może działać zauważalnie szybciej lub wolniej niż pojedynczy fragment kodu, który szybko testujesz.
Częściowym rozwiązaniem, które może zmniejszyć niezgodność, jest użycie -Ofast
do testowania szybkości z dodatkiem asm volatile("" :: "r"(var))
do zmiennych biorących udział w teście w celu zapobieżenia eliminacji martwego kodu / pętli.
Oto przykład testu porównawczego funkcji pierwiastka kwadratowego na komputerze z systemem 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;
}
Również uznanie dla Mike'a Jarvisa za jego Timer.
Pamiętaj (jest to bardzo ważne), że jeśli zamierzasz uruchamiać większe fragmenty kodu, to naprawdę musisz zmniejszyć liczbę iteracji, aby zapobiec zawieszaniu się komputera.