2012-11-21 8 views
6

W poniższym programie mam 2 bufory, jeden z nich jest wyrównany do 64 bitów, a drugi, który zakładam, jest wyrównany na 16 bajtów na moim 64 serwerze Linuxa z jądrem 2.6.x.Dlaczego dostęp do bufora z pamięcią w pamięci jest droższy w systemie Linux?

Linia pamięci podręcznej ma długość 64 bajtów. Tak więc w tym programie po prostu uzyskuję dostęp do jednej linii pamięci podręcznej na raz. Miałem nadzieję, że zobaczę, że posix_memaligned jest równy, jeśli nie jest szybszy niż bufor bez wyrównania. Oto dane

./readMemory 10000000 

time taken by posix_memaligned buffer: 293020299 
time taken by standard buffer: 119724294 

./readMemory 100000000 

time taken by posix_memaligned buffer: 548849137 
time taken by standard buffer: 211197082 

#include <errno.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <linux/time.h> 

void now(struct timespec * t); 

int main(int argc, char **argv) 
{   
    char *buf;   
    struct timespec st_time, end_time;   
    int runs;   
    if (argc !=2) 
    { 
      printf("Usage: ./readMemory <number of runs>\n");     
      exit(1);   
    }   
    errno = 0;   
    runs = strtol(argv[1], NULL, 10);   
    if (errno !=0)  { 
      printf("Invalid number of runs: %s \n", argv[1]); 
      exit(1); 
    } 

    int returnVal = -1; 

    returnVal = posix_memalign((void **)&buf, 64, 1024); 
    if (returnVal != 0) 
    { 
      printf("error in posix_memaligh\n"); 
    } 

    char tempBuf[64]; 
    char * temp = buf; 

    size_t cpyBytes = 64; 

    now(&st_time); 
    for(int x=0; x<runs; x++) { 
    temp = buf; 
    for(int i=0; i < ((1024/64) -1); i+=64) 
    { 
      memcpy(tempBuf, temp, cpyBytes); 
      temp += 64; 
    } 
    } 
    now(&end_time); 

    printf("time taken by posix_memaligned buffer: %ld \n", (end_time.tv_nsec - st_time.tv_nsec)); 

    char buf1[1024];   
    temp = buf1;   
    now(&st_time);   
    for(int x=0; x<runs; x++) 
    {   
     temp = buf1;   
     for(int i=0; i < ((1024/64) -1); i+=64)   
    {     
     memcpy(tempBuf, temp, cpyBytes);     
     temp += 64;   
     }   
    }   
    now(&end_time);   
    printf("time taken by standard buffer: %ld \n", (end_time.tv_nsec - st_time.tv_nsec)); 
    return 0; 
} 

void now(struct timespec *tnow) 
{ 
    if(clock_gettime(CLOCK_MONOTONIC_RAW, tnow) <0) 
    { 
      printf("error getting time"); 
      exit(1); 
    } 
} 

rozkładanie do pierwszej pętli

movq -40(%rbp), %rdx   
    movq -48(%rbp), %rcx   
    leaq -176(%rbp), %rax 
    movq %rcx, %rsi 
    movq %rax, %rdi 
    call memcpy 
    addq $64, -48(%rbp) 
    addl $64, -20(%rbp) 

demontaż drugiego pętli

movq -40(%rbp), %rdx 
    movq -48(%rbp), %rcx 
    leaq -176(%rbp), %rax 
    movq %rcx, %rsi 
    movq %rax, %rdi 
    call memcpy 
    addq $64, -48(%rbp) 
    addl $64, -4(%rbp) 
+0

Czy możesz pokazać demontaż dwóch wewnętrznych pętli? – Mysticial

+1

Jasne, że nie ma to nic wspólnego z montażem ... hehe – Mysticial

+0

Moja wersja GCC optymalizuje pętle do zera ... więc oba działają równie szybko. To na Mac OS X 10.8.2 z 'i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (na podstawie Apple Inc. build 5658) (LLVM build 2336.11.00)'. –

Odpowiedz

1

Jest możliwe, że przyczyną jest względne wyrównanie buforów.

memcpy działa najszybciej podczas kopiowania danych dopasowanych do słowa (32/64 bitów).
Jeśli oba bufory są wyrównane, wszystko jest w porządku.
Jeśli oba bufory są źle ustawione w ten sam sposób, memcpy obsługuje je, kopiując bajt małego przedrostka bajtowego, a następnie uruchamiając słowo po słowie w pozostałej części.

Ale jeśli jeden bufor jest wyrównany do słowa, a drugi nie, to nie ma sposobu, aby oba wpisywały i zapisywały wyrównane słowo. Tak więc memcpy nadal działa słowo po słowie, ale połowa dostępu do pamięci jest źle wyrównana.

Jeśli oba bufory stosu nie są wyrównane w ten sam sposób (np. Oba adresy to 8 * x + 2), ale bufor od posix_memalign jest wyrównany, może wyjaśnić, co widzisz.

1

Istnieje kilka problemów z Twojego odniesienia:

  • Twój run-czas jest zbyt krótki, stąd można się zobaczyć wiele hałasu/jitter.
  • Jeśli masz włączone skalowanie częstotliwości procesora, pierwsza pętla może być wykonywana, zanim procesor przełączy się na pełną/turbo częstotliwość. Najpierw należy rozgrzać procesor lub, lepiej, wyłączyć skalowanie częstotliwości podczas testów porównawczych.
  • Możesz obserwować harmonogram, ponieważ nie używasz priorytetu w czasie rzeczywistym.
  • W każdym biegu otrzymujesz tylko jedną próbkę, potrzebujesz co najmniej 30 przebiegów, aby móc dokonać jakiejkolwiek naukowej oceny (badanie naukowe z jedną próbką jest powszechnie nazywane anegdotą).
