Multiplication efficace de la matrice 4x4 (C vs assemblage)

Je suis à la recherche d'une plus rapide et plus délicat moyen de multiplier deux matrices 4x4 dans C. Ma recherche actuelle est axée sur l'architecture x86-64 assemblée avec des extensions SIMD. Jusqu'à présent, j'ai créé une fonction qui est d'environ 6x plus rapide que le naïf C mise en œuvre, qui a dépassé mes attentes pour l'amélioration de la performance. Malheureusement, cela reste vrai que si aucune options d'optimisation sont utilisés pour la compilation (GCC 4.7). Avec -O2C est plus rapide et mon effort devient vide de sens.

Je sais que les compilateurs modernes utilisent des complexes techniques d'optimisation pour atteindre un presque parfait code, généralement plus rapide qu'un ingénieux morceau de main-crafed de l'assemblée. Mais dans une minorité de critiques des performances des cas, un homme peut essayer de se battre pour les cycles d'horloge avec le compilateur. En particulier, lorsque certains de mathématiques soutenu avec un moderne ISA peuvent être explorées (comme c'est mon cas).

Ma fonction se présente comme suit (AT&T de la syntaxe, de l'Assembleur GNU):

    .text
    .globl matrixMultiplyASM
    .type matrixMultiplyASM, @function
matrixMultiplyASM:
    movaps   (%rdi), %xmm0    # fetch the first matrix (use four registers)
    movaps 16(%rdi), %xmm1
    movaps 32(%rdi), %xmm2
    movaps 48(%rdi), %xmm3
    xorq %rcx, %rcx           # reset (forward) loop iterator
.ROW:
    movss (%rsi), %xmm4       # Compute four values (one row) in parallel:
    shufps $0x0, %xmm4, %xmm4 # 4x 4FP mul's, 3x 4FP add's 6x mov's per row,
    mulps %xmm0, %xmm4        # expressed in four sequences of 5 instructions,
    movaps %xmm4, %xmm5       # executed 4 times for 1 matrix multiplication.
    addq $0x4, %rsi

    movss (%rsi), %xmm4       # movss + shufps comprise _mm_set1_ps intrinsic
    shufps $0x0, %xmm4, %xmm4 #
    mulps %xmm1, %xmm4
    addps %xmm4, %xmm5
    addq $0x4, %rsi           # manual pointer arithmetic simplifies addressing

    movss (%rsi), %xmm4
    shufps $0x0, %xmm4, %xmm4
    mulps %xmm2, %xmm4        # actual computation happens here
    addps %xmm4, %xmm5        #
    addq $0x4, %rsi

    movss (%rsi), %xmm4       # one mulps operand fetched per sequence
    shufps $0x0, %xmm4, %xmm4 #  |
    mulps %xmm3, %xmm4        # the other is already waiting in %xmm[0-3]
    addps %xmm4, %xmm5
    addq $0x4, %rsi           # 5 preceding comments stride among the 4 blocks

    movaps %xmm5, (%rdx,%rcx) # store the resulting row, actually, a column
    addq $0x10, %rcx          # (matrices are stored in column-major order)
    cmpq $0x40, %rcx
    jne .ROW
    ret
.size matrixMultiplyASM, .-matrixMultiplyASM

Il calcule l'ensemble de la colonne de la matrice résultante par itération, par le traitement de quatre chars, emballés dans des 128 bits des registres SSE. Le plein de vectorisation est possible avec un peu de maths (opération de réorganisation et de regroupement) et mullps/addps instructions en parallèle de multiplication/addition de 4xfloat paquets. Le code réutilise les registres signifiait pour passer des paramètres (%rdi%rsi%rdx : GNU/Linux ABI), des avantages de (intérieure) déroulement de la boucle et est titulaire d'une matrice entièrement dans les registres XMM afin de réduire les lectures de mémoire. Vous pouvez le voir, j'ai étudié le sujet et pris mon temps pour mettre en œuvre le mieux que je peux.

Le naïf C calcul de la conquête de mon code ressemble à ceci:

void matrixMultiplyNormal(mat4_t *mat_a, mat4_t *mat_b, mat4_t *mat_r) {
    for (unsigned int i = 0; i < 16; i += 4)
        for (unsigned int j = 0; j < 4; ++j)
            mat_r->m[i + j] = (mat_b->m[i + 0] * mat_a->m[j +  0])
                            + (mat_b->m[i + 1] * mat_a->m[j +  4])
                            + (mat_b->m[i + 2] * mat_a->m[j +  8])
                            + (mat_b->m[i + 3] * mat_a->m[j + 12]);
}

J'ai étudié l'optimisation de l'assemblée de la sortie de la ci-dessus du code C qui, tout en stockant les flotteurs dans les registres XMM, n'implique pas en parallèle des opérations de – juste scalaire des calculs, de l'arithmétique des pointeurs et des sauts conditionnels. Le compilateur de code semble être moins délibérée, mais il est encore un peu plus efficace que mon vectorisées version devrait être d'environ 4x plus rapide. Je suis sûr que l'idée générale est correcte – programmeurs de faire des choses similaires avec des résultats satisfaisants. Mais quel est le problème ici? Il n'existe aucun registre de l'allocation ou de la planification d'instructions questions, je ne suis pas au courant? Savez-vous tout x86-64 outils de montage ou des astuces pour appuyer mon combat contre la machine?

source d'informationauteur Krzysztof Abramowicz