2009-11-11 16 views
29

Wykonuję przetwarzanie obrazu w języku C, które wymaga skopiowania dużych porcji danych wokół pamięci - źródło i miejsce docelowe nigdy się nie pokrywają.Bardzo szybkie memcpy do przetwarzania obrazu?

Jaki jest absolutny najszybszy sposób na zrobienie tego na platformie x86 przy użyciu GCC (gdzie dostępne są SSE, SSE2, ale NIE SSE3)?

Oczekuję, że rozwiązanie będzie albo w zespole, albo przy użyciu elementów wewnętrznych GCC?

znalazłem poniższy link, ale nie mam pojęcia, czy to jest to najlepszy sposób, aby przejść o nim (autor mówi także, że ma kilka błędów): http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2006-02/msg00123.html

EDIT: zauważ, że kopia jest to konieczne, nie mogę ominąć konieczności kopiowania danych (mogę wyjaśnić dlaczego, ale będę oszczędzić wyjaśnienie :))

+0

Czy możesz napisać swój kod, aby kopia nie była wymagana? – Ron

+0

Ron, nie, nie mogę :( – horseyguy

+1

Jeśli uda Ci się zdobyć kompilator Intela, możesz mieć większe szanse na przekonwertowanie optymalizatora na instrukcje procesora wektorowego. –

Odpowiedz

38

Dzięki uprzejmości William Chan i Google. 30-70% szybciej niż memcpy w Microsoft Visual Studio 2005.

void X_aligned_memcpy_sse2(void* dest, const void* src, const unsigned long size) 
{ 

    __asm 
    { 
    mov esi, src; //src pointer 
    mov edi, dest; //dest pointer 

    mov ebx, size; //ebx is our counter 
    shr ebx, 7;  //divide by 128 (8 * 128bit registers) 


    loop_copy: 
     prefetchnta 128[ESI]; //SSE2 prefetch 
     prefetchnta 160[ESI]; 
     prefetchnta 192[ESI]; 
     prefetchnta 224[ESI]; 

     movdqa xmm0, 0[ESI]; //move data from src to registers 
     movdqa xmm1, 16[ESI]; 
     movdqa xmm2, 32[ESI]; 
     movdqa xmm3, 48[ESI]; 
     movdqa xmm4, 64[ESI]; 
     movdqa xmm5, 80[ESI]; 
     movdqa xmm6, 96[ESI]; 
     movdqa xmm7, 112[ESI]; 

     movntdq 0[EDI], xmm0; //move data from registers to dest 
     movntdq 16[EDI], xmm1; 
     movntdq 32[EDI], xmm2; 
     movntdq 48[EDI], xmm3; 
     movntdq 64[EDI], xmm4; 
     movntdq 80[EDI], xmm5; 
     movntdq 96[EDI], xmm6; 
     movntdq 112[EDI], xmm7; 

     add esi, 128; 
     add edi, 128; 
     dec ebx; 

     jnz loop_copy; //loop please 
    loop_copy_end: 
    } 
} 

może być w stanie zoptymalizować go dalej w zależności od dokładnej sytuacji i wszelkich założeń jesteś w stanie zrobić.

Możesz również chcieć sprawdzić źródło memcpy (memcpy.asm) i usunąć jego specjalną obsługę. Dalsza optymalizacja może być możliwa!

+6

Uwaga: wydajność tej notatki będzie szalenie zależna od ilości danych do skopiowania i rozmiaru pamięci podręcznej. Na przykład, pobieranie wstępne i ruchy niezwiązane z czasem mogą pogorszyć wydajność w przypadku mniejszych (dopasowanych do L2) kopii w porównaniu do zwykłych plików movdqa. –

+2

poręcze: nie zapomnij wysłać do niego maila, że ​​wykorzystałeś jego kod w swoim projekcie;) [http://williamchan.ca/portfolio/assembly/ssememcpy/source/viewsource.php?id=readme.txt] – ardsrk

+3

Pamiętam czytanie tego kodu w podręczniku AMD64. Kod nie jest optymalny na intel, gdzie ma problemy z aliasami banku podręcznego. – hirschhornsalz

2

Jeśli używasz systemu Windows, należy użyć DirectX API, które ma szczególne GPU zoptymalizowane procedury obsługi grafiki (jak szybko może to być? Twój procesor nie jest załadowany. Zrób coś innego, podczas gdy GPU go psuje).

Jeśli chcesz być agnostykiem systemu operacyjnego, wypróbuj OpenGL.

Nie trajkuj z asemblerem, ponieważ jest zbyt prawdopodobne, że przegrasz marnie, by osiągnąć lepszą wydajność niż 10-letnia + biegła bibliotekarz.

+1

Potrzebuję go do wykonania w MEMORY, czyli nie może się zdarzyć na GPU. :) Ponadto, nie zamierzam, aby przewyższać funkcje biblioteki (stąd dlaczego zadam to pytanie), ale jestem pewien, że jest ktoś na stackoverflow, który _ może_ przewyższyć biblioteki :) Ponadto, autorzy bibliotek są zwykle ograniczeni przez wymagania dotyczące przenośności - jak stwierdziłem, dbam tylko o platformę x86, więc możliwe są dalsze konkretne optymalizacje x86. – horseyguy

+0

+1, ponieważ jest to dobra pierwsza rada, którą należy podać - nawet jeśli nie ma zastosowania w przypadku poręczy. – peterchen

+1

Nie jestem pewien, czy to dobra rada. Typowa nowoczesna maszyna ma mniej więcej taką samą przepustowość pamięci dla procesora i GPU. Na przykład wiele popularnych laptopów korzysta z grafiki Intel HD, która korzysta z tej samej pamięci RAM co procesor. Procesor może już nasycić magistralę pamięci. Dla memcpy oczekiwałbym podobnej wydajności na procesorze lub GPU. –

