【问题标题】:application memory optimization应用内存优化
【发布时间】:2015-10-02 02:27:47
【问题描述】:

我们有一个用 ANSI C 编写的项目。通常内存消耗不是一个大问题,但现在我们需要将我们的程序放入 256 KB 的 RAM 中。我手头没有这个确切的平台,所以我在 32 位 x86 Linux 下编译我的项目(因为它提供了足够不同的工具来评估内存消耗),优化我可以做的,删除一些功能,最终我必须拥有结论:我们需要牺牲哪些功能才能在非常小的系统上运行(如果我们能够的话)。首先,我研究了 linux 中的内存大小到底是多少,看来我必须优化 RSS 大小,而不是 VSZ。但在 linux 中,即使是打印“Hello world!”的最小程序也是如此。每秒一次在 RSS 中消耗 285-320 KB:

#include  <stdio.h>
#include  <unistd.h>
#include  <signal.h>

unsigned char  cuStopCycle = 0;

void SigIntHandler(int signo)
{
   printf("SIGINT received, terminating the program\n");
   cuStopCycle = 1;
}

int main()
{  
   signal( SIGINT, SigIntHandler);

   while(!cuStopCycle)
   {
      printf("Hello, World!\n");
      sleep(1);
   }
   printf("Exiting...\n");
}

user@Ubuntu12-vm:~/tmp/prog_size$ size ./prog_size     
text       data     bss     dec     hex filename    
1456        272      12    1740     6cc ./prog_size

root@Ubuntu12-vm:/home/app# ps -C prog_size -o pid,rss,vsz,args   
PID     RSS    VSZ   COMMAND 
22348   316   2120   ./prog_size

显然,这个程序可以在 64KB RAM 的小型 PLC 上完美运行。它只是 linux 加载了很多库。我为这个程序生成了一个映射文件,所有这些数据 + bss 都来自 CRT 库。我需要提到的是,如果我在这个项目中添加一些代码 - 10,000 次“a = a + b”或操作数组 2000 个 long int 变量,我会看到代码大小、bss 大小的差异,但最终过程的 RSS 大小是一样,不影响)

所以我把它作为基线,我想达到的点(我永远不会达到,因为我需要更多的功能,而不仅仅是每秒打印一次消息)。

所以我的项目来了,我删除了所有额外的功能,删除了所有辅助功能,删除了除基本功能之外的所有内容。有一些方法可以优化更多,但不是那么多,可以删除的已经被带走了:

root@Ubuntu12-vm:/home/app/workspace/proj_sizeopt/Cmds# ls -l App 
-rwxr-xr-x 1 root root 42520 Jul 13 18:33 App

root@Ubuntu12-vm:/home/app/workspace/proj_sizeopt/Cmds# size ./App 
   text    data     bss     dec     hex filename
  37027     404     736   38167    9517 ./App

所以我有 ~36KB 的代码和 ~1KB 的数据。我不在我的项目中调用 malloc,我使用带有包装库的共享内存分配,这样我就可以控制分配了多少内存:

The total memory size allocated is 2052 bytes

在后台显然有 malloc 调用,如果我用总结所有分配请求的函数替换“malloc”调用,我会看到分配了 ~2.3KB 的内存:

 root@Ubuntu12-vm:/home/app/workspace/proj_sizeopt/Cmds# LD_PRELOAD=./override_malloc.so ./App
Malloc allocates 2464 bytes total

现在我运行我的项目并看到它消耗了 600KB 的 RAM

root@Ubuntu12-vm:/home/app/workspace/proj_sizeopt# ps -C App -o pid,rss,vsz,args
  PID   RSS    VSZ COMMAND
22093   604   2340 ./App

我不明白为什么它会吃掉这么多内存。代码量很小。分配的内存不多。数据量很小。为什么会占用这么多内存?我试着分析了进程的映射:

