链接器性能与交换空间有关?

有时候用一个C程序来模拟一些使用大量静态内存的东西是很方便的。 我注意到,在转换到Fedora 15之后,程序花了长时间来编译。 我们正在谈论30s与0.1s。 更奇怪的是,ld(链接器)正在耗尽CPU,慢慢地开始占用所有可用的内存。 经过一番捣鼓,我设法find了这个新问题和我的交换文件的大小之间的关系。 以下是本次讨论的示例程序:

#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 * 8MB = 1.6GB的静态内存。 编译这个程序需要花费过多的时间:

 [me@bleh]$ time gcc HugeTest.c real 0m12.954s user 0m6.995s sys 0m3.890s [me@bleh]$ 

13s〜13线C程序! 那是不对的。 关键号码是静态存储空间的大小。 一旦它大于总交换空间,它就会开始快速编译。 例如,我有5.3GB的交换空间,所以将GIANT_SIZE改为(1000 * M)给出以下时间:

 [me@bleh]$ time gcc HugeTest.c real 0m0.087s user 0m0.026s sys 0m0.027s 

啊,那更像是它! 为了进一步说服自己(和你自己,如果你在家里尝试这种方式),交换空间确实是一个神奇的数字,我尝试将可用交换空间改为真正的大容量19GB,并试图再次编译(1000 * M)版本:

 [me@bleh]$ ls -ali /extraswap 5986 -rw-r--r-- 1 root root 14680064000 Jul 26 15:01 /extraswap [me@bleh]$ sudo swapon /extraswap [me@bleh]$ time gcc HugeTest.c real 4m28.089s user 0m0.016s sys 0m0.010s 

4.5分钟后甚至没有完成!

很明显,链接器在这里做的是错的,但是我不知道如何解决这个问题,而不是重写程序或者使用交换空间。 我很想知道是否有解决scheme,或者是我偶然发现了一些奥术错误。

顺便说一下,这些程序都是正确编译和运行的,与所有的交换业务无关。

作为参考,这里有一些可能相关的信息:

 []$ 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:当ac程序的(大)静态内存略小于可用的交换空间时,链接程序将永远连接程序。 但是,当静态空间比可用的交换空间略大时,它是相当快速的。 那是怎么回事!?

我能够在Ubuntu 10.10系统( GNU ld (GNU Binutils for Ubuntu) 2.20.51-system.20100908 )上重现这一点,我想我有你的答案。 首先是一些方法论。

在确认这发生在一个小的虚拟机(512MB内存,2GB交换)之后,从这里我决定最简单的事情就是对gcc进行testing,看看究竟发生了什么事情:

 ~# 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, ".\4@\0\0\0\0\0>\4@\0\0\0\0\0N\4@\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实际上是在试图匿名地映射这个数组的整个静态内存空间(或者可能是整个程序,很难说,因为程序的其他部分太小了,它可能都适合在额外的4096)。

所以这一切都很好,但为什么当我们超过系统上的可用交换时,它会工作? 让我们把swapoff再次运行strace -f

 [pid 3618] lseek(13, 4096, SEEK_SET) = 4096 [pid 3618] read(13, ".\4@\0\0\0\0\0>\4@\0\0\0\0\0N\4@\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似乎做了上次尝试的同样的事情,来映射整个空间。 但系统不再能够做到这一点,它失败了! ld再次尝试,并再次失败,然后ld做一些意想不到的事情…它继续前进,更less的内存。

奇怪的是,我想我们最好再看看ld代码 。 Drat,它不会做明确的mmap 。 这必须来自一个普通的旧malloc里面。 我们必须用一些debugging符号来编译ld来跟踪这个。 不幸的是,当我build立bin-utils 2.21.1时,问题就消失了。 Perhap已经在bin-utils的新版本中修复了?

我受了折磨,testing了我的OpenSuse 11.4(每周12.1)

我有4GiB RAM + 2GiB交换,并没有注意到严重的慢下来,系统可能有时被捣毁,但编译时间仍然很短。

最长的时间是6秒,而交换。

 [tester@ulises ~]$ free -m total used free shared buffers cached Mem: 3456 3426 30 0 4 249 -/+ buffers/cache: 3172 284 Swap: 2055 1382 672 [tester@ulises ~]$ 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 [tester@ulises ~]$ free -m total used free shared buffers cached Mem: 3456 3389 67 0 5 289 -/+ buffers/cache: 3094 362 Swap: 2055 1455 599 [tester@ulises ~]$ free -m total used free shared buffers cached Mem: 3456 3373 82 0 4 264 -/+ buffers/cache: 3104 352 Swap: 2055 1442 612 [tester@ulises ~]$ 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 [tester@ulises ~]$ 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 [tester@ulises ~]$ free -m total used free shared buffers cached Mem: 3456 3376 79 0 4 252 -/+ buffers/cache: 3119 336 Swap: 2055 1436 618 [tester@ulises ~]$ 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 VM,Eclipse,大型PDF文件,单独使用800+ MiB的FireFox。 我没有去极限,否则许多应用程序将被操作系统杀死。 它有一个消灭Firefox的偏好.. 🙂

我也去了极端的定义:

 #define M 1048576 #define GIANT_SIZE (20000*M) 

即使如此,没有什么显着变化。

 [tester@ulises ~]$ 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 

编辑:我重新testing在512MiB RAM和1.5GiB交换的虚拟机上使用Fedora16和事情是相似的,除了我的“最大压力版本”20000兆字节分配给arrays的错误消息。 错误说数组大小是负的。

 [ricardo@localhost ~]$ 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),因为它使用了很多交换空间,所以我无法在Fedora上使用swapoff。 在OpenSuse 12.1和我之前,我没有呆滞和内存问题。 两者基本上都具有相同版本的内核,gcc等。两者都使用KDE作为桌面环境进行安装

我无法重现你的问题,也许是一个与gcc相关的问题。 尝试下载像4.5这样的旧版本,看看会发生什么

我没有观察到这种行为(在8Gb桌面上的Debian / Sid / AMD64,gcc 4.6.2,binutils gold ld(GNU Binutils for 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 rx-- /home/basile/tmp/big 0000000000401000 4K rw--- /home/basile/tmp/big 0000000000402000 15625000K rw--- [ anon ] 00007f2d15a44000 1512K rx-- /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 rx-- /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 rx-- [ anon ] ffffffffff600000 4K rx-- [ anon ] total 15628908K ./big 0.00s user 0.00s system 0% cpu 0.004 total 

我相信安装最近的GCC(例如GCC 4.6 )和binutils Gold连接器对于这样的程序是非常重要的。

我没有听到任何涉及的交换。