2012-09-28 26 views
50

Giả sử chúng tôi đang cố gắng sử dụng tsc để giám sát hiệu suất và chúng tôi muốn ngăn chặn việc sắp xếp lại lệnh.Sự khác biệt giữa rdtscp, rdtsc: bộ nhớ và cpuid/rdtsc?

Đây là những lựa chọn của chúng tôi:

1:rdtscp là lời kêu gọi serializing. Nó ngăn cản sắp xếp lại xung quanh cuộc gọi đến rdtscp.

__asm__ __volatile__("rdtscp; "   // serializing read of tsc 
        "shl $32,%%rdx; " // shift higher 32 bits stored in rdx up 
        "or %%rdx,%%rax" // and or onto rax 
        : "=a"(tsc)  // output to tsc variable 
        : 
        : "%rcx", "%rdx"); // rcx and rdx are clobbered 

Tuy nhiên, rdtscp chỉ khả dụng trên các CPU mới hơn. Vì vậy, trong trường hợp này chúng ta phải sử dụng rdtsc. Nhưng rdtsc không được tuần tự hóa, vì vậy việc sử dụng nó một mình sẽ không ngăn CPU sắp xếp lại nó.

Vì vậy, chúng ta có thể sử dụng một trong hai tùy chọn này để ngăn chặn sắp xếp lại:

2: Đây là một lời kêu gọi cpuid và sau đó rdtsc. cpuid là một cuộc gọi nối tiếp.

volatile int dont_remove __attribute__((unused)); // volatile to stop optimizing 
unsigned tmp; 
__cpuid(0, tmp, tmp, tmp, tmp);     // cpuid is a serialising call 
dont_remove = tmp;        // prevent optimizing out cpuid 

__asm__ __volatile__("rdtsc; "   // read of tsc 
        "shl $32,%%rdx; " // shift higher 32 bits stored in rdx up 
        "or %%rdx,%%rax" // and or onto rax 
        : "=a"(tsc)  // output to tsc 
        : 
        : "%rcx", "%rdx"); // rcx and rdx are clobbered 

3: Đây là một lời kêu gọi rdtsc với memory trong danh sách clobber, mà ngăn sắp xếp lại

__asm__ __volatile__("rdtsc; "   // read of tsc 
        "shl $32,%%rdx; " // shift higher 32 bits stored in rdx up 
        "or %%rdx,%%rax" // and or onto rax 
        : "=a"(tsc)  // output to tsc 
        : 
        : "%rcx", "%rdx", "memory"); // rcx and rdx are clobbered 
                // memory to prevent reordering 

hiểu biết của tôi cho các tùy chọn thứ 3 là như sau:

Lập hãy gọi __volatile__ ngăn trình tối ưu hóa loại bỏ asm hoặc di chuyển nó qua bất kỳ hướng dẫn nào có thể cần kết quả (hoặc thay đổi đầu vào) của asm. Tuy nhiên nó vẫn có thể di chuyển nó đối với các hoạt động không liên quan. Vì vậy, __volatile__ là không đủ.

Thông báo cho bộ nhớ trình biên dịch đang bị ghi đè: : "memory"). Các "memory" clobber có nghĩa là GCC không thể thực hiện bất kỳ giả định về nội dung bộ nhớ còn lại trên cùng một asm, và do đó sẽ không sắp xếp lại xung quanh nó.

Vì vậy, câu hỏi của tôi là:

  • 1: Liệu sự hiểu biết của tôi về __volatile__"memory" có đúng không?
  • 2: Thực hiện hai cuộc gọi thứ hai có thực hiện tương tự không?
  • 3: Sử dụng "memory" trông đơn giản hơn nhiều so với sử dụng hướng dẫn tuần tự hóa khác. Tại sao mọi người sử dụng tùy chọn thứ 3 trong tùy chọn thứ 2?
+9

Bạn có thể nhầm lẫn việc sắp xếp lại các lệnh do trình biên dịch tạo ra, bạn có thể tránh bằng cách sử dụng 'volatile' và' memory' và sắp xếp lại các lệnh được thực hiện bởi bộ xử lý (aka _out of order execution_). cpuid'. – hirschhornsalz

+0

@hirschhornsalz nhưng sẽ không có 'bộ nhớ' trong danh sách clobber ngăn bộ xử lý sắp xếp lại hướng dẫn? Không 'bộ nhớ' hoạt động như một hàng rào bộ nhớ? –

+0

hoặc có lẽ 'bộ nhớ' trong danh sách các bộ ghi đè chỉ được phát ra gcc và mã máy kết quả không hiển thị bộ xử lý này? –

Trả lời

35

Như đã đề cập trong một chú thích, có sự khác biệt giữa một rào cản biên dịch và một rào cản xử lý. volatilememory trong tuyên bố asm hoạt động như một rào cản trình biên dịch, nhưng bộ xử lý vẫn miễn phí để sắp xếp lại hướng dẫn.

