web-dev-qa-db-fra.com

Réinitialiser le tableau C int à zéro: le moyen le plus rapide?

En supposant que nous ayons 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 le réinitialiser plusieurs fois dans mon programme)? Peut-être avec memset?

Même question pour un tableau dynamique comme T *myarray = new T[100].

90
Vincent

memset (de <string.h>) est probablement le moyen standard le plus rapide, car il s’agit généralement d’une routine écrite directement dans Assembly et optimisée à la main.

memset(myarray, 0, sizeof(myarray)); // for automatically-allocated arrays
memset(myarray, 0, N*sizeof(*myarray)); // for heap-allocated arrays, where N is the number of elements

À propos, en C++, la méthode idiomatique serait d’utiliser std::fill (de <algorithm>):

std::fill(myarray, myarray+N, 0);

qui peut être optimisé automatiquement dans un memset; Je suis sûr que cela fonctionnera aussi vite que memset pour ints, alors que les performances pourraient être légèrement moins bonnes pour les types plus petits si l'optimiseur n'est pas assez intelligent. Cependant, en cas de doute, profilez.

154
Matteo Italia

Cette question, bien qu’ancienne, nécessite quelques repères, car elle ne demande pas la manière la plus idiomatique, ni la manière qui peut être écrite avec le moins de lignes possible, mais la manière la plus rapide. Et il est stupide de répondre à cette question sans faire d’essais concrets. J'ai donc comparé quatre solutions, memset et std :: fill, contre ZERO de la réponse de AnT à une solution que j'ai développée à l'aide de la technique intrinsèque 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 est en train de faire quelque chose de incorrect.

#include<immintrin.h>
#define intrin_ZERO(a,n){\
size_t x = 0;\
const size_t inc = 32 / sizeof(*(a));/*size of 256 bit register over size of variable*/\
for (;x < n-inc;x+=inc)\
    _mm256_storeu_ps((float *)((a)+x),_mm256_setzero_ps());\
if(4 == sizeof(*(a))){\
    switch(n-x){\
    case 3:\
        (a)[x] = 0;x++;\
    case 2:\
        _mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
    case 1:\
        (a)[x] = 0;\
        break;\
    case 0:\
        break;\
    };\
}\
else if(8 == sizeof(*(a))){\
switch(n-x){\
    case 7:\
        (a)[x] = 0;x++;\
    case 6:\
        (a)[x] = 0;x++;\
    case 5:\
        (a)[x] = 0;x++;\
    case 4:\
        _mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
    case 3:\
        (a)[x] = 0;x++;\
    case 2:\
        ((long long *)(a))[x] = 0;break;\
    case 1:\
        (a)[x] = 0;\
        break;\
    case 0:\
        break;\
};\
}\
}

Je ne prétends pas qu'il s'agit de 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 correcte dépendant de l'architecture et plus rapide que memset.

Maintenant, sur les résultats. J'ai calculé les performances pour les tableaux de taille 100 long et long, 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. Je ne montrerai donc que les performances de tableau dynamique. 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 (Utilisation de l'interface de clang-cl, indicateurs d'optimisation =/OX/Arch: AVX/Oi/Ot)

int:
memset:      99
fill:        97
ZERO:        98
intrin_ZERO: 90

long long:
memset:      285
fill:        286
ZERO:        285
intrin_ZERO: 188

