为什么Windows中重复的内存分配速度减慢?

我想了解为什么下面的代码在我的linux和windows 7机器上表现不同:在linux上,每次迭代需要大约120ms。 在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上稳定运行?

编辑:我在VS2013在Windows上做了一个发布版本,我从外部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文章)中,有一些关于常见堆缓慢下降的消息,其中指出了一些

  • 由于分配操作而导致速度放慢。
  • 由于免费操作而导致速度放慢。
  • 由于频繁的分配和重新分配导致速度放慢。
  • 这有助于解释为什么会出现减速(即频繁的分配和重新分配),但它并不能真正解释为什么会出现减速。

    首先需要注意的是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 ,然后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
    }
    

    此外,由于new / malloc (在Windows上)调用HeapAlloc ,所以发布的代码使用malloc / freeLocalAlloc / LocalFree和/或HeapAlloc / HeapFree生成类似的结果。 原因在于Windows如何管理它的堆内存和释放内存的位置。 当页面必须被删除时,清空空闲块列表需要完成,并且列表可能需要相应地调整。

    正是这种调整可能需要时间,在搜索过程中以及从列表中替换或移除旧的内存块。 如果块不在干净的边界上,可能需要对可用堆块列表进行额外的调整。

    深入研究Windows堆管理的方式和原因将涉及到解释Windows内核的设计和内存管理。 进入这个问题将超出这个问题/答案的范围,但是,我上面链接的一些文章有很好的概述,并很好地解释了如何以及为什么。

    但是,你确实问过

    如何让代码在Windows上稳定运行?

    如上所述,改变类型将允许更一致的时间,另外,如另一个答案中所述,以相反顺序删除列表也将实现更一致的时间安排;

    for (int i = n; i > 0; --i )
    {
        delete[] blocks[i-1];
    }
    

    这是由于Windows内存管理器使用单向链表来维护堆的位置,因此,为什么在delete时间可能会增加,因为列表正在被遍历,为什么在Windows上与Linux相比时间会更慢(尽管我的测试实际上在进行这些更改时产生了类似的时间)。

    我希望可以提供帮助。


    有趣的问题。 我能够重现。

    我通过按照分配的相反顺序delete[]获得一致性 - 尽管仍然有些呆滞 - 性能:

    for( int i = 0; i<n; ++i )
        delete[] blocks[n - 1 - i];
    

    我怀疑它可能都涉及合并开销 - 从MSDN这里:

    由于免费操作而导致速度放慢。 空闲操作消耗更多的周期,主要是启用了联合。 在合并过程中,每个自由操作应该“发现”其邻居,将其拉出来构建一个更大的块,然后将较大的块重新插入空闲列表中。 在查找过程中,内存可能会以随机顺序触摸,导致缓存未命中并导致性能下降。

    关于它的奇怪之处很少:

  • 我的测量结果显示,尽管delete[]在第一次或第三次迭代时花费了大约80%的时间,但是在new[] ,耗时几乎一样长。

  • 当我new long[91134]到... 91135 :这非常接近356kb,但我没有设法谷歌任何相关的东西时,问题突然出现了。


  • 非常有趣的问题。 我无法在Windows 10上使用MS Visual Studio Community 2013重现它,但是如果您正在分配/取消分配大量固定大小的内存块,则可以尝试用固定大小的内存块分配算法(也称为内存)替换新/删除池。 它以更快,更稳定的速度工作。 在这里你可以找到一个基于BSD许可证的例子:https://github.com/intelmm/FixMemAlloc/blob/master/Sources/MemoryPool.h。 也许这可能有帮助。

    链接地址: http://www.djcxy.com/p/91401.html

    上一篇: Why does repeated memory allocation in windows slow down?

    下一篇: How to test valid UUID/GUID?