动态堆栈堆栈(VLA)与堆性能

大多数时候我们可以假设堆栈更快,更干净。没有内存碎片,易于缓存,分配迅速。这就是为什么人们总是认为分配在堆栈上的静态缓冲区要比堆上的动态缓冲区快得多的原因。是吗? 我经常看到的一个误解是,人们认为c99扩展(在GCC等常见C ++编译器中作为非标准扩展支持)在堆栈上分配动态大小的数组的性能与静态大小一样快。我认为这里不是这样。堆栈操作速度更快,因为我们已经知道堆栈大小和所有偏移量,因此编译器可以轻松地访问之前分配的内容,也可以按恒定偏移量跳转。如果堆栈变成动态的,则无法再通过这种方式对其进行优化。您只需编写带有分配在堆栈上的静态和动态数组的代码,检查汇编代码和第二个选项就会更加复杂。

现在的问题是:使用像VLA这样的非标准扩展真的比堆上动态数组更有效吗?如果是这样,那么在哪种情况下它会更快,在哪些情况下可以媲美甚至更差?

我试图在快速测试平台的示例代码上运行一些基准测试。我想通过大小分配来测试性能差异。为了使它更加广泛,我想模拟在动态分配的堆栈之前访问分配的内容,以查看其是否受到任何影响。


#define STATIC_BUF_SIZE 10
#define TEMP_BUF_SIZE 5000

static void Static(benchmark::State& state) {
  char sthAllocatedBefore[STATIC_BUF_SIZE];
  for (int i = 0; i < STATIC_BUF_SIZE; ++i)
  {
    sthAllocatedBefore[i] = 0;
  }
  for (auto _ : state) {
    char arr[TEMP_BUF_SIZE];
    for (int i = 0; i < TEMP_BUF_SIZE; ++i)
    {
      arr[i] = i;
      benchmark::DoNotOptimize(arr[i]);
    }
    for (int i = 0; i < STATIC_BUF_SIZE; ++i)
    {
      sthAllocatedBefore[i]++;
      benchmark::DoNotOptimize(sthAllocatedBefore[i]);
    }
  }
}
BENCHMARK(Static);

static void Heap(benchmark::State& state) {
  char sthAllocatedBefore[STATIC_BUF_SIZE];
  for (int i = 0; i < STATIC_BUF_SIZE; ++i)
  {
    sthAllocatedBefore[i] = 0;
  }
  for (auto _ : state) {
    volatile int size = TEMP_BUF_SIZE;
    benchmark::DoNotOptimize(size);
    char* arr = new char[size];
    for (int i = 0; i < TEMP_BUF_SIZE; ++i)
    {
      arr[i] = i;
      benchmark::DoNotOptimize(arr[i]);
    }
    for (int i = 0; i < STATIC_BUF_SIZE; ++i)
    {
      sthAllocatedBefore[i]++;
      benchmark::DoNotOptimize(sthAllocatedBefore[i]);
    }
    delete[] arr;
  }
}
BENCHMARK(Heap);

static void DynamicStack(benchmark::State& state) {
  char sthAllocatedBefore[STATIC_BUF_SIZE];
  for (int i = 0; i < STATIC_BUF_SIZE; ++i)
  {
    sthAllocatedBefore[i] = 0;
  }
  for (auto _ : state) {
    volatile int size = TEMP_BUF_SIZE;
    benchmark::DoNotOptimize(size);
    char arr[size];
    for (int i = 0; i < TEMP_BUF_SIZE; ++i)
    {
      arr[i] = i;
      benchmark::DoNotOptimize(arr[i]);
    }
    for (int i = 0; i < STATIC_BUF_SIZE; ++i)
    {
      sthAllocatedBefore[i]++;
      benchmark::DoNotOptimize(sthAllocatedBefore[i]);
    }
  }
}
BENCHMARK(DynamicStack);

测试很简单。

  1. 在堆栈上创建静态缓冲区并对其进行初始化。
  2. 开始循环测量。
  3. 分配临时缓冲区。
  4. 使用值填充缓冲区。
  5. 修改静态缓冲区中的值。
  6. 删除临时缓冲区。

测试#1 -静态缓冲区上的5k操作和临时缓冲区的5k大小 它们看起来可比,尽管静态分配似乎对它们更糟,这有点可疑。

动态堆栈堆栈(VLA)与堆性能

测试#2 -在静态缓冲区上进行10次操作,临时缓冲区的大小为5k 我不确定此测试是否有效,快速测试表明递增计数器占用了大部分执行时间。

动态堆栈堆栈(VLA)与堆性能

测试#3 -在静态缓冲区上执行5k操作,在临时缓冲区上执行10次大小操作 尽管性能差异很小,但看起来更加合理。

动态堆栈堆栈(VLA)与堆性能

测试#4 -在静态缓冲区上进行10次操作,在临时缓冲区上进行10次大小调整 堆分配会产生巨大的开销,但是静态动态堆栈仍然比恒定堆栈缓冲区慢得多。

动态堆栈堆栈(VLA)与堆性能

我在不同版本中执行了相同的测试,尽管我不确定所得到的结果是否可靠,但有时不确定结果,但多数情况下“ DoNotOptimize”如何影响此代码。

相似的测试,但是不使用临时缓冲区,只是创建临时堆栈而已。 大静态缓冲区,小温度。

动态堆栈堆栈(VLA)与堆性能

对于让您清楚地看到差异并解释发生的情况的情况,我大多感到好奇。同样,获得更好的基准设置和理解也将是一件好事。

在我看来,使用VLA只是懒惰。对于更大的分配,性能是可比的。对于许多小的分配,在堆栈上使用恒定大小的缓冲区仍然会更好。对于许多大分配来说,这可能是好的,因为它减少了内存碎片,但随后可能使用了一些大的静态缓冲区?对于加载非常大的文件,最好使用内存映射。 我正在寻找一些理由,为什么要使用不受支持的VLA扩展,这的真正好处是什么?为较小的消息创建const大小的小缓冲区,为不适合该消息的消息创建动态缓冲区,可能更有意义。

yjyyjywww 回答:动态堆栈堆栈(VLA)与堆性能

暂时没有好的解决方案,如果你有好的解决方案,请发邮件至:iooj@foxmail.com
本文链接:https://www.f2er.com/3105786.html

大家都在问