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

How to fix GCC compilation error when compiling >2 GB of code?

本文关键字:编译 GCC 错误 代码 何修复 gt GB      更新时间:2023-10-16

我有大量的函数,总计约2.8 GB的目标代码(不幸的是,科学计算…)

当我尝试链接它们时,我会得到(预期的)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

有什么线索可以从哪里找到这个问题吗?

编辑:

首先,感谢大家的讨论。。。

为了澄清一点,我有数百个函数(每个函数在单独的对象文件中的大小约为1MB),如下所示:

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。。。,而CCD_ 5只是从外部库返回一个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定义的代码大小减少了大约四分之一。我现在也看到了一些方法,可以通过在生成代码之前应用一些其他技巧将其减少一个数量级左右(这将使这部分减少到大约100MB),我希望这个想法能奏效。

现在与您的答案相关:

我正在尝试在funcs中再次将循环回滚,在那里CAS不会有多大帮助,但我已经有了一些想法。例如,根据x12, x13,...等变量对表达式进行排序,使用Python解析csc,并生成将它们相互关联的表。那么我至少可以将这些部分生成为循环。由于这似乎是迄今为止最好的解决方案,我认为这是最好的答案。

不过,我也要赞扬VJo。GCC 4.6确实能更好地运行,生成更小的代码,速度更快。使用大模型可以按原样处理代码。所以从技术上讲,这是正确的答案,但改变整个概念是一个更好的方法。

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

备注:

只是对其他一些答案的一些评论:我试图运行的代码并不是源于简单函数/算法的扩展和愚蠢的不必要的展开。实际情况是,我们从非常复杂的数学对象开始,将它们转换为数字可计算形式会生成这些表达式。问题实际上在于潜在的物理理论。众所周知,中间表达式的复杂性是按因子缩放的,但当将所有这些东西组合成物理上可测量的东西时——一种可观察的东西——它只能归结为构成表达式基础的少数非常小的函数。(在这方面,广义且可用的被称为"微扰理论"的ansatz肯定存在"错误"。)我们试图将这种ansatz提升到另一个层次,这在分析上不再可行,并且所需函数的基础尚不清楚。所以我们试着像这样暴力。这不是最好的方法,但希望最终能帮助我们理解手头的物理。。。

上次编辑:

多亏了你的所有建议,我使用Mathematica和func的代码生成器的修改,在一定程度上减少了代码大小:)

我用Mathematica简化了csc函数,使其减少到92MB。这是不可约的部分。第一次尝试花了很长时间,但经过一些优化,现在在单个CPU上只需10分钟即可完成。

funcs的影响是显著的:它们的整个代码大小降到了大约9 MB,因此代码现在的总量在100 MB的范围内。现在,启用优化是有意义的,并且执行速度相当快。

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

因此,您已经有了一个生成以下文本的程序:

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">
  • 创建这样的偏移数组
  • 编写一个计算器,它接受上面的数组和结构指针的基地址,并产生一个结果

数组+计算器将表示与某个函数相同的逻辑,但只有计算器是代码。数组是"数据",可以在运行时生成,也可以保存在磁盘上并读取i块或使用内存映射文件。

对于func1中的特定示例,想象一下,如果您可以访问scsc的基地址,以及需要添加到基地址以获得x14ds8csc[51370]的常数和偏移量的矢量表示,您将如何通过计算器重写函数

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

Linux使用的x86-64 ABI专门定义了一个"大模型"来避免这种大小限制,其中包括GOT和PLT的64位重定位类型。(参见第4.4.2节中的表格和3.5.5中的指令序列,说明了它们的使用方法。)

由于您的函数占用了2.8GB,因此您运气不佳,因为gcc不支持大型模型。您可以做的是以这样一种方式重新组织代码,即允许您将代码拆分为共享库,并动态链接这些库。

如果这不可能,正如有人建议的那样,而不是将数据放入代码中(编译和链接它),因为它是巨大的,你可以在运行时加载它(作为一个普通文件,或者你可以对它进行mmap)。

编辑

看起来gcc 4.6支持大型模型(请参阅本页)。您可以尝试一下,但以上内容仍然适用于重新组织代码。

对于这方面的程序,代码的缓存未命中很可能超过运行时循环的成本。我建议你回到你的代码生成器,让它为它想要评估的东西生成一些紧凑的表示(即,一个可能适合D-cache的表示),然后在你的程序中用解释器执行它。您还可以查看是否可以排除仍有大量操作的较小内核,然后将其用作解释代码中的"指令"。

发生错误是因为您有太多的CODE,而不是数据!这通过例如从_start引用的__libc_csu_fini(它是一个函数)来指示,并且重新定位被截断以适合。这意味着_start(程序的真正入口点)正试图通过SIGNED 32位偏移量调用该函数,该偏移量的范围仅为2 GB。由于您的目标代码总量约为2.8;GB,事实证明了。

如果你可以重新设计你的数据结构,你的大部分代码可以通过将巨大的表达式重写为简单的循环来"压缩"。

