En supposant que nous avons un T myarray[100]
avec T = int, unsigned int, long long int ou unsigned long long int, quel est le moyen le plus rapide de réinitialiser tout son contenu à zéro (non seulement pour l'initialisation, mais pour réinitialiser le contenu plusieurs fois dans mon programme) ? Peut-être avec memset?
Même question pour un tableau dynamique comme T *myarray = new T[100]
.
new
c'est du C ++ ...memset
quand C ++ est en quelque sorte impliqué ... :)for
boucle. Mais, étonnamment, vous pouvez faire bien pire en essayant d'être intelligent.Réponses:
memset
(from<string.h>
) est probablement le moyen standard le plus rapide, car il s'agit généralement d'une routine écrite directement dans l'assemblage et optimisée à la main.À propos, en C ++, la manière idiomatique serait d'utiliser
std::fill
(from<algorithm>
):qui peut être optimisé automatiquement en un
memset
; Je suis tout à fait sûr que cela fonctionnera aussi vite quememset
pourint
s, alors qu'il peut fonctionner légèrement moins bien pour les types plus petits si l'optimiseur n'est pas assez intelligent. Pourtant, en cas de doute, profil.la source
memset
définirait un entier à 0; il n'y avait aucune déclaration spécifique que tous les bits-zéro est une représentation de0
. Un rectificatif technique a ajouté une telle garantie, qui est incluse dans la norme ISO C 2011. Je crois que tous les bits-zéro est une représentation valide de0
pour tous les types entiers dans toutes les implémentations C et C ++ existantes, c'est pourquoi le comité a pu ajouter cette exigence. (Il n'y a pas de garantie similaire pour les types à virgule flottante ou pointeur.)0
. (Avec les bits de remplissage, la possibilité existe que tous les bits à zéro pourraient être une représentation d'interruption). Mais dans tous les cas, le TC est censé reconnaître et remplacer le texte défectueux, donc à partir de 2004, nous devrions agir comme si C99 contenait toujours ce texte.int (*myarray)[N] = malloc(sizeof(*myarray));
.N
est la taille , mais dans la grande majorité des cas, si vous l'avez utilisé,malloc
vous ne le saviez qu'au moment de l'exécution.Cette question, bien qu'assez ancienne, a besoin de quelques repères, car elle ne demande pas la manière la plus idiomatique, ou la manière qui peut être écrite avec le moins de lignes, mais la manière la plus rapide . Et il est ridicule de répondre à cette question sans des tests réels. J'ai donc comparé quatre solutions, memset vs std :: fill vs ZERO de la réponse d'AnT vs une solution que j'ai créée en utilisant les intrinsèques AVX.
Notez que cette solution n'est pas générique, elle ne fonctionne que sur des données de 32 ou 64 bits. Veuillez commenter si ce code fait quelque chose d'incorrect.
Je ne prétendrai pas que c'est la méthode la plus rapide, car je ne suis pas un expert en optimisation de bas niveau. Il s'agit plutôt d'un exemple d'implémentation dépendante de l'architecture correcte qui est plus rapide que memset.
Maintenant, sur les résultats. J'ai calculé les performances pour les tableaux de taille 100 int et longs, à la fois alloués statiquement et dynamiquement, mais à l'exception de msvc, qui a éliminé le code mort sur les tableaux statiques, les résultats étaient extrêmement comparables, donc je ne montrerai que les performances des tableaux dynamiques. Les marquages de temps sont en ms pour 1 million d'itérations, en utilisant la fonction d'horloge de faible précision de time.h.
clang 3.8 (En utilisant le frontend clang-cl, les indicateurs d'optimisation = / OX / arch: AVX / Oi / Ot)
gcc 5.1.0 (indicateurs d'optimisation: -O3 -march = native -mtune = native -mavx):
msvc 2015 (indicateurs d'optimisation: / OX / arch: AVX / Oi / Ot):
Il se passe beaucoup de choses intéressantes ici: llvm tuant gcc, les optimisations irrégulières typiques de MSVC (il effectue une élimination impressionnante du code mort sur les tableaux statiques et a ensuite des performances terribles pour le remplissage). Bien que mon implémentation soit beaucoup plus rapide, cela peut être uniquement dû au fait qu'elle reconnaît que la compensation de bits a beaucoup moins de frais généraux que toute autre opération de réglage.
La mise en œuvre de Clang mérite plus d'être examinée, car elle est beaucoup plus rapide. Quelques tests supplémentaires montrent que son memset est en fait spécialisé pour zéro - les memsets non nuls pour un tableau de 400 octets sont beaucoup plus lents (~ 220 ms) et sont comparables à ceux de gcc. Cependant, le memsetting différent de zéro avec un tableau de 800 octets ne fait aucune différence de vitesse, ce qui explique probablement pourquoi dans ce cas, leur memset a des performances pires que mon implémentation - la spécialisation est uniquement pour les petits tableaux et la coupure est juste autour de 800 octets. Notez également que gcc 'fill' et 'ZERO' ne sont pas optimisés pour memset (en regardant le code généré), gcc génère simplement du code avec des caractéristiques de performances identiques.
Conclusion: memset n'est pas vraiment optimisé pour cette tâche et les gens le prétendraient (sinon le memset de gcc et msvc et llvm aurait les mêmes performances). Si les performances comptent, memset ne devrait pas être une solution finale, en particulier pour ces tableaux de taille moyenne maladroits, car il n'est pas spécialisé pour l'effacement de bits et il n'est pas optimisé à la main, pas mieux que le compilateur ne peut le faire seul.
la source
a
ajustement dans un registre. Ensuite, il boucle sur tous les blocs de 32 octets, qui devraient être entièrement écrasés en utilisant l'arithmétique du pointeur ((float *)((a)+x)
). Les deux intrinsèques (en commençant par_mm256
) créent simplement un registre de 32 octets initialisé à zéro et le stockent dans le pointeur actuel. Ce sont les 3 premières lignes. Le reste ne gère que tous les cas spéciaux où le dernier bloc de 32 octets ne doit pas être complètement écrasé. C'est plus rapide grâce à la vectorisation. - J'espère que ça aide.De
memset()
:Vous pouvez utiliser
sizeof(myarray)
si la taille demyarray
est connue au moment de la compilation. Sinon, si vous utilisez un tableau de taille dynamique, comme obtenu viamalloc
ounew
, vous devrez garder une trace de la longueur.la source
sizeof
est toujours évalué au moment de la compilation (et ne peut pas être utilisé avec les VLA). En C99, il peut s'agir d'une expression d'exécution dans le cas des VLA.c
etc++
. J'ai commenté la réponse d'Alex, qui dit: «Vous pouvez utiliser sizeof (myarray) si la taille de myarray est connue au moment de la compilation».Vous pouvez utiliser
memset
, mais uniquement parce que notre sélection de types est limitée aux types intégraux.Dans le cas général en C, il est logique d'implémenter une macro
Cela vous donnera une fonctionnalité de type C ++ qui vous permettra de "réinitialiser à zéro" un tableau d'objets de tout type sans avoir à recourir à des hacks comme
memset
. Fondamentalement, il s'agit d'un analogue C du modèle de fonction C ++, sauf que vous devez spécifier l'argument de type explicitement.En plus de cela, vous pouvez créer un «modèle» pour les tableaux non dégradés
Dans votre exemple, il serait appliqué comme
Il est également intéressant de noter que spécifiquement pour les objets de types scalaires, on peut implémenter une macro indépendante du type
et
transformer l'exemple ci-dessus en
la source
;
après lewhile(0)
, donc on peut appelerZERO(a,n);
, +1 bonne réponsedo{}while(0)
idiome n'exige aucun;
dans la définition de macro. Fixé.Pour la déclaration statique, je pense que vous pouvez utiliser:
Pour la déclaration dynamique, je suggère la même manière:
memset
la source
zero(myarray);
est tout ce dont vous avez besoin en C ++.Ajoutez simplement ceci à un en-tête:
la source
zero
est également correcte pour, par exemple,T=char[10]
comme cela pourrait être le cas lorsque l'arr
argument est un tableau multidimensionnel, par exemplechar arr[5][10]
.ARRAY_SIZE
macro, qui donne la mauvaise taille si elle est utilisée sur un tableau multidimensionnel, un meilleur nom serait peut-êtreARRAY_DIM<n>_SIZE
.Voici la fonction que j'utilise:
Vous pouvez l'appeler comme ceci:
Ci-dessus, il y a plus de C ++ 11 que d'utiliser memset. Vous obtenez également une erreur de compilation si vous utilisez un tableau dynamique avec la spécification de la taille.
la source