root@Ubuntu12-vm:/home/app/workspace/proj_sizeopt# pmap -x 22093
22093:   ./App
Address   Kbytes     RSS   Dirty Mode   Mapping
08048000       0      28       0 r-x--  App
08052000       0       4       4 r----  App
08053000       0       4       4 rw---  App
09e6a000       0       4       4 rw---    [ anon ]
b7553000       0       4       4 rw---    [ anon ]
b7554000       0      48       0 r-x--  libpthread-2.15.so
b756b000       0       4       4 r----  libpthread-2.15.so
b756c000       0       4       4 rw---  libpthread-2.15.so
b756d000       0       8       8 rw---    [ anon ]
b7570000       0     300       0 r-x--  libc-2.15.so
b7714000       0       8       8 r----  libc-2.15.so
b7716000       0       4       4 rw---  libc-2.15.so
b7717000       0      12      12 rw---    [ anon ]
b771a000       0      16       0 r-x--  librt-2.15.so
b7721000       0       4       4 r----  librt-2.15.so
b7722000       0       4       4 rw---  librt-2.15.so
b7731000       0       4       4 rw-s-    [ shmid=0x70000c ]
b7732000       0       4       4 rw-s-    [ shmid=0x6f800b ]
b7733000       0       4       4 rw-s-    [ shmid=0x6f000a ]
b7734000       0       4       4 rw-s-    [ shmid=0x6e8009 ]
b7735000       0      12      12 rw---    [ anon ]
b7738000       0       4       0 r-x--    [ anon ]
b7739000       0     104       0 r-x--  ld-2.15.so
b7759000       0       4       4 r----  ld-2.15.so
b775a000       0       4       4 rw---  ld-2.15.so
bfb41000       0      12      12 rw---    [ stack ]
-------- ------- ------- ------- -------
total kB    2336       -       -       -

看起来程序大小(在 RSS 中)只有 28KB,其余部分由共享库消耗。顺便说一句,我不使用 posix 线程,我没有显式链接到它,但是链接器无论如何都会链接这个库,我不知道为什么(这并不重要)。如果我们更详细地查看映射:

root@Ubuntu12-vm:/home/app/workspace/proj_sizeopt# cat /proc/22093/smaps 
08048000-08052000 r-xp 00000000 08:01 344838     /home/app/workspace/proj_sizeopt/Cmds/App
Size:                 40 kB
Rss:                  28 kB
Pss:                  28 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:        28 kB
Private_Dirty:         0 kB
Referenced:           28 kB
Anonymous:             0 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB

...

09e6a000-09e8b000 rw-p 00000000 00:00 0          [heap]
Size:                132 kB
Rss:                   4 kB
Pss:                   4 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         4 kB
Referenced:            4 kB
Anonymous:             4 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB

...

b7570000-b7714000 r-xp 00000000 08:01 34450      /lib/i386-linux-gnu/libc-2.15.so
Size:               1680 kB
Rss:                 300 kB
Pss:                   7 kB
Shared_Clean:        300 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:          300 kB
Anonymous:             0 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB

...

b7739000-b7759000 r-xp 00000000 08:01 33401      /lib/i386-linux-gnu/ld-2.15.so
Size:                128 kB
Rss:                 104 kB
Pss:                   3 kB
Shared_Clean:        104 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:          104 kB
Anonymous:             0 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB

...

bfb41000-bfb62000 rw-p 00000000 00:00 0          [stack]
Size:                136 kB
Rss:                  12 kB
Pss:                  12 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:        12 kB
Referenced:           12 kB
Anonymous:            12 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
  1. 所以我看到我的项目的 RSS 大小是 40KB,但只使用了 28 KB。这是否意味着该项目将适合 256 KB 的 RAM
  2. 大小为132KB,但只使用了4 KB。这是为什么?我相信在小型嵌入式平台上会有所不同。
  3. 堆栈136KB,但仅使用 12KB
  4. GLIBC/LD 显然会消耗一些内存,但在嵌入式平台上它究竟是什么内存?

我不看 PSS,因为它对我来说没有任何意义,我只看 RSS。

我可以从这张图片中得出什么结论?如何准确评估应用程序的内存消耗?看进程的RSS大小?或者从所有映射系统库的这个大小的 RSS 中减去?堆/堆栈大小是多少?

对于 RAM 量极少的平台,我将非常感谢任何建议、注释、内存消耗优化技术、DO 和 DON'T(除了明显的 - 将数据和代码量保持在最低限度)。 我也将感谢您解释为什么具有少量代码和数据(并且不分配太多内存)的程序仍然在 RSS 中消耗大量 RAM。

提前谢谢你

