Tri Fusion avec vecteur SSE

1.1. Introduction

Le but de ce projet est de comparer deux implantations du tri fusion (merge sort) sur les entiers 32 bits :

En triant quatre valeurs dans un registre SSE, on se trouve confronté à trois problèmes majeurs :

1.2. Rappels sur le tri fusion (Merge Sort)

L'algorithme du tri-fusion est composé de deux phases :

Voir le principe sur Wikipedia

Merge Sort Algo

Le principal avantage du tri fusion est qu'il possède une complexité en $N × log(N)$ mais l'étape de fusion nécessite la création d'un tableau temporaire comme le montre le code suivant :

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. using namespace std;
  6.  
  7. /**
  8.  * improved version of merge
  9.  * t is an array of size n that was divided
  10.  * at index m
  11.  * @param t array to merge which is composed of two sub arrays
  12.  *        from [0,m[ and [m,n[
  13.  * @param n size of the array
  14.  * @param m index of the element in the middle
  15.  */
  16. void merge(int *t, int n, int m) {
  17.     int i, j, k;
  18.  
  19.     int *x = new int[n];
  20.     for (i = 0, j = m, k = 0; k < n; ++k) {
  21.         x[k] = (j == n)     ? t[i++]
  22.              : (i == n)     ? t[j++]
  23.              : t[j] < t[i]  ? t[j++]
  24.              :                t[i++];
  25.     }
  26.     memcpy(t, x, n * sizeof(int));
  27.     delete [] x;
  28. }
  29.  
  30. /**
  31.  * Recursively split initial array t of size sz into two smaller arrays
  32.  * until array size is less or equal than two
  33.  * @param t array to sort
  34.  * @param sz size of array
  35.  */
  36. void split(int *t, int sz) {
  37.     if (sz <= 1) {
  38.         return ;
  39.     }
  40.     // divide array t in its middle
  41.     int m = (sz / 2);
  42.     // split into two sub arrays
  43.     split(t, m);
  44.     split(t + m, sz - m);
  45.     // merge sub arrays
  46.     merge(t, sz, m);
  47. }
  48.  
  49. /**
  50.  * check if array is sorted, return true if it is the case
  51.  */
  52. bool is_sorted(int *t, int sz) {
  53.     for (int i = 1; i < sz; ++i) {
  54.         if (t[i-1] > t[i]) return false;
  55.     }
  56.     return true;
  57. }
  58.  
  59. /**
  60.  * main function
  61.  */
  62. int main(int argc, char *argv[]) {
  63.     int *tab;
  64.     int size = 1024;
  65.    
  66.     // get size of array from command line argument if provided
  67.     if (argc > 1) {
  68.         size = atoi( argv[1] );
  69.     }
  70.    
  71.     cout << "size=" << size << endl;
  72.    
  73.     // create array and fill with values in descending order
  74.     tab = new int [ size ];
  75.     for (int i=0;i<size; ++i) tab[i] = size-i;
  76.    
  77.     // print first elements before sort
  78.     cout << "tab.before=[";
  79.     for (int i=0; i<10; ++i) cout << tab[i] << " ";
  80.     cout << "]\n";
  81.    
  82.     // sort in ascending order
  83.     split(tab, size);
  84.    
  85.     // check if array is really sorted
  86.     if (!is_sorted(tab, size)) {
  87.         cout << "!!! NOT SORTED !!!" << endl;
  88.     }
  89.    
  90.     // print first elements after sort
  91.     cout << "tab.after=[";
  92.     for (int i=0; i<10; ++i) cout << tab[i] << " ";
  93.     cout << "]\n";
  94.    
  95.     return 0;
  96. }
  97.  

Voici un ordre de grandeur sous Ryzen 1700X des temps d'éxécution de l'implantation pour différentes tailles de tableaux :

 Taille   Temps (s) 
 2_097_152 (2^21)   0.07 
 4_194_304 (2^22)   0.16 
 8_388_608 (2^23)   0.29 
 16_777_216 (2^24)   0.64 
 33_554_432 (2^25)   1.32 
 67_108_864 (2^26)   2.61 
 134_217_728 (2^27)   5.45 
