Tôi đang sử dụng quá nhiều RAM. Làm thế nào điều này có thể được đo lường?


19

Tôi muốn biết tôi đang sử dụng bao nhiêu RAM trong dự án của mình, theo như tôi có thể nói, không có cách nào để thực sự giải quyết được điều đó (ngoài việc tự mình trải qua và tính toán nó). Tôi đã đến một giai đoạn trong một dự án khá lớn, nơi tôi đã xác định rằng tôi sắp hết RAM.

Tôi đã xác định điều này bởi vì tôi có thể thêm một phần và sau đó tất cả các địa ngục vỡ ra ở một nơi khác trong mã của tôi mà không có lý do rõ ràng. Nếu tôi #ifndefmột cái gì đó khác, nó hoạt động trở lại. Không có gì sai về mặt lập trình với mã mới.

Tôi đã nghi ngờ một lúc rằng tôi sắp hết RAM. Tôi không nghĩ rằng tôi đang sử dụng quá nhiều stack (mặc dù điều đó là có thể), cách tốt nhất để xác định lượng RAM tôi thực sự đang sử dụng là bao nhiêu?

Đi qua và cố gắng để giải quyết nó, tôi gặp vấn đề khi tôi nhận được enum và structs; Chúng có giá bao nhiêu bộ nhớ?

Chỉnh sửa đầu tiên : CSONG, tôi đã chỉnh sửa bản phác thảo của mình rất nhiều kể từ khi bắt đầu, đây không phải là kết quả thực tế ban đầu tôi nhận được, nhưng chúng là những gì tôi đang nhận được bây giờ.

  text     data     bss     dec     hex filename
 17554      844     449   18847    499f HA15_20140317w.cpp.elf
 16316      694     409   17419    440b HA15_20140317w.cpp.elf
 17346      790     426   18562    4882 HA15_20140317w.cpp.elf

Dòng đầu tiên (với văn bản 17554) không hoạt động, sau khi chỉnh sửa nhiều, dòng thứ hai (với văn bản 16316) đang hoạt động như bình thường.

chỉnh sửa: dòng thứ ba có mọi thứ hoạt động, đọc nối tiếp, các chức năng mới của tôi, v.v. Về cơ bản tôi đã loại bỏ một số biến toàn cục và mã trùng lặp. Tôi đề cập đến điều này bởi vì (như nghi ngờ) nó không phải là về mã này cho mỗi sae, nó phải là về việc sử dụng RAM. Điều này đưa tôi trở lại câu hỏi ban đầu, "làm thế nào để đo lường tốt nhất" Tôi vẫn đang kiểm tra một số câu trả lời, cảm ơn.

Làm thế nào để tôi thực sự giải thích các thông tin trên?

Cho đến nay sự hiểu biết của tôi là:

`TEXT` is program instruction memory
`DATA` is variables (unitialised?) in program memory
`BSS`  is variables occupying RAM

vì BSS nhỏ hơn đáng kể so với 1024 byte, tại sao cái thứ hai hoạt động, nhưng cái thứ nhất thì không? Nếu đó là DATA+BSScả hai chiếm hơn 1024.

chỉnh sửa lại: Tôi đã chỉnh sửa câu hỏi để bao gồm mã, nhưng bây giờ tôi đã xóa nó vì nó thực sự không liên quan gì đến vấn đề (ngoài việc có thể thực hành mã hóa kém, khai báo biến và tương tự). Bạn có thể xem lại mã bằng cách xem lại các chỉnh sửa nếu bạn thực sự muốn xem nó. Tôi muốn quay trở lại câu hỏi trong tay, chủ yếu dựa trên: Cách đo mức sử dụng RAM.


