在g++上进行聚合初始化的std::数组生成大量代码
std::array with aggregate initialization on g++ generates huge code
在g++4.9.2和5.3.1上,此代码需要几秒钟的编译时间,并生成52776字节的可执行文件:
#include <array>
#include <iostream>
int main()
{
constexpr std::size_t size = 4096;
struct S
{
float f;
S() : f(0.0f) {}
};
std::array<S, size> a = {}; // <-- note aggregate initialization
for (auto& e : a)
std::cerr << e.f;
return 0;
}
增加size
似乎线性地增加了编译时间和可执行文件大小。无论是clang 3.5还是Visual C++2015,我都无法重现这种行为。使用-Os
没有区别。
$ time g++ -O2 -std=c++11 test.cpp
real 0m4.178s
user 0m4.060s
sys 0m0.068s
检查汇编代码显示a
的初始化已展开,生成4096movl
指令:
main:
.LFB1313:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
subq $16384, %rsp
.cfi_def_cfa_offset 16400
movl $0x00000000, (%rsp)
movl $0x00000000, 4(%rsp)
movq %rsp, %rbx
movl $0x00000000, 8(%rsp)
movl $0x00000000, 12(%rsp)
movl $0x00000000, 16(%rsp)
[...skipping 4000 lines...]
movl $0x00000000, 16376(%rsp)
movl $0x00000000, 16380(%rsp)
只有当T
有一个非平凡的构造函数并且使用{}
初始化数组时,才会发生这种情况。如果我做以下任何一项,g++会生成一个简单的循环:
- 删除
S::S()
- 在类中移除
S::S()
并初始化S::f
- 删除聚合初始化(
= {}
) - 不使用
-O2
进行编译
我完全赞成将循环展开作为一种优化,但我认为这不是一个很好的优化。在我报告这是一个错误之前,有人能确认这是否是预期的行为吗?
[edit:我为此打开了一个新的bug,因为其他bug似乎不匹配。它们更多的是关于长编译时间,而不是奇怪的代码生成。]
似乎有一个相关的bug报告,bug 59659-初始化std::array编译时间过长。对于4.9.0,它被认为是"固定的",所以我认为这个测试用例要么是回归,要么是补丁没有覆盖的边缘用例。值得一提的是,错误报告的两个测试用例1,2在GCC 4.9.0和5.3.1 上都对我表现出症状
还有两个相关的错误报告:
错误68203-А关于带有-std=c++11 的嵌套对数组的结构的无限编译时间
Andrew Pinski 2015-11-04 07:56:57 UTC
这很可能是内存占用,会产生大量默认值构造函数,而不是它们之上的循环。
那个声称是这个的复制品:
Bug 56671-Gcc使用大量的内存和处理器功率与大型C++11位集
Jonathan Wakely 2016-01-26 15:12:27 UTC
生成此constexpr构造函数的数组初始化是问题:
constexpr _Base_bitset(unsigned long long __val) noexcept : _M_w{ _WordT(__val) } { }
事实上,如果我们将其更改为S a[4096] {};
,我们就不会遇到问题。
使用perf
,我们可以看到GCC在哪里花费了大部分时间。第一:
perf record g++ -std=c++11 -O2 test.cpp
然后perf report
:
10.33% cc1plus cc1plus [.] get_ref_base_and_extent
6.36% cc1plus cc1plus [.] memrefs_conflict_p
6.25% cc1plus cc1plus [.] vn_reference_lookup_2
6.16% cc1plus cc1plus [.] exp_equiv_p
5.99% cc1plus cc1plus [.] walk_non_aliased_vuses
5.02% cc1plus cc1plus [.] find_base_term
4.98% cc1plus cc1plus [.] invalidate
4.73% cc1plus cc1plus [.] write_dependence_p
4.68% cc1plus cc1plus [.] estimate_calls_size_and_time
4.11% cc1plus cc1plus [.] ix86_find_base_term
3.41% cc1plus cc1plus [.] rtx_equal_p
2.87% cc1plus cc1plus [.] cse_insn
2.77% cc1plus cc1plus [.] record_store
2.66% cc1plus cc1plus [.] vn_reference_eq
2.48% cc1plus cc1plus [.] operand_equal_p
1.21% cc1plus cc1plus [.] integer_zerop
1.00% cc1plus cc1plus [.] base_alias_check
除了GCC开发人员之外,这对任何人来说都没有多大意义,但看看是什么占用了这么多编译时间仍然很有趣。
Clang 3.7.0在这方面比GCC做得更好。在-O2
中,编译所需时间不到一秒钟,生成的可执行文件要小得多(8960字节),而这个程序集:
0000000000400810 <main>:
400810: 53 push rbx
400811: 48 81 ec 00 40 00 00 sub rsp,0x4000
400818: 48 8d 3c 24 lea rdi,[rsp]
40081c: 31 db xor ebx,ebx
40081e: 31 f6 xor esi,esi
400820: ba 00 40 00 00 mov edx,0x4000
400825: e8 56 fe ff ff call 400680 <memset@plt>
40082a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0]
400830: f3 0f 10 04 1c movss xmm0,DWORD PTR [rsp+rbx*1]
400835: f3 0f 5a c0 cvtss2sd xmm0,xmm0
400839: bf 60 10 60 00 mov edi,0x601060
40083e: e8 9d fe ff ff call 4006e0 <_ZNSo9_M_insertIdEERSoT_@plt>
400843: 48 83 c3 04 add rbx,0x4
400847: 48 81 fb 00 40 00 00 cmp rbx,0x4000
40084e: 75 e0 jne 400830 <main+0x20>
400850: 31 c0 xor eax,eax
400852: 48 81 c4 00 40 00 00 add rsp,0x4000
400859: 5b pop rbx
40085a: c3 ret
40085b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
另一方面,对于GCC 5.3.1,在没有优化的情况下,它编译得非常快,但仍然可以生成95328大小的可执行文件。使用-O2
进行编译将可执行文件的大小减少到53912,但编译时间需要4秒。我肯定会向他们的bugzilla报告这件事。
您的GCC错误71165,然后与92385合并,已在GCC 12上修复。
https://gcc.godbolt.org/z/eGMq16esP
- 使用动态分配的数组会导致代码分析发出虚假的C6386缓冲区溢出警告
- 为什么我需要C++中不同的排序格式来对这个USACO代码上的数组和优先级队列进行排序
- 有没有一种代码密度较低的方法来使用非默认构造函数初始化数组?
- 请解释字谜的代码,我看不懂计数器数组,每个值已经是0
- 代码使用向量成功运行,但使用数组显示错误
- C++逗号分隔的输入数组代码过早退出
- 如何修改代码以打印出数组 v2
- 我正在尝试解决一个需要数组总和值但代码不起作用的问题,我想做这样的事情
- 如何在 c# 代码中通过引用调用时从 c++ dll 更新数组值?
- 使用基于数组和范围的 For 循环替换一些基本代码行
- G++ 发出警告,要求删除一个代码的数组,但不删除另一个代码的数组
- 为什么我的代码在尝试复制字符数组时引发 C6386 错误?
- 我的旋转数组代码给出了大数组大小的错误
- 如何修复将传输到 java 的 2d 数组代码使用 JNI
- [C++]重新定义 y 值的基本数组代码。它正在工作,但无法解释为什么
- 为什么我的数组代码的反向不能正常工作
- C++ 数组代码错误
- 这个字符数组代码有什么问题?
- 我的数组代码有什么问题
- 为什么相同的数组代码的执行时间之间存在差异