Perché la somma raggruppata è più lenta con i gruppi ordinati rispetto ai gruppi non ordinati?


27

Ho 2 colonne di numeri interi delimitati da tabulazioni, il primo dei quali è un numero intero casuale, il secondo un numero intero che identifica il gruppo, che può essere generato da questo programma. ( generate_groups.cc)

#include <cstdlib>
#include <iostream>
#include <ctime>

int main(int argc, char* argv[]) {
  int num_values = atoi(argv[1]);
  int num_groups = atoi(argv[2]);

  int group_size = num_values / num_groups;
  int group = -1;

  std::srand(42);

  for (int i = 0; i < num_values; ++i) {
    if (i % group_size == 0) {
      ++group;
    }
    std::cout << std::rand() << '\t' << group << '\n';
  }

  return 0;
}

Quindi uso un secondo programma ( sum_groups.cc) per calcolare le somme per gruppo.

#include <iostream>
#include <chrono>
#include <vector>

// This is the function whose performance I am interested in
void grouped_sum(int* p_x, int *p_g, int n, int* p_out) {
  for (size_t i = 0; i < n; ++i) {
    p_out[p_g[i]] += p_x[i];
  }
}

int main() {
  std::vector<int> values;
  std::vector<int> groups;
  std::vector<int> sums;

  int n_groups = 0;

  // Read in the values and calculate the max number of groups
  while(std::cin) {
    int value, group;
    std::cin >> value >> group;
    values.push_back(value);
    groups.push_back(group);
    if (group > n_groups) {
      n_groups = group;
    }
  }
  sums.resize(n_groups);

  // Time grouped sums
  std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
  for (int i = 0; i < 10; ++i) {
    grouped_sum(values.data(), groups.data(), values.size(), sums.data());
  }
  std::chrono::system_clock::time_point end = std::chrono::system_clock::now();

  std::cout << (end - start).count() << std::endl;

  return 0;
}

Se quindi eseguo questi programmi su un set di dati di dimensioni specifiche, quindi mischio l'ordine delle righe dello stesso set di dati, i dati mescolati calcolano le somme ~ 2x o più velocemente dei dati ordinati.

g++ -O3 generate_groups.cc -o generate_groups
g++ -O3 sum_groups.cc -o sum_groups
generate_groups 1000000 100 > groups
shuf groups > groups2
sum_groups < groups
sum_groups < groups2
sum_groups < groups2
sum_groups < groups
20784
8854
8220
21006

Mi sarei aspettato che i dati originali, ordinati per gruppo, avessero una migliore localizzazione dei dati e fossero più veloci, ma osservo il comportamento opposto. Mi chiedevo se qualcuno potesse ipotizzare il motivo?


1
Non lo so, ma stai scrivendo su elementi fuori portata del vettore somme - se hai fatto la cosa normale e hai passato riferimenti a vettori invece di puntatori agli elementi di dati, e poi hai usato .at()o una modalità di debug operator[]che fa i limiti controllando vedresti.
Shawn,

Hai verificato che il file "groups2" contiene tutti i tuoi dati e che è stato letto ed elaborato? C'è forse un personaggio EOF nel mezzo da qualche parte?
1201 Programma Allarme

2
Il programma ha un comportamento indefinito perché non si ridimensiona mai sum. Invece di sums.reserve(n_groups);te devi chiamare sums.resize(n_groups);- questo è ciò che suggeriva @Shawn.
Eugene,

1
Nota (vedi ad esempio qui o qui ) che un vettore di coppie, anziché due vettori (valori e gruppo), si comporta come previsto.
Bob__

1
Hai ordinato i dati in base ai valori, giusto? Ma poi questo ordina anche i gruppi e ciò ha un impatto sulla xpressione p_out[p_g[i]] += p_x[i];. Forse nell'ordine originale criptato, i gruppi mostrano effettivamente un buon raggruppamento per quanto riguarda l'accesso p_outall'array. L'ordinamento dei valori può causare un modello di accesso indicizzato a gruppi scadenti p_out.
Kaz,

