2 votes

Étendez efficacement des nombres 8 bits à 12 bits dans un seul registre neon arm

Je constate 4 octets chargés dans un registre neon. Comment puis-je convertir efficacement cela en 12 bits, par exemple j'ai besoin d'insérer 4 bits nuls après le premier octet, 8 bits nuls après le deuxième, et ainsi de suite. Par exemple, si j'avais ces 4 octets en hexadécimal:

01 02 03 04

Cela se terminerait avec cela en hexadécimal:

01 20 00 03 40

Même opération exprimée en tant que simple fonction c qui opère sur une variable de 32 bits représentant les 4 octets d'entrée:

uint64_t expand12(uint32_t i)
{
    uint64_t r = (i & 0xFF);
    r |= ((i & 0x0000ff00) << 4); // décaler le deuxième octet de 4 bits
    r |= ((i & 0x00ff0000) << 8); // décaler le troisième octet de 8 bits
    r |= (((uint64_t)(i & 0xff000000)) << 12); // 4ème de 12
    return r;
}

Donc, si j'ai ces octets dans un registre neon uint8x8_t, quelle serait une bonne façon de mettre en œuvre la même opération en neon de sorte que le même registre se termine avec ces valeurs décalées?

Remarque, que les quatre octets ont des zéros dans les 4 bits supérieurs si cela aide d'une quelconque manière.

Mise à jour: Dans mon cas, j'ai 4 registres uint16x8_t et pour chacun je dois calculer la somme de toutes les voies (vaddv_u16), puis effectuer vclz_u16 sur cette somme et ensuite combiner ces quatre sommes dans un registre neon en les plaçant à 12 bits d'intervalle:

uint64_t compute(uint16x8_t a, uint16x8_t b, uint16x8_t c, uint16x8_t d)
{
    u16 a0 = clz(vaddv(a));
    u16 b0 = clz(vaddv(b));
    u16 c0 = clz(vaddv(c));
    u16 d0 = clz(vaddv(d));
    return (a0 << 36) | (b0 << 24) | (c0 << 12) | (d0);
}

Remarque, il s'agit d'un pseudo-code et j'ai besoin du résultat dans un registre neon.

Au cas où cela importe, dans mon code j'ai une fonction qui trouve les indices des éléments max dans 4 registres uint16x8_t. Dans cette fonction, ces quatre registres sont vandés avec l'élément max dupliqué sur toutes les voies et ensuite le résultat a été vorré avec le masque de bits {1<<15, 1<<14, ... 1<<0}; Ensuite, j'effectue l'addition par paire de toutes les voies et le clz de cela me donne l'index de l'élément max de chaque registre. Tout cela doit être entrelacé avec 4 bits nuls supplémentaires insérés entre les éléments et stockés dans un registre neon. Exemple en C:

void compute(uint16_t *src, uint64_t* dst)
{
    uint64_t x[4];
    for (int i = 0; i < 4; ++i, src+=16)
    {
        int max = 0;
        for (int j = 0; j < 16; ++j)
        {
            if (src[j] > src[max])
                max = j;
        }
        x[i] = max;
    }
    *dst = (x[0] << 36) | (x[1] << 24) | (x[2] << 12) | (x[3]);
}

Cette fonction fait partie d'une grande fonction qui effectue ce calcul des millions de fois dans une boucle et les résultats de cette fonction sont utilisés et doivent être dans un registre neon. Considérez-le comme du pseudo-code qui décrit l'algorithme, s'il n'est pas clair ce que cela signifie: cela signifie que seul l'algorithme importe, il n'y a pas de charges ou de stockages qui doivent être optimisés

2voto

Jake 'Alquimista' LEE Points 1830

Vous devez penser hors du cadre. Ne vous en tenez pas au type de données et à la largeur des bits.

uint32_t n'est rien d'autre qu'un tableau de 4 uint8_t que vous pouvez facilement étaler via vld4 en chargeant sur-le-champ.

Le problème devient ainsi beaucoup plus gérable.


void foo(uint32_t *pDst, uint32_t *pSrc, uint32_t length)
{
    length >>= 4;
    int i;
    uint8x16x4_t in, out;
    uint8x16_t temp0, temp1, temp2;

    for (i = 0; i < length; ++i)
    {
        in = vld4q_u8(pSrc);
        pSrc += 16;

        temp0 = in.val[1] << 4;
        temp1 = in.val[3] << 4;
        temp1 += in.val[1] >> 4;

        out.val[0] = in.val[0] | temp0;
        out.val[1] = in.val[2] | temp1;
        out.val[2] = in.val[3] >> 4;
        out.val[3] = vdupq_n_u8(0);

        vst4q_u8(pDst, out);
        pDst += 16;
    }
}

Notez que j'ai omis le traitement résiduel, et cela fonctionnerait beaucoup plus vite si vous déroulez plus profondément.

Plus important encore, j'écrirais cette fonction en langage machine sans hésiter, car je ne pense pas que le compilateur gérerait aussi intelligemment les registres pour que out.val[3] soit initialisé à zéro une seule fois en dehors de la boucle.

Et je doute aussi que temp1 += in.val[1] >> 4; se traduirait par vsra en raison de la nature de l'instruction non opérande cible distincte. Qui sait?

Les compilateurs sont nuls.


Mise à jour : D'accord, voici des codes qui répondront à vos besoins, écrits en langage machine, pour les deux architectures.


aarch32

vtrn.16     q0, q1
vtrn.16     q2, q3
vtrn.32     q0, q2
vtrn.32     q1, q3

vadd.u16    q0, q1, q0
vadd.u16    q2, q3, q2

adr     r12, shift_table

vadd.u16    q0, q2, q0

vld1.64     {q3}, [r12]

vadd.u16    d0, d1, d0
vclz.u16    d0, d0          // d0 contient les zéros de tête

vmovl.u16   q0, d0

vshl.u32    q1, q0, q3

vpadal.u32  d3, d2          // d3 contient le résultat final

.balign 8
shift_table:
    .dc.b   0x00, 0x00, 0x00, 0x00,     0x0c, 0x00, 0x00, 0x00,     0x18, 0x00, 0x00, 0x00,     0x04, 0x00, 0x00, 0x00 // 0, 12, 24, 4

aarch64

trn1        v16.8h, v0.8h, v1.8h
trn1        v18.8h, v2.8h, v3.8h
trn2        v17.8h, v0.8h, v1.8h
trn2        v19.8h, v2.8h, v3.8h

trn2        v0.4s, v18.4s, v16.4s
trn1        v1.4s, v18.4s, v16.4s
trn2        v2.4s, v19.4s, v17.4s
trn1        v3.4s, v19.4s, v17.4s

add         v0.8h, v1.8h, v0.8h
add         v2.8h, v3.8h, v2.8h

adr     x16, shift_table

add         v0.8h, v2.8h, v0.8h

ld1         {v3.2d}, [x16]

mov         v1.d[0], v0.d[1]

add         v0.4h, v1.4h, v0.4h

clz         v0.4h, v0.4h                // v0 contient les zéros de tête

uxtl        v0.4s, v0.4h

ushl        v0.4s, v0.4s, v3.4s

mov         v1.d[0], v0.d[1]

uadalp      v1.1d, v0.2s                // v1 contient le résultat final

.balign 8
shift_table:
.dc.b   0x00, 0x00, 0x00, 0x00,     0x0c, 0x00, 0x00, 0x00,     0x18, 0x00, 0x00, 0x00,     0x04, 0x00, 0x00, 0x00 // 0, 12, 24, 4

** Vous devrez peut-être changer .dc.b en .byte dans Clang

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X