2012-05-25 5 views
8

Tôi đang phát triển một ứng dụng có hiệu suất rất quan trọng. Tôi muốn GCC dịch một số lời gọi cụ thể đến memset() như một lệnh với một tiền tố lặp lại như "rep stos QWORD PTR es: [rdi], rax". GCC thực hiện điều này tự động khi kích thước vừa được biết và nhỏ. Tuy nhiên, GCC ánh xạ các cuộc gọi đến memset() với độ dài ngẫu nhiên thông qua một lời gọi đến memset() thông qua PLT, điều này gây ra sự hiểu sai chi nhánh vì bộ nhớ cache dự đoán nhánh là lạnh.Buộc GCC sử dụng tiền tố lặp lại trong memset() gọi

Có cách nào để buộc GCC phải làm những gì tôi muốn (ngoài lắp ráp nội tuyến) không? Lưu ý rằng tôi không muốn hành vi này cho toàn bộ chương trình, chỉ cho một số cuộc gọi memset cụ thể().

Trên một chủ đề liên quan, tôi cũng quan tâm đến bất cứ hack mà ngăn GCC từ nhánh khi một hướng dẫn cmovcc sẽ thực hiện công việc (Tôi biết về việc sử dụng &, +, vv. Thay vì & &).

Cảm ơn bạn rất nhiều vì đã giúp đỡ.

@FrankH:

Đó là cơ bản những gì tôi đã kết thúc làm. Đây là mã của tôi:

static finline void app_zero(void *dst, uint32_t size, uint32_t count) 
{ 
    // Warning: we tell gcc to use 'dst' both as source and destination here. 
    // This does not cause problems because we don't reuse 'dst'. 
    #ifdef APP_ARCH_X86 
    #define STOS(X,Y) do { \ 
     int c = (size/Y)*count; \ 
     __asm__ __volatile__("cld; xor %%eax, %%eax; rep stos"X"\n\n" \ 
          : "+D"(dst), "+c"(c) :: "rax", "flags"); \ 
     } while (0) 
    if (size % 8 == 0)  STOS("q", 8); 
    else if (size % 4 == 0) STOS("l", 4); 
    else if (size % 2 == 0) STOS("w", 2); 
    else     STOS("b", 1); 
    #undef STOS 
    #else 
    memset(dst, 0, size*count); 
    #endif 
} 

Lưu ý rằng ví dụ của bạn hoạt động trong thiết lập thử nghiệm của bạn, nhưng nó sẽ không làm việc thường. GCC có thể thay đổi cờ hướng, do đó, hướng dẫn cld là cần thiết. Hơn nữa, bạn phải nói với gcc rằng %rdi%rcx sẽ thay đổi bằng cách hướng dẫn stos, và vì gcc sẽ không cho phép bạn xác định rằng một thanh ghi là cả một đầu vào và clobbered, bạn phải sử dụng vụng "+" cú pháp (mà cũng sẽ làm hỏng giá trị đầu vào của bạn).

Điều này không tối ưu do lệnh 'cld', có độ trễ là 4 chu kỳ trên Nehalem. GCC theo dõi trạng thái đăng ký cờ nội bộ (AFAICT) vì vậy nó không cần phải ban hành lệnh đó mỗi lần.

+1

Cách duy nhất tôi có thể nghĩ đến thậm chí cố gắng đạt được điều gì đó chỉ áp dụng cho một số phần nhất định của mã sẽ là thuộc tính của GCC (các thuộc tính của hàm, biến và kiểu). Tuy nhiên, trong nháy mắt, không ai trong số họ đạt được những gì bạn đang tìm kiếm. – zxcdw

+0

Cảm ơn, tôi xác nhận rằng không có thuộc tính/pragma nào như vậy tồn tại. –

Trả lời

2

Tôi không biết về GCC, nhưng theo bản xây dựng mới hơn của MSVC, sử dụng vòng lặp để thực hiện cài đặt/sao chép buộc sử dụng REP STOS (và nó vẫn cho phép tối ưu hóa cho biết kích cỡ và tự động vector) làm việc thử theo GCC.

thay thế để kiểm tra xem GCC có được xây dựng trong tương tự như __stosq, nếu không bạn có thể sẽ cần phải đi xuống để nội tuyến lắp ráp, nhưng thats không xấu cả thuộc GCC (và có lẽ là cách đơn giản nhất và nhanh nhất của nó).

