Uczę się korzystać z możliwości SIMD, ponownie pisząc moją osobistą bibliotekę przetwarzania obrazów, używając wektorów wewnętrznych. Jedną z podstawowych funkcji jest proste "array +=
", czyliDodawanie tablicy SIMD dla dowolnych długości tablic
void arrayAdd(unsigned char* A, unsigned char* B, size_t n) {
for(size_t i=0; i < n; i++) { B[i] += A[i] };
}
Dla dowolnych długościach tablicy, oczywistym kod SIMD (zakładając dopasowane przez 16 lat) jest coś takiego jak:
size_t i = 0;
__m128i xmm0, xmm1;
n16 = n - (n % 16);
for (; i < n16; i+=16) {
xmm0 = _mm_load_si128((__m128i*) (A + i));
xmm1 = _mm_load_si128((__m128i*) (B + i));
xmm1 = _mm_add_epi8(xmm0, xmm1);
_mm_store_si128((__m128i*) (B + i), xmm1);
}
for (; i < n; i++) { B[i] += A[i]; }
Ale czy to możliwe do all dodatki za pomocą instrukcji SIMD? Pomyślałem o wypróbowaniu tego:
__m128i mask = (0x100<<8*(n - n16))-1;
_mm_maskmoveu_si128(xmm1, mask, (__m128i*) (B + i));
dla dodatkowych elementów, ale czy spowoduje to niezdefiniowane zachowanie? mask
powinien zagwarantować, że dostęp nie zostanie faktycznie przekroczony poza granicami tablicy (chyba). Alternatywą jest najpierw wykonanie dodatkowych elementów, ale tablica musi zostać wyrównana przez n-n16
, co nie wydaje się właściwe.
Czy istnieje inny, bardziej optymalny wzór takich wektoryzowanych pętli?
można upewnić się, że w kodzie długości tablicy są zawsze wielokrotnością 16 bajtów (choć być może mniej elementy są rzeczywiście używane), więc ten epilog nigdy nie wyjdzie. Ale epilog naprawdę nie jest ważny pod względem szybkości. – Walter