J'ai appris que memset(ptr, 0, nbytes)
est vraiment rapide, mais existe-t-il un moyen plus rapide (au moins sur x86) ?
Je suppose que memset utilise mov
cependant, lors de la mise à zéro de la mémoire, la plupart des compilateurs utilisent xor
car c'est plus rapide, correct ? edit1 : Faux, comme l'a souligné GregS, cela ne fonctionne qu'avec les registres. Qu'est-ce qui m'a pris ?
J'ai également demandé à une personne qui connaissait mieux l'assembleur que moi de regarder la stdlib, et il m'a dit que sur x86, memset ne profite pas pleinement des registres de 32 bits. Cependant, à ce moment-là, j'étais très fatigué, donc je ne suis pas sûr d'avoir compris correctement.
edit2 : J'ai réexaminé cette question et fait quelques tests. Voici ce que j'ai testé :
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <sys/time.h>
#define TIME(body) do { \
struct timeval t1, t2; double elapsed; \
gettimeofday(&t1, NULL); \
body \
gettimeofday(&t2, NULL); \
elapsed = (t2.tv_sec - t1.tv_sec) * 1000.0 + (t2.tv_usec - t1.tv_usec) / 1000.0; \
printf("%s\n --- %f ---\n", #body, elapsed); } while(0) \
#define SIZE 0x1000000
void zero_1(void* buff, size_t size)
{
size_t i;
char* foo = buff;
for (i = 0; i < size; i++)
foo[i] = 0;
}
/* I foolishly assume size_t has register width */
void zero_sizet(void* buff, size_t size)
{
size_t i;
char* bar;
size_t* foo = buff;
for (i = 0; i < size / sizeof(size_t); i++)
foo[i] = 0;
// fixes bug pointed out by tristopia
bar = (char*)buff + size - size % sizeof(size_t);
for (i = 0; i < size % sizeof(size_t); i++)
bar[i] = 0;
}
int main()
{
char* buffer = malloc(SIZE);
TIME(
memset(buffer, 0, SIZE);
);
TIME(
zero_1(buffer, SIZE);
);
TIME(
zero_sizet(buffer, SIZE);
);
return 0;
}
les résultats :
zero_1 est le plus lent, sauf pour -O3. zero_sizet est le plus rapide avec des performances à peu près égales entre -O1, -O2 et -O3. memset a toujours été plus lent que zero_sizet. (deux fois plus lent pour -O3). une chose intéressante est qu'à -O3 zero_1 était aussi rapide que zero_sizet. cependant la fonction désassemblée avait environ quatre fois plus d'instructions (je pense que c'est dû au déroulement de la boucle). De plus, j'ai essayé d'optimiser davantage zero_sizet, mais le compilateur m'a toujours surpassé, mais ce n'est pas une surprise.
Pour l'instant, memset gagne, les résultats précédents étaient faussés par le cache du CPU. (tous les tests ont été effectués sous Linux) Des tests supplémentaires sont nécessaires. Je vais essayer l'assembleur ensuite :)
edit3 : correction d'un bug dans le code de test, les résultats du test ne sont pas affectés
edit4 : En fouillant dans le runtime C de VS2010 désassemblé, j'ai constaté que memset
a une routine optimisée SSE pour le zéro. Il sera difficile de la battre.
5 votes
Au lieu de supposer que
memset
utilisemov
pourquoi ne pas essayer de désassembler la sortie de votre compilateur ? Des compilateurs différents font des choses différentes. Sixor
est plus rapide sur une architecture donnée, alors il ne serait pas surprenant que certains compilateurs optimisentmemset(ptr, 0, nbytes)
enxor
des instructions.14 votes
Je n'ai pas connaissance d'un compilateur qui utilise XOR pour mettre la mémoire à zéro. Peut-être un registre, mais pas la mémoire. Afin d'utiliser XOR pour mettre la mémoire à zéro, vous devez d'abord lire la mémoire, puis XOR, puis écrire la mémoire.
5 votes
Le cas échéant,
calloc
peut être effectivement libre, car l'implémentation peut mettre à zéro les pages à l'avance, alors que le CPU est par ailleurs inactif. Cela compte-t-il ? ;-)0 votes
@Greg Le compilateur Intel aime initialiser les registres avec XOR.
1 votes
@aaa carpe, GregS a raison de dire que l'utilisation d'un XOR pour effacer la mémoire ne sera presque jamais la bonne réponse. L'exception qui vient à l'esprit est celle des plus petits processeurs PIC de Microchip, où toute la RAM disponible n'est en fait qu'un grand champ de registres d'usage général. Effacer un registre avec un XOR est courant dans les architectures CISC. Certaines architectures RISC incluent effectivement un registre d'usage général qui est câblé pour contenir zéro juste pour cet usage.
0 votes
Votre
zero_sizet
triche en ne faisant pas tout le travailmemset
fait. Ils ne sont équivalents que si vos copiés sont des mutliples desizeof (size_t)
. Dans les autres casmemset
doit également annuler les 1 à 3(7) octets restants. Un autre problème peut survenir si votre adresse n'est pas alignée, un bonmemset
s'efforcera d'aligner sessize_t *
avant de boucler. Cela pénalise un peu dans le bon cas, mais gagne beaucoup dans le cas impair. Une bonne implémentation choisira également l'alignement 64 bits, car le bus mémoire des PC est de 64 bits depuis l'époque du Pentium 1.0 votes
@tristopia vous avez raison, j'ai manqué ça. D'après mon expérience, l'alignement ne semble pas être un gros problème avec les processeurs x86 modernes. Comme je l'ai dit, j'ai essayé quelques astuces mais je me suis toujours retrouvé dans le chemin du compilateur.
0 votes
Je réalise que je suis un peu en retard à la fête, mais si vous vraiment se soucier d'effacer une tonne de mémoire très rapidement et vous êtes sur POSIX et vous savez que votre mémoire est alignée sur une page de 4k (ou vous êtes prêt à le faire),
madvise
avecMADV_DONTNEED
effacera très rapidement d'énormes sections de la mémoire. Cela vaut probablement la peine de noter que cela dépend fortement de la plate-forme, donc vérifiez d'abord comment votre noyau réagit à cette opération :-)2 votes
@TravisGockel Aussi très imprudent :
madvise()
peut juste être un non-retour. Cela peut fonctionner sur un version spécifique du noyau et de la libc mais il semble qu'il pourrait facilement se casser si vous mettez à niveau l'un ou l'autre.0 votes
"J'ai appris que memset(ptr, 0, nbytes) est vraiment rapide, mais existe-t-il un moyen plus rapide..." - l'autre question à se poser, c'est
memset(ptr, 0, nbytes)
garanti de survivre à l'optimisation. Ce cas d'utilisation consiste à réduire à zéro le matériel sensible en mémoire. La réponse à cela est NON .memset_s
est garanti pour survivre aux optimisations, mais les gens de la Glibc refusent de le fournir. Voir aussi Problème 17879 : La bibliothèque manque de memset_s .0 votes
Essayer de faire une boucle inverse --i