为什么windows中的重复内存分配会减慢
Why does repeated memory allocation in windows slow down?
我想了解为什么以下代码在我的linux和windows7机器上表现不同:在linux上,每次迭代大约需要120毫秒。在windows 7上,第一次迭代需要0.4秒,随后的迭代需要更长的时间。迭代8大约需要11秒,迭代22大约需要1分钟。
我在不同的硬件上观察到了这种行为。它似乎与windows有关。
#include <iostream>
#include <time.h>
#include <chrono>
void iteration() {
int n = 25000;
// Allocate memory
long** blocks = new long*[n];
for( int i = 0; i<n; ++i )
{
blocks[i] = new long[100008];
}
// Free all allocated memory
for( int i = 0; i<n; ++i )
{
delete[] blocks[i];
}
delete[] blocks;
}
int main(int argc, char **argv) {
int nbIter = 30;
for( int i = 0; i < nbIter; ++i )
{
auto start = std::chrono::system_clock::now();
iteration();
auto end = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Iteration #" << i << ": time=" << elapsed.count() << "ms" << std::endl;
}
return 0;
}
有人能告诉我这里发生了什么,以及如何让代码在windows上稳定运行吗?
edit:我在windows上的VS2013中做了一个版本构建,我从VS外部执行了这个程序。以下是一些更精确的运行时间(以秒为单位):
Iteration #0: time=0.381000
Iteration #1: time=0.391000
Iteration #2: time=0.451000
Iteration #3: time=1.507000
Iteration #4: time=1.711000
Iteration #5: time=2.938000
Iteration #6: time=4.770000
Iteration #7: time=7.840000
Iteration #8: time=10.563000
Iteration #9: time=14.606000
Iteration #10: time=20.732000
Iteration #11: time=24.948000
Iteration #12: time=30.255000
Iteration #13: time=34.608000
Iteration #14: time=38.114000
Iteration #15: time=43.217000
Iteration #16: time=39.926000
Iteration #17: time=43.506000
Iteration #18: time=43.660000
Iteration #19: time=45.291000
Iteration #20: time=50.003000
在关于Windows上堆的参考文献(以及一些关于它的infosec文章)中,有一些关于常见堆减速的花絮,其中指出一些是
- 分配操作导致的减速
- 由于自由操作,速度减慢
- 由于频繁的alloc和realloc,速度减慢
这确实有助于解释为什么会慢下来(即频繁的allocs和reallocs),但并不能真正解释为什么会慢下来。
首先需要注意的是sizeof(long) != sizeof(long)
,也就是说,在我使用g++
和Visual Studio 12对64位构建进行的测试中,Windows上的sizeof(long)
为4,Linux上为8。这是分配/释放内存时的一个重要注意事项。如果您将代码从long
类型更改为sizeof(T) == 8
类型(如long long
),那么问题就会消失,并且迭代的时间是一致的。示例:
void iteration() {
int n = 25000;
// Allocate memory
long long** blocks = new long long*[n];
for (int i = 0; i < n; ++i) {
blocks[i] = new long long[100008];
}
// Free all allocated memory
for (int i = 0; i < n; ++i) {
delete[] blocks[i];
}
delete[] blocks;
}
// sizeof(long long) == 8 on my Linux/Unix and Windows 64-bit machines
还应该注意的是,定时问题只会在这个代码中消失。
如果保留long long
的类型,但将100008
调整为16666
,则问题再次出现;此外,如果您将其更改为16668
,并立即运行long long
迭代,然后运行long
版本,则long long
函数的时间将向上,然后long
的时间将向下,例如:
template < typename T >
void iteration() {
int n = 25000;
// Allocate memory
T** blocks = new T*[n];
for (int i = 0; i < n; ++i) {
blocks[i] = new T[16668];
}
// Free all allocated memory
for (int i = 0; i < n; ++i) {
delete[] blocks[i];
}
delete[] blocks;
}
for (int i = 0; i < nbItrs; ++i) {
iteration<long long>(); // time goes UP
}
for (int i = 0; i < nbItrs; ++i) {
iteration<long>(); // time goes DOWN
}
此外,发布的代码使用malloc
/free
、LocalAlloc
/LocalFree
和/或HeapAlloc
/HeapFree
产生类似的结果,因为new
/malloc
(在Windows上)调用HeapAlloc
。原因与Windows如何管理堆内存以及释放内存的位置有关。当必须删除页面时,需要对可用阻止列表进行清理,并且可能需要相应地调整列表。
在搜索和替换或从列表中删除旧内存块的过程中,这种调整可能需要时间。如果块没有落在干净的边界上,可能需要对空闲堆块的列表进行额外的调整。
深入了解Windows堆管理的方式和原因,需要解释Windows内核的设计及其内存管理。进入这一领域将超出这个问题/答案的范围,但是,我在上面链接的一些文章有很好的概述,并很好地解释了如何以及为什么。
然而,你确实问过
如何使代码在windows上稳定运行?
如上所述,改变类型将允许更一致的定时,此外,如另一个答案所述,以相反顺序删除列表也将实现更一致的时序;
for (int i = n; i > 0; --i )
{
delete[] blocks[i-1];
}
这是因为Windows内存管理器使用一个单独的链表来维护堆位置,因此,当delete
时,由于遍历列表,时间可能会增加,并且与Linux相比,Windows上的时间可能会更慢(尽管我的测试实际上在两次更改中产生了相似的时间)。
我希望这能有所帮助。
有趣的问题。我能够繁殖。
通过delete[]
以与分配相反的顺序对块进行分配,我获得了一致的性能,尽管仍然有些迟缓:
for( int i = 0; i<n; ++i )
delete[] blocks[n - 1 - i];
我怀疑这可能都与合并开销有关——来自MSDN这里:
由于自由操作,速度减慢。自由操作消耗更多的周期,主要是在启用合并的情况下。在合并过程中,每个空闲操作都应该"找到"它的邻居,将它们拉出来构造一个较大的块,并将较大的块重新插入空闲列表。在查找过程中,内存可能会按随机顺序被触摸,从而导致缓存未命中和性能下降。
不过,它也有一些奇怪的地方:
我的测量结果表明,虽然
delete[]
在第一次或三次迭代中花费了约80%的时间,但在六次之后,new[]
几乎花费了同样长的时间。当我从
new long[91134]
转到。。。91135
:差不多是356kb,但我没能在谷歌上搜索到任何相关内容。
非常有趣的问题。我无法在带有MS Visual Studio Community 2013的Windows 10上复制它,但是,如果您正在分配/释放大量固定大小的内存块,您可以尝试用固定大小的存储块分配算法(也称为内存池)替换新的/删除。它工作得更快,而且速度恒定。在这里,您可以找到一个基于BSD许可证的示例:https://github.com/intelmm/FixMemAlloc/blob/master/Sources/MemoryPool.h.也许这会有所帮助。
- 在c++中为我自己的基于指针的数组分配内存的正确方法
- 给定一个指向堆分配内存的指针,智能指针实现如何为其找到合适的释放函数?
- 如果 const 不分配内存,为什么我可以获取 const 的地址?
- 在函数中分配内存时出现问题
- 如何为 std::vector 分配内存,然后稍后为某些元素调用构造函数?
- constexpr new 如何分配内存?
- 在构造函数中分配内存失败是如何冒泡的
- LLVM 传递以在特定地址分配内存
- CudaMalloc 在分配内存时失败
- 为什么它在不分配内存的情况下工作正常
- 为什么在正确解除分配内存时出现内存泄漏?
- 如何通过 malloc 为队列数组分配内存?
- vector是否为std::移动的对象连续分配内存
- 删除类成员的动态分配内存的最佳方法是什么
- 唯一指针是否在堆或堆栈上分配内存?
- 如果不分配内存,我如何能够为变量创建和分配值?
- std::initializer_list 堆是否分配内存?
- 如何按顺序或在指定的地址分配内存?
- 是否可以使用 malloc 为类对象分配内存?
- 迭代器是否分配内存(如指针)?