此外,您可以在不同的程序中计算csc[],将结果存储在文件中,并在必要时加载它们。

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

如果我正确理解你的问题,你可以使用某种代码生成器G来生成一组函数func1...N,这些函数以一组映射csc1...M为输入。您要做的是计算csc1...M,并对不同的输入运行1000000次循环,每次都找到s = func1 + func2 + ... + funcN。但是,您没有指定fucn1...Ncsc1...M的关系。

如果所有这些都是真的,那么你似乎应该能够以不同的方式彻底解决问题,这可能更容易管理,甚至可能更快(即让你的机器的缓存真正发挥作用)。

除了对象文件大小的实际问题外,您当前的程序将不会高效,因为它没有本地化对数据的访问(太多巨大的映射),也没有本地化的代码执行(太多非常长的函数)。

把你的程序分成三个阶段:第一阶段构建csc1...M并存储它们。阶段2一次构建一个func,对每个输入运行1000000次,并存储结果。阶段3为1000000次的每次运行找到存储的func1...N结果的总和。这个解决方案的好处在于,它可以很容易地在几个独立的机器上并行。

编辑:@bbtrb,你能在其他地方提供一个函数和一个csc吗?它们似乎是高度规则和可压缩的。例如,func1似乎只是表达式的总和,每个表达式由1个系数、s中变量的2个索引和csc中的1个索引组成。所以它可以简化为一个漂亮的循环。如果您提供完整的示例,我相信可以找到将它们压缩为循环而不是长表达式的方法。

如果我正确地读取了您的错误,那么使您超出限制的是初始化的数据部分(如果是代码,您将有更多的错误IMHO)。您有大型的全局数据阵列吗?如果是这样的话,我会重组程序,以便动态分配它们。如果数据被初始化,我会从配置文件中读取它。

BTW看到这个:

(.text+0x20):未定义对"main"的引用

我想你还有另一个问题。

在我看来,代码是在使用某种自适应深度方法进行数值积分。不幸的是,代码生成器(或者更确切地说是代码生成器的作者)是如此的愚蠢,以至于每个补丁生成一个函数,而不是每个类型的补丁都生成一个。因此,它产生了太多需要编译的代码,即使可以编译,它的执行也会很痛苦,因为从来没有在任何地方共享过任何东西。(你能想象从磁盘加载每一页目标代码所带来的痛苦吗?因为从来没有共享过任何东西,所以它总是操作系统驱逐的候选者。更不用说指令缓存了,它们将毫无用处。)

解决方法是停止展开所有内容;对于这类代码,您希望最大限度地共享,因为以更复杂的模式访问数据的额外指令的开销将被处理(可能)大型底层数据集的成本所吸收。也有可能,代码生成器甚至会在默认情况下这样做,科学家看到了一些展开的选项(注意,这些选项有时会提高速度),并立即将其全部打开,现在坚持要求计算机接受由此产生的混乱,而不是接受机器的实际限制并使用默认生成的数字正确版本。但如果代码生成器不这样做,就找一个会这样做的(或者破解现有代码)。

底线:编译和链接2.8GB的代码不起作用,也不应该被迫起作用另辟蹊径

几个建议:-优化大小(-Os)。进行内联函数调用,普通函数调用。启用字符串池。

试着把这些东西分成不同的DLL(共享对象,linux的.so,MacOSX的.dlib)。确保它们可以卸载。然后实现一些东西来按需加载,并在不需要的时候释放它们。

如果没有,请将代码拆分为不同的可执行文件,并使用一些东西在它们之间进行通信(管道、套接字,甚至写入/读取文件)。很笨拙,但你有什么选择?

完全可供选择:-使用JIT的动态语言。就在我的脑海中——使用LuaJIT——并在Lua或其他允许垃圾收集代码的语言和运行时中重写(重新生成?)许多这样的表达式。

LuaJIT非常高效,有时在某些方面胜过C/C++,但通常非常接近(有时由于垃圾收集不力而速度较慢)。自行检查:

http://luajit.org/performance_x86.html

从那里下载scimark2.lua文件,并将其与"C"版本进行比较(用谷歌搜索)——通常结果非常接近。

链接器试图在二进制文件中生成32位重定位偏移量,该偏移量以某种方式超过了这些限制。尝试减少主程序的地址空间要求。

您能否将部分/大部分目标代码拆分为一个或多个库(也使用-fpic/-fpic编译)?然后生成一个非静态二进制文件,该文件链接到这些库。库将位于离散内存块中,并且您的重新定位偏移将是动态/绝对的(64位),而不是相对的(32位)。

对我来说,这些表达式看起来很像一个交替的序列。我不知道代码的其余部分是什么样子,但派生生成表达式似乎并不那么困难。在执行时,这可能也是值得的,尤其是如果您有2.8;GB,共2个;KB展开的代码。

这看起来像是代码生成出错的结果,可能是由于符号代数和/或手动展开。众所周知,符号操作在表达式树或计算图的深度中呈指数增长。这里很可能可以使用自动区分,这将使代码大小非常小,并显著加快执行速度。