Comment puis-je optimiser une matrice 4D en boucle avec la multiplication vectorielle avec ARM NEON?

Je travaille sur l'optimisation d'une multiplication de matrice-vecteur 4D (128 bits) en utilisant ARM NEON Assembler.

Si je charge la matrice, et que le vecteur dans le NEON les enregistre et le transforme, je n'aurai pas une excellente performance, car le passage aux registres NEON coûte 20 cycles. En outre, je recharge la matrice pour chaque multiplication, même si elle n'a pas changé.

  • Comment puis-je animer une vue dans Android et l'avoir-t-il dans la nouvelle position / taille?
  • Listview dynamique ajoutant "Charger plus d'éléments" à la fin du défilement
  • Comment modifier l'orientation du buffer de rappel de l'aperçu de la caméra?
  • Android Studio signé APK Not Installing
  • Décodage de polyligne avec nouvelle API Google Maps
  • Qu'est-ce que setContentView (R.layout.main)?
  • Il existe suffisamment d'espace de registre pour effectuer la transformation sur plus de vecteurs par heure. Ceci est une performance croissante.

    Mais..

    Je me demande combien cette opération serait rapide si je fais la boucle sur tous les sommets (pointeurs croissants) dans l'assembleur. Mais je suis au tout début de l'assemblage Néon et je ne sais pas comment faire cela. Quelqu'un peut-il m'en prendre une idée?

    Ce que je veux atteindre:

    1. Matrice de charge et premier vecteur
    2. Compte en boucle de magasin "count" et …
    3. – LOOP_START –
    4. Effectuer des multiplications (faire la transformation)
    5. Écrire q0 à vOut
    6. Augmente les pointeurs vIn et vOut par 4 (128 Bit)
    7. CHARGEMENT vIn à q5.
    8. – LOOP_END –

    Version C existante de la boucle:

    void TransformVertices(ESMatrix* m, GLfloat* vertices, GLfloat* normals, int count) { GLfloat* pVertex = vertices; int i; // iterate trough vertices only one at a time for (i = 0; i < count ; i ++) { Matrix4Vector4Mul( (float *)m, (float *)pVertex, (float *)pVertex); pVertex += 4; } //LoadMatrix( (const float*) m); //// two at a time //for (i = 0; i < count ; i += 2) //{ // Matrix4Vector4Mul2( (float *)m, (float *)pVertex, (float *)(pVertex + 4)); // pVertex += 8; //} } 

    Le code suivant pour NEON-Version en effectuant une seule transformation:

     void Matrix4Vector4Mul (const float* m, const float* vIn, float* vOut) { asm volatile ( "vldmia %1, {q1-q4 } \n\t" "vldmia %2, {q5} \n\t" "vmul.f32 q0, q1, d10[0] \n\t" "vmla.f32 q0, q2, d10[1] \n\t" "vmla.f32 q0, q3, d11[0] \n\t" "vmla.f32 q0, q4, d11[1] \n\t" "vstmia %0, {q0}" : // no output : "r" (vOut), "r" (m), "r" (vIn) : "memory", "q0", "q1", "q2", "q3", "q4", "q5" ); } 

    C-Version de la transformation:

     void Matrix4Vector4Mul (const float* m, const float* vIn, float* vOut) { Vertex4D* v1 = (Vertex4D*)vIn; Vertex4D vOut1; Vertex4D* l0; Vertex4D* l1; Vertex4D* l2; Vertex4D* l3; // 4x4 Matrix with members m00 - m33 ESMatrix* m1 = (ESMatrix*)m; l0 = (Vertex4D*)&m1->m00; vOut1.x = l0->x * v1->x; vOut1.y = l0->y * v1->x; vOut1.z = l0->z * v1->x; vOut1.w = l0->w * v1->x; l1 = (Vertex4D*)&m1->m10; vOut1.x += l1->x * v1->y; vOut1.y += l1->y * v1->y; vOut1.z += l1->z * v1->y; vOut1.w += l1->w * v1->y; l2 = (Vertex4D*)&m1->m20; vOut1.x += l2->x * v1->z; vOut1.y += l2->y * v1->z; vOut1.z += l2->z * v1->z; vOut1.w += l2->w * v1->z; l3 = (Vertex4D*)&m1->m30; vOut1.x += l3->x * v1->w; vOut1.y += l3->y * v1->w; vOut1.z += l3->z * v1->w; vOut1.w += l3->w * v1->w; *(vOut) = vOut1.x; *(vOut + 1) = vOut1.y; *(vOut + 2) = vOut1.z; *(vOut + 3) = vOut1.w; } 

    Performance: (Transform> 90 000 vertices | Android 4.0.4 SGS II)

     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 ) 

  • Barrière de développement mobile à l'entrée
  • Cette application ne fonctionnera que si vous mettez à jour Google Play Services (via Bazaar)
  • Configurez une application Qt5 5.7 pour Android avec CMake
  • Appareil photo Android: l'intention des données renvoie nulle
  • Est-ce que «Samsung Apps» prend en charge un schéma URI pour rediriger vers des applications spécifiques?
  • Android: je n'obtiens pas de stacktrace, le téléphone se raccroche
  • 3 Solutions collect form web for “Comment puis-je optimiser une matrice 4D en boucle avec la multiplication vectorielle avec ARM NEON?”

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

     -mcpu=cortex-a9 -mtune=cortex-a9 -mfloat-abi=softfp -mfpu=neon -O3 

    Fait du bon travail pour moi dans ce cas (gcc 4.4.3, distribué avec Android NDK 8b). Essayez d'avoir un code source étroit en définissant les fonctions internes statiques et en ligne ainsi que la matrice en mouvement (m [X] [0]) vers des variables globales statiques ou simplement fusionner Matrix4Vector4Mul en boucle et créer des variables locales matricielles au lieu de continuer à la passer en fonction – Gcc n'est pas intelligent là-bas.

    Quand je fais cela, je suis en dessous de la boucle principale.

      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> 

    Ayant 4 charges, 4 multiplient, 12 multiplient et s'accumulent et 4 magasins qui correspondent à ce que vous faites dans Matrix4Vector4Mul.

    Si vous n'êtes toujours pas satisfait du code généré par le compilateur, passez le compilateur '-S' pour obtenir la sortie d'assemblage et utilisez-le comme point de départ pour améliorer davantage au lieu de démarrer à partir de zéro.

    Vous devez également vérifier que les vertices sont alignés dans la ligne de cache (32 octets pour Cortex-A9) pour obtenir un bon flux de données.

    Pour la vectorisation, il existe des options gcc comme -ftree-vectorizer-verbose=9 pour imprimer des informations ce qui a été vectorisé. Consultez également la documentation de gcc pour voir comment vous pouvez diriger gcc ou ce que vous devez modifier pour que vos multiplications soient vectorisées. Cela pourrait sembler beaucoup pour creuser, mais il serait plus fructueux pour vous à long terme que «vectoriser à la main».

    La version néon à la main souffre de la dépendance entre toutes les opérations, tandis que gcc est capable de faire des ordonnances hors service pour la version c. Vous devriez pouvoir améliorer la version NEON en calculant en parallèle deux ou plusieurs threads indépendants:

    L'incrément de pointeur (post-incrémentation) dans NEON se fait avec un point d'exclamation. Ces registres devraient ensuite être inclus dans la liste de registre de sortie "= r" (vOut)

     vld1.32 {d0,d1}, [%2]! ; // next round %2=%2 + 16 vst1.32 {d0}, [%3]! ; // next round %3=%3 + 8 

    Un autre mode d'adressage permet l'incrément postérieur par une «foulée» définie dans un autre registre de bras. L'option est disponible uniquement sur certaines commandes de chargement (car il existe une variété d'options d'entrelacement ainsi que le chargement sur les éléments choisis de d1 [1] (partie supérieure)).

     vld1.16 d0, [%2], %3 ; // increment by register %3 

    L'incrément du compteur se produit avec la séquence

     1: subs %3, %3, #1 ; // with "=r" (count) as fourth argument bne 1b ; // create a local label 

    L'étiquette locale est utilisée, car deux instructions "bne loop" dans le même fichier provoquent une erreur

    On devrait pouvoir augmenter le parallélisme d'un facteur quatre en calculant des multiplications fusionnées multipliées pour des vecteurs au lieu d'éléments uniques.

    Dans ce cas, il est intéressant d'effectuer une transposition matricielle à l'avance (avant d'appeler la routine ou avec un mode d'adressage spécial).

     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 

    Lire et écrire sur des blocs alignés de 128 bits (assurez-vous que le ptr de données est également aligné)
    Il y a un malloc avec align, ou simplement ajuster manuellement ptr=((int)ptr + 15) & ~15 .

    Tout comme il y a un bloc de boucle de publication qui écrit les résultats, on peut écrire un bloc de pré-boucle similaire qui saute la première écriture de non-sens à vOut (qui pourrait aussi être surmontée par une écriture conditionnelle). On ne peut malheureusement écrire que des registres à 64 bits conditionnellement.

    Il s'agit maintenant d'un sujet d'une seule année, mais je pense qu'il est important de vous donner la réponse «correcte» puisque quelque chose est très poissonneux ici, et personne ne l'a souligné jusqu'à présent:

    1. Vous devez éviter d'utiliser q4-q7 si possible car ils doivent être conservés avant utilisation

    2. Corrigez-moi si je me trompe, mais si ma mémoire ne me manque pas, seulement d0 ~ d3 (ou d0 ~ d7) peut contenir des scalaires. Je me demande vraiment pourquoi gcc tolère d10 et d11 en tant qu'opérandes scalaires. Comme il est physiquement impossible de cette façon, je suppose que gcc fait encore quelque chose de fou de votre assemblage en ligne. Vérifiez le démontage de votre code d'assemblage en ligne.

    Certes, votre code d'assemblage en ligne souffre de deux interverrouillages (2 cages après chargement et 9 cycles avant le magasin), mais il est inimaginable pour moi que le code NEON soit plus lent que le code C.

    C'est une prétention très forte de mon côté que gcc enregistre un certain nombre de registres importants en va-et-vient en crachant un message d'erreur. Et ce n'est pas exactement une faveur dans ce cas.

    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.