Tôi nghĩ rằng tôi sẽ thêm, tôi đã thêm các phần mã mới khác nhau trong vài tuần qua, sau đó tối ưu hóa nó cho đến khi nó hoạt động, nhưng bây giờ tôi chỉ thêm một nửa byte byte doz và tôi đã hoàn thành ... :(
Madivad

Bạn có sử dụng Stringloại trong chương trình của bạn? Điều này được biết là thực hiện phân bổ và phát hành bộ nhớ động thường xuyên, có thể phân mảnh heap đến điểm mà bạn có thể không còn nhớ.
jfpoilpret

@jfpoilpret Tôi tránh xa Strings vì chi phí quá cao. Tôi rất vui khi làm việc với các mảng char, điều đó nói rằng, tôi hầu như luôn xác định tất cả các mảng char của mình với một kích thước cố định (hiện tại, tôi có MỘT mảng byte không hoàn toàn vì tôi thay đổi độ dài nội dung cho các biên dịch lại khác nhau.
Madivad

Đăng mã của bạn ở đây (hoặc lên pastebin nếu nó quá lớn) có thể hekp tìm hiểu những vấn đề bạn gặp phải với bộ nhớ.
jfpoilpret

@jfpoilpret Tôi thực sự không thể đăng mã, nó rất lớn và không may rất bực bội, trải rộng trên 16 tệp. Đó là một dự án mà tôi đã cho phép phát triển vượt xa những gì được yêu cầu (đó là một số dự án được hợp nhất với nhau). Tôi đang bắt đầu để phá vỡ nó mà tôi chắc chắn sẽ giúp khắc phục vấn đề. Mặc dù có một số phần tôi cần mọi người xem (hoặc hướng dẫn cho tôi), tôi sẽ đăng chúng sau.
Madivad 17/03/2016

Câu trả lời:


15

Bạn có thể sử dụng các chức năng được cung cấp AVRGCC: Giám sát việc sử dụng ngăn xếp

Chức năng này nhằm kiểm tra việc sử dụng ngăn xếp nhưng những gì nó báo cáo là RAM thực tế chưa bao giờ được sử dụng (trong khi thực hiện). Nó làm như vậy bằng cách "vẽ" (điền) RAM với giá trị đã biết (0xC5), sau đó kiểm tra vùng RAM đếm xem có bao nhiêu byte vẫn có cùng giá trị ban đầu.
Báo cáo sẽ hiển thị RAM chưa được sử dụng (RAM miễn phí tối thiểu) và do đó bạn có thể tính toán RAM tối đa đã được sử dụng (Tổng RAM - RAM được báo cáo).

Có hai chức năng:

  • StackPaint được thực thi tự động trong quá trình khởi tạo và "vẽ" RAM với giá trị 0xC5 (có thể thay đổi nếu cần).

  • StackCount có thể được gọi tại bất kỳ điểm nào để đếm RAM chưa được sử dụng.

Dưới đây là một ví dụ về việc sử dụng. Không làm gì nhiều nhưng dự định chỉ ra cách sử dụng các chức năng.

// -----------------------------------------------------------------------------
extern uint8_t _end;
extern uint8_t __stack;

void StackPaint(void) __attribute__ ((naked)) __attribute__ ((section (".init1")));

void StackPaint(void)
{
#if 0
    uint8_t *p = &_end;

    while(p <= &__stack)
    {
        *p = 0xc5;
        p++;
    }
#else
    __asm volatile ("    ldi r30,lo8(_end)\n"
                    "    ldi r31,hi8(_end)\n"
                    "    ldi r24,lo8(0xc5)\n" /* STACK_CANARY = 0xc5 */
                    "    ldi r25,hi8(__stack)\n"
                    "    rjmp .cmp\n"
                    ".loop:\n"
                    "    st Z+,r24\n"
                    ".cmp:\n"
                    "    cpi r30,lo8(__stack)\n"
                    "    cpc r31,r25\n"
                    "    brlo .loop\n"
                    "    breq .loop"::);
#endif
} 


uint16_t StackCount(void)
{
    const uint8_t *p = &_end;
    uint16_t       c = 0;

    while(*p == 0xc5 && p <= &__stack)
    {
        p++;
        c++;
    }

    return c;
} 

// -----------------------------------------------------------------------------

void setup() {

Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
Serial.println(StackCount(), DEC);  // calls StackCount() to report the unused RAM
delay(1000);
}

đoạn mã thú vị, cảm ơn. Tôi đã sử dụng nó và nó gợi ý rằng có sẵn hơn 600 byte, nhưng khi tôi chôn nó trong phần phụ sâu hơn thì nó sẽ giảm, nhưng không xóa sạch được. Vì vậy, vấn đề của tôi là ở nơi khác.
Madivad 17/03/2016

@Madivad Lưu ý rằng hơn 600 byte này đại diện cho lượng RAM tối thiểu khả dụng cho đến khi bạn gọi StackCount. Nó không thực sự tạo ra sự khác biệt về mức độ bạn thực hiện cuộc gọi, nếu phần lớn mã và các cuộc gọi lồng nhau đã được thực thi trước khi gọi StackCount thì kết quả sẽ chính xác. Vì vậy, ví dụ bạn có thể để bảng của mình hoạt động trong một thời gian (miễn là phải có đủ phạm vi bảo hiểm mã hoặc lý tưởng cho đến khi bạn nhận được thông tin sai mà bạn mô tả) và sau đó nhấn nút để nhận RAM được báo cáo. Nếu có đủ thì đó không phải là nguyên nhân của vấn đề.
alexan_e

1
Cảm ơn @alexan_e, tôi đã tạo một khu vực trên màn hình của mình báo cáo điều này ngay bây giờ, vì vậy khi tôi tiến bộ trong vài ngày tới, tôi sẽ quan tâm đến con số này, đặc biệt là khi thất bại! Cảm ơn một lần nữa
Madivad 17/03/2016

@Madivad Xin lưu ý rằng hàm đã cho sẽ không báo cáo kết quả chính xác nếu malloc () được sử dụng trong mã
alexan_e

cảm ơn vì điều đó, tôi biết, nó đã được đề cập Theo như tôi biết, tôi không sử dụng nó (Tôi biết rằng có thể có một thư viện sử dụng nó, tôi chưa kiểm tra đầy đủ).
Madivad

10

Các vấn đề chính bạn có thể có với việc sử dụng bộ nhớ trong thời gian chạy là:

  • không có bộ nhớ khả dụng trong heap để phân bổ động (malloc hoặc new)
  • không còn chỗ trên ngăn xếp khi gọi hàm

Cả hai đều thực sự giống như SRAM AVR (2K trên Arduino) được sử dụng cho cả hai (ngoài dữ liệu tĩnh mà kích thước không bao giờ thay đổi trong khi thực hiện chương trình).

Nói chung, cấp phát bộ nhớ động hiếm khi được sử dụng trên MCU, chỉ một số thư viện thường sử dụng nó (một trong số đó là Stringlớp mà bạn đã đề cập bạn không sử dụng và đó là một điểm tốt).

Ngăn xếp và đống có thể được nhìn thấy trong hình dưới đây (lịch sự của Adafbean ): nhập mô tả hình ảnh ở đây

Do đó, vấn đề được mong đợi nhất đến từ tràn ngăn xếp (tức là khi ngăn xếp phát triển theo hướng heap và tràn vào nó, và sau đó - heap không được sử dụng ở tất cả các vùng tràn dữ liệu tĩnh của SRAM. bạn có nguy cơ cao:

  • hỏng dữ liệu (tức là ngăn xếp ovewrites heap hoặc dữ liệu tĩnh), mang lại cho bạn hành vi không thể hiểu được
  • ngăn xếp tham nhũng (tức là heap hoặc dữ liệu tĩnh ghi đè lên nội dung ngăn xếp), thường dẫn đến sự cố

Để biết lượng bộ nhớ còn lại giữa đỉnh heap và đỉnh stack (thực ra, chúng ta có thể gọi nó là đáy nếu chúng ta đại diện cho cả heap và stack trên cùng một hình ảnh như được mô tả bên dưới), bạn có thể sử dụng chức năng sau:

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

Trong đoạn mã trên, __brkvaltrỏ đến đỉnh heap nhưng là 0khi heap chưa được sử dụng, trong trường hợp này chúng ta sử dụng &__heap_startđiểm nào để__heap_start , biến đầu tiên đánh dấu đáy của heap; &vtất nhiên các điểm trên đỉnh của ngăn xếp (đây là biến cuối cùng được đẩy trên ngăn xếp), do đó công thức trên trả về số lượng bộ nhớ có sẵn cho ngăn xếp (hoặc heap nếu bạn sử dụng nó) để phát triển.

Bạn có thể sử dụng chức năng này ở nhiều vị trí khác nhau trong mã của mình để thử và tìm xem kích thước này đang giảm đáng kể ở đâu.

Tất nhiên, nếu bạn thấy hàm này trả về số âm thì đã quá muộn: bạn đã tràn vào ngăn xếp!


1
Đối với người kiểm duyệt: xin lỗi vì đã đăng bài đăng này lên wiki cộng đồng, tôi chắc chắn đã làm gì đó sai trong khi gõ, ở giữa bài. Xin vui lòng đặt nó trở lại đây vì hành động này là vô ý. Cảm ơn.
jfpoilpret 17/03/2016

cảm ơn vì câu trả lời này, tôi thực sự chỉ JUST tìm thấy đoạn mã đó cách đây chỉ một giờ (ở dưới cùng của sân chơi.arduino.cc / Code / Av AvailableMemory # .UycOrueSxfg ). Tôi chưa bao gồm nó (nhưng tôi sẽ) vì tôi có một khu vực khá lớn để gỡ lỗi trên màn hình của mình. Tôi nghĩ rằng tôi đã nhầm lẫn về công cụ gán động. Là mallocnewcách duy nhất tôi có thể làm điều đó? Nếu vậy, thì tôi không có gì năng động. Ngoài ra, tôi mới biết UNO có 2K SRAM. Tôi nghĩ đó là 1K. Cân nhắc những điều này, tôi sẽ không hết RAM! Tôi cần phải tìm nơi khác.
Madivad

Ngoài ra, cũng có calloc. Nhưng bạn có thể đang sử dụng lib của bên thứ 3 sử dụng phân bổ động mà bạn không biết (bạn phải kiểm tra mã nguồn của tất cả các phụ thuộc của mình để chắc chắn về điều đó)
jfpoilpret 17/03/2016

2
Hấp dẫn. "Vấn đề" duy nhất là nó báo cáo RAM miễn phí tại điểm được gọi, vì vậy trừ khi nó được đặt ở phần bên phải, bạn có thể không nhận thấy ngăn xếp bị tràn. Chức năng tôi đã cung cấp dường như có lợi thế ở khu vực đó vì nó báo cáo RAM miễn phí tối thiểu cho đến thời điểm đó, một khi địa chỉ RAM đã được sử dụng, nó không được báo cáo miễn phí nữa (ở phía bên dưới có thể có một số RAM bị chiếm dụng byte phù hợp với giá trị "paint" và được báo cáo là miễn phí). Ngoài ra, có thể một cách phù hợp hơn so với cách khác tùy thuộc vào những gì người dùng muốn.
alexan_e

Điểm tốt! Tôi đã không nhận thấy điểm cụ thể này trong câu trả lời của bạn (và đối với tôi trông giống như một lỗi trong thực tế), bây giờ tôi thấy điểm "vẽ" vùng phía trước miễn phí. Có lẽ bạn có thể làm cho điểm này rõ ràng hơn trong câu trả lời của bạn?
jfpoilpret

7

Khi bạn tìm ra cách xác định vị trí tệp .elf được tạo trong thư mục tạm thời của mình, bạn có thể thực thi lệnh bên dưới để kết xuất việc sử dụng SRAM, nơi project.elfsẽ được thay thế bằng .elftệp được tạo . Ưu điểm của đầu ra này là khả năng kiểm tra SRAM của bạn được sử dụng như thế nào . Có phải tất cả các biến cần phải là toàn cầu, chúng có thực sự cần thiết không?

avr-objdump -S -j .bss project.elf

project.elf:     file format elf32-avr


Disassembly of section .bss:

00800060 <__bss_start>:
        ...

00800070 <measurementReady>:
        ...

00800071 <cycles>:
        ...

00800073 <measurement>:
  800073:       00 00 00 00                                         ....

00800077 <measurementStart>:
  800077:       00 00 00 00                                         ....

0080007b <timerOverflows>:
  80007b:       00 00 00 00

Lưu ý rằng điều này không hiển thị ngăn xếp hoặc sử dụng bộ nhớ động như Ignacio Vazquez-Abrams đã lưu ý trong các bình luận bên dưới.

Ngoài ra, avr-objdump -S -j .data project.elfcó thể kiểm tra một cái , nhưng không có chương trình nào của tôi xuất ra bất cứ thứ gì với điều đó vì vậy tôi không thể biết chắc là nó có hữu ích không. Nó được liệt kê để liệt kê 'dữ liệu khởi tạo (khác không).


Hoặc bạn chỉ có thể sử dụng avr-size. Nhưng điều đó sẽ không cho bạn thấy phân bổ động hoặc sử dụng ngăn xếp.
Ignacio Vazquez-Abrams

@ IgnacioVazquez-Abrams về động lực học, tương tự cho giải pháp của tôi. Chỉnh sửa câu trả lời của tôi.
jippie 16/03 '

Ok, đây là câu trả lời thú vị nhất cho đến nay. Tôi đã thử nghiệm avr-objdumpavr-sizevà tôi sẽ chỉnh sửa bài viết của tôi trên một thời gian ngắn. Cảm ơn vì điều đó.
Madivad 17/03/2016

3

Tôi đã nghi ngờ một lúc rằng tôi sắp hết RAM. Tôi không nghĩ rằng tôi đang sử dụng quá nhiều stack (mặc dù điều đó là có thể), cách tốt nhất để xác định lượng RAM tôi thực sự đang sử dụng là bao nhiêu?

Tốt nhất nên sử dụng kết hợp ước lượng thủ công và bằng cách sử dụng sizeoftoán tử. Nếu tất cả các khai báo của bạn là tĩnh, thì điều này sẽ cung cấp cho bạn một hình ảnh chính xác.

Nếu bạn đang sử dụng phân bổ động, thì bạn có thể gặp sự cố khi bạn bắt đầu giải phóng bộ nhớ. Điều này là do sự phân mảnh bộ nhớ trên heap.

Đi qua và cố gắng để giải quyết nó, tôi gặp vấn đề khi tôi nhận được enum và structs; Chúng có giá bao nhiêu bộ nhớ?

Một enum chiếm nhiều không gian như một int. Vì vậy, nếu bạn có một bộ gồm 10 phần tử trong một enumkhai báo, thì đó sẽ là 10*sizeof(int). Ngoài ra, mỗi biến sử dụng phép liệt kê chỉ đơn giản là một int.

Đối với các cấu trúc, nó sẽ dễ sử dụng nhất sizeofđể tìm hiểu. Các cấu trúc chiếm một không gian (tối thiểu) bằng tổng các thành viên của nó. Nếu trình biên dịch thực hiện căn chỉnh cấu trúc, thì nó có thể nhiều hơn, tuy nhiên điều này khó xảy ra trong trường hợp avr-gcc.


Tôi làm tĩnh chỉ định mọi thứ xa nhất có thể. Tôi chưa bao giờ nghĩ đến việc sử dụng sizeofcho mục đích này. Hiện tại, tôi có gần 400 byte đã được tính (trên toàn cầu). Bây giờ tôi sẽ đi qua và tính toán các enum (thủ công) và các cấu trúc (trong đó tôi có một vài con và tôi sẽ sử dụng sizeof), và báo cáo lại.
Madivad

Không chắc chắn bạn thực sự cần sizeofphải biết kích thước của dữ liệu tĩnh của mình vì dữ liệu này được in bởi avrdude IIRC.
jfpoilpret

@jfpoilpret Đó là phụ thuộc phiên bản, tôi nghĩ vậy. Không phải tất cả các phiên bản và nền tảng cung cấp điều đó. Của tôi (Linux, nhiều phiên bản) không hiển thị sử dụng bộ nhớ cho một, trong khi các phiên bản Mac thì có.
asheeshr

Tôi đã tìm kiếm đầu ra dài dòng, tôi nghĩ nó nên ở đó, không phải thế
Madivad

@AsheeshR Tôi không biết điều đó, tôi hoạt động tốt trên Windows.
jfpoilpret

1

Có một chương trình được gọi là Arduino Builder cung cấp một hình ảnh gọn gàng về lượng flash, SRAM và EEPROM mà chương trình của bạn đang sử dụng.

Arduino Builder

Trình xây dựng Arduino tạo thành một phần của giải pháp CodeBlocks Arduino IDE . Nó có thể được sử dụng như một chương trình độc lập hoặc thông qua CodeBlocks Arduino IDE.

Thật không may, Arduino Builder hơi nhưng nó sẽ hoạt động với hầu hết các chương trình và hầu hết các Arduinos, chẳng hạn như Uno.

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.