7
votes

Comment optimiser une multiplication de la matrice 4D-vectorielle en boucle avec le néon du bras?

Je travaille sur l'optimisation d'une multiplication de vecteur matricielle 4D (128 bits) à l'aide de l'assembleur de bras Neon.

Si je charge la matrice et le vecteur dans les registres du néon et le transformez pas, je ne vais pas obtenir de Excellente performance Boost, car le passage aux registres néon a coûté 20 cycles. En outre, je rechargez la matrice pour chaque multiplication, malgré cela n'a pas changé. P>

Il y a suffisamment d'espace de registre pour effectuer la transformation sur plusieurs vecteurs. Cela augmente des performances. P>

mais .. p>

Je me demande à quelle vitesse cette opération serait si je fais la boucle sur tous les sommets (d'augmentation des pointeurs) au sein de l'assembleur. Mais je suis au tout début de Neon Assembler et bien que je ne sais pas comment faire cela. Est-ce que quelqu'un peut me donner une main sur cela? P>

Ce que je veux réaliser: p>

  1. Matrice de chargement et premier vecteur li>
  2. Compte de boucle de magasin "Compte" et .. li>
  3. - LOOP_START - LI>
  4. Effectuez Multiply-Ajouter des ajouts (faire la transformation) li>
  5. Écrivez Q0 à Vout Li>
  6. Augmentez les pointeurs Vin et Vout de 4 (128 bits) Li>
  7. Chargez VIN à Q5. LI>
  8. - LOOP_END - LI> ol>

    version C existante de boucle: p> xxx pré>

    code suivant pour neon-version sur ne faisant qu'une transformation: p> xxx

    C-Version de la transformation: P>

    C-Version:    190 FPS 
    NEON-Version: 162 FPS ( .. slower -.- )
    
    --- LOAD Matrix only ONCE (seperate ASM) and then perform two V's at a time ---
    
    NEON-Version: 217 FPS ( + 33 % NEON | + 14 % C-Code )
    


4 commentaires

Indiquez votre boucle dans Simple C, les gens l'obtiendraient plus facilement.


Oh ouais .. suppose que tu as raison!


Fournissez également matrix4vector4mul, en fait, il suffit de les faire une boucle, comme vous l'écririez dans la plaine c.


... J'ai fourni tous les codes pour l'opération maintenant. C'est une boucle avec juste la fonction de transformation. J'espère que je t'ai raison?


3 Réponses :


0
votes

La version au néon ajustée à la main souffre de dépendance entre toutes les opérations, tandis que GCC est capable de faire une planification hors commander pour la version C. Vous devriez être capable d'améliorer la version au néon en calculant dans deux fils ou plus indépendants parallèles:

Incrément du pointeur (Post Incrément) dans le néon est effectué avec une marque d'exclamation. Ces registres doivent ensuite être inclus dans la liste de registres de sortie "= r" (vout) p> xxx pré>

Un autre mode d'adressage permet d'incrémenter post-incrément par une "foulée" définie dans un autre registre de bras. L'option est disponible uniquement sur certaines commandes de charge (car il existe une variété d'options d'entrelacement ainsi que de chargement aux éléments choisis de D1 [1] (partie supérieure)). P>

asm(
   "vld1.32 {d0[0],d2[0],d4[0],d6[0]}, [%0]! \n\t"
   "vld1.32 {d0[1],d2[1],d4[1],d6[1]}, [%0]! \n\t"
   "vld1.32 {d1[0],d3[0],d5[0],d7[0]}, [%0]! \n\t"
   "vld1.32 {d1[1],d3[1],d5[1],d7[1]}, [%0]! \n\t"

   "vld1.32 {q8}, [%2:128]! \n\t"
   "vld1.32 {q9}, [%2:128]! \n\t"
   "vld1.32 {q10}, [%2:128]! \n\t"
   "vld1.32 {q11}, [%2:128]! \n\t"

   "subs %0, %0, %0 \n\t"   // set zero flag

   "1: \n\t"
   "vst1.32 {q4}, [%1:128]! \n\t"
   "vmul.f32 q4, q8, q0 \n\t"
   "vst1.32 {q5}, [%1:128]! \n\t"
   "vmul.f32 q5, q9, q0 \n\t"
   "vst1.32 {q6}, [%1:128]! \n\t"
   "vmul.f32 q6, q10, q0 \n\t"
   "vst1.32 {q7}, [%1:128]!  \n\t"
   "vmul.f32 q7, q11, q0 \n\t"

   "subne %1,%1, #64    \n\t"    // revert writing pointer in 1st iteration 

   "vmla.f32 q4, q8, q1 \n\t"
   "vmla.f32 q5, q9, q1 \n\t"
   "vmla.f32 q6, q10, q1 \n\t"
   "vmla.f32 q7, q11, q1 \n\t"
   "subs %2, %2, #1 \n\t"
   "vmla.f32 q4, q8, q2 \n\t"
   "vmla.f32 q5, q9, q2 \n\t"
   "vmla.f32 q6, q10, q2 \n\t"
   "vmla.f32 q7, q11, q2 \n\t"

   "vmla.f32 q4, q8, q3 \n\t"
   "vld1.32 {q8}, [%2:128]! \n\t"  // start loading vectors immediately
   "vmla.f32 q5, q9, q3 \n\t"
   "vld1.32 {q9}, [%2:128]! \n\t"  // when all arithmetic is done
   "vmla.f32 q6, q10, q3 \n\t"
   "vld1.32 {q10}, [%2:128]! \n\t"
   "vmla.f32 q7, q11, q3 \n\t"
   "vld1.32 {q11}, [%2:128]! \n\t"
   "jnz b1 \n\t"
   "vst1.32 {q4,q5}, [%1:128]! \n\t"  // write after first loop
   "vst1.32 {q6,q7}, [%1:128]! \n\t"
 : "=r" (m), "=r" (vOut), "=r" (vIn), "=r" ( N ), 
 :
 : "d0","d1","q0", ... ); // marking q0 isn't enough for some gcc version 


3 commentaires

agréable! :) Les ajouts multiples fusionnés sont uniquement pris en charge dans VFPv4 .. Je pense que Cortex A15 (et facultatif).