Merge sort sur Ryzen 1700X, temps en secondes en fonction de la taille du tableau

1.3. Tri Fusion modifié

1.3.1. Modification de l'algorithme

La modification de l'algorithme consiste

Il faut donc trouver un moyen pour savoir comment les ordonner.

1.3.2. Comment ordonner les valeurs

Pour ordonner les valeurs on dispose de l'instruction PSHUFD mais celle-ci n'admet qu'un argument de type constante qui indique comment sélectionner les valeurs.

pshufd

Comme indiqué préalablement on modifiera la constante de l'instruction à la volée.

		0x66, 0x0f, 0x70, 0xc0, 0xe0 ; pshufd xmm0,xmm0,0xe0

Pour trouver un identifiant unique lié à la réorganisation des valeurs, on va comparer 4 entiers adjacents à des réordonnancements de ces 4 valeurs comme sur l'exemple suivant, on compare le vecteur V1 à V2 et V3

V1 (xmm0) = 1 2 3 4
V2 (xmm1) = 4 1 2 3
V3 (xmm2) = 3 4 1 2

Pour réaliser la comparaison, on s'appuiera sur l'instruction PCMPGTD (Compare Packed Signed Integers for Greater Than) qui permet de comparer deux registres SSE.

pcmpgtd

Dans l'exemple précédent, si on compare V1 à V2, puis V1 à V3, on obtient les résultats suivants mais on voudrait obtenir des valeurs comme dans la colonne de droite :

Cmp(V2,V1) 0xFFFFFFFF 0x00000000 0x00000000 0x00000000 = 1000_b = 8
Cmp(V3,V1) 0xFFFFFFFF 0xFFFFFFFF 0x00000000 0x00000000 = 1100_b = 12

Puis en fonction des deux valeurs obtenues sur 4 bits, on génère une valeur sur 8 bits :

C = 16 * Cmp(V2,V1) + Cmp(V3,V1) = 16 * 8 + 12 = 140

Ce qui permet pour chaque cas ( [1,2,3,4], [1,4,3,2], [1,2,2,3], ...) d'obtenir une valeur unique et de calculer la constante à utiliser pour PSHUFD.

Le problème est que pour récupérer le résultat de la comparaison on est contraint de passer par l'instruction PMOVMSKB (Move Byte Mask) :

pmovmskb

PMOVMSKB appliqué aux comparaisons de V2 avec V1 et V3 avec V1 donne les résultats suivants et on désire récupérer les bits en rouge :

PMOVMSKB de Cmp(V2,V1) 0xF000 1111_0000_0000_0000_b
PMOVMSKB de Cmp(V3,V1) 0xFF00 1111_1111_0000_0000_b

Pour cela on dispose de l'instruction PEXT (Parallel Bits Extract) qui utilise le masque du troisième opérande afin de sélectionner les bits du second opérande et mettre le résultat dans le premier opérande :

PEXT r32a, r32b, r/m32

Dans notre cas, il suffira d'appliquer le masque 0x1111 sur le résultat de PMOVMSKB.

1.3.3. Modification du code à la volée

La modification de l'instruction à la volée est un véritable challenge pour le processeur puisque l'instruction est probablement en train d'être décodée, voire traitée. et il faut la recharger.

Merge Sort Path

1.3.4. Le code principal