0

Po zamianie bloków pomiarowych - to znaczy, wykonaj najpierw standardowy pomiar bufora, a następnie posix_memalign, otrzymam dokładnie odwrotne wyniki. Innymi słowy, pierwsza pętla kopiowania dla mojego procesora (Intel Core 2) jest prawie zawsze wolniejsza niż druga, bez względu na to, jak są wyrównane.

Próbowałem standardowego bufora malloc(), a nie go na stosie - prawie nie robi żadnej różnicy dla prędkości, pierwsza pętla jest wciąż wolniejsza.

Próbowałem również posix_memalign() Twojego małego 64-bajtowego bufora - nie miało to żadnego znaczenia.

EDYCJA: Zmodyfikowałem twój kod, aby wykonać 3 pomiary: wyrównane posix, malloc'ed i bufor na stosie (zobacz poniższy kod).

Okazuje się, że tylko pierwsza pętla jest wolna. Każda kolejna pętla trwa prawie dokładnie w tym samym czasie (z niewielkim hałasem).

Sądzę, że obserwujemy program planujący Linuksa, który przyspiesza procesor tak szybko, jak widzi 100% obciążenia procesora.

Wyniki mojego biegu:

$ ./readmemory 2000000 5 
time taken by posix aligned: 19599140 
time taken by std malloc : 14711350 
time taken by std on stack : 14680668 
time taken by posix aligned: 14729273 
time taken by std malloc : 14685338 
time taken by std on stack : 14839183 
time taken by posix aligned: 14709836 
time taken by std malloc : 15551900 
time taken by std on stack : 14659350 
time taken by posix aligned: 14721298 
time taken by std malloc : 14691732 
time taken by std on stack : 14691246 
time taken by posix aligned: 14722127 
time taken by std malloc : 15538286 
time taken by std on stack : 14723657 

zaktualizowany kod:

// compile with: g++ readmemory.c -o readmemory -lrt 

#include <time.h> 
#include <errno.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define BUF_SIZE 1024 
#define COPY_BYTES 64 

void now(struct timespec *tnow) { 
    if (clock_gettime(CLOCK_MONOTONIC, tnow) < 0) { 
     printf("error getting time"); 
     exit(1); 
    } 
} 

void measure(char * buf, int runs, const char * msg) { 
    char tempBuf[64]; 
    struct timespec st_time, end_time; 
    char * temp; 
    now(&st_time); 
    for (int x=0; x<runs; x++) { 
     temp = buf; 
     for (int i=0; i < ((BUF_SIZE/COPY_BYTES) - 1); i+=COPY_BYTES) { 
      memcpy(tempBuf, temp, COPY_BYTES); 
      temp += COPY_BYTES; 
     } 
    } 
    now(&end_time); 
    printf("time taken by %s: %ld\n", msg, end_time.tv_nsec - st_time.tv_nsec); 
} 

int main(int argc, char **argv) { 
    char * buf1;   // posix_memalign'ed 
    char * buf2;   // malloc'ed 
    char buf3[BUF_SIZE]; // alloc on stack 
    int rc = -1; 
    int runs; 
    int loops; 
    if (argc != 3) { 
     printf("Usage: ./readMemory <runs> <loops>\n"); 
     exit(1); 
    } 
    errno = 0; 
    runs = strtol(argv[1], NULL, 0); 
    if (errno != 0) { 
     printf("Invalid number of runs: %s \n", argv[1]); 
     exit(1); 
    } 
    loops = strtol(argv[2], NULL, 0); 

    rc = posix_memalign((void **)&buf1, COPY_BYTES, BUF_SIZE); 
    if (rc != 0) { 
     printf("error in posix_memalign\n"); 
     exit(1); 
    } 
    buf2 = (char *) malloc(BUF_SIZE); 
    if (buf2 == NULL) { 
     printf("error in malloc\n"); 
     exit(1); 
    } 

    for (int i=0; i<loops; i++) { 
     measure(buf1, runs, "posix aligned"); 
     measure(buf2, runs, "std malloc "); 
     measure(buf3, runs, "std on stack "); 
    } 

    return 0; 
} 

myślę obserwujemy dość skomplikowane sposoby wdrożenia nowoczesnych procesorów buforowania.

+0

Poprawiłem program, tak aby pętla posix_memalign była uruchamiana 10 razy. Zgadzam się z waszą obserwacją, że pierwsza pętla jest zawsze powolna, ALE, po niej, wynik jest zgodny z tym samym wzorcem, jak opisano powyżej, gdzie pozostałe 9 pomiarów dla pętli posix_memalign, chociaż jest szybsze niż pierwsza pętla, wciąż jest znacznie wyższe niż standardowe pętla buforowa. Również pozostałe 9 pętli nie wykazuje dużej zmienności zgodnie z oczekiwaniami. Spróbuj uruchomić ten test z run = 100, 1000, 10000, 100000 i tak dalej, a zauważysz, że przy mniejszych przebiegach takich jak 100 000 posix_memalign jest znacznie wyższy niż standardowy bufor. – Jimm

+0

Czy http://stackoverflow.com/a/13490025/412080 nie wyjaśnia, co tu obserwujesz? –

+0

@MaximYegorushkin jak? Sugeruje to, że procesor się rozgrzewa, co wyeliminowałem teraz, uruchamiając obie pętle kilka razy i wygładzając wszelkie początkowe powolne odczyty ... – Jimm

Powiązane problemy