Risposte:


33

Imposta / rallenta

Prima di tutto, il programma funziona all'incirca nello stesso momento, indipendentemente da:

sumspeed$ time ./sum_groups < groups_shuffled 
11558358

real    0m0.705s
user    0m0.692s
sys 0m0.013s

sumspeed$ time ./sum_groups < groups_sorted
24986825

real    0m0.722s
user    0m0.711s
sys 0m0.012s

La maggior parte del tempo è trascorso nel ciclo di input. Ma dal momento che siamo interessati a grouped_sum(), ignoriamolo.

Cambiando il ciclo di riferimento da 10 a 1000 iterazioni, grouped_sum()inizia a dominare il tempo di esecuzione:

sumspeed$ time ./sum_groups < groups_shuffled 
1131838420

real    0m1.828s
user    0m1.811s
sys 0m0.016s

sumspeed$ time ./sum_groups < groups_sorted
2494032110

real    0m3.189s
user    0m3.169s
sys 0m0.016s

perf diff

Ora possiamo usare perfper trovare i punti più caldi del nostro programma.

sumspeed$ perf record ./sum_groups < groups_shuffled
1166805982
[ perf record: Woken up 1 times to write data ]
[kernel.kallsyms] with build id 3a2171019937a2070663f3b6419330223bd64e96 not found, continuing without symbols
Warning:
Processed 4636 samples and lost 6.95% samples!

[ perf record: Captured and wrote 0.176 MB perf.data (4314 samples) ]

sumspeed$ perf record ./sum_groups < groups_sorted
2571547832
[ perf record: Woken up 2 times to write data ]
[kernel.kallsyms] with build id 3a2171019937a2070663f3b6419330223bd64e96 not found, continuing without symbols
[ perf record: Captured and wrote 0.420 MB perf.data (10775 samples) ]

E la differenza tra loro:

sumspeed$ perf diff
[...]
# Event 'cycles:uppp'
#
# Baseline  Delta Abs  Shared Object        Symbol                                                                  
# ........  .........  ...................  ........................................................................
#
    57.99%    +26.33%  sum_groups           [.] main
    12.10%     -7.41%  libc-2.23.so         [.] _IO_getc
     9.82%     -6.40%  libstdc++.so.6.0.21  [.] std::num_get<char, std::istreambuf_iterator<char, std::char_traits<c
     6.45%     -4.00%  libc-2.23.so         [.] _IO_ungetc
     2.40%     -1.32%  libc-2.23.so         [.] _IO_sputbackc
     1.65%     -1.21%  libstdc++.so.6.0.21  [.] 0x00000000000dc4a4
     1.57%     -1.20%  libc-2.23.so         [.] _IO_fflush
     1.71%     -1.07%  libstdc++.so.6.0.21  [.] std::istream::sentry::sentry
     1.22%     -0.77%  libstdc++.so.6.0.21  [.] std::istream::operator>>
     0.79%     -0.47%  libstdc++.so.6.0.21  [.] __gnu_cxx::stdio_sync_filebuf<char, std::char_traits<char> >::uflow
[...]

Più tempo a disposizione main(), che probabilmente è grouped_sum()inline. Ottimo, grazie mille, perf.

perf annotate

C'è differenza nel tempo trascorso all'interno main() ?

mescolate:

