2015-08-05 12 views
6

Khi tôi cần thực hiện một thao tác vectơ có toán hạng chỉ là một phao được phát cho mọi thành phần, tôi nên tính toán trước __m256 hoặc __m128 và tải nó khi cần hoặc truyền phao đến thanh ghi bằng cách sử dụng _mm_set1_ps mọi thời gian tôi cần vector?Đối với một vector SSE có tất cả các thành phần giống nhau, tạo ra khi đang bay hoặc tiền tố?

Tôi đã precomputing các vectơ rất quan trọng và được sử dụng cao và tạo ra trên bay những cái mà ít quan trọng. Nhưng tôi có thực sự đạt được tốc độ với precomputing? Nó có đáng giá không?

Có phải _mm_set1_ps được triển khai với một hướng dẫn duy nhất không? Điều đó có thể trả lời câu hỏi của tôi.

+0

Khi bạn nói "tải" tôi giả sử bạn có nghĩa là bằng cách sao chép biến hiện có (đăng ký), phải không? Không phải từ bộ nhớ? – Mehrdad

+0

@Mehrdad Vâng, chủ yếu là tôi có ý nghĩa từ bộ nhớ, trừ khi nó xảy ra trong sổ đăng ký rồi. Đối với tùy chọn tiền lọc, ý tôi là đặt __m128 ở đâu đó trong mã của bạn, và sau đó tham khảo nó bất cứ nơi nào bạn cần, thay vì tạo cùng một véc tơ với _mm_set1_ps. Đó là giải thích đơn giản nhất. – Thomas

+2

Thật khó để nói hoàn toàn vì 'mm_set1_ps' không tương ứng với bất kỳ lệnh nào. Nó thậm chí có thể dịch những gì bạn đang đề xuất làm. Tại sao không kiểm tra lắp ráp và xem? Ngoài ra, vì bạn đã gắn thẻ bài đăng của mình với AVX, bạn có thể muốn biết có nội tại mới hơn tương ứng với một hướng dẫn duy nhất: '_mm_broadcast_ss' – hayesti

Trả lời

2

Đương nhiên nó sẽ phụ thuộc rất nhiều vào mã của bạn, nhưng tôi đã triển khai hai hàm đơn giản bằng cả hai cách tiếp cận. See code

__m128 calc_set1(float num1, float num2) 
{ 
    __m128 num1_4 = _mm_set1_ps(num1); 
    __m128 num2_4 = _mm_set1_ps(num2); 
    __m128 result4 = _mm_mul_ps(num1_4, num2_4); 

    return result4; 
} 

__m128 calc_mov(float* num1_4_addr, float* num2_4_addr) 
{ 
    __m128 num1_4 = _mm_load_ps(num1_4_addr); 
    __m128 num2_4 = _mm_load_ps(num2_4_addr); 
    __m128 result4 = _mm_mul_ps(num1_4, num2_4); 

    return result4; 
} 

và lắp ráp

calc_set1(float, float): 
    shufps $0, %xmm0, %xmm0 
    shufps $0, %xmm1, %xmm1 
    mulps %xmm1, %xmm0 
    ret 
calc_mov(float*, float*): 
    movaps (%rdi), %xmm0 
    mulps (%rsi), %xmm0 
    ret 

Bạn có thể thấy rằng calc_mov() không như những gì bạn mong muốn và calc_set1() sử dụng một hướng dẫn ngẫu nhiên duy nhất.

Hướng dẫn movps có thể mất khoảng bốn chu kỳ để tạo địa chỉ + hơn nếu cổng tải của bộ nhớ cache L1 bận + hơn trong trường hợp hiếm hoi của bộ nhớ cache bị thiếu.

shufps sẽ thực hiện một chu kỳ đơn trên bất kỳ vi kiến ​​trúc Intel gần đây nào. Tôi tin rằng điều này là đúng cho dù đó là cho SSE128 hoặc AVX256. Do đó, tôi khuyên bạn nên sử dụng phương pháp mm_set1_ps.

Tất nhiên, lệnh phát ngẫu nhiên giả định phao đã có trong thanh ghi SSE/AVX. Trong trường hợp bạn đang tải nó từ bộ nhớ, sau đó phát sóng sẽ tốt hơn vì nó sẽ chụp tốt nhất của movpsshufps trong một hướng dẫn duy nhất.

3

Tôi tin rằng tốt nhất là nên tính toán vector SSE của bạn từ mã của bạn (ví dụ: vòng lặp) và sử dụng nó bất cứ khi nào bạn cần, giả định bạn cẩn thận không vô tình ép nó vào bộ nhớ. (Ví dụ, nếu bạn lấy địa chỉ của nó hoặc vượt qua nó bằng cách tham chiếu đến một hàm khác, thì nó có thể bị buộc vào bộ nhớ và bạn có thể có hành vi kỳ quặc.)
Ý tưởng là thường là cách tốt nhất là tránh chuyển các giá trị vào và trong số các thanh ghi SSE, và nếu xảy ra trường hợp này trong trường hợp cụ thể của bạn, trình biên dịch đã biết cách giá trị được xây dựng và có thể rematerialize nếu cần. Tôi nghĩ rằng điều này dễ hơn rất nhiều so với loop-invariant code motion nói chung, đó là tối ưu hóa ngược lại (tức là nơi trình biên dịch đưa nó ra cho bạn) và yêu cầu trình biên dịch chứng minh rằng mã thực sự là vòng lặp bất biến.

3

Tôi đang phát xung quanh với các chương trình phát sóng để có câu trả lời cho fastest way to fill a vector (SSE2) with a certain value. Templates friendly. Có một cái nhìn một số bãi chứa asm của chương trình phát sóng.

set1 mỗi khi được sử dụng sẽ không tạo ra nhiều khác biệt, miễn là trình biên dịch biết giá trị được phát sóng không phải là bí danh. (Nếu trình biên dịch không thể giả sử nó không phải là bí danh, nó sẽ phải làm lại phát sóng sau mỗi lần ghi vào một mảng hoặc con trỏ có thể là bí danh.)

Thường là kiểu tốt để lưu trữ kết quả set1 trong biến được đặt tên. Nếu trình biên dịch hết sổ đăng ký véc tơ, nó có thể tràn vectơ vào ngăn xếp và tải lại sau hoặc có thể phát lại. Tôi không chắc chắn nếu phong cách mã hóa sẽ ảnh hưởng đến quyết định này.

Tôi sẽ không sử dụng biến số static const để lưu bộ nhớ cache giữa các cuộc gọi đến một hàm. (Điều đó có thể dẫn đến mã tạo trình biên dịch để kiểm tra xem biến đã được khởi tạo mọi cuộc gọi chưa.)

Phát các hằng số biên dịch đôi khi dẫn đến việc phát chương trình biên dịch, vì vậy mã của bạn chỉ có 16B dữ liệu const ký ức.

Phát sóng AVX1 của giá trị đã có trong sổ đăng ký là trường hợp xấu nhất. AVX1 chỉ cung cấp nguồn bộ nhớ vbroadcastps (chỉ sử dụng cổng tải). Chương trình phát sóng có số shufps/vinsertf128.

Yêu cầu AVX2 cho vbroadcastps ymm, xmm (sử dụng cổng trộn)).

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