On charge le vecteur [4, 3, 2, 1], dans la suite les valeurs contenues dans les registres SSE sont affichées dans l'ordre inverse afin d'afficher la partie haute du registre SSE à gauche et la partie basse à droite :

 Instruction   Valeur / Vecteur 
 movdqu xmm0, [ecx]   [00 00 00 01_00 00 00 02_00 00 00 03_ 00 00 00 04] 
 pshufd xmm1, xmm0, 00111001b ; 0x39   [00 00 00 04_00 00 00 01_00 00 00 02_00 00 00 03] 
 pshufd xmm2, xmm0, 01001110b ; 0x4E   [00 00 00 03_00 00 00 04_00 00 00 01_00 00 00 02] 
 push ebx    
 mov edx, 0x1111   edx=00.00.11.11_h 
 pcmpgtd xmm1, xmm0   [FF FF FF FF_00 00 00 00_00 00 00 00_00 00 00 00] 
 pmovmskb eax, xmm1   eax = F000 
 pext ebx, eax, edx    ebx= 1000_b = 8 
 shl ebx, 4   ebx * 16 = 128 
 pcmpgtd xmm2, xmm0    [FF FF FF FF_FF FF FF FF_00 00 00 00_00 00 00 00] 
 pmovmskb eax, xmm2   eax = FF00 
 pext eax, eax, edx   eax = 1100_b = 12 
 add eax, ebx   eax = 140 
 mov edx, pshufd_label_text    
 movzx eax, byte [pshufd_table + eax]   eax = 1B = 27 
Code

Au final le réarrangement est 1B_h = 0001_1011_b

  1. ; ------------------------------------------------------------------
  2. ; Author: Jean-Michel RICHER
  3. ; Email: jean-michel.richer@univ-angers.fr
  4. ; Date: September 2018
  5. ; ------------------------------------------------------------------
  6.  
  7. ;
  8. ; version with .text section writable
  9. ; so the instruction in the .text section is modified
  10. ;
  11.  
  12. %include "src/asm_config.inc"
  13.  
  14. global asm_sse_sort
  15.  
  16. ; ==============
  17. ; ==== DATA ====
  18. ; ==============
  19. section .data
  20.  
  21. ; -------------------------------------
  22. ; table of values for PSHUFD             
  23. align 16
  24. pshufd_table: db 228,0,0,0,0,0,0,0,0,
  25.     db 0,0,0,0,0,0,0,0,
  26.     db 228,0,0,0,0,0,0,0,
  27.     db 108,0,0,0,0,0,0,0,
  28.     db 0,225,177,0,0,0,0,0,
  29.     db 0,0,0,0,0,0,0,0,
  30.     db 180,0,180,0,0,0,0,0,
  31.     db 156,0,0,0,0,0,0,0,
  32.     db 0,0,0,210,0,198,0,0,
  33.     db 0,0,0,0,0,0,0,0,
  34.     db 0,0,216,0,0,210,0,0,
  35.     db 120,0,0,114,0,0,0,0,
  36.     db 0,225,225,0,0,201,0,0,
  37.     db 0,0,0,0,0,0,0,0,
  38.     db 0,0,228,0,0,0,0,0,
  39.     db 0,0,0,0,0,0,0,0,
  40.     db 0,0,0,0,0,0,0,147,
  41.     db 0,0,0,27,0,0,0,0,
  42.     db 0,0,0,0,0,0,0,99,
  43.     db 99,0,0,75,0,0,0,0,
  44.     db 0,0,141,0,0,45,0,0,
  45.     db 135,0,0,39,0,0,0,0,
  46.     db 0,0,0,0,0,0,0,0,
  47.     db 147,0,0,0,0,0,0,0,
  48.     db 0,0,0,54,0,54,0,0,
  49.     db 0,0,0,30,0,0,0,0,
  50.     db 0,0,0,0,0,0,0,0,
  51.     db 0,0,0,78,0,0,0,0,
  52.     db 0,0,0,0,0,57,0,0,
  53.     db 0,0,0,0,0,0,0,0,
  54.     db 0,0,0,0,0,0,0,0,
  55.     db 0,0,0,0,0,0,0
  56.  
  57. ; ==============
  58. ; ==== TEXT ====
  59. ; ==============    
  60. section .text
  61.    
  62. ; ------------------------------------------   
  63. ; !!!!!!!!!!!!!!!!!! Note !!!!!!!!!!!!!!!!!!
  64. ; this is a fast call subprogram so first
  65. ; parameter t is placed in ECX in 32 bits
  66. ; for GCC/G++
  67. ;
  68. ; void asm_sse_sort(int *t)
  69. ;
  70. ; ------------------------------------------
  71. asm_sse_sort:
  72.  
  73.     movdqu  xmm0, [ecx]
  74.    
  75.     ; xmm1 is a rotation of xmm0
  76.     pshufd  xmm1, xmm0, 00111001b ; 0x39
  77.    
  78.     ; xmm2 is a rotation of xmm0
  79.     pshufd  xmm2, xmm0, 01001110b ; 0x4E
  80.  
  81.     ; save ebx cause it will be modified
  82.     push        ebx              
  83.    
  84.     ; mask for PEXT instruction
  85.     mov         edx, 0x1111      
  86.    
  87.     ; compare xmm1 to xmm0 and get result in xmm1  
  88.     pcmpgtd     xmm1, xmm0        
  89.     ; get result of comparison
  90.     pmovmskb    eax, xmm1        
  91.     pext        ebx, eax, edx    
  92.    
  93.     shl         ebx, 4            
  94.    
  95.     ; compare xmm2 to xmm0 and get result in xmm2      
  96.     pcmpgtd     xmm2, xmm0        
  97.     pmovmskb    eax, xmm2        
  98.     pext        eax, eax, edx    
  99.    
  100.     ; compute identifier
  101.     add         eax, ebx           
  102.  
  103.     ; modify PSHUFD constant
  104.     mov         edx, pshufd_label_text
  105.     movzx       eax, byte [pshufd_table + eax] ;
  106.     mov         [edx+4], al
  107.  
  108.     ; restore EBX
  109.     pop         ebx              
  110.    
  111. pshufd_label_text:
  112.     pshufd      xmm0,xmm0,0xe0    ; <---- modify here
  113.     movdqu      [ecx],xmm0
  114.     ret
  115.  
  116.  

