我有一个我正在测试的简单示例,我注意到当涉及operator new时,gcc优化(-O3)似乎不如clang那样好.我想知道可能是什么问题,是否有可能迫使gcc以某种方式生成更优化的代码?
template<typename T>
T* create() { return new T(); }
int main() {
auto result = 0;
for (auto i = 0; i < 1000000; ++i) {
result += (create<int>() != nullptr);
}
return result;
}
#clang3.6++ -O3 -s --std=c++11 test.cpp
#size a.out
text data bss dec hex filename
1324 616 8 1948 79c a.out
#time ./a.out
real 0m0.002s
user 0m0.001s
sys 0m0.000s
#gcc4.9 -O3 -s --std=c++11 test.cpp
#size a.out
text data bss dec hex filename
1484 …Run Code Online (Sandbox Code Playgroud) 另一个问题讨论优化器删除对以下调用的合法性new:是否允许编译器优化堆内存分配?。我已阅读问题、答案和N3664。
根据我的理解,编译器可以在“as-if”规则下删除或合并动态分配,即,相对于标准中定义的抽象机,结果程序的行为就像没有进行任何更改一样。
我测试了使用 clang++ 和 g++ 编译以下两个文件程序以及-O1优化,但我不明白如何允许删除分配。
// main.cpp
#include <cstdio>
extern int g_alloc;
static int* foo(int n)
{
// operator new is globally overridden in the other file.
return new int(n);
}
int main(int argc, char** argv)
{
foo(argc);
foo(argc*2);
printf("allocated: %d\n", g_alloc);
return g_alloc;
}
Run Code Online (Sandbox Code Playgroud)
// new.cpp
#include <cstdio>
#include <cstdlib>
#include <new>
int g_alloc = 0;
void* operator new(size_t n)
{
g_alloc += n;
printf("new %lu\n", n);
return malloc(n); …Run Code Online (Sandbox Code Playgroud) 请考虑以下简化示例:
#include <utility>
#include <memory>
int test_lack()
{
auto lam = []
{
return 10;
};
// move lam to the heap
void* ptr = new decltype(lam)(std::move(lam));
// retrieve the result of lam
int res = (*static_cast<decltype(lam)*>(ptr))();
if (ptr) // important
delete static_cast<decltype(lam)*>(ptr);
return res;
}
Run Code Online (Sandbox Code Playgroud)
test_lack():
sub rsp, 8
mov edi, 1
call operator new(unsigned long)
mov esi, 1
mov rdi, rax
call operator delete(void*, unsigned long)
mov eax, 10
add rsp, 8
ret
Run Code Online (Sandbox Code Playgroud)
以下两段是从N4140复制的(重点是我的).
§5.3.4/ 11:
当一个新的表达式调用的分配功能和分配已经不 被延长,则新的表达通过的空间要求的分配的功能类型的第一个参数的量
std::size_t.该参数不得小于正在创建的对象的大小; 仅当对象是数组时,它可能大于正在创建的对象的大小.对于数组char和unsigned char,new-expression的结果之间的差异并且分配函数返回的地址应该是任何对象类型的最严格的基本对齐要求(3.11)的整数倍,该对象类型的大小不大于正在创建的数组的大小.[注意:因为假定分配函数返回指向存储的指针,该存储适当地对齐具有基本对齐的任何类型的对象,所以这种对数组分配开销的约束允许分配字符数组的常用习惯用法,稍后将放置其他类型的对象. - 尾注]
§5.4.3/ 12
当一个新的表达式调用的分配功能和分配已 被延长,其尺寸参数来分配呼叫不得超过上述指定尺寸被省略呼叫的总和,再加上大小为扩展呼叫有它未被扩展,加上在分配的内存中对齐已分配对象所需的任何填充.
我可以理解§5.3.4/ 11,但如上文§5.4.3/ 12所述,扩展的分配函数的概念对我来说是不可理解的.
仅供参考:我在 CLion。这只是我好奇的事情,但我编写了一个程序来使用我所有的内存来娱乐。它创建了一个int* array = new int[3'900'000'000]{};.
当我在调试构建选项中运行该程序时,它确实使用了我的所有内存。但在发布模式下,它不会耗尽我的任何内存。我什至尝试修改数组中的某些值,看看这是否会强制编译器创建完整的数组。在发布模式下,我还尝试使用该方法fill并手动使用for循环来填充数组中的每个数字。
我知道这std::function是用类型擦除惯用法实现的。类型擦除是一种方便的技术,但它的缺点是它需要在堆上存储底层对象的寄存器(某种数组)。
因此,当创建或复制function对象时,需要进行分配,因此该过程应该比简单地将函数作为模板类型进行操作要慢。
为了检查这个假设,我运行了一个测试函数,该函数累积n = cycles连续的整数,然后将总和除以增量数n。首先编码为模板:
#include <iostream>
#include <functional>
#include <chrono>
using std::cout;
using std::function;
using std::chrono::system_clock;
using std::chrono::duration_cast;
using std::chrono::milliseconds;
double computeMean(const double start, const int cycles) {
double tmp(start);
for (int i = 0; i < cycles; ++i) {
tmp += i;
}
return tmp / cycles;
}
template<class T>
double operate(const double a, const int b, T myFunc) {
return myFunc(a, b);
}
Run Code Online (Sandbox Code Playgroud)
和main.cpp:
int …Run Code Online (Sandbox Code Playgroud) c++ ×6
gcc ×3
c++11 ×2
c++14 ×1
clang ×1
clion ×1
new-operator ×1
optimization ×1
ram ×1
templates ×1