Michael Coleman
Michael Coleman

Reputation: 31

Unexpected memory usage behaviour when using static linking in C

I am studying static linking and dynamic linking, and my understanding is that static linking consumes more memory and disk space compared to dynamic linking. It is evident that static linking occupies more disk space because the static libraries are compiled into each executable file. But how can I verify that static linking consumes more memory space? For this purpose, I have designed a small experiment as described below.

Step 1: Create a C file named "mylib.c"

//mylib.c
#include <unistd.h>

// This function is designed to create a massive .text section.
void many_code() {
    asm volatile (
       "movabs $0x1122334455667788, %%rax \n\t"
       "movabs $0x1122334455667788, %%rax \n\t"
       "movabs $0x1122334455667788, %%rax \n\t"  <----- This line is repeated one million times.
       ...
       ::: "rax"
    );
    sleep(-1);
}

Step 2: Execute "gcc -c mylib.c -o mylib.o" to compile the source file into an object file.

Step 3: Create a static library by using the command "ar -r libmy.a mylib.o".

Step 4: Create a second C file named "use_st_lib.c".

// use_st_lib.c
extern void many_code();

int main() {
   many_code();
   return 0;
}

Step 5: Create an executable file "use_st_lib_0.out" by using static linking with the command "gcc use_st_lib.c -static libmy.a -o use_st_lib_0".

Step 6: Create a second executable file by using static linking with the command "gcc use_st_lib.c -static libmy.a -o use_st_lib_1".

➜  static git:(master) ✗ ls -hl
total 91M
-rw-r--r-- 1 root root 9.6M May 29 09:37 libmy.a
-rw-r--r-- 1 root root  51M May 27 23:33 mylib.c
-rw-r--r-- 1 root root 9.6M May 29 09:37 mylib.o
-rwxr-xr-x 1 root root  11M May 29 10:11 use_st_lib_0
-rwxr-xr-x 1 root root  11M May 29 10:12 use_st_lib_1
-rw-r--r-- 1 root root   76 May 27 22:10 use_st_lib.c

Step 7: In another terminal session, use the "top" command and set the refresh time to 1.0s. Apply a filter condition with COMMAND=use_st_lib.

top - 10:21:59 up 15 days, 17:40,  4 users,  load average: 0.00, 0.01, 0.05
Tasks:  79 total,   1 running,  78 sleeping,   0 stopped,   0 zombie
%Cpu(s):  1.0 us,  1.0 sy,  0.0 ni, 98.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   1756.5 total,    793.8 free,    133.2 used,    829.5 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   1469.8 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND 

Step 8: Run the process "use_st_lib_0" in the background by using the command "./use_st_lib_0 &".

➜  static git:(master) ✗ ./use_st_lib_0 &
[1] 31239
top - 10:31:06 up 15 days, 17:49,  4 users,  load average: 0.07, 0.06, 0.06
Tasks:  80 total,   1 running,  79 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   1756.5 total,    793.6 free,    133.1 used,    829.8 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   1469.9 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND          
31239 root      25   5   10.6m   9.5m   9.5m S  0.0  0.5   0:00.00 use_st_lib_0

Step 9: Run the process "use_st_lib_1" in the background by using the command "./use_st_lib_1 &".

➜  static git:(master) ✗ ./use_st_lib_1 &
[2] 31309
top - 10:32:02 up 15 days, 17:50,  4 users,  load average: 0.03, 0.05, 0.05
Tasks:  81 total,   1 running,  80 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  1.0 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   1756.5 total,    793.3 free,    133.4 used,    829.9 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   1469.6 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND          
31309 root      25   5   10.6m   9.5m   9.5m S  0.0  0.5   0:00.00 use_st_lib_1     
31239 root      25   5   10.6m   9.5m   9.5m S  0.0  0.5   0:00.00 use_st_lib_0

Here are my questions:

1、Why does my program show SHR (Shared Memory) usage in the output of the top command? (I expected this column to be 0.)

2、After starting use_st_lib_0, the RES (Resident Memory) shows a usage of 9.5m, but the memory usage displayed at the top of the top command does not increase. (I expected it to change from 133.2 used to 123.7 used.)

3、Similarly, after starting use_st_lib_1, the output of the top command shows little or no change. (I expected it to change from 123.7 used to 114.2 used.)

4、In the above steps, where did I make mistakes, or what kind of deviation might have occurred in my understanding?

Above are the attempts I made and the results I expected.

Upvotes: 0

Views: 222

Answers (1)

Employed Russian
Employed Russian

Reputation: 213877

my understanding is that static linking consumes more memory and disk space compared to dynamic linking.

That understanding is grossly incomplete.

If you have a single ./a.out binary, that binary would generally consume less memory and less disk space when fully statically linked.

This is because:

  • only code and data that is actually referenced is linked in (in contrast, shared library must link everything in, whether used or not).
  • no space is needed for the "dynamic linking support tables" (PLT and GOT), nor for dynamic relocations. These can consume a lot of space.

OTOH, if you have multiple binaries which all use the same shared library, and you run all these binaries at the same time, then the total memory and disk space used by them would generally be smaller with dynamic linking, than if each binary was fully statically linked.

That is because you don't need to make a copy of the library as part of each executable, and you don't need to load the code for that library into memory more that once.

Another complication is that Linux uses demand paging, which means that your executable will not consume much RAM unless it actually runs all the code in it, accesses the data, etc.

P.S.

Using top is generally not a good way to account for memory of the process. Its output could be very misleading. You should read man top carefully.

On Linux you can actually account for every page of physical memory by looking in /proc/$pid/pagemap (if your kernel is configured with CONFIG_PROC_PAGE_MONITOR).

Upvotes: 2

Related Questions