gcc 5.1.0 (drapeaux d'optimisation: -O3 -march = natif -mtune = natif -mavx):

int:
memset:      268
fill:        268
ZERO:        268
intrin_ZERO: 91
long long:
memset:      402
fill:        399
ZERO:        400
intrin_ZERO: 185

msvc 2015 (drapeaux d'optimisation:/OX/Arch: AVX/Oi/Ot):

int
memset:      196
fill:        613
ZERO:        221
intrin_ZERO: 95
long long:
memset:      273
fill:        559
ZERO:        376
intrin_ZERO: 188

Il y a beaucoup de choses intéressantes à faire ici: llvm tuant gcc, optimisations inégales typiques de MSVC (il élimine de manière impressionnante le code mort sur les tableaux statiques et a ensuite une performance affreuse pour le remplissage). Bien que mon implémentation soit nettement plus rapide, c'est peut-être uniquement parce qu'il reconnaît que l'effacement des bits entraîne une charge beaucoup moins importante que toute autre opération de réglage.

La mise en œuvre de Clang mérite une plus grande attention, car elle est nettement plus rapide. Certains tests supplémentaires montrent que son memset est en fait spécialisé pour les memsets zéro - non nuls pour une matrice de 400 octets, sont beaucoup plus lents (~ 220 ms) et sont comparables à ceux de gcc. Cependant, le memsetting non nul avec un tableau de 800 octets ne fait aucune différence de vitesse, ce qui explique probablement pourquoi, dans ce cas, leurs performances sont plus mauvaises que celles de mon implémentation: la spécialisation concerne uniquement les petits tableaux et la coupure se situe aux alentours de 800 octets. Notez également que gcc "fill" et "ZERO" ne optimisent pas 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 aussi bien que les gens le prétendent (sinon, mccet de gcc et msvc et llvm aurait les mêmes performances). Si les performances importent, alors memset ne devrait pas constituer une solution définitive, en particulier pour ces tableaux de taille moyenne, car il n'est pas spécialisé dans le nettoyage de bits, et il n'est pas optimisé à la main mieux que ce que le compilateur peut faire par lui-même.

14
Benjamin

De memset() :

memset(myarray, 0, sizeof(myarray));

Vous pouvez utiliser sizeof(myarray) si la taille de myarray est connue au moment de la compilation. Sinon, si vous utilisez un tableau de taille dynamique, tel que celui obtenu via malloc ou new, vous devrez suivre la longueur.

11
Alex Reynolds

Vous pouvez utiliser memset, mais uniquement parce que notre sélection de types est limitée aux types intégraux.

En général, en C, il est logique de mettre en œuvre une macro

#define ZERO_ANY(T, a, n) do{\
   T *a_ = (a);\
   size_t n_ = (n);\
   for (; n_ > 0; --n_, ++a_)\
     *a_ = (T) { 0 };\
} while (0)

Cela vous donnera une fonctionnalité de type C++ qui vous permettra de "réinitialiser en zéros" un tableau d'objets de tout type sans avoir à recourir à des hacks comme memset. Fondamentalement, il s’agit d’un modèle C analogique de modèle de fonction C++, sauf que vous devez spécifier explicitement l’argument type.

En plus de cela, vous pouvez construire un "modèle" pour les tableaux non-décomposés

#define ARRAY_SIZE(a) (sizeof (a) / sizeof *(a))
#define ZERO_ANY_A(T, a) ZERO_ANY(T, (a), ARRAY_SIZE(a))

Dans votre exemple, il serait appliqué comme

int a[100];

ZERO_ANY(int, a, 100);
// or
ZERO_ANY_A(int, a);

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.

#define ZERO(a, n) do{\
   size_t i_ = 0, n_ = (n);\
   for (; i_ < n_; ++i_)\
     (a)[i_] = 0;\
} while (0)

et

#define ZERO_A(a) ZERO((a), ARRAY_SIZE(a))

transformer l'exemple ci-dessus en

 int a[100];

 ZERO(a, 100);
 // or
 ZERO_A(a);
5
AnT

Pour la déclaration statique, je pense que vous pourriez utiliser:

T myarray[100] = {0};

Pour la déclaration dynamique, je suggère la même manière: memset

3
Bruno Soares

zero(myarray); est tout ce dont vous avez besoin en C++.

Ajoutez simplement ceci à un en-tête:

template<typename T, size_t SIZE> inline void zero(T(&arr)[SIZE]){
    memset(arr, 0, SIZE*sizeof(T));
}
2
Navin

Voici la fonction que j'utilise:

template<typename T>
static void setValue(T arr[], size_t length, const T& val)
{
    std::fill(arr, arr + length, val);
}

template<typename T, size_t N>
static void setValue(T (&arr)[N], const T& val)
{
    std::fill(arr, arr + N, val);
}

Vous pouvez l'appeler comme ça:

//fixed arrays
int a[10];
setValue(a, 0);

//dynamic arrays
int *d = new int[length];
setValue(d, length, 0);

Ce qui précède est plus un moyen C++ 11 que d’utiliser memset. Aussi, vous obtenez une erreur de compilation si vous utilisez un tableau dynamique en spécifiant la taille.

1
Shital Shah