1.3.5. Permettre la modification de la section .text

  1. #include <errno.h>
  2. #include <unistd.h>
  3. #include <sys/mman.h>
  4.  
  5. int change_page_permissions_of_address(void *addr) {
  6.     int page_size = getpagesize();
  7.     addr -= (unsigned long)addr % page_size;
  8.  
  9.     int protection = PROT_READ | PROT_WRITE | PROT_EXEC;
  10.     if(mprotect(addr, page_size, protection) == -1) {
  11.         return -1;
  12.     }
  13.  
  14.     return 0;
  15. }
  16.  
  17. extern "C" {
  18.     // note that for fast call parameter t is in ECX
  19.     void asm_sse_sort(int *t) __attribute__((fastcall));
  20. }
  21.  
  22. int main(int argc, char *argv[]) {
  23.  
  24.     void *proc_addr = (void*)asm_sse_sort;
  25.  
  26.     if(change_page_permissions_of_address(proc_addr) == -1) {
  27.         cerr << "Error while changing page permissions" << endl;
  28.         cerr << strerror(errno) << endl;
  29.         return 1;
  30.     }
  31.  
  32.  
  33.     ....
  34. }
  35.  

1.3.6. remplacer PEXT par AND/SHR/OR

Comme indiqué dans l'introduction, certains processeurs ne disposent pas de l'instruction PEXT qui est relativement récente et date de 2013. On va donc la remplacer par une série de décalages et sélection de bits.

Ici eax est l'entrée et contient une valeur du type 000X.0000Y.000Z.000T_b et on veut obtenir XYZT_b. Le registre edx contient le masque de sélection 0001.0001.0001.0001_b = 1111_h :

