Tại sao trình biên dịch GCC bỏ qua một số mã?


9

Tôi không thể hiểu tại sao trình biên dịch GCC cắt bỏ một phần mã của tôi trong khi nó bảo toàn hoàn toàn cùng một mã trong vùng lân cận?

Mã C:

#define setb_SYNCO do{(PORTA|= (1<<0));} while(0);

ISR(INT0_vect){
    unsigned char i;

    i = 10;
    while(i>0)i--;   // first pause - omitted

    setb_SYNCO;
    setb_GATE;
    i=30;
    clrb_SYNCO;
    while(i>0)i--;  // second pause - preserved
    clrb_GATE;
}

Phần tương ứng của LSS (tệp trình biên dịch, được tạo bởi trình biên dịch):

ISR(INT0_vect){
  a4:   1f 92           push    r1
  a6:   0f 92           push    r0
  a8:   0f b6           in  r0, 0x3f    ; 63
  aa:   0f 92           push    r0
  ac:   11 24           eor r1, r1
  ae:   8f 93           push    r24
    unsigned char i;

    i = 10;
    while(i>0)i--;

    setb_SYNCO;
  b0:   d8 9a           sbi 0x1b, 0 ; 27
    setb_GATE;
  b2:   d9 9a           sbi 0x1b, 1 ; 27
    i=30;
    clrb_SYNCO;
  b4:   d8 98           cbi 0x1b, 0 ; 27
  b6:   8e e1           ldi r24, 0x1E   ; 30
  b8:   81 50           subi    r24, 0x01   ; 1
    while(i>0)i--;
  ba:   f1 f7           brne    .-4         ; 0xb8 <__vector_1+0x14>
    clrb_GATE;
  bc:   d9 98           cbi 0x1b, 1 ; 27
}
  be:   8f 91           pop r24
  c0:   0f 90           pop r0
  c2:   0f be           out 0x3f, r0    ; 63
  c4:   0f 90           pop r0
  c6:   1f 90           pop r1
  c8:   18 95           reti

Tôi có thể giả sử rằng trình biên dịch tìm ra rằng mã như vậy là giả và cắt nó ra nhưng tại sao nó lại bảo tồn cùng một mã ở cuối mã?

Có bất kỳ hướng dẫn biên dịch để ngăn chặn tối ưu hóa như vậy?


1
Bạn cũng có thể yêu cầu trình biên dịch không tối ưu hóa một chức năng duy nhất, có lẽ nên thử phương pháp này với ISR ​​của bạn. Xem câu hỏi này trên stackoverflow.
Vladimir Cravero

2
Này Roman, tôi đã thêm thẻ "c" vào câu hỏi của bạn khi xóa atmega. Tôi đã phải xóa một thẻ vì có giới hạn (năm) và khi hỏi một câu hỏi liên quan đến mã thêm tên ngôn ngữ làm thẻ thì rất tuyệt vì tất cả mã (Q & As) đều được tô sáng.
Vladimir Cravero

5
Nói chung, các ngôn ngữ cấp cao hơn (như C) được thiết kế rõ ràng để không bị ràng buộc với mối quan hệ 1: 1 với hội đồng kết quả của chúng. Nếu bạn cần đếm hướng dẫn để có thời gian phù hợp, bạn luôn phải dựa vào lắp ráp (như một số câu trả lời đã làm). Điểm chung của các ngôn ngữ cấp cao là trình biên dịch có một số quyền tự do để giúp mã của bạn nhanh hơn - mạnh hơn - tốt hơn trước. Các chi tiết như phân bổ đăng ký và dự đoán nhánh tốt hơn nhiều cho trình biên dịch ... ngoại trừ trong những lúc như thế này, nơi bạn, lập trình viên, biết chính xác các hướng dẫn bạn muốn.
Cort Ammon

5
Câu hỏi hay hơn là, tại sao GCC không tối ưu hóa cả hai vòng lặp đó?
Ilmari Karonen

5
Đầu tiên Gcc hủy bỏ vòng lặp và chỉ sau đó thông báo rằng mã tương ứng là vô dụng. Với kích thước vòng lặp là 30, việc bỏ kiểm soát sẽ là điều ngu ngốc và gcc không làm điều đó. Ở mức tối ưu hóa cao hơn cả hai đều được tối ưu hóa đi.
Marc Glisse

Câu trả lời:


9

Vì trong một bình luận, bạn nói rằng "mỗi đánh dấu CPU là xứng đáng", tôi khuyên bạn nên sử dụng một số lắp ráp nội tuyến để thực hiện vòng lặp trì hoãn như bạn muốn. Giải pháp này vượt trội so với nhiều loại khác nhau volatilehoặc -O0vì nó cho thấy rõ ý định của bạn là gì.

unsigned char i = 10;
__asm__ volatile ( "loop: subi    %0, 0x01\n\t"
                   "      brne    loop"
                   : "+rm" (i)
                   : /* no inputs */
                   : /* no dirty registers to decleare*/);

Điều đó sẽ làm các trick. Điều dễ bay hơi là có để nói với trình biên dịch "Tôi biết điều này không làm gì cả, chỉ cần giữ nó và tin tưởng tôi". Ba câu lệnh "asm" khá tự giải thích, bạn có thể sử dụng bất kỳ thanh ghi nào thay vì r24, tôi tin rằng trình biên dịch thích các thanh ghi thấp hơn nên bạn có thể muốn sử dụng một thanh ghi cao. Sau lần đầu tiên, :bạn nên liệt kê các biến đầu ra (đọc và ghi) c, và không có biến nào, sau lần thứ hai :bạn nên liệt kê các biến đầu vào (ronly) c, một lần nữa, không có và tham số thứ ba là danh sách các thanh ghi được sửa đổi bằng dấu phẩy , trong trường hợp này r24. Tôi không chắc chắn nếu bạn nên bao gồm cả thanh ghi trạng thái vì ZEROtất nhiên các thay đổi cờ, tôi đã không bao gồm nó.

