2010-11-13 25 views
11

Tôi đang tối ưu hóa một số mã cho kiến ​​trúc vi mô Intel x86 Nehalem sử dụng nội tại SSE.Cách hiệu quả nhất để lưu trữ các sản phẩm 4 chấm vào một mảng liền kề trong C bằng cách sử dụng nội tại SSE

Một phần chương trình của tôi tính các sản phẩm 4 chấm và thêm mỗi kết quả vào các giá trị trước đó trong một đoạn liền kề của một mảng. Cụ thể hơn,

tmp0 = _mm_dp_ps(A_0m, B_0m, 0xF1); 
tmp1 = _mm_dp_ps(A_1m, B_0m, 0xF2); 
tmp2 = _mm_dp_ps(A_2m, B_0m, 0xF4); 
tmp3 = _mm_dp_ps(A_3m, B_0m, 0xF8); 

tmp0 = _mm_add_ps(tmp0, tmp1); 
tmp0 = _mm_add_ps(tmp0, tmp2); 
tmp0 = _mm_add_ps(tmp0, tmp3); 
tmp0 = _mm_add_ps(tmp0, C_0n); 

_mm_storeu_ps(C_2, tmp0); 

Lưu ý rằng tôi sẽ sử dụng 4 thanh ghi xmm tạm thời để giữ kết quả của mỗi sản phẩm chấm. Trong mỗi thanh ghi XMM, kết quả được đặt vào một độc đáo 32 bit so với thanh ghi XMM tạm thời khác như vậy mà kết quả cuối cùng trông như thế này:

tmp0 = R0-zero-zero-zero

tmp1 = zero -R1-zero-zero

tmp2 = zero-zero-R2-zero

tmp3 = zero-zero-zero-R3

tôi kết hợp các giá trị chứa trong mỗi biến tmp vào một biến XMM bởi tổng hợp chúng với các hướng dẫn sau:

tmp0 = _mm_add_ps(tmp0, tmp1); 
tmp0 = _mm_add_ps(tmp0, tmp2); 
tmp0 = _mm_add_ps(tmp0, tmp3); 

Cuối cùng, tôi thêm thanh ghi chứa tất cả 4 kết quả của các sản phẩm chấm cho một phần tiếp giáp của một mảng để chỉ số của mảng được tăng lên bởi một sản phẩm dấu chấm, như vậy (C_0n là 4 giá trị hiện tại mảng được cập nhật; C_2 là địa chỉ trỏ đến những 4 giá trị):

tmp0 = _mm_add_ps(tmp0, C_0n); 
_mm_storeu_ps(C_2, tmp0); 

Tôi muốn biết nếu có một cách ít tròn về, hiệu quả hơn để lấy kết quả của các sản phẩm chấm và thêm chúng vào các đoạn tiếp giáp của mảng. Bằng cách này, tôi đang thực hiện 3 bổ sung giữa các thanh ghi chỉ có 1 giá trị khác 0. Có vẻ như có một cách hiệu quả hơn để thực hiện việc này.

Tôi đánh giá cao tất cả trợ giúp. Cảm ơn bạn.

Trả lời

6

Đối với mã như thế này, tôi muốn lưu trữ "chuyển vị" của A và B, sao cho {A_0m.x, A_1m.x, A_2m.x, A_3m.x} được lưu trữ trong một vectơ, v.v. Sau đó, bạn có thể làm các sản phẩm dấu chấm chỉ bằng cách nhân và thêm, và khi bạn đã hoàn tất, bạn có tất cả các sản phẩm 4 dấu chấm trong một vectơ mà không bị xáo trộn.

Điều này được sử dụng thường xuyên trong raytracing, để kiểm tra 4 tia cùng một lúc đối với một mặt phẳng (ví dụ: khi đi ngang qua cây kd). Tuy nhiên, nếu bạn không có quyền kiểm soát dữ liệu đầu vào, chi phí thực hiện quá trình chuyển đổi có thể không đáng giá. Mã này cũng sẽ chạy trên các máy trước SSE4, mặc dù đó có thể không phải là một vấn đề.


Một hiệu quả lưu ý nhỏ trên mã hiện có: thay vì điều này

tmp0 = _mm_add_ps(tmp0, tmp1); 
tmp0 = _mm_add_ps(tmp0, tmp2); 
tmp0 = _mm_add_ps(tmp0, tmp3); 
tmp0 = _mm_add_ps(tmp0, C_0n); 

Nó có thể tốt hơn một chút để làm điều này:

tmp0 = _mm_add_ps(tmp0, tmp1); // 0 + 1 -> 0 
tmp2 = _mm_add_ps(tmp2, tmp3); // 2 + 3 -> 2 
tmp0 = _mm_add_ps(tmp0, tmp2); // 0 + 2 -> 0 
tmp0 = _mm_add_ps(tmp0, C_0n); 

Khi hai mm_add_ps 'đầu tiên s là hoàn toàn độc lập ngay bây giờ. Ngoài ra, tôi không biết thời gian tương đối của việc thêm so với xáo trộn, nhưng điều đó có thể nhanh hơn một chút.


Hy vọng điều đó sẽ hữu ích.

1

Bạn có thể thử để lại kết quả sản phẩm chấm trong từ thấp và sử dụng cửa hàng vô hướng op _mm_store_ss để lưu một phao từ mỗi sổ đăng ký m128 vào vị trí phù hợp của mảng. Bộ đệm cửa hàng của Nehalem nên tích lũy liên tiếp viết trên cùng một dòng và xả chúng sang L1 theo lô.

Cách chuyên nghiệp để làm điều đó là cách tiếp cận chuyển vị của celion. Macro _MM_TRANSPOSE4_PS của MSVC sẽ thực hiện chuyển đổi cho bạn.

+0

Bạn vẫn phải thêm giá trị cũ (C_0n) vào từng sản phẩm chấm trước cửa hàng. Tất cả chúng đều độc lập, vì vậy nó có thể không quá chậm, nhưng nó không đẹp hơn nhiều :) – celion

3

Cũng có thể sử dụng SSE3. Nó nhanh hơn sử dụng _dot_ps, trong một số thử nghiệm tầm thường. Điều này trả về 4 sản phẩm có thể được thêm vào.

static inline __m128 dot_p(const __m128 x, const __m128 y[4]) 
{ 
    __m128 z[4]; 

    z[0] = x * y[0]; 
    z[1] = x * y[1]; 
    z[2] = x * y[2]; 
    z[3] = x * y[3]; 
    z[0] = _mm_hadd_ps(z[0], z[1]); 
    z[2] = _mm_hadd_ps(z[2], z[3]); 
    z[0] = _mm_hadd_ps(z[0], z[2]); 

    return z[0]; 
} 
1

Tôi nhận thấy câu hỏi này cũ, nhưng tại sao lại sử dụng _mm_add_ps? Thay thế bằng:

tmp0 = _mm_or_ps(tmp0, tmp1); 
tmp2 = _mm_or_ps(tmp2, tmp3); 
tmp0 = _mm_or_ps(tmp0, tmp2); 

Bạn có thể ẩn một số độ trễ _mm_dp_ps. Đầu tiên _mm_or_ps không chờ đợi cho các sản phẩm cuối cùng 2 chấm, và đó là một (nhanh) bit-khôn ngoan hoạt động. Cuối cùng:

_mm_storeu_ps(C_2, _mm_add_ps(tmp0, C_0)); 
Các vấn đề liên quan