; par exemple 
; eax = 1111.1110.1110.1111_b
; edx = 0001.0001.0001.0001_b
and    eax, edx   ;  eax = 0001.0000.0000.0001_b   application du masque
mov    ebx, eax   ;  ebx = 0001.0000.0000.0001_b   sauvegarde dans ebx de eax
shr    eax, 3     ;  eax =     00010.0000.0000_b   décalage vers la droite  
or     ebx, eax   ;  ebx = 0001.0010.0000.0001_b
shr    eax, 3     ;  eax =        00.0100.0000_b
or     ebx, eax   ;  ebx = 0001.0010.0100.0001_b
shr    eax, 3     ;  eax =           0000.1000_b
or     ebx, eax   ;  ebx = 0001.0010.0100.1001_b
and    ebx, 1111b ;  ebx = 0000.0000.0000.1001_b

On peut écrire la même chose en C :

z = ((z & 0x1000) >> 9) + ((z & 0x0100) >> 6) + ((z & 0x0010) >> 3) + (z & 1);

Par exemple GCC traduira le code précédent sous la forme suivante :

mov     ecx, DWORD [esp+4] ; ecx = z
mov     eax, ecx           ; eax = z
mov     edx, ecx           ; edx = z
shr     eax, 9             ; récupère les deux bits de poids fort
shr     edx, 6
and     eax, 8
and     edx, 4
or      edx, eax
mov     eax, ecx           ; récupère les deux bits de poids faible
and     ecx, 1
shr     eax, 3
and     eax, 2
or      ecx, eax
lea     eax, [edx+ecx]

1.3.7. Switch / Case of

Enfin, on peut se demander si utiliser un switch dégraderait ou améliorerait la performance de l'algorithme. Plutôt que de modifier la constante de PSHUFD, on va se diriger vers du code spécifique :

