Производительность компоновщика, связанная с местом подкачки?
Иногда бывает полезно макетировать что-то с небольшой программой на C, которая использует большой кусок статической памяти. Я заметил, что после перехода на Fedora 15 программа заняла много времени
компиляции. Мы говорим о 30s против 0.1s. Еще более странным было то, что ld (
компоновщик) максимизировал процессор и медленно начал есть все доступные
Память. После некоторых попыток я справился
найти корреляцию между этой новой проблемой и размером моего свопа
файл. Вот пример программы для целей этого обсуждения:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define M 1000000
#define GIANT_SIZE (200*M)
size_t g_arr[GIANT_SIZE];
int main( int argc, char **argv){
int i;
for(i = 0; i<10; i++){
printf("This should be zero: %d\n",g_arr[i]);
}
exit(1);
}
Эта программа имеет гигантский массив с объявленным размером около
200 * 8 МБ = 1,6 ГБ статической памяти. Компиляция этой программы требует
чрезмерное количество времени:
[[email protected]]$ time gcc HugeTest.c
real 0m12.954s
user 0m6.995s
sys 0m3.890s
[[email protected]]$
13s Для 13-строчной программы C!? Это не так. Номер ключа - это
размер статического пространства памяти. Как только он будет больше, чем
общее пространство подкачки, оно начинает быстро компилироваться. Например, я
имеют 5,3 Гбайт пространства подкачки, поэтому изменение GIANT_SIZE на (1000 * M) дает
следующее время:
[[email protected]]$ time gcc HugeTest.c
real 0m0.087s
user 0m0.026s
sys 0m0.027s
А, это больше нравится! Чтобы еще больше убедить себя (и себя, если
вы пытаетесь сделать это дома), что место подкачки было действительно волшебным
номер, я попытался изменить доступное пространство подкачки на действительно массивное
19GB и попытаться скомпилировать версию (1000 * M):
[[email protected]]$ ls -ali /extraswap
5986 -rw-r--r-- 1 root root 14680064000 Jul 26 15:01 /extraswap
[[email protected]]$ sudo swapon /extraswap
[[email protected]]$ time gcc HugeTest.c
real 4m28.089s
user 0m0.016s
sys 0m0.010s
Это не закончилось даже через 4,5 минуты!
Очевидно, что компоновщик делает что-то неправильно здесь, но я не знаю, как
чтобы обойти это иначе, чем переписывать программу или возиться
с местом подкачки. Мне бы хотелось узнать, есть ли решение, или если у меня есть
наткнулся на какую-то загадочную ошибку.
Кстати, все программы компилируются и выполняются корректно, независимо от всего своп-бизнеса.
Для справки, вот, возможно, релевантная информация:
[]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 27027
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 1024
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
[]$ uname -r
2.6.40.6-0.fc15.x86_64
[]$ ld --version
GNU ld version 2.21.51.0.6-6.fc15 20110118
Copyright 2011 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) a later version.
This program has absolutely no warranty.
[]$ gcc --version
gcc (GCC) 4.6.1 20110908 (Red Hat 4.6.1-9)
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
[]$ cat /proc/meminfo
MemTotal: 3478272 kB
MemFree: 1749388 kB
Buffers: 16680 kB
Cached: 212028 kB
SwapCached: 368056 kB
Active: 489688 kB
Inactive: 942820 kB
Active(anon): 401340 kB
Inactive(anon): 803436 kB
Active(file): 88348 kB
Inactive(file): 139384 kB
Unevictable: 32 kB
Mlocked: 32 kB
SwapTotal: 19906552 kB
SwapFree: 17505120 kB
Dirty: 172 kB
Writeback: 0 kB
AnonPages: 914972 kB
Mapped: 60916 kB
Shmem: 1008 kB
Slab: 55248 kB
SReclaimable: 26720 kB
SUnreclaim: 28528 kB
KernelStack: 3608 kB
PageTables: 63344 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 21645688 kB
Committed_AS: 11208980 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 139336 kB
VmallocChunk: 34359520516 kB
HardwareCorrupted: 0 kB
AnonHugePages: 151552 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 730752 kB
DirectMap2M: 2807808 kB
TL; DR: Когда (большая) статическая память программы c немного меньше, чем доступное пространство подкачки, компоновщик навсегда связывает программу. Тем не менее, это довольно быстро, когда статическое пространство немного больше, чем доступное пространство подкачки. Что с этим!?
Ответы
Ответ 1
Я могу воспроизвести это в системе Ubuntu 10.10 (GNU ld (GNU Binutils for Ubuntu) 2.20.51-system.20100908
), и я думаю, что у меня есть ваш ответ. Во-первых, некоторая методология.
После подтверждения этого происходит со мной в небольшой виртуальной машине (512 МБ RAM, своп 2 ГБ), здесь я решил, что проще всего было бы спрятать gcc и посмотреть, что именно происходит, когда все ушло в ад:
~# strace -f gcc swap.c
Он подсвечивает следующее:
vfork() = 3589
[pid 3589] execve("/usr/lib/gcc/x86_64-linux-gnu/4.4.5/collect2", ["/usr/lib/gcc/x86_64-linux-gnu/4."..., "--build-id", "--eh-frame-hdr", "-m", "elf_x86_64", "--hash-style=gnu", "-dynamic-linker", "/lib64/ld-linux-x86-64.so.2", "-o", "swap", "-z", "relro", "/usr/lib/gcc/x86_64-linux-gnu/4."..., "/usr/lib/gcc/x86_64-linux-gnu/4."..., "/usr/lib/gcc/x86_64-linux-gnu/4."..., "-L/usr/lib/gcc/x86_64-linux-gnu/"..., ...], [/* 26 vars */]) = 0
...
[pid 3589] vfork() = 3590
...
[pid 3590] execve("/usr/bin/ld", ["/usr/bin/ld", "--build-id", "--eh-frame-hdr", "-m", "elf_x86_64", "--hash-style=gnu", "-dynamic-linker", "/lib64/ld-linux-x86-64.so.2", "-o", "swap", "-z", "relro", "/usr/lib/gcc/x86_64-linux-gnu/4."..., "/usr/lib/gcc/x86_64-linux-gnu/4."..., "/usr/lib/gcc/x86_64-linux-gnu/4."..., "-L/usr/lib/gcc/x86_64-linux-gnu/"..., ...], [/* 27 vars */]) = 0
...
[pid 3590] lseek(13, 4096, SEEK_SET) = 4096
[pid 3590] read(13, ".\[email protected]\0\0\0\0\0>\[email protected]\0\0\0\0\0N\[email protected]\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096
[pid 3590] mmap(NULL, 1600004096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1771931000
<system comes to screeching halt>
Казалось бы, как мы могли бы заподозрить, похоже, что ld
на самом деле пытается анонимно mmap
все статическое пространство памяти этого массива (или, возможно, всю программу, трудно сказать, так как остальные программы настолько мала, что все они могут вписаться в дополнительные 4096).
Итак, все хорошо и хорошо, но почему это работает, когда мы превосходим доступный обмен в системе? Поверните swapoff
и снова запустите strace -f
...
[pid 3618] lseek(13, 4096, SEEK_SET) = 4096
[pid 3618] read(13, ".\[email protected]\0\0\0\0\0>\[email protected]\0\0\0\0\0N\[email protected]\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096
[pid 3618] mmap(NULL, 1600004096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
[pid 3618] brk(0x60638000) = 0x1046000
[pid 3618] mmap(NULL, 1600135168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
[pid 3618] mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7fd011864000
...
Неудивительно, что ld, похоже, делает то же самое, что и в прошлый раз, для mmap всего пространства. но система больше не в состоянии это сделать, она терпит неудачу! ld пытается снова, и он снова терпит неудачу, тогда ld делает что-то неожиданное... он движется с меньшим объемом памяти.
Странно, я думаю, нам лучше посмотреть код ld
. Drat, он не делает явного mmap
. Это должно происходить из простого старого malloc
. Мы должны будем построить ld с некоторыми символами отладки, чтобы отслеживать это. К сожалению, когда я построил bin-utils 2.21.1, проблема исчезла. Perhap был исправлен в новых версиях bin-utils?
Ответ 2
Я пытал протестировать мой OpenSuse 11.4 (переход на 12,1 в неделю)
У меня есть 4GiB ram + 2GiB swap и он не заметил серьезного замедления, система может время от времени рушиться, но время компиляции было коротким.
Самый длинный был 6 секунд, а тяжелая замена.
[[email protected] ~]$ free -m
total used free shared buffers cached
Mem: 3456 3426 30 0 4 249
-/+ buffers/cache: 3172 284
Swap: 2055 1382 672
[[email protected] ~]$ time cc -Wall -O test2.c
test2.c: In function ‘main’:
test2.c:13:2: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘size_t’
real 0m6.501s
user 0m0.101s
sys 0m0.078s
[[email protected] ~]$ free -m
total used free shared buffers cached
Mem: 3456 3389 67 0 5 289
-/+ buffers/cache: 3094 362
Swap: 2055 1455 599
[[email protected] ~]$ free -m
total used free shared buffers cached
Mem: 3456 3373 82 0 4 264
-/+ buffers/cache: 3104 352
Swap: 2055 1442 612
[[email protected] ~]$ time cc -Wall -O test2.c
test2.c: In function ‘main’:
test2.c:13:2: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘size_t’
real 0m1.122s
user 0m0.086s
sys 0m0.045s
[[email protected] ~]$ time cc -Wall -O test2.c
test2.c: In function ‘main’:
test2.c:13:2: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘size_t’
real 0m0.095s
user 0m0.047s
sys 0m0.032s
[[email protected] ~]$ free -m
total used free shared buffers cached
Mem: 3456 3376 79 0 4 252
-/+ buffers/cache: 3119 336
Swap: 2055 1436 618
[[email protected] ~]$ time cc -Wall -O test2.c
test2.c: In function ‘main’:
test2.c:13:2: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘size_t’
real 0m0.641s
user 0m0.054s
sys 0m0.040s
Между запуском я загрузил и выгрузил виртуальные машины Virtualbox Box, Eclipse, большие файлы PDF, только один mi firefox, используя 800+ MiB. Я не пошел на предел, иначе многие приложения будут убиты ОС. Он предпочитает убивать Firefox..: -)
Я также дошел до крайнего определения:
#define M 1048576
#define GIANT_SIZE (20000*M)
и даже тогда ничего не меняется значительно.
[[email protected] ~]$ time cc -Wall -O test2.c
test2.c:7:14: warning: integer overflow in expression
test2.c:7:8: error: size of array ‘g_arr’ is negative
test2.c:7:1: warning: variably modified ‘g_arr’ at file scope
test2.c: In function ‘main’:
test2.c:13:2: warning: format ‘%d’ expects type ‘int’, but argument 2 has type ‘size_t’
real 0m0.661s
user 0m0.043s
sys 0m0.031s
Изменить:
Я повторно протестировал Fedora16 на виртуальной машине с 512 Мбайт оперативной памяти и с заменой 1.5GiB, и все было похоже, за исключением сообщения об ошибке в моей "максимальной стрессовой версии", где 20000 мегабайт были назначены массиву. Ошибка говорит, что размер массива был отрицательным.
[[email protected] ~]$ time gcc -Wall test2.c
test2.c:7:14: warning: integer overflow in expression [-Woverflow]
test2.c:7:8: error: size of array ‘g_arr’ is negative
test2.c:7:1: warning: variably modified ‘g_arr’ at file scope [enabled by default]
test2.c: In function ‘main’:
test2.c:13:2: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘size_t’ [-Wformat]
real 0m1.053s
user 0m0.050s
sys 0m0.137s
Тот же ответ возникает в opensuse 12.1 VM. Fedora 16 установил медленную паузу и голодную память (во время установки мне пришлось использовать 800MiB против OpenSuse 512 MiB), я не мог использовать swapoff для Fedora, потому что он использовал большое количество подкачки. У OpenSuse 12.1 и не было проблем с памятью и памяти. Оба имеют по существу одни и те же версии ядра, gcc и т.д. Оба варианта использования с KDE в качестве среды рабочего стола
Я не мог воспроизвести ваши проблемы. Возможно, это проблема, связанная с gcc. Попробуйте загрузить более старую версию, например 4.5, и посмотрите, что произойдет
Ответ 3
Я не наблюдаю этого поведения (с Debian/Sid/AMD64 на рабочем столе 8Gb, gcc 4.6.2, binutils gold ld (GNU Binutils для Debian 2.22) 1.11). Вот измененная программа (отображение ее карты памяти с помощью pmap
).
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define M 1000000
#define GIANT_SIZE (2000*M)
size_t g_arr[GIANT_SIZE];
int main( int argc, char **argv){
int i;
char cmd[80];
for(i = 0; i<10; i++){
printf("This should be zero: %d\n",g_arr[i*1000]);
}
sprintf (cmd, "pmap %d", (int)getpid());
system(cmd);
exit(0);
}
Вот его компиляция:
% time gcc -v -O big.c -o big
Using built-in specs.
COLLECT_GCC=/usr/bin/gcc-4.6.real
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.6/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.6.2-4' --with-bugurl=file:///usr/share/doc/gcc-4.6/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++,go --prefix=/usr --program-suffix=-4.6 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.6 --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-plugin --enable-objc-gc --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.6.2 (Debian 4.6.2-4)
COLLECT_GCC_OPTIONS='-v' '-O' '-o' 'big' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/4.6/cc1 -quiet -v -imultilib . -imultiarch x86_64-linux-gnu big.c -quiet -dumpbase big.c -mtune=generic -march=x86-64 -auxbase big -O -version -o /tmp/ccWThBP5.s
GNU C (Debian 4.6.2-4) version 4.6.2 (x86_64-linux-gnu)
compiled by GNU C version 4.6.2, GMP version 5.0.2, MPFR version 3.1.0, MPC version 0.9
warning: MPFR header version 3.1.0 differs from library version 3.1.0-p3.
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/4.6/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/4.6/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
GNU C (Debian 4.6.2-4) version 4.6.2 (x86_64-linux-gnu)
compiled by GNU C version 4.6.2, GMP version 5.0.2, MPFR version 3.1.0, MPC version 0.9
warning: MPFR header version 3.1.0 differs from library version 3.1.0-p3.
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 4b128876859f8f310615c7040fa3cb67
COLLECT_GCC_OPTIONS='-v' '-O' '-o' 'big' '-mtune=generic' '-march=x86-64'
as --64 -o /tmp/ccm7905b.o /tmp/ccWThBP5.s
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.6/:/usr/lib/gcc/x86_64-linux-gnu/4.6/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.6/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.6/:/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-O' '-o' 'big' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/4.6/collect2 --build-id --no-add-needed --eh-frame-hdr -m elf_x86_64 --hash-style=both -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o big /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/4.6 -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../.. /tmp/ccm7905b.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o
gcc -v -O big.c -o big 0.07s user 0.01s system 90% cpu 0.089 total
и его выполнение:
% time ./big
This should be zero: 0
This should be zero: 0
This should be zero: 0
This should be zero: 0
This should be zero: 0
This should be zero: 0
This should be zero: 0
This should be zero: 0
This should be zero: 0
This should be zero: 0
8835: ./big
0000000000400000 4K r-x-- /home/basile/tmp/big
0000000000401000 4K rw--- /home/basile/tmp/big
0000000000402000 15625000K rw--- [ anon ]
00007f2d15a44000 1512K r-x-- /lib/x86_64-linux-gnu/libc-2.13.so
00007f2d15bbe000 2048K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007f2d15dbe000 16K r---- /lib/x86_64-linux-gnu/libc-2.13.so
00007f2d15dc2000 4K rw--- /lib/x86_64-linux-gnu/libc-2.13.so
00007f2d15dc3000 20K rw--- [ anon ]
00007f2d15dc8000 124K r-x-- /lib/x86_64-linux-gnu/ld-2.13.so
00007f2d15fb4000 12K rw--- [ anon ]
00007f2d15fe4000 12K rw--- [ anon ]
00007f2d15fe7000 4K r---- /lib/x86_64-linux-gnu/ld-2.13.so
00007f2d15fe8000 4K rw--- /lib/x86_64-linux-gnu/ld-2.13.so
00007f2d15fe9000 4K rw--- [ anon ]
00007ffff5b5b000 132K rw--- [ stack ]
00007ffff5bff000 4K r-x-- [ anon ]
ffffffffff600000 4K r-x-- [ anon ]
total 15628908K
./big 0.00s user 0.00s system 0% cpu 0.004 total
Я считаю, что для таких программ важно установить недавний GCC (например, GCC 4.6) с бинтулисом Gold linker.
Я не слышу никаких обменов.