C++ 中使用 std::vector 进行转义分析

问题描述 投票:0回答:2

我想知道 Clang 或 GCC 中是否有任何优化选项可用于对 C++ 中的

std::vector
进行转义分析。 由于下面示例中的
std::vector<int>
不需要将
v
的实际数据分配在堆或栈中。编译器实际上可以在堆栈上分配
v.data()
以获得更好的性能。

  1. 假设Clang/GCC不做转义分析,是否有什么特别的动机不使用转义分析?

  2. 假设Clang/GCC进行了逃逸分析,为什么

    v.data()
    &x
    的值如此不同?

#include<cstdio>
#include<vector>
int main() {
    int x = 0;
    std::vector<int> v(3, 0);
    std::printf("&x: %p\n", &x);
    //std::printf("&v: %p\n", &v); // we intentionally don't print the pointer to v here.
    std::printf("v.data(): %p\n", v.data());
    return x + v[0]; // we want compiler not to optimize everything out
}

预期结果

&x: <some address>
v.data(): <some address> + 4

Clang 和 GCC 的实际结果

[*****@localhost test]$ g++ test.cc -O3
[khanh@localhost test]$ ./a.out 
&x: 0x7ffe2af5a59c
v.data(): 0xadde70
[*****@localhost test]$ clang++ test.cc -O3
[*****@localhost test]$ ./a.out 
&x: 0x7fff66ce1ab4
v.data(): 0xfeee70

谢谢!

c++ compiler-optimization escape-analysis
2个回答
3
投票

Clang编译器存在逃逸分析。

示例代码:来自@geza https://godbolt.org/z/N1GLUI

int fn(int a, int b, int c) {
    int *t = new int[3];

    t[0] = a;
    t[1] = b;
    t[2] = c;

    int r = t[0]+t[1]+t[2];

    delete[] t;

    return r;
}

海湾合作委员会

fn(int, int, int):
  push r12
  mov r12d, edx
  push rbp
  mov ebp, esi
  push rbx
  mov ebx, edi
  mov edi, 12
  call operator new[](unsigned long)
  mov DWORD PTR [rax], ebx
  add ebx, ebp
  mov rdi, rax
  mov DWORD PTR [rax+4], ebp
  mov DWORD PTR [rax+8], r12d
  add r12d, ebx
  call operator delete[](void*)
  mov eax, r12d
  pop rbx
  pop rbp
  pop r12
  ret

叮当

fn(int, int, int):                               # @fn(int, int, int)
        lea     eax, [rdi + rsi]
        add     eax, edx
        ret

0
投票

gcc和clang都存在逃逸分析,但由于逃逸属性的依赖,对其他优化很敏感。

考虑一些例子:

int fn3(int a, int b, int c) {
    int *t = new int[3];

    t[0] = a;
    t[1] = b;
    t[2] = c;

    int r = t[0]+t[1]+t[2];

    delete[] t;

    return r;
}

这个例子没有使用逃逸分析,事实上,它通过另一种策略进行优化,称为复制传播。 复制传播 make

r = t[0] + t[1] + t[2];

成为

t = a + b + c;

了解更多详细信息:复制传播(来自 geeksforgeeks)

考虑这个例子:

int fn(int a, int b, int c, int len) {
    int *t = new int[len];

    for(int i = 0; i < len; ++i) {
        if(i % 3 == 0) {
            t[i] = a;
        } else if(i % 3 == 1) {
            t[i] = b;
        } else {
            t[i] = c;
        }
    }

    int r = t[len % a] + t[len - 1] + t[0] + t[len / c];

    delete[] t;

    return r;
}

动态数组没有逃逸函数作用域,但编译器没有将其转换为堆栈分配。

ARM gcc 13.2.0 产生:

fn(int, int, int, int):
  push {r3, r4, r5, r6, r7, r8, r9, lr}
  mov r4, r3
  mvn r3, #-536870912
  cmp r4, r3
  bcs .L19
  lsls r7, r4, #2
  mov r9, r0
  mov r0, r7
  mov r8, r1
  mov r6, r2
  bl operator new[](unsigned int) 
  // ...  

您可以从这里找到整个输出和其他示例:godbolt

© www.soinside.com 2019 - 2024. All rights reserved.