Полное безошибочное решение для планирования потоков, которое должно давать одинаковое время для каждого теста, - это скомпилировать вашу программу так, чтобы она не зависела от ОС, и загрузила компьютер, чтобы запустить программу в среде, свободной от ОС. Тем не менее, это в значительной степени непрактично и в лучшем случае будет затруднительно.
Хорошая замена отказу от ОС - просто установить привязку текущего потока к 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;
}
Также благодарю Майка Джарвиса за его таймер.
Обратите внимание (это очень важно), что если вы собираетесь запускать большие фрагменты кода, вам действительно необходимо уменьшить количество итераций, чтобы ваш компьютер не завис.