2011-11-02 40 views
5

Khi tôi diassembled chương trình của tôi, tôi thấy rằng gcc đã sử dụng jmp cho phần thứ hai pthread_wait_barrier gọi khi biên soạn với O3. Tại sao nó như vậy?Tại sao là gcc sử dụng jmp để gọi một chức năng trong phiên bản tối ưu hóa

Lợi ích nào nhận được bằng cách sử dụng jmp thay vì gọi. Trình biên dịch nào đang chơi ở đây? Tôi đoán tối ưu hóa cuộc gọi đuôi của nó ở đây.

Bằng cách tôi đang sử dụng liên kết tĩnh tại đây.

__attribute__ ((noinline)) void my_pthread_barrier_wait( 
    volatile int tid, pthread_barrier_t *pbar) 
{ 
    pthread_barrier_wait(pbar); 
    if (tid == 0) 
    { 
     if (!rollbacked) 
     { 
      take_checkpoint_or_rollback(++iter == 4); 
     } 
    } 
    //getcontext(&context[tid]); 
    SETJMP(tid); 
    asm("addr2jmp:"); 
    pthread_barrier_wait(pbar); 
    // My suspicion was right, gcc was performing tail call optimization, 
    // which was messing up with my SETJMP/LONGJMP implementation, so here I 
    // put a dummy function to avoid that. 
    dummy_var = dummy_func(); 
} 
+1

Hiển thị cho chúng tôi mã nguồn. –

+1

jmp chỉ chuyển giao thực thi đến một vị trí mới. gọi đẩy công cụ lên ngăn xếp và do đó hơi tốn kém hơn để sử dụng. –

+1

Hình như tối ưu hóa cuộc gọi đuôi thực hiện. – MetallicPriest

Trả lời

12

Như bạn không hiển thị một ví dụ, tôi chỉ có thể đoán: được gọi là hàm có kiểu trả về giống như gọi một, và điều này hoạt động giống như

return func2(...) 

hoặc không có kiểu trả lại nào cả (void).

Trong trường hợp này, "chúng tôi" để lại địa chỉ trả lại "của chúng tôi" trên ngăn xếp, để địa chỉ đó trở thành "họ" để sử dụng địa chỉ đó để quay lại "người gọi" của chúng tôi.

6

Có lẽ đây là cuộc gọi có tính đệ quy đuôi. GCC có một số bước thực hiện tối ưu hóa đệ quy đuôi.

Nhưng tại sao bạn nên bận tâm? Nếu hàm được gọi là một hàm extern, thì nó là công khai, và GCC nên gọi nó theo các quy ước ABI (có nghĩa là nó tuân theo quy ước gọi).

Bạn không nên quan tâm nếu hàm được gọi bằng jmp.

Và nó cũng có thể là một cuộc gọi đến một chức năng thư viện động (ví dụ: với PLT cho dynamic linking)

2

jmp có ít bước đầu hơn gọi. jmp chỉ nhảy, gọi đẩy một số nội dung trên stack và nhảy

+0

-1 cho câu trả lời chưa hoàn chỉnh. Tôi cũng biết jmp có chi phí thấp hơn. Câu hỏi đặt ra là cách gcc sử dụng jmp để thực hiện cùng chức năng như cuộc gọi trong phiên bản được tối ưu hóa. – MetallicPriest

+2

Những gì bạn đề cập trong nhận xét của bạn không được nêu rõ trong câu hỏi, có thể bạn nên chỉnh sửa nó. Câu hỏi duy nhất tôi không trả lời là "Những gì các trình biên dịch trình biên dịch đang chơi ở đây?", Đó là không rõ ràng và bị hỏng tiếng Anh. – TJD

+0

JMP có ít chi phí hơn, chắc chắn. Bây giờ các chức năng bạn nhảy đến thay vì cuộc gọi đã trở lại tại một số điểm. Do đó, địa chỉ trả về phải được đẩy lên ngăn xếp trước JMP hoặc ở cuối hàm bạn cần gọi một JMP khác để trở về nơi bạn đến, điều đó, tất cả kết thúc đều khá giống nhau. Bạn có thể lưu một vài chu kỳ với một JMP đôi vì không có thao tác ngăn xếp nhưng bạn sẽ phải lưu trữ địa chỉ trả về trong thanh ghi hoặc một cái gì đó. –

2

Tôi giả định rằng đây là một cuộc gọi đuôi, có nghĩa là hàm hiện tại trả về kết quả của hàm được gọi chưa sửa đổi hoặc (đối với hàm trả về void) trả về ngay sau cuộc gọi hàm. Trong cả hai trường hợp, không cần sử dụng call.

Hướng dẫn call thực hiện hai chức năng. Đầu tiên, nó đẩy địa chỉ của lệnh sau khi cuộc gọi vào ngăn xếp như một địa chỉ trả về. Sau đó, nó nhảy đến đích của cuộc gọi. ret bật địa chỉ trả lại khỏi ngăn xếp và chuyển đến vị trí đó.

Vì hàm gọi trả về kết quả của hàm được gọi, không có lý do nào để hoạt động trở lại sau khi hàm được gọi trả về. Do đó, bất cứ khi nào có thể và nếu mức tối ưu cho phép nó, GCC sẽ hủy khung ngăn xếp của nó trước khi gọi hàm, để đầu ngăn xếp chứa địa chỉ trả về cho hàm được gọi, và sau đó chỉ cần nhảy đến hàm được gọi. Kết quả là, khi hàm được gọi trả về, nó trả về trực tiếp hàm đầu tiên thay vì hàm gọi.

-1

Bạn sẽ không bao giờ biết, nhưng một trong những lý do có thể là "bộ nhớ cache" (trong số các lý do khác như tối ưu hóa cuộc gọi đuôi đã đề cập).

Nội tuyến có thể làm cho mã nhanh hơn và có thể làm cho mã chậm hơn, vì nhiều mã hơn có nghĩa là ít mã trong bộ nhớ cache L1 cùng một lúc.

JMP cho phép trình biên dịch sử dụng lại cùng một đoạn mã với chi phí ít hoặc không tốn chút nào. Các bộ vi xử lý hiện đại có đường ống dẫn sâu, và các đường ống đi qua một JMP mà không có vấn đề gì (không có khả năng xảy ra sự lừa dối ở đây!). Trong trường hợp trung bình, nó sẽ chi phí ít nhất là 1-2 chu kỳ, trong trường hợp tốt nhất không chu kỳ, bởi vì CPU sẽ phải chờ đợi trên một hướng dẫn trước đó để nghỉ hưu anyway. Điều này rõ ràng phụ thuộc hoàn toàn vào mã cá nhân tương ứng.
Trình biên dịch có thể về nguyên tắc thậm chí làm điều đó với một số hàm có phần chung.

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