覆盖与分配/释放内存 - 效率

5
我正在编写一个C++应用程序,需要一个内存块(大约1000字节)作为一些文本处理的临时缓冲区。该操作每秒最多可以重复10,000次。
请问是否确认每次需要缓冲区时分配内存(即使用智能指针进行new,当超出作用域时内存被释放)比拥有固定的缓冲区并在每次处理完成后清空它(将其每个字节都写成零)更昂贵?
对于C++来说这听起来像常识,但我在互联网上找不到任何证实它的东西。
对于具有自动垃圾回收功能的计算机语言(例如Java、.NET),情况是否不同?

静态缓冲区显然是有问题的!!! 如果需要将所有内存清零,请使用memset :) - toto
8个回答

16

每次需要内存时分配和释放内存可能更昂贵,但更重要的问题是:这有关系吗?以最简单的方式编写可以正确工作且不会泄漏/破坏内存的代码。只有在性能不足时,才对代码进行分析以查看可以改进哪些地方。


3
每秒维持10,000个分配听起来很重要 —— 如果一秒内的9,999次迭代太少的话……如果有实时约束,将可证明的限制纳入设计可能是必要的。并非所有的设计时间“优化”都是过早的。 - Chris Becke
1
我认为Chris说得很对。为了保证吞吐量水平,必须仔细考虑代码的算法复杂度。静态和本地分配具有可预测的性能。这对于动态分配来说并不是真的。 - Constantin

6

虽然我不能给你学术上的"确认",但请考虑以下事项:

CPU每执行一条指令都需要一定时间。如果你撤销缓冲区并重新分配它,CPU将不得不执行取消/重新分配操作。如果您重复使用缓冲区,则无需执行这些操作。

可以肯定的是,重复使用缓冲区更快(我们甚至还没有谈到内存局部性,即相同的内存可以留在CPU缓存中)。

话虽如此,除非你正在编写必须非常严格的东西(比如实时应用程序),或者除非你已经确定这个缓冲区工作是应用程序的瓶颈(当你正在进行性能测量时),否则我建议您按最合理、最易于维护的方式编写。在现代计算机上,您的维护成本可能占软件总成本的较大比例,而管理1000字节的性能成本则相对较低。


4

分配内存(通过new或malloc)不会清空它。如果必须在使用之前将内存设置为0,则需要进行清除。在这种情况下,使用静态缓冲区是一个很大的优势。此外,如果您只使用缓冲区的一部分,可以跟踪使用量,并且只需清除已使用的部分。

calloc确实将整个缓冲区设置为0,但我无法想象它比malloc + memset更快。


2

关于Java:

一个很大的区别是Java保证新分配的内存为0,而且可能比手动操作更快。此外,Java的垃圾回收器在分配和释放短生命周期对象方面非常高效,而不必要的长生命周期对象会导致额外的工作量。因此,在Java中每次重新分配数组的机会更有可能表现得比C++更好。

但是垃圾回收性能很难预测,所以我建议两种方法都试一下,看哪个更快。这可能比在这里提问并阅读所有答案要花费更少的时间。


谢谢。我问Java问题是因为我知道您无法控制已释放的内存。但是,您指出任何分配的内存都将填充为零,这一点是正确的,不像C++中需要自己清理。 - Andy

1

一如既往,回答这个问题的最佳方式是两种方法都实现,并对两种解决方案进行时间/基准测试,以便进行实际比较,而不是基于猜测或他人在他们的经验中有效的方法(可能与你的情况有所不同,你可能没有意识到)。


1

我将跳过标准的“不要担心优化”的建议,因为其他人已经提到了。最快的方法是将缓冲区声明为函数的本地变量,并使用memset将其清零。使用本地缓冲区,编译器通过移动堆栈指针正确数量的字节来“分配”空间。分配速度不可能更快了。在Visual Studio中,您可以添加#pragma intrinsic(memset)。我知道gcc也支持内置函数,但我不记得如何告诉它使用它们。我认为最新版本会在可能的情况下自动使用它们,而无需告诉它们。内置的memset扩展为一些内联指令,告诉处理器将一段内存清零。你不可能比这更快了。话虽如此,如果不需要清零内存,则不要这样做。

此外,使用本地声明的缓冲区将使您的代码更加清晰。从您所说的内容来看,您的缓冲区不需要在使用它的例程范围之外存在。在现代条件下,1000个字节很小。使用动态内存而不是自动内存将添加必须进行测试和维护的大量代码。不要这样做。自动内存是显而易见的选择。


请记住,堆栈空间并不一定是无限的,您可能无法分配任意大的堆栈对象。 - David Thornley
True。默认情况下,Visual Studio 的堆栈大小仅为 1M。 - Rob K

1

如果你在算法效率方面进行过任何形式的研究(如大O符号等),你会知道(或能够计算出)大多数免费存储实现无法保证在查找可用块以满足新的/ malloc()请求时将执行的算法迭代的下限(甚至上限)。

重复使用一个固定缓冲区将提供数量级更高的性能:特别是在垃圾收集环境中,未使用的内存块可能会在freestore中滞留,直到运行垃圾收集周期。


实际上,有可能会有保证。但是,与重用所得到的O(1)保证不同,这里没有任何保证。 - David Thornley
我希望能够找到一个关于自由存储算法效率的在线分析。 - Chris Becke

0

我认为,当涉及到大内存分配时,最好一次性完成,而不是每次需要分配时都进行。原因是内存可能会变得碎片化和缓慢(在内存中创建和删除大量内容需要消耗大量资源)。如果您有一个数据结构为您的操作保留了足够的内存,那么这可能更好。这样做的缺点是将占用大部分内存。

以下是C++中new和delete的实现方式:

#include <cstdlib>
using std::malloc;
using std::free;
#include <new>
using std::bad_alloc;

void * operator new(size_t n)
{
    void * p = malloc(n);
    if(!p) throw bad_alloc();
    return p;
}

void operator delete (void *p)
{
    if (p) free(p);
}

一直执行new和delete操作可能会花费大量时间!这就是为什么像C#和Java这样的语言比C++慢的原因。唯一优点是垃圾回收器可以将内存中的所有内容聚合在一起(它可以对内存进行碎片整理),以便于您的程序。但如果您的程序中有大量东西存储在内存中,这可能会很昂贵。

此外,请查看STL中的算法。它可以通过优化某些操作来帮助您。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接