如何修复GCC编译超过2GB代码时的编译错误?

110

我有大量的函数,总共约2.8GB的目标代码(不幸的是,科学计算中无法避免...)

当我尝试链接它们时,我会得到(预期的)relocation truncated to fit: R_X86_64_32S错误,我希望通过指定编译器标志-mcmodel=medium来规避这些错误。所有我可以控制的附加链接库都使用-fpic标志进行编译。

然而,错误仍然存在,我认为一些我链接到的库没有使用PIC进行编译。

以下是错误信息:

/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crt1.o: In function `_start':
(.text+0x12): relocation truncated to fit: R_X86_64_32S against symbol `__libc_csu_fini'     defined in .text section in /usr/lib64/libc_nonshared.a(elf-init.oS)
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crt1.o: In function `_start':
(.text+0x19): relocation truncated to fit: R_X86_64_32S against symbol `__libc_csu_init'    defined in .text section in /usr/lib64/libc_nonshared.a(elf-init.oS)
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crti.o: In function    `call_gmon_start':
(.text+0x7): relocation truncated to fit: R_X86_64_GOTPCREL against undefined symbol      `__gmon_start__'
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/crtbegin.o: In function `__do_global_dtors_aux':
crtstuff.c:(.text+0xb): relocation truncated to fit: R_X86_64_PC32 against `.bss' 
crtstuff.c:(.text+0x13): relocation truncated to fit: R_X86_64_32 against symbol `__DTOR_END__' defined in .dtors section in /usr/lib/gcc/x86_64-redhat-linux/4.1.2/crtend.o
crtstuff.c:(.text+0x19): relocation truncated to fit: R_X86_64_32S against `.dtors'
crtstuff.c:(.text+0x28): relocation truncated to fit: R_X86_64_PC32 against `.bss'
crtstuff.c:(.text+0x38): relocation truncated to fit: R_X86_64_PC32 against `.bss'
crtstuff.c:(.text+0x3f): relocation truncated to fit: R_X86_64_32S against `.dtors'
crtstuff.c:(.text+0x46): relocation truncated to fit: R_X86_64_PC32 against `.bss'
crtstuff.c:(.text+0x51): additional relocation overflows omitted from the output
collect2: ld returned 1 exit status
make: *** [testsme] Error 1

我链接到的系统库:

-lgfortran -lm -lrt -lpthread

请问在哪里可以找到问题的线索?

编辑:

首先,感谢讨论...

为了更清楚地说明,我有数百个函数(每个函数大约1 MB),都是像这样的独立目标文件:

double func1(std::tr1::unordered_map<int, double> & csc, 
             std::vector<EvaluationNode::Ptr> & ti, 
             ProcessVars & s)
{
    double sum, prefactor, expr;

    prefactor = +s.ds8*s.ds10*ti[0]->value();
    expr =       ( - 5/243.*(s.x14*s.x15*csc[49300] + 9/10.*s.x14*s.x15*csc[49301] +
           1/10.*s.x14*s.x15*csc[49302] - 3/5.*s.x14*s.x15*csc[49303] -
           27/10.*s.x14*s.x15*csc[49304] + 12/5.*s.x14*s.x15*csc[49305] -
           3/10.*s.x14*s.x15*csc[49306] - 4/5.*s.x14*s.x15*csc[49307] +
           21/10.*s.x14*s.x15*csc[49308] + 1/10.*s.x14*s.x15*csc[49309] -
           s.x14*s.x15*csc[51370] - 9/10.*s.x14*s.x15*csc[51371] -
           1/10.*s.x14*s.x15*csc[51372] + 3/5.*s.x14*s.x15*csc[51373] +
           27/10.*s.x14*s.x15*csc[51374] - 12/5.*s.x14*s.x15*csc[51375] +
           3/10.*s.x14*s.x15*csc[51376] + 4/5.*s.x14*s.x15*csc[51377] -
           21/10.*s.x14*s.x15*csc[51378] - 1/10.*s.x14*s.x15*csc[51379] -
           2*s.x14*s.x15*csc[55100] - 9/5.*s.x14*s.x15*csc[55101] -
           1/5.*s.x14*s.x15*csc[55102] + 6/5.*s.x14*s.x15*csc[55103] +
           27/5.*s.x14*s.x15*csc[55104] - 24/5.*s.x14*s.x15*csc[55105] +
           3/5.*s.x14*s.x15*csc[55106] + 8/5.*s.x14*s.x15*csc[55107] -
           21/5.*s.x14*s.x15*csc[55108] - 1/5.*s.x14*s.x15*csc[55109] -
           2*s.x14*s.x15*csc[55170] - 9/5.*s.x14*s.x15*csc[55171] -
           1/5.*s.x14*s.x15*csc[55172] + 6/5.*s.x14*s.x15*csc[55173] +
           27/5.*s.x14*s.x15*csc[55174] - 24/5.*s.x14*s.x15*csc[55175] +
           // ...
           ;

        sum += prefactor*expr;
    // ...
    return sum;
}

对象s相对较小,保留所需的常量x14、x15、...、ds0等,而ti只是从外部库返回一个double。正如您所看到的,csc[]是预先计算的值映射,也在以下形式的单独对象文件中进行评估(再次有数百个,每个大小约为~1MB):

void cscs132(std::tr1::unordered_map<int,double> & csc, ProcessVars & s)
{
    {
    double csc19295 =       + s.ds0*s.ds1*s.ds2 * ( -
           32*s.x12pow2*s.x15*s.x34*s.mbpow2*s.mWpowinv2 -
           32*s.x12pow2*s.x15*s.x35*s.mbpow2*s.mWpowinv2 -
           32*s.x12pow2*s.x15*s.x35*s.x45*s.mWpowinv2 -
           32*s.x12pow2*s.x25*s.x34*s.mbpow2*s.mWpowinv2 -
           32*s.x12pow2*s.x25*s.x35*s.mbpow2*s.mWpowinv2 -
           32*s.x12pow2*s.x25*s.x35*s.x45*s.mWpowinv2 +
           32*s.x12pow2*s.x34*s.mbpow4*s.mWpowinv2 +
           32*s.x12pow2*s.x34*s.x35*s.mbpow2*s.mWpowinv2 +
           32*s.x12pow2*s.x34*s.x45*s.mbpow2*s.mWpowinv2 +
           32*s.x12pow2*s.x35*s.mbpow4*s.mWpowinv2 +
           32*s.x12pow2*s.x35pow2*s.mbpow2*s.mWpowinv2 +
           32*s.x12pow2*s.x35pow2*s.x45*s.mWpowinv2 +
           64*s.x12pow2*s.x35*s.x45*s.mbpow2*s.mWpowinv2 +
           32*s.x12pow2*s.x35*s.x45pow2*s.mWpowinv2 -
           64*s.x12*s.p1p3*s.x15*s.mbpow4*s.mWpowinv2 +
           64*s.x12*s.p1p3*s.x15pow2*s.mbpow2*s.mWpowinv2 +
           96*s.x12*s.p1p3*s.x15*s.x25*s.mbpow2*s.mWpowinv2 -
           64*s.x12*s.p1p3*s.x15*s.x35*s.mbpow2*s.mWpowinv2 -
           64*s.x12*s.p1p3*s.x15*s.x45*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.p1p3*s.x25*s.mbpow4*s.mWpowinv2 +
           32*s.x12*s.p1p3*s.x25pow2*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.p1p3*s.x25*s.x35*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.p1p3*s.x25*s.x45*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.p1p3*s.x45*s.mbpow2 +
           64*s.x12*s.x14*s.x15pow2*s.x35*s.mWpowinv2 +
           96*s.x12*s.x14*s.x15*s.x25*s.x35*s.mWpowinv2 +
           32*s.x12*s.x14*s.x15*s.x34*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.x14*s.x15*s.x35*s.mbpow2*s.mWpowinv2 -
           64*s.x12*s.x14*s.x15*s.x35pow2*s.mWpowinv2 -
           32*s.x12*s.x14*s.x15*s.x35*s.x45*s.mWpowinv2 +
           32*s.x12*s.x14*s.x25pow2*s.x35*s.mWpowinv2 +
           32*s.x12*s.x14*s.x25*s.x34*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.x14*s.x25*s.x35pow2*s.mWpowinv2 -
           // ...
    
       csc.insert(cscMap::value_type(192953, csc19295));
    }

    {
       double csc19296 =      // ... ;

       csc.insert(cscMap::value_type(192956, csc19296));
    }

    // ...
}

大致就是这样了。最后一步就是调用所有这些func[i]并将结果加起来。

关于这是一个相当特殊和不寻常的情况:是的,确实如此。这就是人们在尝试为粒子物理进行高精度计算时必须应对的问题。

编辑2:

我还应该补充说明x12、x13等并不是真正的常数。它们被设定为特定的值,运行所有这些函数并返回结果,然后选择一个新的x12、x13等来产生下一个值。而这必须重复105到106次……

编辑3:

感谢至今为止提出的建议和讨论……我会设法在代码生成过程中将循环合并起来,但是说实话,我不确定该如何做到这一点,但这是最好的办法。

顺便说一句,我没有试图掩饰“这是科学计算——无法优化”的事实。
只是因为这个代码的基础是从一个“黑盒子”中得出的,我并没有真正的访问权限,而且整个过程在简单的例子中表现良好,我主要是感到在实际应用中会发生什么而感到不知所措……

编辑4:

所以,我通过简化计算机代数系统(Mathematica)中的表达式,成功将csc定义的代码大小减少了四分之一左右。现在我看到了通过在生成代码之前应用其他技巧来将其减少一个数量级左右的方法(这将使这部分代码降至约100 MB),我希望这个想法能够奏效。

现在关于你的答案:

我正在尝试将循环重新合并到func中,其中计算机代数系统并没有什么帮助,但我已经有了一些想法。例如,按变量(如x12、x13等)对表达式进行排序,使用Python解析csc并生成将它们彼此相关联的表格。然后我至少可以将这些部分生成为循环。由于这似乎是目前最好的解决方案,因此我将其标记为最佳答案。

但是,我也要赞扬VJo。 GCC 4.6确实工作得更好,产生的代码更小,速度更快。使用大型模型可以直接运行代码。因此,在技术上,这是正确的答案,但改变整个概念是更好的方法。

感谢大家的建议和帮助。如果有人感兴趣,我将在准备好后尽快发布最终结果。

备注:

只是对其他答案的一些评论:我试图运行的代码并不源于简单函数/算法的扩展和愚蠢无用的展开。实际上,我们开始的东西是相当复杂的数学对象,将它们带到一个可以计算的形式会生成这些表达式。问题实际上在于基础物理理论。中间表达式的复杂度呈阶乘增长,这是众所周知的,但当将所有这些东西组合成某个物理可测量的东西(即可观测量)时,它最终只归结为少量非常小的函数,这些函数构成了表达式的基础。(这里有一个“错误”,就是唯一可用的ansatz——微扰理论——存在问题)我们试图将这个假设带到另一个水平,这在解析上不再可行,需要的函数基础也不清楚。所以我们试图像这样 brute-force 它。这不是最好的方法,但希望这种方法能最终帮助我们理解手头的物理问题...

最后编辑:

感谢您所有的建议,我使用 Mathematica 和对 func 的代码生成器进行了修改,成功地将代码大小大大简化,这与顶部答案的思路有些类似 :)

我用 Mathematica 简化了 csc 函数,将其缩小到了 92 MB。这是不可约的部分。最初的尝试花费了很长时间,但经过一些优化,现在在单个 CPU 上大约需要 10 分钟。

< p > 对于func而言,这种效果是惊人的:它们的整个代码大小缩小到了约9 MB左右,因此现在的代码总量在100 MB范围内。现在开启优化也有意义,并且执行速度非常快。

再次感谢您的建议,我学到了很多。


18
如果你有那么多数据,应该将其从源文件中移出,而是在运行时从外部二进制文件自己使用 mmap 映射。 - R.. GitHub STOP HELPING ICE
7
我的第一反应和R..类似,这听起来像是一个设计问题。诚然,我不知道在科学计算领域中什么是常见的,但我从未听说过有人尝试链接一个2.8GB的目标文件,或者任何接近它的东西,而且我不确定GCC是否真正支持它。坦白地说,我期望那样大小的代码块是纯粹的“意大利面条式代码”。 - Nicholas Knight
46
最佳解决方案绝对不可能涉及2GB的目标文件。 - David Heffernan
35
不要将您的数据放在代码中。 - David Heffernan
4
“高精度计算”用双精度?嗯?为什么不像其他人一样从“数据”文件中加载你的数据呢?(注意,“data”在这里指的是同一个东西,翻译时需要改变词序) - houbysoft
显示剩余32条评论
11个回答

53

所以,你已经有一个生成这段文本的程序:

prefactor = +s.ds8*s.ds10*ti[0]->value();
expr = ( - 5/243.*(s.x14*s.x15*csc[49300] + 9/10.*s.x14*s.x15*csc[49301] +
       1/10.*s.x14*s.x15*csc[49302] - 3/5.*s.x14*s.x15*csc[49303] -...

并且

double csc19295 =       + s.ds0*s.ds1*s.ds2 * ( -
       32*s.x12pow2*s.x15*s.x34*s.mbpow2*s.mWpowinv2 -
       32*s.x12pow2*s.x15*s.x35*s.mbpow2*s.mWpowinv2 -
       32*s.x12pow2*s.x15*s.x35*s.x45*s.mWpowinv2 -...

对吗?

如果您所有的函数都有类似的“格式”(将n个数字乘以m次并加上结果 - 或类似的内容),那么我认为您可以这样做:

  • 将生成器程序更改为输出偏移量而不是字符串(即,它将产生offsetof(ProcessVars,ds0)而不是字符串“ s.ds0”
  • 创建一个这样的偏移量数组
  • 编写一个接受上述数组和结构指针的基地址并产生结果的求值器

数组+求值器将表示与您的某个函数相同的逻辑,但仅求值器将成为代码。数组是“数据”,可以在运行时生成,也可以保存在磁盘上并以块或内存映射文件的形式读取。

对于您的特定示例func1,请想象一下如果您可以访问scsc的基地址以及常量向量和需要添加到基地址才能到达x14ds8csc [51370]的偏移量的情况下,您将如何通过求值器重写该函数

您需要创建一种新的“数据”形式,描述如何处理传递给您的大量函数的实际数据。


46
Linux使用的x86-64 ABI定义了一个“大模型”,专门为避免这种尺寸限制而设计,其中包括用于GOT和PLT的64位重定位类型(请参见第4.4.2节中的表格,以及第3.5.5节中显示它们如何使用的指令序列)。

由于您的函数占用了2.8 GB的空间,因此不幸的是,gcc不支持大模型。您可以重新组织代码,使其能够分割成共享库并进行动态链接。

如果不可能,您可以按照某人建议的做法,将数据放入运行时加载(作为普通文件或者您可以mmap映射) 代替将其编译和链接到代码中(因为它太大了)。

编辑

看起来gcc 4.6支持大模型(请参见该页面)。您可以尝试使用它,但上述关于重新组织代码的建议仍然适用。


3
没问题,我可以翻译成中文。您说,但我仍会寻找另一种实现您的功能的方法。我打赌您的编译需要很长时间。 - BЈовић
19
这代码肯定是由某些脚本生成的,没人会手写几兆字节的代码!生成代码的同样逻辑也可以用来运行计算。 - zvrba
7
我强烈建议尝试使用gcc 4.6,它很可能会为该程序生成比gcc 4.1更优秀的代码; 它甚至可以将整个程序压缩到2GB以内,而无需进行任何聪明的操作,从而消除问题(尝试使用-Os,-fwhole-program和-flto的组合——对于这么多的代码,优化大小就是优化速度)。但是,如果这还不够帮助,你还应该知道,为了使大型模型正常工作,你需要重新构建C库的至少一部分(crt * .o,libc_nonshared.a和libpthread_nonshared.a)。 - zwol
1
@bdonlan 静态链接也是一种可能性。 - zvrba
@Zack 如果整个程序优化在这种代码上运行一个月,我不会感到惊讶 ;) - hobbs
显示剩余6条评论

37

如果使用这样的程序,代码缓存未命中的概率很高,这会导致运行时循环的成本超过了缓存未命中的成本。我建议您返回到代码生成器,并让它生成一些紧凑的表示形式,以便进行评估(即,可能适合于D-cache),然后在程序中使用解释器执行它。您还可以查看是否可以分解出仍具有大量操作的较小内核,然后将其用作解释代码中的“指令”。


22
错误的原因是你的代码太多了,而不是数据!例如,__libc_csu_fini(这是一个函数)被从 _start 引用,并且重定位被截断以适合。这意味着 _start(程序的真实入口点)正在尝试通过有符号32位偏移量调用该函数,该偏移量仅具有2 GB的范围。由于你的目标代码总量约为2.8 GB,所以事实是可以证实的。
如果重新设计数据结构,很多代码都可以通过将庞大的表达式重写为简单的循环来“压缩”。
此外,你可以在另一个程序中计算,将结果存储在文件中,需要时只需加载即可。

你能提供一个例子,说明如何使用简单的循环重写这些函数吗?我没有完全明白你的意思。 csc [] 需要经常计算,并且我想避免磁盘 I/O。 - bbtrb
4
例如,对于上面的func1函数,可以这样写:for (int i = 0; i < N; ++i) expr += constants[i].*s.x14*s.x15*csc[49300 + i]; - HighCommander4
@HighCommander4:完全同意。我只是不知道如何自动生成这样的东西。也许可以用一个单独的数组来存储索引... - bbtrb
2
@bbtrb:既然没有人手写足够的源代码来生成2.8GB的目标代码,尤其是使用这种不易理解的符号名称,那么肯定使用了代码生成器。从这个角度入手。 - Donal Fellows

15

我认为每个人都同意,应该有一种不同的方式来做你想做的事情。编译数百兆(千兆?)的代码,将其链接到一个多千兆字节大小的可执行文件中并运行它听起来非常低效。

如果我正确理解了你的问题,你使用某种代码生成器G生成一堆函数func1...N,这些函数以一堆映射csc1...M作为输入。你想要做的是计算csc1...M,并为不同的输入运行1,000,000次循环,并每次找到s = func1 + func2 + ... + funcN。不过,你没有说明fucn1...Ncsc1...M之间的关系。

如果所有这些都是真的,似乎你应该能够以不同的方式解决问题,这可能会更容易管理,甚至可能更快(即让您机器的缓存实际运作)。除了目标文件大小的实际问题外,由于你当前程序没有本地化数据访问(太多巨大的映射)和没有本地化代码执行(太多非常长的函数),所以你当前的程序不会高效。

那么,将你的程序分解成3个阶段怎么样:第1阶段构建csc1...M并将它们存储。第2阶段逐个构建一个func,在每个输入上运行1,000,000次,并存储结果。第3阶段找到每次运行中存储的func1...N结果之和中的总和。这种解决方案的好处是,它可以轻松地并行在几个独立的机器上运行。

编辑:@bbtrb,您能够在某个地方提供一个func和一个csc吗?它们似乎非常规则且可压缩。例如,func1似乎只是由每个表达式的1个系数、2个变量s的索引和1个csc的索引组成的表达式之和。因此,可以将其简化为一个循环。如果您提供完整的示例,我相信可以找到将它们压缩成循环而不是长表达式的方法。


是的,你理解得很正确 :) 不过你的建议有几个问题:1. 最糟糕的 func 函数依赖于几乎所有的 csc 数字,这些数字必须计算 10^6 次。2. 输入将从自适应 Monte Carlo 积分器中获得,这意味着积分器必须在每个点知道完整的结果,以便在必要时通过在该点附近细化网格来减少结果误差。3. csc 的大表达式仍然存在... - bbtrb
1
那么这是否意味着在每次迭代中你不能独立计算每个csc?如果它们是独立的,你仍然可以运行每个csc 10^6 次并存储结果。但是,如果它们之间存在依赖关系,也许你需要找出哪一个与哪一个相关,类似于依赖图,然后尝试看看是否可以将其分解为多个独立的子图。总之,我认为关键是将问题分解为多个独立的子问题。 - AlefSin

5
如果我正确地读取了您的错误,那么导致您超出限制的是已初始化数据部分(如果是代码,您会有更多的错误)。您是否有大量的全局数据数组?如果是这种情况,我建议重新构造程序,使它们动态分配。如果数据已经初始化,我建议从配置文件中读取它。
另外,看到这个:
“(.text+0x20):对'main'的未定义引用”
我认为您还有其他问题。

1
是的,你说得对,这是个愚蠢的错误,但它并不能解决其他的错误。 - bbtrb

3
一些建议: - 优化大小(-Os)。将内联函数调用转换为普通函数调用。启用字符串池。 尝试将事物拆分为不同的DLL(共享对象,.so适用于Linux,.dylib适用于Mac OS X)。确保它们可以被卸载。然后实现一些东西以按需加载内容,并在不需要时释放它们。
如果不行,将您的代码拆分为不同的可执行文件,并使用某些方法在它们之间进行通信(管道、套接字,甚至写入/读取文件)。笨拙,但你还有什么选择?
完全替代方案: - 使用JIT动态语言。我想到的是使用LuaJIT - 并在Lua或其他允许垃圾回收的语言和运行时中重写(重新生成)很多这些表达式。
LuaJIT非常高效,有时会击败C/C++某些功能,但通常非常接近(有时可能由于垃圾回收较差而变慢)。请自行查看:

http://luajit.org/performance_x86.html

从那里下载 scimark2.lua 文件,并将其与“C”版本进行比较(谷歌它) - 往往结果非常接近。

3
我认为这段代码正在使用某种自适应深度方法进行数值积分。不幸的是,代码生成器(或者说代码生成器的作者)太“愚蠢”了,会为每个补丁生成一个函数,而不是为每个补丁类型生成一个函数。因此,它产生了太多的代码无法编译,即使能编译,由于没有共享,执行也会很痛苦。(你能想象从磁盘加载每个对象代码页面的痛苦吗?因为没有共享,所以它总是候选被操作系统驱逐。更不用说指令缓存,它们将会无用。)
解决方法是停止展开所有内容;对于这种代码,您希望最大化共享,因为访问更复杂模式中的数据的额外指令开销将被处理(假设)底层数据集的成本吸收。代码生成器甚至可能默认执行此操作,科学家看到了一些展开选项(有时会提高速度),然后同时打开它们,并坚持计算机接受这个结果混乱的代码,而不是接受机器的实际限制并使用默认生成的数值正确版本。但如果代码生成器不这样做,请找一个可以这样做的代码生成器(或者黑客现有代码)。
最重要的是:编译和链接2.8GB的代码是不可行的,也不应该被强制执行。找到另一种方法。

2

这些表达式看起来很像交替级数。我不知道代码的其余部分是什么样子的,但似乎推导出生成表达式并不难。特别是如果您有2.8 GB的2 KB展开的代码,那么在执行时推导出表达式可能会更值得。


2
链接器试图在一个二进制文件中生成超出限制的32位重定位偏移量。尝试减少主程序对地址空间的需求。
您能将一些/大部分目标代码拆分为一个或多个库(也使用-fpic/-fPIC编译)吗?然后生成一个链接到这些库的非静态二进制文件。这些库将生存在离散的内存块中,您的重定位偏移量将是动态/绝对(64位)而不是相对(32位)的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接