Pourquoi un système 32 bits besoin de 4 fois plus de temps pour enregistrer des valeurs de 32 bits que pour économiser 8-bit valeurs?
Il n'a pas. Mais il existe 3 types de problèmes avec votre référence que vous donner ces résultats.
- Vous n'êtes pas pré-failles de la mémoire. Si vous êtes à la page des failles d'effondrement du cours des tableaux de référence. Ces défauts de page avec le noyau de système d'exploitation de l'interaction sont un facteur dominant dans le temps.
- Le compilateur
-O3
est complètement à battre l'indice de référence de convertir tous vos boucles en memset()
.
- Votre indice de référence liés à la mémoire. Si vous êtes à la mesure de la vitesse de votre mémoire à la place du cœur.
Problème 1: Les Données de Test n'est pas Prefaulted
Vos tableaux sont déclarées, mais n'est pas utilisée avant l'indice de référence. En raison de la manière dont le noyau et l'allocation de la mémoire des œuvres, ils ne sont pas mappés en mémoire encore. C'est seulement lorsque vous les touchez cela se passe. Et quand il le fait, il contracte une très grande peine à partir du noyau de la carte de la page.
Cela peut être fait en touchant tous les tableaux avant de l'indice de référence.
Pas de Pré-Défaillant: http://coliru.stacked-crooked.com/a/1df1f3f9de420d18
g++ -O3 -Wall main.cpp && ./a.out
Time of processing int8 array: 28983us.
Time of processing int16 array: 57100us.
Time of processing int32 array: 113361us.
Time of processing int64 array: 224451us.
Avec Pré-Défaillant: http://coliru.stacked-crooked.com/a/7e62b9c7ca19c128
g++ -O3 -Wall main.cpp && ./a.out
Time of processing int8 array: 6216us.
Time of processing int16 array: 12472us.
Time of processing int32 array: 24961us.
Time of processing int64 array: 49886us.
Le temps de chute de près d'un facteur 4. En d'autres termes, l'original de référence a été de mesurer plus du noyau que le code réel.
Problème 2: Le Compilateur est de Vaincre l'indice de Référence
Le compilateur est reconnaissant de votre motif de l'écriture de zéros et de l'est de remplacer complètement tous vos boucles avec des appels à des memset()
. Donc en effet, vous êtes en mesure d'appels d' memset()
avec des tailles différentes.
call std::chrono::_V2::system_clock::now()
xor esi, esi
mov edx, 67108864
mov edi, OFFSET FLAT:int8Array
mov r14, rax
call memset
call std::chrono::_V2::system_clock::now()
xor esi, esi
mov edx, 134217728
mov edi, OFFSET FLAT:int16Array
mov r13, rax
call memset
call std::chrono::_V2::system_clock::now()
xor esi, esi
mov edx, 268435456
mov edi, OFFSET FLAT:int32Array
mov r12, rax
call memset
call std::chrono::_V2::system_clock::now()
xor esi, esi
mov edx, 536870912
mov edi, OFFSET FLAT:int64Array
mov rbp, rax
call memset
call std::chrono::_V2::system_clock::now()
L'optimisation de la faire, c'est -ftree-loop-distribute-patterns
. Même si vous désactiver cette fonction, le vectorizer vous donnera un effet similaire.
Avec -O2
, la vectorisation et la reconnaissance des formes sont tous deux désactivés. Ainsi, le compilateur vous donne ce que vous écrivez.
.L4:
mov BYTE PTR [rax], 0 ;; <<------ 1 byte at a time
add rax, 1
cmp rdx, rax
jne .L4
call std::chrono::_V2::system_clock::now()
mov rbp, rax
mov eax, OFFSET FLAT:int16Array
lea rdx, [rax+134217728]
.L5:
xor ecx, ecx
add rax, 2
mov WORD PTR [rax-2], cx ;; <<------ 2 bytes at a time
cmp rdx, rax
jne .L5
call std::chrono::_V2::system_clock::now()
mov r12, rax
mov eax, OFFSET FLAT:int32Array
lea rdx, [rax+268435456]
.L6:
mov DWORD PTR [rax], 0 ;; <<------ 4 bytes at a time
add rax, 4
cmp rax, rdx
jne .L6
call std::chrono::_V2::system_clock::now()
mov r13, rax
mov eax, OFFSET FLAT:int64Array
lea rdx, [rax+536870912]
.L7:
mov QWORD PTR [rax], 0 ;; <<------ 8 bytes at a time
add rax, 8
cmp rdx, rax
jne .L7
call std::chrono::_V2::system_clock::now()
Avec -O2
: http://coliru.stacked-crooked.com/a/edfdfaaf7ec2882e
g++ -O2 -Wall main.cpp && ./a.out
Time of processing int8 array: 28414us.
Time of processing int16 array: 22617us.
Time of processing int32 array: 32551us.
Time of processing int64 array: 56591us.
Maintenant, il est clair que le plus petit mot tailles sont plus lents. Mais vous attendez à ce que le temps d'être à plat si tous le mot tailles ont la même vitesse. Et la raison pour laquelle ils ne sont pas est à cause de la bande passante mémoire.
Problème 3: Bande Passante De La Mémoire
Parce que l'indice de référence (comme l'écrit) est seulement écrit des zéros, il est facile de saturer la bande passante de la mémoire pour le core/système. Donc, la référence devient affectée par la quantité de mémoire est touché.
Pour remédier à cela, nous avons besoin pour réduire l'ensemble de données afin qu'il s'intègre dans le cache. Pour compenser cela, nous avons en boucle sur les mêmes données plusieurs fois.
std::array<std::int8_t, 512> int8Array;
std::array<std::int16_t, 512> int16Array;
std::array<std::int32_t, 512> int32Array;
std::array<std::int64_t, 512> int64Array;
...
auto point1 = std::chrono::high_resolution_clock::now();
for (int c = 0; c < 64 * 1024; c++) for (auto &v : int8Array) v = 0;
auto point2 = std::chrono::high_resolution_clock::now();
for (int c = 0; c < 64 * 1024; c++) for (auto &v : int16Array) v = 0;
auto point3 = std::chrono::high_resolution_clock::now();
for (int c = 0; c < 64 * 1024; c++) for (auto &v : int32Array) v = 0;
auto point4 = std::chrono::high_resolution_clock::now();
for (int c = 0; c < 64 * 1024; c++) for (auto &v : int64Array) v = 0;
auto point5 = std::chrono::high_resolution_clock::now();
Maintenant, nous voyons les délais qui sont beaucoup plus plats pour les différents mot-tailles:
http://coliru.stacked-crooked.com/a/f534f98f6d840c5c
g++ -O2 -Wall main.cpp && ./a.out
Time of processing int8 array: 20487us.
Time of processing int16 array: 21965us.
Time of processing int32 array: 32569us.
Time of processing int64 array: 26059us.
La raison pourquoi il n'est pas complètement à plat est probablement parce qu'il y a de nombreux autres facteurs impliqués avec les optimisations du compilateur. Vous pourriez avoir besoin de recourir à la boucle-déroulage de s'en approcher.