原始 new[]/delete[] 与 std::vector 的优化
Optimization of raw new[]/delete[] vs std::vector
让我们来研究一下非常基本的动态分配内存。我们取一个向量 3,设置它的元素和 return 向量的总和。
在第一个测试用例中,我使用了带有 new[]
/delete[]
的原始指针。在第二个我使用 std::vector
:
#include <vector>
int main()
{
//int *v = new int[3]; // (1)
auto v = std::vector<int>(3); // (2)
for (int i = 0; i < 3; ++i)
v[i] = i + 1;
int s = 0;
for (int i = 0; i < 3; ++i)
s += v[i];
//delete[] v; // (1)
return s;
}
(1) 的组装 (new[]
/delete[]
)
main: # @main
mov eax, 6
ret
(2) 的组装 (std::vector
)
main: # @main
push rax
mov edi, 12
call operator new(unsigned long)
mov qword ptr [rax], 0
movabs rcx, 8589934593
mov qword ptr [rax], rcx
mov dword ptr [rax + 8], 3
test rax, rax
je .LBB0_2
mov rdi, rax
call operator delete(void*)
.LBB0_2: # %std::vector<int, std::allocator<int> >::~vector() [clone .exit]
mov eax, 6
pop rdx
ret
两个输出都来自 https://gcc.godbolt.org/ 和 -std=c++14 -O3
在两个版本中,returned 值是在编译时计算的,因此我们只看到 mov eax, 6; ret
.
对于原始 new[]
/delete[]
,动态分配已被完全删除。然而,使用std::vector
,内存被分配、设置和释放。
即使使用未使用的变量 auto v = std::vector<int>(3)
也会发生这种情况:调用new
,设置内存,然后调用delete
。
我意识到这很可能是一个几乎不可能给出的答案,但也许有人有一些见解并且可能会弹出一些有趣的答案。
在 std::vector
情况下,不允许编译器优化删除内存分配的影响因素是什么,就像在原始内存分配情况下一样?
当使用指向动态分配数组的指针时(直接使用 new[] 和 delete[]),编译器优化了对 operator new
和 operator delete
的调用,即使它们有可观察的一面效果。 C++ 标准第 5.3.4 节第 10 段允许此优化:
An implementation is allowed to omit a call to a replaceable global
allocation function (18.6.1.1, 18.6.1.2). When it does so, the storage
is instead provided by the implementation or...
我会在最后显示剩余的句子,这很重要。
这个优化相对较新,因为它首先在 C++14 中被允许(提案 N3664). Clang supported it since 3.4。最新版本的 gcc,即 5.3.0,没有利用 as-if规则。它产生以下代码:
main:
sub rsp, 8
mov edi, 12
call operator new[](unsigned long)
mov DWORD PTR [rax], 1
mov DWORD PTR [rax+4], 2
mov rdi, rax
mov DWORD PTR [rax+8], 3
call operator delete[](void*)
mov eax, 6
add rsp, 8
ret
MSVC 2013 也不支持此优化。它产生以下代码:
main:
sub rsp,28h
mov ecx,0Ch
call operator new[] ()
mov rcx,rax
mov dword ptr [rax],1
mov dword ptr [rax+4],2
mov dword ptr [rax+8],3
call operator delete[] ()
mov eax,6
add rsp,28h
ret
我目前无法访问 MSVC 2015 Update 1,因此我不知道它是否支持此优化。
最后是icc 13.0.1生成的汇编代码:
main:
push rbp
mov rbp, rsp
and rsp, -128
sub rsp, 128
mov edi, 3
call __intel_new_proc_init
stmxcsr DWORD PTR [rsp]
mov edi, 12
or DWORD PTR [rsp], 32832
ldmxcsr DWORD PTR [rsp]
call operator new[](unsigned long)
mov rdi, rax
mov DWORD PTR [rax], 1
mov DWORD PTR [4+rax], 2
mov DWORD PTR [8+rax], 3
call operator delete[](void*)
mov eax, 6
mov rsp, rbp
pop rbp
ret
很明显,它不支持这个优化。我无法访问最新版本的 icc,即 16.0.
所有这些代码片段都是在启用优化的情况下生成的。
使用 std::vector
时,所有这些编译器都没有优化分配。当编译器不执行优化时,要么是因为某些原因它不能执行,要么就是还不受支持。
What are the contributing factors that don't allow compiler
optimizations to remove the memory allocation in the std::vector case,
like in the raw memory allocation case?
编译器没有执行优化,因为不允许这样做。要看这个,我们来看5.3.4第10段剩下的句子:
An implementation is allowed to omit a call to a replaceable global
allocation function (18.6.1.1, 18.6.1.2). When it does so, the storage
is instead provided by the implementation or provided by extending the
allocation of another new-expression.
这就是说,只有当它源自 new-expression 时,您才可以忽略对可替换全局分配函数的调用。 Anew-expression同节第1段定义
下面的表达式
new int[3]
是一个 new-expression,因此允许编译器优化关联的分配函数调用。
另一方面,以下表达式:
::operator new(12)
不是 new-expression(参见 5.3.4 第 1 段)。这只是一个函数调用表达式。换句话说,这被视为典型的函数调用。此函数无法优化掉,因为它是从另一个共享库导入的(即使您静态链接运行时,函数本身也会调用另一个导入的函数)。
std::vector
使用的默认分配器使用 ::operator new
分配内存,因此不允许编译器优化它。
我们来测试一下。这是代码:
int main()
{
int *v = (int*)::operator new(12);
for (int i = 0; i < 3; ++i)
v[i] = i + 1;
int s = 0;
for (int i = 0; i < 3; ++i)
s += v[i];
delete v;
return s;
}
使用Clang 3.7编译得到如下汇编代码:
main: # @main
push rax
mov edi, 12
call operator new(unsigned long)
movabs rcx, 8589934593
mov qword ptr [rax], rcx
mov dword ptr [rax + 8], 3
test rax, rax
je .LBB0_2
mov rdi, rax
call operator delete(void*)
.LBB0_2:
mov eax, 6
pop rdx
ret
这与使用 std::vector
时生成的汇编代码完全相同,除了 mov qword ptr [rax], 0
来自 std::vector 的构造函数(编译器应该删除它但没有这样做所以因为它的优化算法存在缺陷)。
让我们来研究一下非常基本的动态分配内存。我们取一个向量 3,设置它的元素和 return 向量的总和。
在第一个测试用例中,我使用了带有 new[]
/delete[]
的原始指针。在第二个我使用 std::vector
:
#include <vector>
int main()
{
//int *v = new int[3]; // (1)
auto v = std::vector<int>(3); // (2)
for (int i = 0; i < 3; ++i)
v[i] = i + 1;
int s = 0;
for (int i = 0; i < 3; ++i)
s += v[i];
//delete[] v; // (1)
return s;
}
(1) 的组装 (new[]
/delete[]
)
main: # @main
mov eax, 6
ret
(2) 的组装 (std::vector
)
main: # @main
push rax
mov edi, 12
call operator new(unsigned long)
mov qword ptr [rax], 0
movabs rcx, 8589934593
mov qword ptr [rax], rcx
mov dword ptr [rax + 8], 3
test rax, rax
je .LBB0_2
mov rdi, rax
call operator delete(void*)
.LBB0_2: # %std::vector<int, std::allocator<int> >::~vector() [clone .exit]
mov eax, 6
pop rdx
ret
两个输出都来自 https://gcc.godbolt.org/ 和 -std=c++14 -O3
在两个版本中,returned 值是在编译时计算的,因此我们只看到 mov eax, 6; ret
.
对于原始 new[]
/delete[]
,动态分配已被完全删除。然而,使用std::vector
,内存被分配、设置和释放。
即使使用未使用的变量 auto v = std::vector<int>(3)
也会发生这种情况:调用new
,设置内存,然后调用delete
。
我意识到这很可能是一个几乎不可能给出的答案,但也许有人有一些见解并且可能会弹出一些有趣的答案。
在 std::vector
情况下,不允许编译器优化删除内存分配的影响因素是什么,就像在原始内存分配情况下一样?
当使用指向动态分配数组的指针时(直接使用 new[] 和 delete[]),编译器优化了对 operator new
和 operator delete
的调用,即使它们有可观察的一面效果。 C++ 标准第 5.3.4 节第 10 段允许此优化:
An implementation is allowed to omit a call to a replaceable global allocation function (18.6.1.1, 18.6.1.2). When it does so, the storage is instead provided by the implementation or...
我会在最后显示剩余的句子,这很重要。
这个优化相对较新,因为它首先在 C++14 中被允许(提案 N3664). Clang supported it since 3.4。最新版本的 gcc,即 5.3.0,没有利用 as-if规则。它产生以下代码:
main:
sub rsp, 8
mov edi, 12
call operator new[](unsigned long)
mov DWORD PTR [rax], 1
mov DWORD PTR [rax+4], 2
mov rdi, rax
mov DWORD PTR [rax+8], 3
call operator delete[](void*)
mov eax, 6
add rsp, 8
ret
MSVC 2013 也不支持此优化。它产生以下代码:
main:
sub rsp,28h
mov ecx,0Ch
call operator new[] ()
mov rcx,rax
mov dword ptr [rax],1
mov dword ptr [rax+4],2
mov dword ptr [rax+8],3
call operator delete[] ()
mov eax,6
add rsp,28h
ret
我目前无法访问 MSVC 2015 Update 1,因此我不知道它是否支持此优化。
最后是icc 13.0.1生成的汇编代码:
main:
push rbp
mov rbp, rsp
and rsp, -128
sub rsp, 128
mov edi, 3
call __intel_new_proc_init
stmxcsr DWORD PTR [rsp]
mov edi, 12
or DWORD PTR [rsp], 32832
ldmxcsr DWORD PTR [rsp]
call operator new[](unsigned long)
mov rdi, rax
mov DWORD PTR [rax], 1
mov DWORD PTR [4+rax], 2
mov DWORD PTR [8+rax], 3
call operator delete[](void*)
mov eax, 6
mov rsp, rbp
pop rbp
ret
很明显,它不支持这个优化。我无法访问最新版本的 icc,即 16.0.
所有这些代码片段都是在启用优化的情况下生成的。
使用 std::vector
时,所有这些编译器都没有优化分配。当编译器不执行优化时,要么是因为某些原因它不能执行,要么就是还不受支持。
What are the contributing factors that don't allow compiler optimizations to remove the memory allocation in the std::vector case, like in the raw memory allocation case?
编译器没有执行优化,因为不允许这样做。要看这个,我们来看5.3.4第10段剩下的句子:
An implementation is allowed to omit a call to a replaceable global allocation function (18.6.1.1, 18.6.1.2). When it does so, the storage is instead provided by the implementation or provided by extending the allocation of another new-expression.
这就是说,只有当它源自 new-expression 时,您才可以忽略对可替换全局分配函数的调用。 Anew-expression同节第1段定义
下面的表达式
new int[3]
是一个 new-expression,因此允许编译器优化关联的分配函数调用。
另一方面,以下表达式:
::operator new(12)
不是 new-expression(参见 5.3.4 第 1 段)。这只是一个函数调用表达式。换句话说,这被视为典型的函数调用。此函数无法优化掉,因为它是从另一个共享库导入的(即使您静态链接运行时,函数本身也会调用另一个导入的函数)。
std::vector
使用的默认分配器使用 ::operator new
分配内存,因此不允许编译器优化它。
我们来测试一下。这是代码:
int main()
{
int *v = (int*)::operator new(12);
for (int i = 0; i < 3; ++i)
v[i] = i + 1;
int s = 0;
for (int i = 0; i < 3; ++i)
s += v[i];
delete v;
return s;
}
使用Clang 3.7编译得到如下汇编代码:
main: # @main
push rax
mov edi, 12
call operator new(unsigned long)
movabs rcx, 8589934593
mov qword ptr [rax], rcx
mov dword ptr [rax + 8], 3
test rax, rax
je .LBB0_2
mov rdi, rax
call operator delete(void*)
.LBB0_2:
mov eax, 6
pop rdx
ret
这与使用 std::vector
时生成的汇编代码完全相同,除了 mov qword ptr [rax], 0
来自 std::vector 的构造函数(编译器应该删除它但没有这样做所以因为它的优化算法存在缺陷)。