【问题讨论】:

  • 尽管付出了努力,但我发现很难理解这个问题..
  • 没有代码可以查看。代码的第一个 sn-p 仅用于演示目的。主要问题是关于内存消耗。如何了解应用程序的实际大小以及如何减少应用程序的占用空间
  • 如果自己很容易弄清楚我就不会问了;)
  • 我想展示我为解决问题所做的努力。我不只是来问“我的应用程序的大小是多少”或“如何优化它”。我正在提供细节,我花时间学习这些细节,以便它可以帮助其他开发人员(希望如此)。但我仍然有疑问,这就是我在这里问的原因

标签: c memory memory-management rss


【解决方案1】:

... 将我们的程序装入 256 KB 的 RAM。我手头没有这个确切的平台,所以我在 32 位 x86 Linux 下编译我的项目..

您现在看到的是,Linux 平台工具对您可能需要堆栈和堆做出了合理的假设,因为它现在运行在一台大型机器上,并且链接到一组合理的库函数以满足您的需要.有些您不需要,但它“免费”提供给您。

要在您的目标平台上适应 256 Kb,您必须针对您的目标平台进行编译并使用目标平台的链接器链接到目标平台的库(和 CRT)。

那些会做出不同的假设,使用可能更小的库占用空间,对堆栈和堆空间做出更小的假设等等。例如,为目标平台创建“Hello World”并检查其在该目标平台上的需求。或者使用目标平台和库的真实模拟器(不要忘记,操作系统,它部分地规定了库必须做什么)。

如果它仍然太大,您必须重新编写或调整整个 CRT 和所有库....

【讨论】:

  • 感谢您的回答。很明显,我只能对目标平台做出假设,而无需实际使用它。但我想对足迹做出假设。那么我可以安全地假设我的程序将消耗 28 KB + 4KB (heap) + 12 KB (stack) 吗?在此处添加 libc 和其他系统库,但我需要单独评估我的程序。这是对内存消耗的真实估计吗?
【解决方案2】:

程序需要根据嵌入式设备进行编译/链接。

为了获得最佳效果,请使用生成文件

使用为嵌入式设备编写的'rt'库

使用 start.s 文件,该文件通过 makefile 定位,执行开始的地方。

在链接器参数中使用“静态”

使用链接器参数不包括任何库,但具体要求。

不要使用为您的开发机器编写的库。仅使用为嵌入式设备编写的库。

除非专门为嵌入式设备编写,否则不要包含 stdio.h 等

不要在信号处理程序中调用 printf()。

如果可能,不要调用 printf()。

改为编写一个小字符输出函数,让它通过 uart 执行输出。

不要使用信号,而是使用中断

生成的应用程序不会在您的 PC 上运行。但是,一旦加载,将在 256k 设备上运行

不要调用 sleep(),而是编写自己的函数,使用设备定时器外设,设置定时器并将设备置于断电模式。

时间中断需要使设备退出断电模式。

在makefile中,具体设置栈、堆等的大小

让链接步骤输出一个 .map 文件。研究该地图文件,直到您了解其中的所有内容。

使用特定于嵌入式设备的编译器/链接器

您可能需要包含一个函数来初始化设备上的外围设备,例如时钟、uart、定时器、看门狗以及代码实际使用的任何其他内置外围设备。

您将需要一个分配中断表的文件,以及一个处理每个中断的小函数,尽管这些函数中的大多数除了清除相应的中断挂起标志并从中断返回之外什么都不做

您可能需要一个函数来定期刷新看门狗,有条件地取决于主函数仍在定期循环的指示。即主函数循环和初始化函数会刷新看门狗

【讨论】:

  • 感谢您与我分享减少足迹的最佳实践,我非常感谢。如果我正确理解您要对我说的内容 - 在大型开发系统上运行代码时,没有公平的方法来估计小型平台的内存消耗?如果我查看我的代码需要 28 + 4 + 12 = 44 KB 的代码,我不能假设它会适合 256 KB 的 RAM?因为系统库?但是,如果只谈论程序本身 - 这是一个很好的估计吗?系统库完全是另一回事 - 我同意我必须将 glibc 的使用限制在最低限度
猜你喜欢
  • 2015-07-05
  • 1970-01-01
  • 2010-09-08
  • 2011-08-18
  • 1970-01-01
  • 2019-06-22
  • 2010-11-15
  • 2017-01-18
  • 2013-10-11
相关资源
最近更新 更多