Một vòng lặp vô hạn bên trong loop () sẽ thực hiện nhanh hơn?


19

Khi bạn đang viết một bản phác thảo điển hình, bạn thường dựa vào loop()việc được gọi nhiều lần miễn là Arduino đang chạy. Di chuyển vào và ra khỏi loop()chức năng phải giới thiệu một chi phí nhỏ.

Để tránh điều đó, có lẽ bạn có thể tạo vòng lặp vô hạn của riêng mình, như thế này:

void loop()
{
    while (true)
    {
        // do stuff...
    }
}

Đó có phải là một cách khả thi để cải thiện hiệu suất? Nó sẽ gây ra vấn đề khác nếu loop()không bao giờ trở lại?

Câu trả lời:


18

Phần mã trên lõi ATmega có thiết lập () và loop () như sau:

#include <Arduino.h>

int main(void)
{
        init();

#if defined(USBCON)
        USBDevice.attach();
#endif

        setup();

        for (;;) {
                loop();
                if (serialEventRun) serialEventRun();
        }

        return 0;
}

Khá đơn giản, nhưng có chi phí hoạt động của serialEventRun (); trong đó.

Hãy so sánh hai bản phác thảo đơn giản:

void setup()
{

}

volatile uint8_t x;

void loop()
{

    x = 1;

}

void setup()
{

}

volatile uint8_t x;

void loop()
{
    while(true)
    {
        x = 1;
    }
}

X và dễ bay hơi chỉ để đảm bảo nó không được tối ưu hóa.

Trong ASM được sản xuất, bạn nhận được các kết quả khác nhau: So sánh hai

Bạn có thể thấy while (true) chỉ thực hiện rjmp (nhảy tương đối) một vài hướng dẫn, trong khi loop () thực hiện phép trừ, so sánh và gọi. Đây là 4 hướng dẫn vs 1 hướng dẫn.

Để tạo ASM như trên, bạn cần sử dụng một công cụ có tên avr-objdump. Điều này được bao gồm với avr-gcc. Vị trí khác nhau tùy thuộc vào hệ điều hành nên dễ dàng nhất để tìm kiếm theo tên.

avr-objdump có thể hoạt động trên các tệp .hex, nhưng những tệp này thiếu nguồn gốc và nhận xét. Nếu bạn vừa xây dựng mã, bạn sẽ có tệp .elf chứa dữ liệu này. Một lần nữa, vị trí của các tệp này thay đổi theo HĐH - cách dễ nhất để xác định vị trí của chúng là bật tính năng biên dịch dài dòng trong các tùy chọn và xem nơi các tệp đầu ra đang được lưu trữ.

Chạy lệnh như sau:

avr-objdump -S output.elf> asm.txt

Và kiểm tra đầu ra trong một trình soạn thảo văn bản.


OK, nhưng không có lý do để gọi hàm serialEventRun ()? Nó dùng để làm gì?
jfpoilpret

1
Đó là một phần của chức năng được sử dụng bởi Phần cứng, không biết tại sao nó không bị loại bỏ khi không cần Nối tiếp.
Cyberg Ribbon

2
Sẽ hữu ích để giải thích ngắn gọn về cách bạn tạo đầu ra ASM để mọi người có thể tự kiểm tra.
jippie

@Cyberg Ribbon nó không bao giờ được lấy ra vì nó là một phần của tiêu chuẩn main.cđược sử dụng bởi Arduino IDE. Tuy nhiên, điều đó không có nghĩa là thư viện Phần cứng được đưa vào bản phác thảo của bạn; thực sự nó không được bao gồm nếu bạn không sử dụng nó (đó là lý do có if (serialEventRun)trong main()chức năng Nếu bạn không sử dụng HardwareSerial thư viện sau đó. serialEventRunsẽ được null, vì thế không có cuộc gọi.
jfpoilpret

1
Vâng, nó là một phần của main.c như được trích dẫn, nhưng tôi hy vọng nó sẽ được tối ưu hóa nếu không cần thiết do đó tôi nghĩ rằng các khía cạnh của Nối tiếp luôn được bao gồm. Tôi thường xuyên viết mã sẽ không bao giờ quay trở lại từ loop () và không nhận thấy các vấn đề với serial.
Cyberg Ribbon

6

Câu trả lời của Cyberg Ribbon mô tả khá độc đáo việc tạo mã lắp ráp và sự khác biệt giữa hai kỹ thuật. Đây được dự định là một câu trả lời bổ sung xem xét vấn đề về sự khác biệt thực tế , nghĩa là cách tiếp cận sẽ tạo ra bao nhiêu sự khác biệt về thời gian thực hiện .


Biến thể mã

Tôi đã làm một phân tích liên quan đến các biến thể sau đây:

  • Cơ bản void loop()(được nội tuyến biên dịch)
  • Không nội tuyến void loop()(sử dụng __attribute__ ((noinline)))
  • Lặp lại với while(1)(được tối ưu hóa)
  • Vòng lặp không được tối ưu hóa while(1)(bằng cách thêm __asm__ __volatile__("");. Đây là nophướng dẫn ngăn tối ưu hóa vòng lặp mà không dẫn đến chi phí bổ sung của một volatilebiến)
  • Không liên void loop()kết với tối ưu hóawhile(1)
  • Không liên void loop()kết với không được tối ưu hóawhile(1)

Các bản phác thảo có thể được tìm thấy ở đây .

Thí nghiệm

Tôi đã chạy từng bản phác thảo này trong 30 giây, qua đó tích lũy 300 điểm dữ liệu mỗi bản . Có một delaycuộc gọi 100 mili giây trong mỗi vòng lặp (không có điều gì xấu xảy ra ).

Các kết quả

Sau đó, tôi đã tính thời gian thực hiện trung bình của mỗi vòng lặp, trừ 100 mili giây từ mỗi vòng và sau đó vẽ kết quả.

http://raw2.github.com/AsheeshR/Arduino-Loop-Analysis/master/Fftime/timeplot.png

Phần kết luận

  • Một while(1)vòng lặp không được tối ưu hóa bên trong void loopnhanh hơn trình biên dịch được tối ưu hóa void loop.
  • Sự khác biệt về thời gian giữa mã không được tối ưu hóa và mã tối ưu hóa mặc định của Arduino là không đáng kể trên thực tế . Bạn sẽ tốt hơn trong việc biên dịch thủ công bằng cách sử dụng avr-gccvà sử dụng các cờ tối ưu hóa của riêng bạn thay vì phụ thuộc vào Arduino IDE để giúp bạn với nó (nếu bạn cần tối ưu hóa micro giây).

LƯU Ý: Các giá trị thời gian thực tế không có ý nghĩa ở đây, sự khác biệt giữa chúng là. Các ~ 90 micro giây của thời gian thực hiện bao gồm một cuộc gọi đến Serial.println, microsdelay.

CHÚ THÍCH 2: Điều này đã được thực hiện bằng cách sử dụng Arduino IDE và các trình biên dịch mặc định mà nó cung cấp.

CHÚ THÍCH 3: Phân tích (cốt truyện và tính toán) được thực hiện bằng R.


1
Làm tốt lắm. Đồ thị có mili giây không phải micro giây nhưng không phải là vấn đề lớn.
Cyberg Ribbon

@Cyberg Ribbon Điều đó khá khó xảy ra vì tất cả các phép đo đều tính bằng micrô giây và tôi không thay đổi tỷ lệ 'ở bất cứ đâu :)
asheeshr
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.