3

Jeśli chodzi o procesory Intela, możesz odnieść korzyści z IPP. Jeśli wiesz, że będzie działał z procesorem graficznym Nvidia, być może użyjesz CUDA - w obu przypadkach lepiej będzie wyglądać szerzej niż optymalizować memcpy() - zapewniają one możliwości ulepszenia twojego algorytmu na wyższym poziomie. Oba są jednak uzależnione od konkretnego sprzętu.

6

na każdym poziomie optymalizacji -O1 lub powyżej, GCC użyje definicji wbudowanych dla funkcji jak memcpy - z prawej -march parametru (-march=pentium4 na zestaw funkcji można wymienić) powinno wygenerować dość optymalnej architektury specyficzny kod inline.

Sprawdziłbym to i zobaczę, co wyjdzie.

6

Kodeks SSE opublikowany przez hapalibashi jest drogą do zrobienia.

Jeśli potrzebujesz jeszcze większej wydajności i nie cofaj się przed długą i krętą drogą pisania sterownika urządzenia: Wszystkie ważne platformy mają obecnie kontroler DMA, który może wykonywać kopiowanie szybciej i szybciej. równolegle do kodu CPU może zrobić.

To wymaga napisania sterownika. Żaden duży system operacyjny, o którym jestem świadomy, nie udostępnia tej funkcji użytkownikom po stronie bezpieczeństwa.

Jednak może być tego warta (jeśli potrzebujesz wydajności), ponieważ żaden kod na ziemi nie może przewyższyć wydajności sprzętu zaprojektowanego do wykonywania takich zadań.

+1

Właśnie napisałem odpowiedź, która mówi o przepustowości pamięci RAM. Jeśli to, co powiem, jest prawdą, to nie sądzę, że silnik DMA mógłby osiągnąć znacznie więcej, niż mógłby osiągnąć procesor. Czy coś przeoczyłem? –

5

To pytanie ma teraz cztery lata i jestem trochę zaskoczony, że nikt jeszcze nie wspomniał o przepustowości pamięci. CPU-Z informuje, że mój komputer ma pamięć RAM PC3-10700. To, że pamięć RAM ma maksymalną szerokość pasma (czyli szybkość transferu, przepustowość itd.) Wynoszącą 10700 MB/s. Procesor w moim komputerze to procesor i5-2430M ze szczytową częstotliwością turbo 3 GHz.

Teoretycznie z nieskończenie szybkiego procesora i pamięci RAM mojego mógłby pójść memcpy na 5300 Mb/s, czyli połowa 10700 ponieważ memcpy musi odczytywać i następnie napisać do pamięci RAM. (edytuj: Jak wskazał v.oddou, jest to uproszczone przybliżenie).

Z drugiej strony, wyobraźmy sobie, że mieliśmy nieskończenie szybką pamięć RAM i realistyczny procesor, co mogliśmy osiągnąć? Użyjmy na przykład mojego procesora 3 GHz. Jeśli mógłby wykonać 32-bitowy odczyt i zapis 32-bitowy w każdym cyklu, mógł przesłać 3e9 * 4 = 12000 MB/sek. Wydaje się to być łatwo dostępne dla nowoczesnego procesora. Już teraz widzimy, że kod działający na CPU nie jest tak naprawdę wąskim gardłem. Jest to jeden z powodów, dla których współczesne maszyny mają pamięć podręczną danych.

Możemy zmierzyć, co naprawdę może zrobić procesor, testując memcpy, gdy wiemy, że dane są przechowywane w pamięci podręcznej. Dokładne wykonanie jest skrzypce. Zrobiłem prostą aplikację, która zapisywała losowe liczby w tablicy, zapamiętywała je do innej tablicy, a następnie sprawdzała sumę skopiowanych danych. Przejrzałem kod w debugerze, aby upewnić się, że sprytny kompilator nie usunął kopii. Zmiana wielkości tablicy zmienia wydajność pamięci podręcznej - małe macierze mieszczą się w pamięci podręcznej, duże mniej. Mam następujące wyniki:

  • 40 kB tablic: 16000 Mb/s
  • 400 kB tablic: 11000 Mb/s
  • 4000 tablice kB: 3100 Mb/s

Oczywiście mój procesor może odczytywać i zapisywać więcej niż 32 bity na cykl, ponieważ 16000 to więcej niż 12000 I obliczonych teoretycznie powyżej. Oznacza to, że procesor jest jeszcze mniej wąskim gardłem, niż sądziłem. Użyłem Visual Studio 2005 i wkraczając w standardową implementację memcpy, widzę, że używa instrukcji movqda na moim komputerze. Sądzę, że to może odczytać i zapisać 64 bity na cykl.

Przyjemny kod hapalibashi wysłany osiąga 4200 MB/s na moim komputerze - około 40% szybciej niż wdrożenie VS 2005. Sądzę, że jest to szybsze, ponieważ używa instrukcji pobierania wstępnego, aby poprawić wydajność pamięci podręcznej.

Podsumowując, kod działający na procesorze nie jest wąskim gardłem i dostrajaniem kodu, który spowoduje jedynie niewielkie ulepszenia.

+0

Twój proces myślenia jest dobry. Jednak brakuje ci myślenia o marketingowych liczbach pamięci RAM, to wszystko jest pompowane w cztery kwadraty, co nie odpowiada prędkości 1 kanału. I to jest także prędkość przed autobusem, są też koszty ogólne zarządzania w modelu numa, który ma rdzeń i7/opteronów. –

Powiązane problemy