2012-04-16 30 views
6

Tôi đang học cách sử dụng các khả năng SIMD bằng cách viết lại thư viện xử lý hình ảnh cá nhân của mình bằng nội tại vectơ. Một chức năng cơ bản là một đơn giản "mảng +=," tức làMảng SIMD thêm cho độ dài mảng tùy ý

void arrayAdd(unsigned char* A, unsigned char* B, size_t n) { 
    for(size_t i=0; i < n; i++) { B[i] += A[i] }; 
} 

Đối với độ dài mảng tùy ý, các mã SIMD rõ ràng (giả sử thẳng hàng 16) là một cái gì đó như:

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]; } 

Nhưng là nó có thể làm tất cả các bổ sung có hướng dẫn SIMD không? Tôi đã nghĩ đến việc thử điều này:

__m128i mask = (0x100<<8*(n - n16))-1; 
_mm_maskmoveu_si128(xmm1, mask, (__m128i*) (B + i)); 

cho các yếu tố bổ sung, nhưng điều đó có dẫn đến hành vi không xác định không? Các mask nên đảm bảo không có quyền truy cập thực sự được thực hiện qua các giới hạn mảng (tôi nghĩ). Cách khác là làm các phần tử phụ trước, nhưng sau đó mảng cần được căn chỉnh theo n-n16, điều này có vẻ không đúng.

Có một mô hình vòng vector hóa tối ưu hơn nữa không?

+0

bạn có thể đảm bảo rằng trong mã của bạn độ dài mảng luôn bội số của 16 byte (mặc dù các yếu tố có thể ít được thực sự sử dụng), vì vậy Epilog này không bao giờ xuất hiện. Nhưng epilog thực sự không quan trọng về mặt tốc độ. – Walter

Trả lời

4

Một tùy chọn là để đệm mảng của bạn thành bội số của 16 byte. Sau đó, bạn có thể làm 128 bit tải/thêm/lưu trữ và chỉ cần bỏ qua các kết quả sau điểm bạn quan tâm.

Đối với mảng lớn mặc dù chi phí của byte theo byte "epilog" sẽ rất nhỏ. Việc bỏ vòng lặp có thể cải thiện hiệu suất nhiều hơn, như sau:

for (; i < n32; i+=32) { 
    xmm0 = _mm_load_si128((__m128i*) (A + i)); 
    xmm1 = _mm_load_si128((__m128i*) (B + i)); 
    xmm2 = _mm_load_si128((__m128i*) (A + i + 16)); 
    xmm3 = _mm_load_si128((__m128i*) (B + i + 16)); 
    xmm1 = _mm_add_epi8(xmm0, xmm1); 
    xmm3 = _mm_add_epi8(xmm2, xmm3); 
    _mm_store_si128((__m128i*) (B + i), xmm1); 
    _mm_store_si128((__m128i*) (B + i + 16), xmm3); 
} 
// Do another 128 bit load/add/store here if required 

Nhưng thật khó để nói mà không làm một số hồ sơ.

Bạn cũng có thể thực hiện tải/lưu trữ không thẳng hàng ở cuối (giả sử bạn có nhiều hơn 16 byte) mặc dù điều này có thể sẽ không tạo ra sự khác biệt lớn. Ví dụ. nếu bạn có 20 byte, bạn thực hiện một lần tải/lưu trữ để bù trừ 0 và tải/thêm/lưu trữ khác không có giá trị (_mm_storeu_si128, __mm_loadu_si128) để bù trừ 4.

Bạn có thể sử dụng _mm_maskmoveu_si128 nhưng bạn cần lấy mặt nạ vào thanh ghi xmm và mã mẫu của bạn sẽ không hoạt động. Bạn có thể muốn thiết lập mặt nạ đăng ký cho tất cả FF và sau đó sử dụng một sự thay đổi để sắp xếp nó. Vào cuối ngày, nó có thể sẽ chậm hơn so với tải/thêm/lưu trữ không được sắp xếp.

Đây sẽ là một cái gì đó như:

mask = _mm_cmpeq_epi8(mask, mask); // Set to all FF's 
mask = _mm_srli_si128(mask, 16-(n%16)); // Align mask 
_mm_maskmoveu_si128(xmm, mask, A + i); 
+0

Trong thực tế, tôi sẽ đặt các mặt nạ trong một bảng tra cứu. Bạn có nghĩ rằng nó sẽ vẫn chậm hơn vòng lặp "epilog"? –

+0

@reve_etrange: Có khả năng không chậm hơn nhưng khó biết mà không đo hai giải pháp. Hãy thử một lần. –

+0

Tôi sẽ chụp. Nhưng nó là một bộ nhớ pháp lý truy cập? Vì * một số * giá trị của 'mặt nạ' có thể gây ra một vi phạm giới hạn mảng. –