静态局部变量可以减少内存分配时间吗?

时间:2010-09-16 19:21:32

标签: c++ static local-variables

假设我在单线程程序中有一个看起来像这样的函数

void f(some arguments){
    char buffer[32];
    some operations on buffer;
}

和f出现在一些经常调用的循环中,所以我想尽可能快地调用它。在我看来,每次调用f时都需要分配缓冲区,但是如果我声明它是静态的,那么就不会发生这种情况。这是正确的推理吗?那是免费的加速吗?只是因为这个事实(它很容易加速),优化编译器是否已经为我做了类似的事情?

11 个答案:

答案 0 :(得分:13)

不,这不是免费加速。

首先,分配几乎是免费的(因为它只包括向堆栈指针添加32),其次,至少有两个原因导致静态变量更慢

  • 你失去了缓存局部性。堆栈上分配的数据已经在CPU缓存中,因此访问它非常便宜。静态数据分配在不同的内存区域,因此可能不会被高速缓存,因此会导致高速缓存未命中,并且您必须等待数百个时钟周期才能从主内存中获取数据。 / LI>
  • 你失去了线程安全。如果两个线程同时执行该函数,它将崩溃并烧录,除非放置一个锁,因此一次只允许一个线程执行该部分代码。这意味着你将失去拥有多个CPU核心的好处。

所以这不是免费加速。但是你的案例可能会更快(尽管我对此表示怀疑)。 所以试一试,对它进行基准测试,看看在你的特定场景中哪种方法最有效。

答案 1 :(得分:10)

在几乎所有系统上,堆栈上增加32个字节几乎不需要任何费用。但你应该测试一下。对静态版本和本地版本进行基准测试并发回。

答案 2 :(得分:8)

对于将堆栈用于局部变量的实现,通常分配涉及推进寄存器(向其添加值),例如堆栈指针(SP)寄存器。这个时间非常微不足道,通常是一条指令或更少。

然而,堆栈变量的初始化需要更长的时间,但同样,并不多。查看汇编语言列表(由编译器或调试器生成)以获取确切的详细信息。标准中没有关于初始化变量所需的持续时间或指令数量的内容。

静态局部变量的分配通常以不同方式处理。一种常见的方法是将这些变量放在与全局变量相同的区域中。通常在调用main()之前初始化此区域中的所有变量。在这种情况下的分配是将地址分配给寄存器或将区域信息存储在存储器中的问题。这里浪费的执行时间不多。

动态分配是执行周期被刻录的情况。但这不在你的问题范围内。

答案 3 :(得分:3)

现在编写它的方式,没有分配成本:32个字节在堆栈上。唯一真正的工作是你需要零初始化。

本地静态不是一个好主意。它不会更快,并且您的函数不能再从多个线程使用,因为所有调用共享相同的缓冲区。更不用说本地静态初始化不保证是线程安全的。

答案 4 :(得分:3)

我建议对这个问题采用更通用的方法是,如果你有一个多次调用的函数需要一些局部变量,那么考虑将它包装在一个类中并使这些变量成为成员函数。考虑一下您是否需要动态调整大小,而不是char buffer[32]而是std::vector<char> buffer(requiredSize)。这比每次通过循环初始化的数组更昂贵

class BufferMunger {
public:
   BufferMunger() {};
   void DoFunction(args);
private:
   char buffer[32];
};

BufferMunger m;
for (int i=0; i<1000; i++) {
   m.DoFunction(arg[i]);  // only one allocation of buffer
}

另一个含义是使缓冲区变为静态,这个函数现在在多线程应用程序中是不安全的,因为两个线程可能会调用它并同时覆盖缓冲区中的数据。另一方面,在每个需要它的线程中使用单独的BufferMunger是安全的。

答案 5 :(得分:3)

请注意,C ++中的块级static变量(与C相反)在首次使用时初始化。这意味着you'll be introducing the cost of an extra runtime check。该分支可能最终会使性能变差,而不是更好。 (但实际上,你应该像其他人提到的那样描述。)

无论如何,我认为这不值得,特别是因为你故意牺牲了重新进入。

答案 6 :(得分:2)

如果您正在为PC编写代码,则无论如何都不可能有任何有意义的速度优势。在某些嵌入式系统上,避免所有局部变量可能是有利的。在其他一些系统中,局部变量可能更快。

前者的一个例子:在Z80上,为具有任何局部变量的函数设置堆栈帧的代码非常长。此外,访问局部变量的代码仅限于使用(IX + d)寻址模式,该模式仅适用于8位指令。如果X和Y都是全局/静态或两个局部变量,则语句“X = Y”可以汇编为:

; If both are static or global: 6 bytes; 32 cycles
  ld HL,(_Y) ; 16 cycles
  ld (_X),HL ; 16 cycles
; If both are local: 12 bytes; 56 cycles
  ld E,(IX+_Y)   ; 14 cycles
  ld D,(IX+_Y+1) ; 14 cycles
  ld (IX+_X),D   ; 14 cycles
  ld (IX+_X+1),E ; 14 cycles

除了设置堆栈帧的代码和时间之外,还有100%的代码空间损失和75%的时间损失!

在ARM处理器上,单个指令可以加载位于地址指针的+/- 2K范围内的变量。如果函数的局部变量总计为2K或更少,则可以使用单个指令访问它们。全局变量通常需要加载两条或更多条指令,具体取决于它们的存储位置。

答案 7 :(得分:1)

使用gcc,我确实看到了一些加速:

void f() {
    char buffer[4096];
}

int main() {
    int i;
    for (i = 0; i < 100000000; ++i) {
        f();
    }
}

时间:

$ time ./a.out

real    0m0.453s
user    0m0.450s
sys  0m0.010s

将缓冲区更改为静态:

$ time ./a.out

real    0m0.352s
user    0m0.360s
sys  0m0.000s

答案 8 :(得分:1)

根据变量的确切含义及其使用方式,速度几乎为零。因为(在x86系统上)堆栈内存是使用简单的单个函数(sub esp,amount)同时为所有本地变量分配的,因此只有一个其他堆栈变量消除了任何增益。唯一的例外是巨大的缓冲区,在这种情况下,编译器可能会在_chkstk中添加内存(但如果你的缓冲区很大,你应该重新评估你的代码)。编译器无法通过优化将堆栈内存转换为静态内存,因为它不能假设该函数将在单线程环境中使用,而且它会混淆对象构造函数&amp;析构函数等

答案 9 :(得分:1)

如果函数中有任何本地自动变量,则需要调整堆栈指针。调整所需的时间是不变的,并且不会根据声明的变量数量而变化。如果您的函数没有任何本地自动变量,那么可能可以节省一些时间。

如果初始化静态变量,则会在某处确定一个标志,以确定该变量是否已初始化。检查标志需要一些时间。在您的示例中,变量未初始化,因此可以忽略此部分。

如果你的函数有可能被递归地调用或者来自两个不同的线程,那么应该避免使用静态变量。

答案 10 :(得分:1)

在大多数实际情况下,它会使功能大大减慢。这是因为静态数据段不在堆栈附近,您将失去缓存一致性,因此当您尝试访问它时,您将获得缓存未命中。但是,当您在堆栈上分配常规char [32]时,它就在您所有其他所需数据的旁边,并且访问成本非常低。基于堆栈的char数组的初始化成本毫无意义。

这忽略了静力学还有许多其他问题。

您确实需要实际配置代码并查看减速的位置,因为没有分析器会告诉您分配静态大小的字符缓冲区是一个性能问题。

相关问题