L'idée est d'utiliser une table avec les adresses du code à exécuter:

  1. ; ------------------------------------------------------------------
  2. ; Author: Jean-Michel RICHER
  3. ; Email: jean-michel.richer@univ-angers.fr
  4. ; Date: September 2018
  5. ; ------------------------------------------------------------------
  6.  
  7. ;
  8. ; version with .text section writable
  9. ; so the instruction in the .text section is modified
  10. ;
  11.  
  12. %include "src/asm_config.inc"
  13.  
  14. global asm_sse_sort
  15.  
  16. section .data
  17.  
  18.              
  19. align(16)
  20.  
  21.    
  22. section .text
  23.    
  24. ; ------------------------------------------   
  25. ; !!!!!!!!!!!!!!!!!! Note !!!!!!!!!!!!!!!!!!
  26. ; this is a fast call subprogram so first
  27. ; parameter t is placed in ECX in 32 bits
  28. ; for GCC/G++
  29. ;
  30. ; void asm_sse_sort(int *t)
  31. ;
  32.  
  33. ;%define BMI2
  34.  
  35. ; PEXT result, input, mask
  36.  
  37. %ifdef BMI2
  38.  
  39.     %macro BMI2_PEXT 3
  40.         pext        %1, %2, %3
  41.     %endmacro
  42.  
  43. %else
  44.  
  45.     %macro BMI2_PEXT 3
  46.         and     %2, %3
  47.         mov     %1, %2
  48.         shr     %2, 3
  49.         or      %1, %2
  50.         shr     %2, 3
  51.         or      %1, %2
  52.         shr     %2, 3
  53.         or      %1, %2
  54.         and     %1, 1111b
  55.     %endmacro
  56.  
  57. %endif
  58.  
  59.  
  60. asm_sse_sort:
  61.     ; xmm0 = [1, 2, 3, 4]
  62.     movdqu  xmm0, [ecx]
  63.     ; xmm1 = [4, 1, 2, 3]
  64.     pshufd  xmm1, xmm0, 00111001b ; 0x39
  65.     ; xmm2 = [3, 4, 1, 2]
  66.     pshufd  xmm2, xmm0, 01001110b ; 0x4E
  67.  
  68.     push        ebx             ; save EBX
  69.    
  70.     mov         edx, 0x1111     ; mask for pext
  71.    
  72.     pcmpgtd     xmm1, xmm0
  73.     pmovmskb    eax, xmm1
  74.     BMI2_PEXT   ebx, eax, edx
  75.    
  76.     ;  and      eax, edx
  77.     ;   mov     ebx, eax
  78.     ;   shr     eax, 3
  79.     ;   or      ebx, eax
  80.     ;   shr     eax, 3
  81.     ;   or      ebx, eax
  82.     ;   shr     eax, 3
  83.     ;   or      ebx, eax
  84.     ;   and     ebx, 1111b
  85.        
  86.     shl         ebx, 4
  87.            
  88.     pcmpgtd     xmm2, xmm0
  89.     pmovmskb    eax, xmm2
  90.    
  91. %ifdef BMI2
  92.     BMI2_PEXT       eax, eax, edx
  93. %else
  94.     push            ebx
  95.     mov             ebx, eax
  96.     BMI2_PEXT       eax, ebx, edx
  97.     pop             ebx
  98. %endif 
  99.    
  100.     add         eax, ebx
  101.  
  102.     pop         ebx     ; restore EBX
  103.    
  104.     mov         eax, dword [cases_table + eax * 4]
  105.     jmp         eax
  106.  
  107.  
  108.  
  109. LP0:
  110.     pshufd xmm0,xmm0,0
  111.     movdqu [ecx],xmm0
  112.     ret
  113. LP228:
  114.     pshufd xmm0,xmm0,228
  115.     movdqu [ecx],xmm0
  116.     ret
  117. LP108:
  118.     pshufd xmm0,xmm0,108
  119.     movdqu [ecx],xmm0
  120.     ret
  121. LP225:
  122.     pshufd xmm0,xmm0,225
  123.     movdqu [ecx],xmm0
  124.     ret
  125. LP177:
  126.     pshufd xmm0,xmm0,177
  127.     movdqu [ecx],xmm0
  128.     ret
  129. LP180:
  130.     pshufd xmm0,xmm0,180
  131.     movdqu [ecx],xmm0
  132.     ret
  133. LP156:
  134.     pshufd xmm0,xmm0,156
  135.     movdqu [ecx],xmm0
  136.     ret
  137. LP210:
  138.     pshufd xmm0,xmm0,210
  139.     movdqu [ecx],xmm0
  140.     ret
  141. LP198:
  142.     pshufd xmm0,xmm0,198
  143.     movdqu [ecx],xmm0
  144.     ret
  145. LP216:
  146.     pshufd xmm0,xmm0,216
  147.     movdqu [ecx],xmm0
  148.     ret
  149. LP120:
  150.     pshufd xmm0,xmm0,120
  151.     movdqu [ecx],xmm0
  152.     ret
  153. LP114:
  154.     pshufd xmm0,xmm0,114
  155.     movdqu [ecx],xmm0
  156.     ret
  157. LP201:
  158.     pshufd xmm0,xmm0,201
  159.     movdqu [ecx],xmm0
  160.     ret
  161. LP147:
  162.     pshufd xmm0,xmm0,147
  163.     movdqu [ecx],xmm0
  164.     ret
  165. LP27:
  166.     pshufd xmm0,xmm0,27
  167.     movdqu [ecx],xmm0
  168.     ret
  169. LP99:
  170.     pshufd xmm0,xmm0,99
  171.     movdqu [ecx],xmm0
  172.     ret
  173. LP75:
  174.     pshufd xmm0,xmm0,75
  175.     movdqu [ecx],xmm0
  176.     ret
  177. LP141:
  178.     pshufd xmm0,xmm0,141
  179.     movdqu [ecx],xmm0
  180.     ret
  181. LP45:
  182.     pshufd xmm0,xmm0,45
  183.     movdqu [ecx],xmm0
  184.     ret
  185. LP135:
  186.     pshufd xmm0,xmm0,135
  187.     movdqu [ecx],xmm0
  188.     ret
  189. LP39:
  190.     pshufd xmm0,xmm0,39
  191.     movdqu [ecx],xmm0
  192.     ret
  193. LP54:
  194.     pshufd xmm0,xmm0,54
  195.     movdqu [ecx],xmm0
  196.     ret
  197. LP30:
  198.     pshufd xmm0,xmm0,30
  199.     movdqu [ecx],xmm0
  200.     ret
  201. LP78:
  202.     pshufd xmm0,xmm0,78
  203.     movdqu [ecx],xmm0
  204.     ret
  205. LP57:
  206.     pshufd xmm0,xmm0,57
  207.     movdqu [ecx],xmm0
  208.     ret
  209.    
  210. cases_table:   
  211. dd LP228, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0,  ;9
  212. dd LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP228, LP0, LP0,  ;19
  213. dd LP0, LP0, LP0, LP0, LP0, LP108, LP0, LP0, LP0, LP0,  ;29
  214. dd LP0, LP0, LP0, LP0, LP225, LP177, LP0, LP0, LP0, LP0,  ;39
  215. dd LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP180,  ;49
  216. dd LP0, LP180, LP0, LP0, LP0, LP0, LP0, LP156, LP0, LP0,  ;59
  217. dd LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP210, LP0,  ;69
  218. dd LP198, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0,  ;79
  219. dd LP0, LP0, LP0, LP216, LP0, LP0, LP210, LP0, LP0, LP120,  ;89
  220. dd LP0, LP0, LP114, LP0, LP0, LP0, LP0, LP0, LP225, LP225,  ;99
  221. dd LP0, LP0, LP201, LP0, LP0, LP0, LP0, LP0, LP0, LP0,  ;109
  222. dd LP0, LP0, LP0, LP0, LP0, LP228, LP0, LP0, LP0, LP0,  ;119
  223. dd LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0,  ;129
  224. dd LP0, LP0, LP0, LP0, LP0, LP0, LP147, LP0, LP0, LP0,  ;139
  225. dd LP27, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0,  ;149
  226. dd LP0, LP0, LP99, LP99, LP0, LP0, LP75, LP0, LP0, LP0,  ;159
  227. dd LP0, LP0, LP0, LP141, LP0, LP0, LP45, LP0, LP0, LP135,  ;169
  228. dd LP0, LP0, LP39, LP0, LP0, LP0, LP0, LP0, LP0, LP0,  ;179
  229. dd LP0, LP0, LP0, LP0, LP0, LP147, LP0, LP0, LP0, LP0,  ;189
  230. dd LP0, LP0, LP0, LP0, LP0, LP0, LP54, LP0, LP54, LP0,  ;199
  231. dd LP0, LP0, LP0, LP0, LP30, LP0, LP0, LP0, LP0, LP0,  ;209
  232. dd LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0,  ;219
  233. dd LP78, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0,  ;229
  234. dd LP57, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0,  ;239
  235. dd LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0, LP0,  ;249
  236. dd LP0, LP0, LP0, LP0, LP0, LP0,
  237.    
  238.  
  239.  
  240.  
  241.  
  242.  
  243.  

1.4. Résultats

Voici un tableau comparatif concernant quelques architectures :

 Processeur   BMI2   std::vector   .data + SSE   .text + SSE   case + SSE 
 AMD Ryzen 1700X   Oui   6.42   7.20   7.24   6.29 
 Intel i5-7400   Oui   7.32   11.42   12.28   7.32 
 Intel i3-6100   Oui   6.29   9.68   10.42   6.23 
 Intel i7-4790   Non   5.23   8.10   8.67   4.95 
 Intel i5-3570K   Non   8.96   11.70   12.27   8.71 
 Intel i7 860   Non   14.33   17.35   17.99   14.53 
 Intel N3060   Non   18.25   23.84   23.99   17.85 
Temps en secondes pour 134_217_728=2^27 valeurs (moyenne pour 10 exécutions)

Quelles remarques peut on faire ?