Désolé, je ne me souvenais pas. J'ai eu un vague souvenir d'écrire quelque chose comme ça dans A8. Mais il y a de toute façon 4 registres laissés dans la banque de registres (Q12-Q15) pour multiplier le résultat intermédiaire avant d'ajouter au Q4-Q7. Il existe également une possibilité d'une mise en œuvre de points fixe et d'utiliser la multiplication accumulée qui est disponible.


Au lieu de "SUBS% 2,% 2, # 1 \ n \ t" SON "SUBS% 3,% 3, # 1 \ n \ t" , non?



1
votes

Avez-vous essayé de jouer avec des drapeaux de compilateur?

  a4:   ed567a03    vldr    s15, [r6, #-12]
  a8:   ee276aa0    vmul.f32    s12, s15, s1
  ac:   ee676aa8    vmul.f32    s13, s15, s17
  b0:   ed564a04    vldr    s9, [r6, #-16]
  b4:   ee277a88    vmul.f32    s14, s15, s16
  b8:   ed165a02    vldr    s10, [r6, #-8]
  bc:   ee677a80    vmul.f32    s15, s15, s0
  c0:   ed565a01    vldr    s11, [r6, #-4]
  c4:   e2833001    add r3, r3, #1
  c8:   ee046a89    vmla.f32    s12, s9, s18
  cc:   e1530004    cmp r3, r4
  d0:   ee446aaa    vmla.f32    s13, s9, s21
  d4:   ee047a8a    vmla.f32    s14, s9, s20
  d8:   ee447aa9    vmla.f32    s15, s9, s19
  dc:   ee056a22    vmla.f32    s12, s10, s5
  e0:   ee456a01    vmla.f32    s13, s10, s2
  e4:   ee057a21    vmla.f32    s14, s10, s3
  e8:   ee457a02    vmla.f32    s15, s10, s4
  ec:   ee056a8b    vmla.f32    s12, s11, s22
  f0:   ee456a83    vmla.f32    s13, s11, s6
  f4:   ee057aa3    vmla.f32    s14, s11, s7
  f8:   ee457a84    vmla.f32    s15, s11, s8
  fc:   ed066a01    vstr    s12, [r6, #-4]
 100:   ed466a04    vstr    s13, [r6, #-16]
 104:   ed067a03    vstr    s14, [r6, #-12]
 108:   ed467a02    vstr    s15, [r6, #-8]
 10c:   e2866010    add r6, r6, #16
 110:   1affffe3    bne a4 <TransformVertices+0xa4>


2 commentaires

Merci! :) Je vais essayer la compilerflags et la publication résultant de ce fil. Mais le CompilerOutput semble fonctionner sur des points flottants à la précision, alors qu'il pourrait également fonctionner sur des quads avec le néon. Cortex A9 exécute 64 bits à la fois (et ce sera 8 cycles), mais avec A15, il y a une exécution de 128 bits avec un cycle unique. Ce sera 4 VS. 16 Instructions pour la transformation. On dit que l'ASM manuscrit ne vaut pas le moment, car A9 et A15 sont beaucoup plus avancés que A8, mais je ne pouvais pas trouver une preuve pour cela.


C'est objdump, utilisez ARM-Linux-Androideabibi-objdump -s . GCC -S Crée un code pouvant être assemblé plus tard, la sortie Objdump ne peut pas. C'est pourquoi j'ai mentionné de la sorte ci-dessus.



0
votes

C'est un sujet d'enfant presque un de l'année d'ici, mais je pense qu'il est important de vous donner la réponse "correcte" puisque quelque chose est très fishy ici, et personne n'a signalé cela jusqu'à présent:

  1. Vous devez éviter d'utiliser Q4-Q7 si possible, car ils doivent être préservés avant d'utiliser

  2. corrigez-moi si je me trompe, mais si ma mémoire ne me manquait pas, seule D0 ~ D3 (ou D0 ~ D7) peut contenir des scalaires. Je me demande vraiment pourquoi GCC tolère D10 et D11 comme opérandes scalaires. Comme il est physiquement impossible de cette façon, je suppose que GCC fait de nouveau quelque chose de fou de votre assemblée en ligne. Découvrez le démontage de votre code d'assemblage en ligne.

    True, votre code d'assemblage en ligne souffre de deux verrouilles (2cycles après la charge et de 9 cycles avant de stocker), mais il est inimaginable pour moi que le code néon fonctionne plus lentement que le code C.

    C'est une supposition très forte de mon côté que GCC enregistre un registre lourd transférant à la place d'un message d'erreur. Et ce n'est pas vraiment une faveur dans ce cas.


0 commentaires