6
votes

Multiplication rapide des valeurs dans un tableau

Y a-t-il un moyen rapide de multiplier des valeurs d'une matrice flottante en C ++, pour optimiser cette fonction (où comptage code> est un multiple de 4):

void multiply(float* values, float factor, int count)
{
    for(int i=0; i < count; i++)
    {
        *value *= factor;
        value++;
    }
}


5 commentaires

Vous semblez déjà connaître la réponse. Êtes-vous coincé d'une certaine manière ou attendez-vous simplement que quelqu'un d'autre écrit le code pour vous?


Ce n'est pas louer-A-coder!


Quelle est la taille de la matrice (> 1,> 10,> 100,> 1000,> 10000)? Vous envisagez d'utiliser l'optimisation de plusieurs cœurs (threads) dans votre cas? Des contraintes sont-elles connues à propos de la matrice à l'avance, d'autres décompte ensuite être multiple de 4?


Ou peut-être l'OP sur-estimé que le travail nécessaire pour commencer à utiliser SSE!


Question: est float * valeurs aligné sur 16 octets? Si oui, des instructions de charge / magasin alignées peuvent être utilisées et constitue une bonne différence de vitesse. (Généralement une fonction devrait fournir à la fois des options et choisir au moment de l'exécution en testant le décalage du pointeur)


7 Réponses :


2
votes

Si vous souhaitez que votre code soit multiplate-forme, vous allez devoir écrire un code indépendant de la plate-forme, ou vous allez devoir écrire une charge de #Ifdef s.

Avez-vous essayé une boucle manuelle déroulante et voyez si cela fait une différence?


0 commentaires

2
votes

Puisque vous savez que le nombre code> est un multiple de 4, vous pouvez vous dérouler votre boucle ...

void multiply(float* values, float factor, int count)
{
    count = count >> 2; // count / 4
    for(int i=0; i < count ; i++)
    {
        *value *= factor;
        *(value+1) *= factor;
        *(value+2) *= factor;
        *(value+3) *= factor;
        value += 4;
    }
}


8 commentaires

Cela ne sera presque certainement pas plus rapide, car il effectue la même quantité de multiplications, avec un pointeur plus complexe arithmétique que l'original. Je serais intéressé de voir vos mesures pour soutenir cela étant une amélioration.


GCC est-ce avec -Funroll-boucles .


@Steve: Cela pourrait bien faire une différence, en fonction de la qualité du compilateur (et de la qualité du prédicteur de la branche de la CPU). Le rapport multiplie à des branches conditionnelles a augmenté de 1: 1 à 4: 1.


@ALEX: GCC ne sait pas que le nombre est un multiple de 4, ce qui pourrait donc être légèrement plus rapide. Il peut aussi être aussi plus rapide pour compter jusqu'à zéro ( pour (int i = compte / 4; i! = 0; - i) ), et pour incrémenter le pointeur sur chaque ligne ( * valeur ++ * = facteur ).


@Steve, ma version Horlocked 0.731S , l'original 0.944S avec une matrice de taille (4 * 10000000) ... 20% plus rapide.


@Mike, * valeur ++ * = facteur pourrait bien être plus rapide ... je l'ai essayé, n'a pas vraiment varié de ma version ....


@stole, quelle vitesse est votre machine? Il devrait être d'environ 200 MHz pour prendre aussi longtemps. Dites que vous étiez à 2,5 GHz, 0,731S prend 62,5 cycles par multiplie. Un Core2 devrait être capable de faire un couple de float mulls par cycle.


@MIKE: GCC applique quelque chose comme le périphérique de Duff en interne lorsque vous déroulez une boucle. Et les chances sont-elles aussi capables d'automatiquement vectoriser la fonction.



2
votes

Disclaimer: évidemment, cela ne fonctionnera pas sur iPhone, iPad, Android ou leurs équivalents futurs.

#include <mmintrin.h>
#include <xmmintrin.h>

__m128 factor4 = _mm_set1_ps(factor);
for (int i=0; i+3 < count; i += 4)
{
   __m128 data = _mm_mul_ps(_mm_loadu_ps(values), factor4);
   _mm_storeu_ps(values, data);
   values += 4;
}
for (int i=(count/4)*4; i < count; i++)
{
   *values *= factor;
   value++;
}


0 commentaires

2
votes

Avez-vous pensé à OpenMP?

Les ordinateurs les plus modernes ont des processeurs multicœurs et presque tous les grands compilateurs semblent avoir OpenMP intégré. Vous gagnez la vitesse à peine tout coût.

voir Article de Wikipedia sur OpenMP .


0 commentaires

0
votes

La meilleure solution consiste à le garder simple et laissez le compilateur l'optimiser pour vous. GCC connaît environ SSE, SSE2, Altivec et quoi d'autre. Si votre code est trop complexe, votre compilateur ne pourra pas l'optimiser sur chaque cible possible.


0 commentaires

0
votes

Comme vous l'avez mentionné, il existe de nombreuses architectures qui ont des extensions SIMD et SIMD est probablement votre meilleur choix lorsqu'il s'agit d'optimisation. Ils sont tous spécifiques à la plate-forme et les langues C et C ++ comme langues ne sont pas simdées.

La première chose que vous devriez essayer, cependant, permet aux drapeaux spécifiques de la SIMD pour votre construction donnée. Le compilateur peut reconnaître les modèles pouvant être optimisés avec SIMD.

La prochaine chose est d'écrire un code SIMD spécifique à la plate-forme à l'aide de l'intrinsion ou de l'assemblage du compilateur, le cas échéant. Vous devez toutefois conserver une implémentation portable non-SIMD pour les plates-formes qui n'ont pas de version optimisée. #Ifdef S Activer la SIMD sur les plates-formes qui le supportent.

Enfin, au moins sur le bras, mais pas sûr de l'Intel, sachez que des types de points entier et flottants plus petits permettent un plus grand nombre d'opérations parallèles par instruction SIMD unique.


0 commentaires

0
votes

Je pense, il n'y a pas beaucoup que vous puissiez faire cela fait une grande différence. Peut-être que vous pouvez accélérer un peu avec OpenMP ou SSE. Mais les processeurs modernes sont déjà assez rapides. Dans certaines applications, la bande passante / la latence est en réalité le goulot d'étranglement et s'aggrave. Nous avons déjà trois niveaux de cache et avons besoin d'algorithmes de Prefetch intelligents pour éviter les retards énormes. Donc, il est logique de penser également aux modèles d'accès à la mémoire. Par exemple, si vous implémentez un tel multiplier et un ajoutez et utilisez-le comme ceci: xxx

Vous passez essentiellement deux fois plus le bloc de la mémoire. En fonction de la taille du vecteur, cela pourrait ne pas s'intégrer dans le cache L1 auquel cas le dépassant deux fois ajoute un peu de temps supplémentaire. C'est évidemment mauvais et vous devriez essayer de garder des accès à la mémoire "local". Dans ce cas, une seule boucle xxx

est susceptible d'être plus rapide. En règle générale: essayez d'accéder à la mémoire linéairement et d'essayer d'accéder à la mémoire "localement" par lequel je veux dire, essayez de réutiliser les données déjà dans le cache L1. Juste une idée.


0 commentaires