Une solution complète et sans faille à la planification des threads, qui devrait donner exactement les mêmes temps pour chaque test, consiste à compiler votre programme pour qu'il soit indépendant du système d'exploitation et à démarrer votre ordinateur afin d'exécuter le programme dans un environnement sans OS. Pourtant, cela est largement impraticable et serait au mieux difficile.
Un bon substitut à la suppression du système d'exploitation consiste simplement à définir l'affinité du thread actuel sur 1 cœur et la priorité sur la plus élevée. Cette alternative devrait fournir des résultats suffisamment cohérents.
Vous devez également désactiver les optimisations qui interféreraient avec le débogage, ce qui pour g ++ ou gcc signifie ajouter -Og
à la ligne de commande , pour éviter que le code testé ne soit optimisé. Le -O0
drapeau ne doit pas être utilisé car il introduit une surcharge inutile supplémentaire qui serait incluse dans les résultats de synchronisation, faussant ainsi la vitesse chronométrée du code.
Au contraire, à la fois en supposant que vous utilisez -Ofast
(ou, à tout le moins, -O3
) sur la version de production finale et en ignorant le problème de l'élimination du code "mort", -Og
effectue très peu d'optimisations par rapport à -Ofast
; -Og
peut ainsi dénaturer la vitesse réelle du code dans le produit final.
En outre, tous les tests de vitesse (dans une certaine mesure) parjure: dans le produit de production final compilé avec -Ofast
, chaque extrait / section / fonction de code n'est pas isolé; au contraire, chaque extrait de code s'écoule en continu dans le suivant, permettant ainsi au compilateur de potentiellement joindre, fusionner et optimiser ensemble des morceaux de code de partout.
Dans le même temps, si vous comparez un extrait de code qui fait un usage realloc()
intensif, alors l'extrait de code peut s'exécuter plus lentement dans un produit de production avec une fragmentation de mémoire suffisamment élevée. Par conséquent, l'expression «le tout est plus que la somme de ses parties» s'applique à cette situation car le code de la version de production finale peut s'exécuter sensiblement plus rapidement ou plus lentement que l'extrait de code individuel que vous testez en vitesse.
Une solution partielle qui peut réduire l'incongruité consiste à utiliser -Ofast
pour les tests de vitesse AVEC l'ajout de asm volatile("" :: "r"(var))
aux variables impliquées dans le test pour empêcher l'élimination du code mort / boucle.
Voici un exemple de comparaison des fonctions de racine carrée sur un ordinateur 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;
}
Aussi, merci à Mike Jarvis pour son chronomètre.
Veuillez noter (ceci est très important) que si vous allez exécuter des extraits de code plus volumineux, vous devez vraiment réduire le nombre d'itérations pour éviter que votre ordinateur ne se fige.