Rào cản bộ xử lý là các hướng dẫn đặc biệt phải được đưa ra một cách rõ ràng, ví dụ: rdtscp, cpuid, hướng dẫn về hàng rào bộ nhớ (mfence, lfence, ...) v.v.

Là một sang một bên, trong khi sử dụng cpuid như một rào cản trước rdtsc là phổ biến, nó cũng có thể rất xấu từ góc độ hiệu suất, vì các nền tảng máy ảo thường bẫy và bắt chước các hướng dẫn cpuid để áp đặt một tập phổ biến của CPU các tính năng trên nhiều máy trong một cụm (để đảm bảo rằng hoạt động di chuyển trực tiếp hoạt động). Vì vậy, tốt hơn nên sử dụng một trong các hướng dẫn hàng rào bộ nhớ.

Nhân Linux sử dụng mfence;rdtsc trên nền tảng AMD và lfence;rdtsc trên Intel. Nếu bạn không muốn bận tâm với việc phân biệt giữa chúng, mfence;rdtsc hoạt động trên cả hai mặc dù nó hơi chậm hơn khi mfence là một rào cản mạnh hơn lfence.

+5

'cpuid; rdtsc' không phải là về hàng rào bộ nhớ, đó là về việc tuần tự hóa luồng lệnh. Thông thường nó được sử dụng cho các mục đích đo điểm chuẩn để đảm bảo không có hướng dẫn "cũ" nào vẫn còn trong bộ đệm sắp xếp lại/đặt chỗ trước. Thời gian thực hiện 'cpuid' (khá dài, tôi nhớ> 200 chu kỳ) sau đó được trừ đi. Nếu kết quả là "chính xác" theo cách này không rõ ràng với tôi, tôi đã thử nghiệm và không có và sự khác biệt dường như ít sai số đo lường tự nhiên hơn, ngay cả ở chế độ người dùng đơn lẻ. – hirschhornsalz

+0

Tôi không chắc chắn, nhưng tôi có thể hướng dẫn hàng rào được sử dụng theo cách này trong hạt nhân không hữu ích ở tất cả ^^ – hirschhornsalz

+4

@hirschhornsalz: Theo nhật ký cam kết git, AMD và Intel xác nhận rằng m/lfence sẽ tuần tự hóa rdtsc trên hiện tại CPU có sẵn. Tôi cho rằng Andi Kleen có thể cung cấp thêm chi tiết về những gì chính xác được nói, nếu bạn quan tâm và hỏi anh ta. – janneb

5

bạn có thể sử dụng nó như hình dưới đây:

asm volatile (
"CPUID\n\t"/*serialize*/ 
"RDTSC\n\t"/*read the clock*/ 
"mov %%edx, %0\n\t" 
"mov %%eax, %1\n\t": "=r" (cycles_high), "=r" 
(cycles_low):: "%rax", "%rbx", "%rcx", "%rdx"); 
/* 
Call the function to benchmark 
*/ 
asm volatile (
"RDTSCP\n\t"/*read the clock*/ 
"mov %%edx, %0\n\t" 
"mov %%eax, %1\n\t" 
"CPUID\n\t": "=r" (cycles_high1), "=r" 
(cycles_low1):: "%rax", "%rbx", "%rcx", "%rdx"); 

Trong đoạn mã trên, các cuộc gọi CPUID đầu tiên thực hiện một rào cản để tránh out-of-trật tự thực hiện các hướng dẫn ở trên và dưới sự hướng dẫn RDTSC. Với phương pháp này, chúng ta tránh gọi lệnh CPUID giữa các lần đọc của thanh ghi thời gian thực

RDTSC đầu tiên sau đó đọc thanh ghi dấu thời gian và giá trị được lưu trữ trong bộ nhớ . Sau đó, mã mà chúng tôi muốn đo được thực hiện. Lệnh RDTSCP đọc thanh ghi thời gian cho lần thứ hai và đảm bảo rằng việc thực hiện tất cả mã mà chúng tôi muốn đo được hoàn thành. Hai lệnh “mov” đến sau đó lưu trữ các giá trị đăng ký edx và eax vào bộ nhớ. Cuối cùng, một cuộc gọi CPUID đảm bảo rằng một rào chắn được thực hiện lại để không thể bất kỳ lệnh nào đến sau được thực thi trước chính CPUID.

+12

Xin chào, có vẻ như bạn đã sao chép câu trả lời này từ giấy trắng Gabriele Paolinis "Cách thực hiện Mã điểm chuẩn trong các kiến ​​trúc Intel® IA-32 và IA-64" (bạn đã bỏ lỡ ngắt dòng). Bạn đang sử dụng tác phẩm của người khác mà không cho tín dụng tác giả. Tại sao không thêm thuộc tính? –

+0

Có, thực sự, nó được đối phó. Tôi cũng tự hỏi nếu hai mov trong đọc thời gian bắt đầu là cần thiết: http://stackoverflow.com/questions/38994549/is-intels-timestamp-reading-asm-code-example-using-two-more-registers -an-là –

+0

Có lý do cụ thể nào để có hai biến cao và thấp không? – ExOfDe

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