Może to być odpowiedź zbyt szczegółowa, ale na platformie ARM obsługującej NEON wektoryzacja NEON może być jeszcze szybsza. Może to być ratowanie życia w środowisku, w którym zasoby są względnie bardziej ograniczone, i prawdopodobnie dlatego ARM jest używany w tym ustawieniu w pierwszej kolejności. Ważnym przykładem jest system Android, na którym większość urządzeń nadal korzysta z architektury ARM v7a, która obsługuje NEON.
Poniższe przykłady pokazują, że jest to pętla do kopiowania półpłaszczyznowej płaszczyzny UV obrazu YUV420sp do płaskiej płaszczyzny UV obrazu YUV420p. Rozmiary buforów źródłowego i docelowego są równe 640*480/2
bajtów. Wszystkie przykłady są kompilowane za pomocą g ++ 4.8 wewnątrz Android NDK r9d. Są one wykonywane na procesorze Samsung Exynos 5420 Octa:
Poziom 1: Regular
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
for(int i=0;i<stride;i++){
dstptr[i] = srcptr[i*2];
dstptr[i + stride] = srcptr[i*2 + 1];
}
}
skompilowany z -O3
tylko, zajmuje około 1,5 ms przeciętnie.
Poziom 2: Rozwinięta i wyciska trochę więcej z ruchomymi wskaźnikami
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
unsigned char* endptr = dstptr + stride;
while(dstptr<endptr){
*(dstptr + 0) = *(srcptr + 0);
*(dstptr + stride + 0) = *(srcptr + 1);
*(dstptr + 1) = *(srcptr + 2);
*(dstptr + stride + 1) = *(srcptr + 3);
*(dstptr + 2) = *(srcptr + 4);
*(dstptr + stride + 2) = *(srcptr + 5);
*(dstptr + 3) = *(srcptr + 6);
*(dstptr + stride + 3) = *(srcptr + 7);
*(dstptr + 4) = *(srcptr + 8);
*(dstptr + stride + 4) = *(srcptr + 9);
*(dstptr + 5) = *(srcptr + 10);
*(dstptr + stride + 5) = *(srcptr + 11);
*(dstptr + 6) = *(srcptr + 12);
*(dstptr + stride + 6) = *(srcptr + 13);
*(dstptr + 7) = *(srcptr + 14);
*(dstptr + stride + 7) = *(srcptr + 15);
srcptr+=16;
dstptr+=8;
}
}
skompilowany z -O3
tylko, zajmuje około 1,15 ms przeciętnie. Prawdopodobnie jest to tak szybkie jak w przypadku zwykłej architektury, jak w przypadku innej odpowiedzi.
Poziom 3 Zwykły + GCC automatyczne NEON wektoryzacji
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
for(int i=0;i<stride;i++){
dstptr[i] = srcptr[i*2];
dstptr[i + stride] = srcptr[i*2 + 1];
}
}
skompilowany z -O3 -mfpu=neon -ftree-vectorize -ftree-vectorizer-verbose=1 -mfloat-abi=softfp
, trwa około 0,6 ms na średniej. Dla porównania, bajty o rozmiarze równym lub dwa razy większym niż to, które tu testowano, zajmują średnio około 0,6 ms.
Na marginesie, drugi kod (rozwijany i wskazywany) skompilowany z powyższymi parametrami NEON zajmuje mniej więcej tyle samo czasu, 0,6 ms.
Standardowa pętla wykonuje co najmniej tak szybko, jak standard dla pętli ... Poza sarkazmem, zależy to od używanej struktury przechowywania danych. W przypadku tablic nie sądzę, że można zrobić coś lepszego niż pętlę for, zwiększaną o twój moduł. – ChrisCM
'memcpy' jest czasami szybszy niż pętla' for', ponieważ optymalizuje go, ponieważ pamięć, na której działa, jest ciągła. Tych optymalizacji nie można tutaj wprowadzić. –
@dauphic Ale dlaczego CUDA ma 'cudaMemcpy2D', który kopiuje ze skokiem? – JackOLantern