C++ 编译器内存优化-重用现有块

C++ 编译器内存优化-重用现有块,c++,c,memory-management,compiler-optimization,C++,C,Memory Management,Compiler Optimization,假设我要分配2个内存块。 我使用第一个内存块来存储一些东西,并使用这些存储的数据。 然后我使用第二个内存块做类似的事情 { int a[10]; int b[10]; setup_0(a); use_0(a); setup_1(b); use_1(b); } || compiler optimizes this to this? \/ { int a[10]; setup_0(a); use_0(a); setup_1(a); use_1(a); } // the

假设我要分配2个内存块。 我使用第一个内存块来存储一些东西,并使用这些存储的数据。 然后我使用第二个内存块做类似的事情

{
int a[10];
int b[10];

setup_0(a);
use_0(a);

setup_1(b);
use_1(b);    
}

 || compiler optimizes this to this?
 \/

{
int a[10];

setup_0(a);
use_0(a);

setup_1(a);
use_1(a);  
}

// the setup functions overwrites all 10 words
现在的问题是:如果编译器知道第一个内存块不会被再次引用,编译器是否会对此进行优化,以便重用现有内存块,而不是分配第二个内存块

如果这是真的: 这也适用于动态内存分配吗? 如果内存持续存在于作用域之外,但使用方式与示例中给出的相同,是否也可以这样做? 我假设只有当setup和foo在同一个c文件中实现(与调用代码存在于同一个对象中)时,这才有效

编译器是否对此进行了优化

只有在询问特定编译器时,才能回答此问题。通过检查生成的代码可以找到答案

如果编译器知道第一个内存块不会被再次引用,那么它们会重用现有的内存块,而不是分配第二个内存块吗

这种优化不会改变程序的行为,因此是允许的。另一个问题是:是否有可能证明内存不会被引用?如果可能的话,那么在合理的时间内证明是否足够容易?我很有把握地说,一般来说是不可能证明的,但在某些情况下是可以证明的

我假设只有当setup和foo在同一个c文件中实现(与调用代码存在于同一个对象中)时,这才有效

这通常需要证明内存的不可接触性。理论上,链路时间优化可能会提高这一要求

这也适用于动态内存分配吗

理论上,因为它不会改变程序的行为。但是,动态内存分配通常由库执行,因此编译器可能无法证明没有副作用,因此无法证明删除分配不会改变行为

如果内存持续存在于作用域之外,但使用方式与示例中给出的相同,是否也可以这样做

如果编译器能够证明内存泄漏,那么可能



即使优化是可能的,也不是很重要。节省一点堆栈空间可能对运行时影响很小。如果数组很大,防止堆栈溢出可能会很有用。

由于编译器看到
a
被用作函数的参数,它不会优化
b
。它不能,因为它不知道在使用
a
b
的函数中发生了什么。对于
a
:编译器不知道
a
不再使用了

就编译器而言,
a
的地址例如可以由
setup0
存储在全局变量中,并在使用
b
调用时由
setup1
使用

如果您遵循该链接,您应该会看到在gcc上编译的代码,并使用-O3优化级别进行编译。生成的asm代码非常简单。由于数组中存储的值在编译时是已知的,编译器可以轻松跳过所有内容,直接设置变量a和b。不需要缓冲区。
下面是与示例中提供的代码类似的代码:

您可以看到发送到函数func1和func2的指针不同,因为在调用func1时使用的第一个指针是rsp,在调用func2时使用的是[rsp+48]

您可以看到,在代码是可预测的情况下,要么编译器完全忽略代码。在另一种情况下,至少对于gcc 7和clang 3.9.1,它没有得到优化


虽然不能流利地阅读本文,但很容易看出,在下面的示例中,malloc和free没有通过gcc或clang进行优化(如果您想尝试使用更多编译器,请根据自己的情况,但不要忘记设置优化标志)。 您可以清楚地看到一个对“malloc”的调用,然后是对“free”的调用,两次


优化堆栈空间不太可能真正影响程序的速度,除非您处理大量数据。 优化动态分配的内存更为重要。如果你打算这样做的话,你将不得不使用第三方库或者运行你自己的系统,这不是一个简单的任务


编辑:忘了提及显而易见的一点,这是非常依赖于编译器的。

是的,理论上,编译器可以按照您所描述的优化代码,假设它可以证明这些函数没有修改作为参数传入的数组

但实际上,不,这不会发生。您可以编写一个简单的测试用例来验证这一点。我避免了定义helper函数,因此编译器无法内联它们,而是通过const引用传递数组,以确保编译器知道函数不会修改它们:

void setup_0(const int (&p)[10]);
void use_0  (const int (&p)[10]);
void setup_1(const int (&p)[10]);
void use_1  (const int (&p)[10]);

void TestFxn()
{
   int a[10];
   int b[10];

   setup_0(a);
   use_0(a);

   setup_1(b);
   use_1(b);
}
如您所见,没有编译器(GCC、Clang、ICC或MSVC)会将其优化为使用10个元素的单堆栈分配数组。当然,每个编译器在堆栈上分配的空间不同。其中一些原因是由于不同的呼叫约定,可能需要也可能不需要红色区域。否则,这是由于优化器的对齐首选项

以GCC的输出为例,可以立即看出它没有重用数组
a
。以下是带注释的反汇编:

;在堆栈上分配104字节
; 通过从堆栈指针中减去RSP。
; (x86上的堆栈始终向下增长。)
副区长,104
; 将堆栈顶部的地址放在RDI中,
; 这就是将数组传递给setup_0()的方式。
移动rdi,rsp
调用设置_0(int const(&)[10])
; 由于安装程序_0()可能已使va崩溃
main:
        mov     DWORD PTR a[rip], 42
        mov     DWORD PTR b[rip], 42
        xor     eax, eax
        ret
#include <cstdlib>

int func1(const int (&tab)[10]);
int func2(const int (&tab)[10]);

int main()
{
  int a[10];
  int b[10];

  func1(a);
  func2(b);

  return 0;
}
main:
        sub     rsp, 104
        mov     rdi, rsp ; first address is rsp
        call    func1(int const (&) [10])
        lea     rdi, [rsp+48] ; second address is [rsp+48]
        call    func2(int const (&) [10])
        xor     eax, eax
        add     rsp, 104
        ret
#include <cstdlib>

extern int * a;
extern int * b;

inline int do_stuff(int ** to)
{
  *to = (int *) malloc(sizeof(int));
  (**to) = 42;
  return **to;
}

int main()
{
  do_stuff(&a);
  free(a);

  do_stuff(&b);
  free(b);

  return 0;
}
main:
        sub     rsp, 8
        mov     edi, 4
        call    malloc
        mov     rdi, rax
        mov     QWORD PTR a[rip], rax
        call    free
        mov     edi, 4
        call    malloc
        mov     rdi, rax
        mov     QWORD PTR b[rip], rax
        call    free
        xor     eax, eax
        add     rsp, 8
        ret
void setup_0(const int (&p)[10]);
void use_0  (const int (&p)[10]);
void setup_1(const int (&p)[10]);
void use_1  (const int (&p)[10]);

void TestFxn()
{
   int a[10];
   int b[10];

   setup_0(a);
   use_0(a);

   setup_1(b);
   use_1(b);
}
void TestFxn()
{
   int* a = malloc(sizeof(int) * 10);
   setup_0(a);
   use_0(a);
   free(a);

   int* b = malloc(sizeof(int) * 10);
   setup_1(b);
   use_1(b);
   free(b);
}