sumspeed$ perf annotate -i perf.data.old
[...]
            // This is the function whose performance I am interested in
            void grouped_sum(int* p_x, int *p_g, int n, int* p_out) {
              for (size_t i = 0; i < n; ++i) {
       180:   xor    %eax,%eax
              test   %rdi,%rdi
             je     1a4
              nop
                p_out[p_g[i]] += p_x[i];
  6,88 190:   movslq (%r9,%rax,4),%rdx
 58,54        mov    (%r8,%rax,4),%esi
            #include <chrono>
            #include <vector>
       
            // This is the function whose performance I am interested in
            void grouped_sum(int* p_x, int *p_g, int n, int* p_out) {
              for (size_t i = 0; i < n; ++i) {
  3,86        add    $0x1,%rax
                p_out[p_g[i]] += p_x[i];
 29,61        add    %esi,(%rcx,%rdx,4)
[...]

Smistato:

sumspeed$ perf annotate -i perf.data
[...]
            // This is the function whose performance I am interested in
            void grouped_sum(int* p_x, int *p_g, int n, int* p_out) {
              for (size_t i = 0; i < n; ++i) {
       180:   xor    %eax,%eax
              test   %rdi,%rdi
             je     1a4
              nop
                p_out[p_g[i]] += p_x[i];
  1,00 190:   movslq (%r9,%rax,4),%rdx
 55,12        mov    (%r8,%rax,4),%esi
            #include <chrono>
            #include <vector>
       
            // This is the function whose performance I am interested in
            void grouped_sum(int* p_x, int *p_g, int n, int* p_out) {
              for (size_t i = 0; i < n; ++i) {
  0,07        add    $0x1,%rax
                p_out[p_g[i]] += p_x[i];
 43,28        add    %esi,(%rcx,%rdx,4)
[...]

No, sono le stesse due istruzioni a dominare. Quindi impiegano molto tempo in entrambi i casi, ma sono anche peggio quando i dati vengono ordinati.

perf stat

Va bene. Ma dovremmo eseguirli lo stesso numero di volte, quindi ogni istruzione deve essere più lenta per qualche motivo. Vediamo cosa perf statdice.

sumspeed$ perf stat ./sum_groups < groups_shuffled 
1138880176

 Performance counter stats for './sum_groups':

       1826,232278      task-clock (msec)         #    0,999 CPUs utilized          
                72      context-switches          #    0,039 K/sec                  
                 1      cpu-migrations            #    0,001 K/sec                  
             4 076      page-faults               #    0,002 M/sec                  
     5 403 949 695      cycles                    #    2,959 GHz                    
       930 473 671      stalled-cycles-frontend   #   17,22% frontend cycles idle   
     9 827 685 690      instructions              #    1,82  insn per cycle         
                                                  #    0,09  stalled cycles per insn
     2 086 725 079      branches                  # 1142,639 M/sec                  
         2 069 655      branch-misses             #    0,10% of all branches        

       1,828334373 seconds time elapsed

sumspeed$ perf stat ./sum_groups < groups_sorted
2496546045

 Performance counter stats for './sum_groups':

       3186,100661      task-clock (msec)         #    1,000 CPUs utilized          
                 5      context-switches          #    0,002 K/sec                  
                 0      cpu-migrations            #    0,000 K/sec                  
             4 079      page-faults               #    0,001 M/sec                  
     9 424 565 623      cycles                    #    2,958 GHz                    
     4 955 937 177      stalled-cycles-frontend   #   52,59% frontend cycles idle   
     9 829 009 511      instructions              #    1,04  insn per cycle         
                                                  #    0,50  stalled cycles per insn
     2 086 942 109      branches                  #  655,014 M/sec                  
         2 078 204      branch-misses             #    0,10% of all branches        

       3,186768174 seconds time elapsed

Solo una cosa spicca: i cicli di stallo-frontend .

Ok, la pipeline di istruzioni si sta bloccando. Nel frontend. Esattamente ciò che ciò significa probabilmente varia tra microarchictectures.

Ho una supposizione, però. Se sei generoso, potresti persino definirlo un'ipotesi.

Ipotesi

Ordinando l'input, si aumenta la località delle scritture. In effetti, saranno molto locali; quasi tutte le aggiunte che fai scriveranno nella stessa posizione della precedente.

È fantastico per la cache, ma non eccezionale per la pipeline. Stai introducendo dipendenze dei dati, impedendo che le istruzioni per l'aggiunta successiva continuino fino al completamento dell'aggiunta precedente (o abbia altrimenti reso il risultato disponibile per le istruzioni successive )

Questo è il tuo problema.

Penso.

Risolvendolo

Vettori di somma multipla

In realtà, proviamo qualcosa. E se usassimo più vettori somma, passando da uno all'altro per ogni aggiunta, e poi li sommassimo alla fine? Ci costa un po 'di località, ma dovrebbe rimuovere le dipendenze dei dati.

(il codice non è carino; non giudicarmi, internet !!)

#include <iostream>
#include <chrono>
#include <vector>

#ifndef NSUMS
#define NSUMS (4) // must be power of 2 (for masking to work)
#endif

// This is the function whose performance I am interested in
void grouped_sum(int* p_x, int *p_g, int n, int** p_out) {
  for (size_t i = 0; i < n; ++i) {
    p_out[i & (NSUMS-1)][p_g[i]] += p_x[i];
  }
}

int main() {
  std::vector<int> values;
  std::vector<int> groups;
  std::vector<int> sums[NSUMS];

  int n_groups = 0;

  // Read in the values and calculate the max number of groups
  while(std::cin) {
    int value, group;
    std::cin >> value >> group;
    values.push_back(value);
    groups.push_back(group);
    if (group >= n_groups) {
      n_groups = group+1;
    }
  }
  for (int i=0; i<NSUMS; ++i) {
    sums[i].resize(n_groups);
  }

  // Time grouped sums
  std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
  int* sumdata[NSUMS];
  for (int i = 0; i < NSUMS; ++i) {
    sumdata[i] = sums[i].data();
  }
  for (int i = 0; i < 1000; ++i) {
    grouped_sum(values.data(), groups.data(), values.size(), sumdata);
  }
  for (int i = 1; i < NSUMS; ++i) {
    for (int j = 0; j < n_groups; ++j) {
      sumdata[0][j] += sumdata[i][j];
    }
  }
  std::chrono::system_clock::time_point end = std::chrono::system_clock::now();

  std::cout << (end - start).count() << " with NSUMS=" << NSUMS << std::endl;

  return 0;
}

(oh, e ho anche corretto il calcolo di n_groups; era spento di uno.)

risultati

Dopo aver configurato il mio makefile per dare un -DNSUMS=...arg al compilatore, ho potuto fare questo:

sumspeed$ for n in 1 2 4 8 128; do make -s clean && make -s NSUMS=$n && (perf stat ./sum_groups < groups_shuffled && perf stat ./sum_groups < groups_sorted)  2>&1 | egrep '^[0-9]|frontend'; done
1134557008 with NSUMS=1
       924 611 882      stalled-cycles-frontend   #   17,13% frontend cycles idle   
2513696351 with NSUMS=1
     4 998 203 130      stalled-cycles-frontend   #   52,79% frontend cycles idle   
1116188582 with NSUMS=2
       899 339 154      stalled-cycles-frontend   #   16,83% frontend cycles idle   
1365673326 with NSUMS=2
     1 845 914 269      stalled-cycles-frontend   #   29,97% frontend cycles idle   
1127172852 with NSUMS=4
       902 964 410      stalled-cycles-frontend   #   16,79% frontend cycles idle   
1171849032 with NSUMS=4
     1 007 807 580      stalled-cycles-frontend   #   18,29% frontend cycles idle   
1118732934 with NSUMS=8
       881 371 176      stalled-cycles-frontend   #   16,46% frontend cycles idle   
1129842892 with NSUMS=8
       905 473 182      stalled-cycles-frontend   #   16,80% frontend cycles idle   
1497803734 with NSUMS=128
     1 982 652 954      stalled-cycles-frontend   #   30,63% frontend cycles idle   
1180742299 with NSUMS=128
     1 075 507 514      stalled-cycles-frontend   #   19,39% frontend cycles idle   

Il numero ottimale di vettori somma dipenderà probabilmente dalla profondità della pipeline della CPU. La mia CPU ultrabook di 7 anni può probabilmente massimizzare la pipeline con meno vettori rispetto a una nuova sofisticata CPU desktop.

Chiaramente, di più non è necessariamente meglio; quando impazzivo con 128 vettori somma, iniziammo a soffrire di più per i mancati cache - come evidenziato dall'input mischiato che diventa più lento di quello ordinato, come inizialmente ti aspettavi. Siamo tornati al punto di partenza! :)

Somma per gruppo nel registro

(questo è stato aggiunto in una modifica)

Eh, secchione ! Se sai che i tuoi input verranno ordinati e stai cercando prestazioni ancora maggiori, la seguente riscrittura della funzione (senza array di somma extra) è ancora più veloce, almeno sul mio computer.

// This is the function whose performance I am interested in
void grouped_sum(int* p_x, int *p_g, int n, int* p_out) {
  int i = n-1;
  while (i >= 0) {
    int g = p_g[i];
    int gsum = 0;
    do {
      gsum += p_x[i--];
    } while (i >= 0 && p_g[i] == g);
    p_out[g] += gsum;
  }
}

Il trucco in questo è che consente al compilatore di mantenere la gsumvariabile, la somma del gruppo, in un registro. Sto indovinando (ma potrebbe essere molto sbagliato) che questo è più veloce perché il ciclo di feedback nella pipeline può essere più breve qui e / o meno accessi alla memoria. Un buon predittore di ramo renderà economico il controllo extra per l'uguaglianza di gruppo.

risultati

È terribile per input mischiato ...

sumspeed$ time ./sum_groups < groups_shuffled
2236354315

real    0m2.932s
user    0m2.923s
sys 0m0.009s

... ma è circa il 40% più veloce della mia soluzione "molte somme" per l'input ordinato.

sumspeed$ time ./sum_groups < groups_sorted
809694018

real    0m1.501s
user    0m1.496s
sys 0m0.005s

Molti piccoli gruppi saranno più lenti di alcuni grandi, quindi se questa è l'implementazione più veloce dipenderà davvero dai tuoi dati qui. E, come sempre, sul tuo modello di CPU.

Più vettori di somme, con offset anziché maschera di bit

Sopel ha suggerito quattro aggiunte non svelate come alternativa al mio approccio di mascheramento dei bit. Ho implementato una versione generalizzata del loro suggerimento, che può gestire diversi NSUMS. Conto sul compilatore che svolge il ciclo interno per noi (cosa che ha fatto, almeno per NSUMS=4).

#include <iostream>
#include <chrono>
#include <vector>

#ifndef NSUMS
#define NSUMS (4) // must be power of 2 (for masking to work)
#endif

#ifndef INNER
#define INNER (0)
#endif
#if INNER
// This is the function whose performance I am interested in
void grouped_sum(int* p_x, int *p_g, int n, int** p_out) {
  size_t i = 0;
  int quadend = n & ~(NSUMS-1);
  for (; i < quadend; i += NSUMS) {
    for (int k=0; k<NSUMS; ++k) {
      p_out[k][p_g[i+k]] += p_x[i+k];
    }
  }
  for (; i < n; ++i) {
    p_out[0][p_g[i]] += p_x[i];
  }
}
#else
// This is the function whose performance I am interested in
void grouped_sum(int* p_x, int *p_g, int n, int** p_out) {
  for (size_t i = 0; i < n; ++i) {
    p_out[i & (NSUMS-1)][p_g[i]] += p_x[i];
  }
}
#endif


int main() {
  std::vector<int> values;
  std::vector<int> groups;
  std::vector<int> sums[NSUMS];

  int n_groups = 0;

  // Read in the values and calculate the max number of groups
  while(std::cin) {
    int value, group;
    std::cin >> value >> group;
    values.push_back(value);
    groups.push_back(group);
    if (group >= n_groups) {
      n_groups = group+1;
    }
  }
  for (int i=0; i<NSUMS; ++i) {
    sums[i].resize(n_groups);
  }

  // Time grouped sums
  std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
  int* sumdata[NSUMS];
  for (int i = 0; i < NSUMS; ++i) {
    sumdata[i] = sums[i].data();
  }
  for (int i = 0; i < 1000; ++i) {
    grouped_sum(values.data(), groups.data(), values.size(), sumdata);
  }
  for (int i = 1; i < NSUMS; ++i) {
    for (int j = 0; j < n_groups; ++j) {
      sumdata[0][j] += sumdata[i][j];
    }
  }
  std::chrono::system_clock::time_point end = std::chrono::system_clock::now();

  std::cout << (end - start).count() << " with NSUMS=" << NSUMS << ", INNER=" << INNER << std::endl;

  return 0;
}

risultati

Tempo di misurare. Nota che da quando stavo lavorando in / tmp ieri, non ho gli stessi dati di input. Quindi, questi risultati non sono direttamente paragonabili ai precedenti (ma probabilmente abbastanza vicini).

sumspeed$ for n in 2 4 8 16; do for inner in 0 1; do make -s clean && make -s NSUMS=$n INNER=$inner && (perf stat ./sum_groups < groups_shuffled && perf stat ./sum_groups < groups_sorted)  2>&1 | egrep '^[0-9]|frontend'; done; done1130558787 with NSUMS=2, INNER=0
       915 158 411      stalled-cycles-frontend   #   16,96% frontend cycles idle   
1351420957 with NSUMS=2, INNER=0
     1 589 408 901      stalled-cycles-frontend   #   26,21% frontend cycles idle   
840071512 with NSUMS=2, INNER=1
     1 053 982 259      stalled-cycles-frontend   #   23,26% frontend cycles idle   
1391591981 with NSUMS=2, INNER=1
     2 830 348 854      stalled-cycles-frontend   #   45,35% frontend cycles idle   
1110302654 with NSUMS=4, INNER=0
       890 869 892      stalled-cycles-frontend   #   16,68% frontend cycles idle   
1145175062 with NSUMS=4, INNER=0
       948 879 882      stalled-cycles-frontend   #   17,40% frontend cycles idle   
822954895 with NSUMS=4, INNER=1
     1 253 110 503      stalled-cycles-frontend   #   28,01% frontend cycles idle   
929548505 with NSUMS=4, INNER=1
     1 422 753 793      stalled-cycles-frontend   #   30,32% frontend cycles idle   
1128735412 with NSUMS=8, INNER=0
       921 158 397      stalled-cycles-frontend   #   17,13% frontend cycles idle   
1120606464 with NSUMS=8, INNER=0
       891 960 711      stalled-cycles-frontend   #   16,59% frontend cycles idle   
800789776 with NSUMS=8, INNER=1
     1 204 516 303      stalled-cycles-frontend   #   27,25% frontend cycles idle   
805223528 with NSUMS=8, INNER=1
     1 222 383 317      stalled-cycles-frontend   #   27,52% frontend cycles idle   
1121644613 with NSUMS=16, INNER=0
       886 781 824      stalled-cycles-frontend   #   16,54% frontend cycles idle   
1108977946 with NSUMS=16, INNER=0
       860 600 975      stalled-cycles-frontend   #   16,13% frontend cycles idle   
911365998 with NSUMS=16, INNER=1
     1 494 671 476      stalled-cycles-frontend   #   31,54% frontend cycles idle   
898729229 with NSUMS=16, INNER=1
     1 474 745 548      stalled-cycles-frontend   #   31,24% frontend cycles idle   

Sì, il ciclo interno con NSUMS=8è il più veloce sul mio computer. Rispetto al mio approccio "gsum locale", ha anche l'ulteriore vantaggio di non diventare terribile per l'input mischiato.

Interessante da notare: NSUMS=16diventa peggio di NSUMS=8. Ciò potrebbe essere dovuto al fatto che stiamo iniziando a vedere più mancati cache o perché non abbiamo abbastanza registri per srotolare correttamente il ciclo interno.


5
È stato divertente. :)
Snild Dolkow,

3
È stato incredibile! Non lo sapevo perf.
Tanveer Badar,

1
Mi chiedo se nel tuo primo approccio srotolare manualmente 4x con 4 accumulatori diversi produrrebbe prestazioni migliori. Qualcosa di simile a godbolt.org/z/S-PhFm
Sopel

Grazie per il suggerimento Sì, ha aumentato le prestazioni e l'ho aggiunto alla risposta.
Snild Dolkow,

Grazie! Avevo considerato qualcosa del genere che poteva essere la possibilità, ma non sapevo come determinarlo, grazie per la tua risposta dettagliata!
Jim,

3

Ecco perché i gruppi ordinati sono più lenti dei gruppi non salvati;

Il primo qui è il codice assembly per il ciclo di somma:

008512C3  mov         ecx,dword ptr [eax+ebx]
008512C6  lea         eax,[eax+4]
008512C9  lea         edx,[esi+ecx*4] // &sums[groups[i]]
008512CC  mov         ecx,dword ptr [eax-4] // values[i]
008512CF  add         dword ptr [edx],ecx // sums[groups[i]]+=values[i]
008512D1  sub         edi,1
008512D4  jne         main+163h (08512C3h)

Vediamo l'istruzione add che è la ragione principale di questo problema;

008512CF  add         dword ptr [edx],ecx // sums[groups[i]]+=values[i]

Quando il processore esegue prima questa istruzione, invierà una richiesta di lettura (caricamento) della memoria all'indirizzo in edx, quindi aggiungerà il valore di ecx e quindi invierà la richiesta di scrittura (archivio) per lo stesso indirizzo.

c'è una funzione nel riordino della memoria del chiamante del processore

Per consentire l'ottimizzazione delle prestazioni dell'esecuzione delle istruzioni, l'architettura IA-32 consente di discostarsi dal modello strongordering chiamato ordinazione dei processori nei processori della famiglia Pentium 4, Intel Xeon e P6. Queste variazioni del processoreordering (chiamato qui il modello di ordinamento della memoria) consentono operazioni di miglioramento delle prestazioni come consentire alle letture di andare avanti rispetto alle scritture bufferizzate. L'obiettivo di ognuna di queste variazioni è aumentare la velocità di esecuzione delle istruzioni, mantenendo la coerenza della memoria, anche nei sistemi a più processori.

e c'è una regola

Le letture possono essere riordinate con scritture precedenti in posizioni diverse ma non con scritture precedenti nella stessa posizione.

Pertanto, se la successiva iterazione raggiunge l'istruzione add prima del completamento della richiesta di scrittura, non attenderà se l'indirizzo edx è diverso dal valore precedente ed emetterà la richiesta di lettura e verrà riordinata sulla richiesta di scrittura precedente e l'istruzione add continuerà. ma se l'indirizzo è lo stesso l'istruzione add attenderà fino al termine della vecchia scrittura.

Si noti che il ciclo è breve e il processore può eseguirlo più velocemente di quanto il controller di memoria completi la richiesta di scrittura nella memoria.

quindi per gruppi ordinati leggerai e scriverai dallo stesso indirizzo molte volte consecutivamente in modo da perdere il miglioramento delle prestazioni usando il riordino della memoria; nel frattempo, se vengono utilizzati gruppi casuali, ciascuna iterazione avrà probabilmente un indirizzo diverso, quindi la lettura non attenderà la scrittura precedente e verrà riordinata prima; le istruzioni di aggiunta non aspetteranno la precedente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.