我正在使用GCC为ARM开发一个C。我遇到了一个问题,我没有启用优化,我无法为我的代码创建二进制(ELF),因为它不适合可用空间。然而,如果我只是启用调试优化(-Og),这是我所知的最低优化,代码很容易适合。
在这两种情况下,都启用了-ffunction-节、-fdata-节、-fno-异常和-Wl、--gc-节。
即使进行了最小的优化,二进制大小也存在巨大差异。
我查看了控制优化的3.11选项,了解了使用-Og标志执行哪些优化的详细信息,看看这是否能让我有所了解。
什么优化标志对二进制大小影响最大?我应该寻找什么来解释这种巨大的差异吗?
快并不意味着小,事实上,速度优化的很大一部分是围绕循环展开展开的,这使得代码生成增加了很多。
如果要优化大小,请使用-Os
,这相当于-O2
,除了所有增加大小的优化(同样,如循环展开)。
对于未优化的构建,大多数代码大小是这样一个事实:默认的-O0
也意味着一个调试构建,即使在同一个函数中使用GDBj
命令跳转到不同的源代码行,也不在语句的寄存器中保留任何内容以进行一致的调试-O0
意味着大量的存储/重新加载,即使是最轻微的优化,对于不能使用内存源操作数的非CISC ISA上的代码大小来说,尤其是灾难性的。为什么clang使用-O0(对于这个简单的浮点和)生成低效的asm?
特别是对于现代C,调试构建是灾难性的,因为简单的模板包装函数通常在简单情况下(或者可能是一条指令)内联并优化为零,而编译为实际的函数调用,这些函数调用必须设置参数并运行调用指令。e、 g.对于std::vector
,运算符[]
成员函数通常可以内联到单个ldr
指令,前提是编译器具有。data()
寄存器中的指针。但如果没有内联,每个呼叫站点都会接受多条指令1
影响代码大小的选项最多:一般来说,分支目标的对齐,或者只是循环,都会消耗一些代码大小。除此之外:
>
-ftree vectorize
-生成SIMD版本循环,如果编译器无法证明迭代计数是向量宽度的倍数,则还需要进行标量清理。(或者,如果不使用restrict
,则指向数组的数组是不重叠的;这可能还需要标量回退)。在GCC11和更早版本中的-O3
处启用。在GCC12和更高版本中的-O2
处启用,如clang。
-funroll循环
/-funroll所有循环
-即使在现代GCC中的-O3
默认情况下也未启用。启用配置文件引导优化(-fprofile use
),当它有来自-fprofile generate
构建的配置文件数据时,可以知道哪些循环实际上是热门的,值得花费代码大小。(它们是冷的,因此应该针对大小进行优化,以便在运行时获得更少的I-cache未命中,并减少对其他代码的逐出。)PGO还影响矢量化决策。
-Os
-优化大小和速度,尽量不要牺牲太多速度。如果您的代码有很多I-cache未命中,那么这是一个很好的权衡,否则-O3
通常更快,或者至少这是GCC的设计目标。可以尝试不同的选项,看看-O2
或-Os
是否能让你的代码在你关心的一些CPU上比-O3
更快;有时,某些微体系结构的遗漏优化或怪癖会造成不同,比如,如果我针对大小而不是速度进行优化,为什么GCC生成的代码要快15-20%?有GCC4的实际基准。6到4.8(当时的电流),用于测试程序中的特定小循环,在许多不同的x86和ARM CPU上,使用和不使用-march=native
来实际调整它们。但是,没有理由期望它能代表其他代码,所以您需要为自己的代码库测试自己。(对于任何给定的循环,微小的代码更改都可以使不同的编译选项在任何给定的CPU上更好地运行。)
显然,-Os
非常有用,如果您需要更小的静态代码大小来适应某些大小限制。
Clang有类似的选项,包括-Os
。但与GCC不同的是,它还有一个clang-Oz
选项,只针对大小进行优化,而不考虑速度。它非常具有攻击性,例如在x86上使用代码高尔夫技巧,比如push 1;pop rax(总共3个字节)而不是mov eax,1个(5个字节)。
不幸的是,GCC的
-Os
选择使用div
而不是乘法逆除以常数,这会耗费大量的速度,但不会节省太多(如果有的话)。(https://godbolt.org/z/x9h4vx1YG适用于x86-64)。对于ARM,如果不使用-mcpu=
,GCC-Os
仍然使用一个反比,这意味着udiv
甚至可用,否则它使用udiv
:https://godbolt.org/z/f4sa9Wqcj .
Clang的
-Os
仍然使用带umull
的乘法逆,只使用带-Oz
的udiv
。(或者调用\uu aeabi\u uidiv
助手函数,而不使用任何-mcpu
选项)。因此,在这方面,clang-Os
比GCC做了更好的权衡,仍然需要花费一点代码大小来避免缓慢的整数除法。
#include <vector>
int foo(std::vector<int> &v) {
return v[0] + v[1];
}
带有默认
-O0
vs.-Os
for-mcpu=cortex-m7
只是为了随机选择一些东西。IDK如果在实际的微控制器上使用动态容器(如std::向量
)是正常的;可能不会。
# -Os (same as -Og for this case, actually, omitting the frame pointer for this leaf function)
foo(std::vector<int, std::allocator<int> >&):
ldr r3, [r0] @ load the _M_start member of the reference arg
ldrd r0, r3, [r3] @ load a pair of words (v[0..1]) from there into r0 and r3
add r0, r0, r3 @ add them into the return-value register
bx lr
与调试构建(asm已启用名称Demanling)
# GCC -O0 -mcpu=cortex-m7 -mthumb
foo(std::vector<int, std::allocator<int> >&):
push {r4, r7, lr} @ non-leaf function requires saving LR (the return address) as well as some call-preserved registers
sub sp, sp, #12
add r7, sp, #0 @ Use r7 as a frame pointer. -O0 defaults to -fno-omit-frame-pointer
str r0, [r7, #4] @ spill the incoming register arg to the stack
movs r1, #0 @ 2nd arg for operator[]
ldr r0, [r7, #4] @ reload the pointer to the control block as the first arg
bl std::vector<int, std::allocator<int> >::operator[](unsigned int)
mov r3, r0 @ useless copy, but hey we told GCC not to spend any time optimizing.
ldr r4, [r3] @ deref the reference (pointer) it returned, into a call-preserved register that will survive across the next call
movs r1, #1 @ arg for the v[1] operator[]
ldr r0, [r7, #4]
bl std::vector<int, std::allocator<int> >::operator[](unsigned int)
mov r3, r0
ldr r3, [r3] @ deref the returned reference
add r3, r3, r4 @ v[1] + v[0]
mov r0, r3 @ and copy into the return value reg because GCC didn't bother to add into it directly
adds r7, r7, #12 @ tear down the stack frame
mov sp, r7
pop {r4, r7, pc} @ and return by popping saved-LR into PC
@ and there's an actual implementation of the operator[] function
@ it's 15 instructions long.
@ But only one instance of this is needed for each type your program uses (vector<int>, vector<char*>, vector<my_foo>, etc.)
@ so it doesn't add up as much as each call-site
std::vector<int, std::allocator<int> >::operator[](unsigned int):
push {r7}
sub sp, sp, #12
...
正如您所见,未优化的GCC更关心的是快速编译时间,而不是最简单的事情,比如避免无用的
mov-reg,reg
指令,即使是在计算一个表达式的代码中。
哪些GCC优化标志对二进制大小的影响最大?
根据程序本身的不同,它会有所不同。找出每个标志如何影响程序的最准确方法是尝试并将结果与基准水平进行比较。
大小优化的一个好选择是使用-Os,它可以实现-O2的所有优化,但那些可能显著增加二进制大小的优化除外(目前):
-falign-functions
-falign-jumps
-falign-labels
-falign-loops
-fprefetch-loop-arrays
-freorder-blocks-algorithm=stc
我正在使用GCC4.4.2构建一些大型项目。因为我想构建它以供发布,所以我使用了GCC优化标志,但不幸的是,它在某种程度上弄乱了我的代码,最终的二进制文件没有按照预期工作,当使用标志(或没有优化)构建时,一切都很好。我之前的项目也有类似的问题,当时是标志在优化级别上造成了问题,我通过搜索本文档中提到的所有标志,就优化级别而言,设法发现它是由该特定标志引起的: http://gcc.gnu.org/
存在与循环有关的问题。为什么?也许是虫子? 我使用的是最新的4.8.0,经过测试的x64、x86以及其他版本。都是同样的行为。
我遇到一个情况,其中一个JVM选项,“-d”标志很大,超过1000个字符。这个值有多大的限制吗?
问题内容: 我对 写 时 复制的 理解是:“每个人都有相同数据的单个共享副本,直到被写入,然后再创建一个副本”。 同一数据的共享副本是由堆和bss段组成还是仅由堆组成? 哪些内存段将被共享,这取决于操作系统吗? 问题答案: 操作系统可以设置所需的任何“写时复制”策略,但是通常,它们都执行相同的操作(即最有意义的操作)。 松散地,对于类似POSIX的系统(Linux,BSD,OSX),有四个感兴趣的
主要内容:1.AOF日志的影响,2.对AOF重写和RDB的影响,3.总结,4.如何避免大 KeyRedis 的持久化方式有两种:AOF 日志和 RDB 快照。 1.AOF日志的影响 Redis 提供了 3 种 AOF 日志写回硬盘的策略 Always,这个单词的意思是「总是」,所以它的意思是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘; Everysec,这个单词的意思是「每秒」,所以它的意思是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区
我们的企业应用程序部署在Jboss Wildfly8.2中。jboss控制台日志是在启动过程中使用环境变量JBoss_Console设置的。这确保使用kill-quit触发的任何线程转储都转储到jboss_console.log。GC统计信息(使用-xx:+printgctimestamps-xx:+printgcdetails收集)也发送到此文件。 日志文件的旋转由 此外,du和ls命令的输出也