-
-
[原创]Mingw64下的性能优化版Binutils
-
发表于:
2023-4-21 11:56
5355
-
[原创]Mingw64下的性能优化版Binutils
长久以来,GNU工具链的性能问题饱受争议。这也是出现gold的原因之一,连自已人都忍受不了。而在windows平台,这个问题尤为严重。虽然lld能部分替代ld,但一来lld尺寸巨大,二来总归还是和ld有不兼容的情况,手工改来改去很是麻烦。
于是,决定动手研究一下,看有没有改进空间。花了一天时间研究binutils的代码,还真是解决了一些问题。
所有的主要问题都集中在bfd库里。
首先,库中对COFF数据的处理十分阳春白雪,没有半点修饰。有大量按索引定位段的逻辑,直接就是顺序查找链表。此为性能之原罪。见coffgen.c中的coff_section_from_bfd_index。
其次,为了定位COMDAT符号,每次都会从头查找符号表。而最后仅仅是为了计算一个无关紧要的标志位。其中,为了一些结构性的设计,数据读取要经过几个抽象层次。明明在x86/x64平台上能直接读取little-endian的数据,非要绕个大圈子,一个字节一个字节去读。
第三,不知道是故意还是无意,计算PE校验和的时候,是一个字节一个字节读取,并且每次读取前还要用seek来定位文件偏移。处理大文件时极慢。
最后,由于没有快速定位段的手段,coff_amd64_rtype_to_howto/coff_i386_rtype_to_howto中直接硬编码从头查找链表,还在注释里表示,没有什么办法,只能硬来。
解决办法很简单,我给bfd添加了一个按需创建的段索引表,当要通过索引定位段时,会自动创建/更新此表,然后使用这个表来定位段。coff_amd64_rtype_to_howto/coff_i386_rtype_to_howto中直接使用优化过的coff_section_from_bfd_index来定位段。计算PE校验和的逻辑也重写了一下,批量进行数据读取并计算。关于COMDAT的处理,由于牵扯很深,一时半会儿难以优化(主要是完全不熟悉binutils的代码,第一次看),但加了一个查找偏移缓存,不用每次都从头查找符号表。
经过这一系列优化,binutil在windows上的性能得以巨大改善。如下:
| v2.40 | 优化后 | 说明 |
ranlib给库添加索引 | 15.1s | 7.7s | 库>500MB |
strip删除库的调试符号 | 7.1s | 2.9s |
|
ar创建库 | 6.9s | 3.3s |
|
ld链接exe带符号 | 241s | 9.1s |
|
ld链接exe不带符号 | 21.7s | 7.8s | ld -s |
编译我的记事本 | 444.2s | 402.2s | 欢迎下载试用,吐槽^_^ |
经过优化,编译大型带调试信息的可执行文件,性能提升了20倍以上。其它基本操作的性能也都翻倍了。以前因为调试版本的程序链接慢,一般不太用gcc进行调试,经过优化,现在已经基本可用了。
哪怕针对整个工程的编译,由于binutils的优化,性能也提升了有10%。
补丁已经提交给binutils项目,接不接收就不是我能控制的了。有想尝鲜的朋友可以在这下载编译好的版本,可以与msys2中的版本对比一下。
另:BFD也是GDB加载符号的基础,这部分优化对于GDB加载大型目标慢的问题也应该有所改善,回头给GDB也打个补丁试试。
2023-04-21补充:GDB已经验证过,性能有约20%提升。不过似乎GDB 13.1在加载巨型目标时没有印象中那么慢了,以前动则几十秒的加载时间现在变成了1秒以内。所以,这20%的提升也显得无关痛痒了。
(内容转自我昨天发在知乎上的文章,感觉这个东西在论坛内更有价值,因而转发与此。为避免有人说我抄袭,特此备注)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)