2013-02-19 24 views
15

Tôi đang cố gắng để thực hiện hiệu quả nhất của phép nhân 4x4 ma trận (M) với một vec-tơ (u) sử dụng SSE. Ý tôi là Mu = vNhân vectơ ma trận 4x4 hiệu quả với SSE: sản phẩm thêm và chấm ngang - điểm là gì?

Theo như tôi hiểu có hai cách chính để đi về điều này:.

method 1) v1 = dot(row1, u), v2 = dot(row2, u), v3 = dot(row3, u), v4 = dot(row4, u) 
    method 2) v = u1 col1 + u2 col2 + u3 col3 + u4 col4. 

Cách 2 là dễ thực hiện trong SSE2. Phương pháp 1 có thể được thực hiện với hướng dẫn thêm ngang trong SSE3 hoặc hướng dẫn sản phẩm chấm trong SSE4. Tuy nhiên, trong tất cả các phương pháp thử nghiệm 2 của tôi luôn luôn hoạt động tốt hơn phương pháp 1.

Một nơi mà mặc dù phương pháp 1 sẽ có lợi thế là ma trận 3x4, ví dụ như biến đổi affine. Trong trường hợp này, sản phẩm dấu chấm cuối cùng là không cần thiết. Nhưng ngay cả trong trường hợp này phương pháp 2 trên ma trận 4x4 nhanh hơn phương pháp 1 trên ma trận 3x4. Phương pháp duy nhất tôi tìm thấy nhanh hơn phương pháp 2 trên ma trận 4x4 là phương pháp 2 trên ma trận 4x3.

Vậy điểm thêm của hướng ngang và hướng dẫn sản phẩm chấm là gì? Trong thực tế, hướng dẫn sản xuất chấm cho hiệu suất tồi tệ nhất trong trường hợp này. Có lẽ nó có liên quan đến định dạng dữ liệu? Nếu người ta không thể xác định làm thế nào ma trận được đặt hàng sau đó một transpose là cần thiết và trong trường hợp đó có lẽ phương pháp 1 sẽ tốt hơn?

Xem bên dưới để biết một số mã.

__m128 m4x4v_colSSE(const __m128 cols[4], const __m128 v) { 
    __m128 u1 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(0,0,0,0)); 
    __m128 u2 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(1,1,1,1)); 
    __m128 u3 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(2,2,2,2)); 
    __m128 u4 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(3,3,3,3)); 

    __m128 prod1 = _mm_mul_ps(u1, cols[0]); 
    __m128 prod2 = _mm_mul_ps(u2, cols[1]); 
    __m128 prod3 = _mm_mul_ps(u3, cols[2]); 
    __m128 prod4 = _mm_mul_ps(u4, cols[3]); 

    return _mm_add_ps(_mm_add_ps(prod1, prod2), _mm_add_ps(prod3, prod4)); 
} 

__m128 m4x4v_rowSSE3(const __m128 rows[4], const __m128 v) { 
    __m128 prod1 = _mm_mul_ps(rows[0], v); 
    __m128 prod2 = _mm_mul_ps(rows[1], v); 
    __m128 prod3 = _mm_mul_ps(rows[2], v); 
    __m128 prod4 = _mm_mul_ps(rows[3], v); 

    return _mm_hadd_ps(_mm_hadd_ps(prod1, prod2), _mm_hadd_ps(prod3, prod4)); 
} 

__m128 m4x4v_rowSSE4(const __m128 rows[4], const __m128 v) { 
    __m128 prod1 = _mm_dp_ps (rows[0], v, 0xFF); 
    __m128 prod2 = _mm_dp_ps (rows[1], v, 0xFF); 
    __m128 prod3 = _mm_dp_ps (rows[2], v, 0xFF); 
    __m128 prod4 = _mm_dp_ps (rows[3], v, 0xFF); 

    return _mm_shuffle_ps(_mm_movelh_ps(prod1, prod2), _mm_movelh_ps(prod3, prod4), _MM_SHUFFLE(2, 0, 2, 0)); 
} 

Trả lời

10

Hướng dẫn sản phẩm thêm và chấm phức tạp được phân tách thành nhiều vi xử lý đơn giản hơn được thực hiện bởi bộ xử lý giống như hướng dẫn đơn giản. Việc phân tách chính xác các hướng dẫn sản phẩm thêm và chấm ngang thành vi xử lý là đặc thù cho bộ vi xử lý, nhưng đối với bộ vi xử lý gần đây của Intel, phần thêm ngang được phân tách thành 2 bộ vi xử lý SHUFFLE + 1 ADD và sản phẩm chấm được phân tách thành 1 MUL + 1 SHUFFLE + 2 ADD microoperations. Bên cạnh số lượng vi xử lý lớn hơn, hướng dẫn này cũng nhấn mạnh bộ giải mã lệnh trong đường ống xử lý: Bộ xử lý Intel chỉ có thể giải mã một lệnh phức tạp trên mỗi chu kỳ (so với 4 hướng dẫn đơn giản). Trên máy ủi AMD, chi phí tương đối của các lệnh phức tạp này thậm chí còn cao hơn.

+0

Cảm ơn, điều đó giải thích lý do tại sao các hướng dẫn chậm. Tuy nhiên, nó không giải thích tại sao chúng được thực hiện. Nhưng tôi nghĩ rằng tôi biết bây giờ. Phương pháp 2 yêu cầu dữ liệu là cấu trúc của mảng (SoA), tức là cột được sắp xếp, tối ưu. Nếu dữ liệu là một mảng các cấu trúc (AoS), tức là hàng được đặt hàng, một quá trình chuyển đổi phải được thực hiện và trong trường hợp này phương pháp 1 nhanh hơn nhiều. Nói cách khác, nếu dữ liệu có thể được xác định làm cho nó là SoA thay vì một AoS và phương pháp sử dụng 2. Nếu không sử dụng phương pháp 1 với bổ sung ngang. Không sử dụng hướng dẫn sản xuất chấm cho phép nhân ma trận. –

+1

Các nhà cung cấp CPU có một lịch sử bổ sung các hướng dẫn mới có thể rất hữu ích, nhưng ban đầu cống hiến rất ít phần cứng để triển khai chúng. Nếu họ nhận được thông qua bởi các chương trình đủ, sau đó họ cuối cùng thêm phần cứng nhiều hơn để thực sự làm cho các hướng dẫn nhanh hơn. Thế hệ đầu tiên '' _mm_dp_ps'' không thực sự nhanh hơn so với phương thức SSE hoặc SSE3 thông thường để thực hiện điều này, mặc dù trong lý thuyết, nó sẽ ít mã hóa hơn một chút nếu bạn đang làm rất nhiều. –

+0

Nếu bạn nhìn vào hướng dẫn Intel Intrinsics: [link] (https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=SSE3,SSE4_1&cats=Arithmetic&expand=2737,2084), bạn sẽ thấy các số liệu hiệu suất. Điều này cũng giúp giải thích lý do tại sao giải pháp dp lại vượt trội hơn cả giải pháp hadd. – St0fF

Các vấn đề liên quan