câu hỏi thứ hai của bạn là cách để chung để thực sự có được một câu trả lời tốt, vì nó phụ thuộc vào trường hợp trong tầm tay, tuy nhiên, GCC nên làm cũng đủ trong việc tối ưu hóa ra chi nhánh trừ trường hợp góc cụ thể (sử dụng SETCC/MOVCC/FMOVCC).

+0

Tôi đã thử sử dụng một vòng lặp đơn giản để thực hiện việc ghi nhớ(). Không may mắn, gcc vẫn khăng khăng sử dụng các nhánh. Tôi hiện đang sử dụng trình bao bọc lắp ráp nội tuyến xung quanh 'stos' và' movs'. Điều này là xa tối ưu vì tôi phải chèn lệnh 'cld' trong trường hợp gcc đặt cờ hướng. Để tối ưu hóa các chi nhánh, kinh nghiệm của tôi là GCC thích sử dụng các nhánh ngay cả đối với các trường hợp đơn giản như 'MIN' và' MAX'. Sử dụng lắp ráp nội tuyến cho những trường hợp này không phải là tối ưu vì nó hạn chế việc sử dụng đăng ký của trình biên dịch. –

4

Nếu bạn muốn ép buộc điều này, tại sao loại trừ lắp ráp nội tuyến làm tùy chọn?

#define my_forced_inline_memset(dst, c, N) \ 
    __asm__ __volatile__(     \ 
     "rep stosq %%rax, (%%rdi)\n\t" 
     : : "D"((dst)), "a"((c)), "c"((N)) : "memory"); 

Sử dụng này trong một chương trình giới thiệu như:

int main(int argc, char **argv) 
{ 
    my_forced_inline_memset(argv[0], 0, argc); 
    return 0; 
} 

tạo tôi lắp ráp này:

00000000004004b0 <main>: 
    4004b0:  89 f9     mov %edi,%ecx 
    4004b2:  31 c0     xor %eax,%eax 
    4004b4:  48 8b 3e    mov (%rsi),%rdi 
    4004b7:  f3 ab     repz stos %rax,%es:(%rdi) 
    4004b9:  c3      retq 

Đó không phải là một lời giải thích lý do tại sao GCC chọn để làm khác nhau, nhưng như đã nói nếu bạn muốn ép buộc hành vi mà bạn có thể, và nếu bạn biết rõ (các) địa điểm mà bạn cần điều này thì có một chút sai lầm khi gọi một số loại bộ nhớ được xác định đặc biệt của riêng bạn?

Lưu ý:repz stos %rax,(%rdi) (hoặc Intel cú pháp QWORD PTR equiv) là không giống như memset() vì granularity cho memset() là một byte duy nhất. Ở trên là khá giống như memset(..., c, N * 8) vì lý do đó. Giữ nó trong tâm trí.

Edit: Nếu bạn viết mã như:

#include <stdint.h>      // for uintptr_t 
#define my_forced_inline_memset(dst, c, N)       \ 
    __asm__ __volatile__(            \ 
     "rep stos %1, (%0)\n\t"          \ 
     :: "D"((dst)), "a"((uintptr_t)(c)), "c"((N)/sizeof(uintptr_t)) \ 
     : "memory"); 

nó biên dịch cho cả 32bit và 64bit.

+0

Cảm ơn sự giúp đỡ, xem câu trả lời của tôi ở trên. –

+0

Về cờ hướng và ABI, có một cuộc thảo luận dài và quanh co về điều này trong danh sách gửi thư gcc, http://gcc.gnu.org/ml/gcc/2008-03/msg00330.html - thông báo đó ngụ ý rằng trình biên dịch sẽ _assure_ 'DF = 0' trước khi lắp ráp nội tuyến, tức là nó không bao giờ cần thiết để làm rõ ràng' cld', gcc được cho là sẽ làm điều đó cho bạn nếu cần thiết. Ngoài ra, tái sử dụng sai chi nhánh do sử dụng PLT, bạn không thể làm việc xung quanh bằng cách gọi 'memset()' thông qua một con trỏ hàm? –

+0

Có lẽ đã thêm: https://lkml.org/lkml/2008/3/5/306 là bản sửa lỗi hạt nhân Linux cho giá trị 'DF' không chính xác mà chuỗi danh sách gửi thư gcc nói. Nó cũng đề cập đến yêu cầu ABI trên 'DF = 0' trước các cuộc gọi hàm. Chuỗi gcc, trong một số khác của nhiều thông điệp của nó, cũng nói rằng 'memmove()' có thể 'std' nhưng được đảm bảo thực hiện' cld' trước khi trả về. –