优化raw new [] / delete [] vs std :: vector

时间:2016-01-04 12:12:05

标签: c++ vector c++14 compiler-optimization

让我们搞乱非常基本的动态分配内存。我们取3的向量,设置其元素并返回向量的总和。

在第一个测试用例中,我使用了一个带有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

在两个版本中,返回值都是在编译时计算的,因此我们只看到mov eax, 6; ret

使用原始new[] / delete[],动态分配已完全删除。但是,使用std::vector时,将分配,设置和释放内存。

即使使用未使用的变量,也会发生 auto v = std::vector<int>(3):调用new,设置内存然后调用delete

我意识到这很可能是一个几乎不可能给出的答案,但也许某人有一些见解,可能会出现一些有趣的答案。

std::vector情况下,允许编译器优化删除内存分配的原因是什么,如原始内存分配情况?

1 个答案:

答案 0 :(得分:14)

当使用指向动态分配的数组的指针(直接使用new []和delete [])时,编译器优化了对operator newoperator delete的调用,即使它们具有可观察到的副作用。 C ++标准第5.3.4节第10段允许进行此优化:

  

允许实现省略对可替换全局的调用   分配函数(18.6.1.1,18.6.1.2)。当它这样做时,存储   而是由实现提供或...

我会在最后显示句子的其余部分,这是至关重要的。

此优化相对较新,因为它首先在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时,所有这些编译器都没有优化分配。当编译器没有执行优化时,它可能是因为它由于某种原因而不能或者它还没有得到支持。

  

不允许编译的因素是什么?   优化以删除std :: vector中的内存分配,   比如在原始内存分配情况下?

编译器没有执行优化,因为它不被允许。为了看到这一点,让我们看看5.3.4中第10段句子的其余部分:

  

允许实现省略对可替换全局的调用   分配函数(18.6.1.1,18.6.1.2)。当它这样做时,存储   而是由实现提供或通过扩展提供   分配另一个新表达

这就是说,只有当它来自new-expression时,才能省略对可替换全局分配函数的调用。新表达式在同一部分的第1段中定义。

以下表达式

new int[3]

是一个new-expression,因此允许编译器优化掉相关的分配函数调用。

另一方面,以下表达式:

::operator new(12)

不是新表达式(见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时生成的汇编代码完全相同,除了来自std :: vector的构造函数的mov qword ptr [rax], 0之外(编译器应该删除它但是由于优化算法的一个缺陷)。