chỉnh sửa câu trả lời được chỉnh sửa theo yêu cầu của OP. Một số lưu ý.

Các "+rm"trước (i)có nghĩa là bạn đang cho phép trình biên dịch quyết định đặt i trong m emory hoặc trong một r egister. Đó là một điều tốt trong hầu hết các trường hợp vì trình biên dịch có thể tối ưu hóa tốt hơn nếu nó miễn phí. Trong trường hợp của bạn, tôi tin rằng bạn chỉ muốn giữ ràng buộc r để buộc tôi phải đăng ký.


Có vẻ như đây là một điều tôi thực sự cần. Nhưng bạn có thể sửa đổi câu trả lời của mình để chấp nhận bất kỳ cbiến nào thay vì nghĩa đen 10mà tôi đã đề cập trong câu trả lời ban đầu không? Tôi đang cố gắng đọc hướng dẫn sử dụng GCC liên quan đến việc sử dụng asm đúng cách nhưng hiện tại nó hơi bị che khuất. Tôi sẽ rất cảm kích!
Roman Matveev

1
@RomanMatveev chỉnh sửa theo yêu cầu của bạn
Vladimir Cravero

13

Bạn có thể thử làm cho vòng lặp thực sự làm một cái gì đó. Vì nó là trình biên dịch hoàn toàn đúng khi nói "Vòng lặp này không làm gì cả - tôi sẽ thoát khỏi nó".

Vì vậy, bạn có thể thử một cấu trúc tôi sử dụng thường xuyên:

int i;
for (i = 0; i < 10; i++) {
    asm volatile ("nop");
}

Lưu ý: không phải tất cả các mục tiêu cho trình biên dịch gcc đều sử dụng cùng một cú pháp lắp ráp nội tuyến - bạn có thể cần phải điều chỉnh nó cho mục tiêu của mình.


Giải pháp của bạn có vẻ thanh lịch hơn nhiều so với của tôi ... có lẽ của tôi là tốt hơn khi yêu cầu số chu kỳ PRECISE? Ý tôi là, toàn bộ cho điều không được đảm bảo để được biên dịch theo một cách nhất định, phải không?
Vladimir Cravero

8
Thực tế đơn giản là sử dụng C có nghĩa là bạn không thể đảm bảo chu kỳ. Nếu bạn cần đếm chu kỳ chính xác thì ASM là cách duy nhất để đi. Ý tôi là, bạn có được thời gian khác nhau với vòng lặp C nếu bạn bật -funroll-loop so với khi bạn không, v.v.
Majenko

Yeah đó là những gì tôi nghĩ. Khi thực hiện độ trễ CT với giá trị i đủ cao (100 trở lên) tôi đoán giải pháp của bạn mang lại kết quả gần như tương tự trong khi tăng cường khả năng đọc.
Vladimir Cravero

6

Vâng, bạn có thể cho rằng. Nếu bạn khai báo biến i là biến động, bạn báo cho trình biên dịch không tối ưu hóa trên i.


1
Điều đó không hoàn toàn đúng theo quan điểm của tôi.
Vladimir Cravero

1
@VladimirCravero, ý của bạn là gì khi nói điều đó? Bạn có thể làm rõ hơn?
Roman Matveev

2
Ý tôi là tôi sẽ không chắc chắn về những gì trình biên dịch làm. Khai báo một biến dễ bay hơi cho trình biên dịch biết rằng nó có thể thay đổi ở một nơi khác để nó thực sự nên thực hiện điều đó trong khi đó.
Vladimir Cravero

1
@Roman Matveev register unsigned char volatile i __asm__("r1");có lẽ?
a3f

2
Tuyên bố inhư dễ bay hơi giải quyết mọi thứ. Điều này được đảm bảo theo tiêu chuẩn C 5.1.2.3. Một trình biên dịch tuân thủ không được tối ưu hóa các vòng lặp đó nếu ikhông ổn định. May mắn thay, GCC là một trình biên dịch phù hợp. Thật không may, nhiều trình biên dịch C sẽ không tuân theo tiêu chuẩn, nhưng điều đó không liên quan đến câu hỏi cụ thể này. Hoàn toàn không cần trình biên dịch nội tuyến.
Lundin

1

Sau vòng lặp đầu tiên ilà một hằng số. Việc khởi tạo ivà vòng lặp không làm gì ngoài việc tạo ra một giá trị không đổi. Không có gì trong tiêu chuẩn quy định rằng vòng lặp này phải được biên dịch nguyên trạng. Tiêu chuẩn cũng không nói gì về thời gian. Mã được biên dịch phải hoạt động như thể vòng lặp đã có và nó thực hiện. Bạn không thể tin cậy rằng tối ưu hóa này được thực hiện theo tiêu chuẩn (thời gian không được tính).

Vòng lặp thứ hai cũng nên được xóa. Tôi coi đó là một lỗi (hoặc thiếu tối ưu hóa) mà nó không phải là. Sau khi vòng lặp ikhông đổi. Mã nên được thay thế bằng cài đặt ivề không.

Tôi nghĩ rằng GCC giữ ixung quanh hoàn toàn vì lý do bạn thực hiện truy cập cổng (mờ) có thể ảnh hưởng i.

Sử dụng

asm volatile ("nop");

để lừa GCC tin rằng vòng lặp làm một cái gì đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.