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>
version C existante de boucle: p> code suivant pour neon-version sur ne faisant qu'une transformation: p> 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 )
3 Réponses :
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> 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
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" code> SON
"SUBS% 3,% 3, # 1 \ n \ t" code>, non?
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>
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 B> VS. 16 B> 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
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: P>
Vous devez éviter d'utiliser Q4-Q7 si possible, car ils doivent être préservés avant d'utiliser P> LI>
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. P> li> ol>
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. P>
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. P>
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?