【问题标题】:How big is the performance gap between std::sort and std::stable_sort in practice?std::sort 和 std::stable_sort 在实践中的性能差距有多大?
【发布时间】:2010-10-23 02:17:38
【问题描述】:

两者都应该在 O(n log n) 中运行,但通常排序比 stable_sort 快。实践中的性能差距有多大?你有这方面的经验吗?

我想对大量大小约为 20 字节的结构进行排序。在我的情况下,结果的稳定性会很好,但这不是必须的。目前底层容器是一个普通数组,也许稍后可以将其更改为 std::deque。

【问题讨论】:

  • 请注意,std::sort 通常实现为快速排序,std::stable_sort 是一种复杂的合并排序形式。

标签: c++ sorting stl


【解决方案1】:

理论上比较算法有很好的答案。出于好奇,我用google/benchmarkstd::sortstd::stable_sort 进行了基准测试。

提前指出这一点很有用;

  • 基准机有1 X 2500 MHz CPU1 GB RAM
  • 基准操作系统Arch Linux 2015.08 x86-64
  • 使用g++ 5.3.0clang++ 3.7.0-std=c++11-O3-pthread)编译的基准测试
  • BM_Base* 基准测试尝试测量填充 std::vector<> 的时间。应该从排序结果中减去该时间以便更好地进行比较。

第一个基准对 std::vector<int>512k 大小进行排序。

[ g++ ]# benchmark_sorts --benchmark_repetitions=10
Run on (1 X 2500 MHz CPU )
2016-01-08 01:37:43
Benchmark                         Time(ns)    CPU(ns) Iterations
----------------------------------------------------------------
...
BM_BaseInt/512k_mean              24730499   24726189         28
BM_BaseInt/512k_stddev              293107     310668          0
...
BM_SortInt/512k_mean              70967679   70799990         10
BM_SortInt/512k_stddev             1300811    1301295          0
...
BM_StableSortInt/512k_mean        73487904   73481467          9
BM_StableSortInt/512k_stddev        979966     925172          0
[ clang++ ]# benchmark_sorts --benchmark_repetitions=10
Run on (1 X 2500 MHz CPU )
2016-01-08 01:39:07
Benchmark                         Time(ns)    CPU(ns) Iterations
----------------------------------------------------------------
...
BM_BaseInt/512k_mean              26198558   26197526         27
BM_BaseInt/512k_stddev              320971     348314          0
...
BM_SortInt/512k_mean              70648019   70666660         10
BM_SortInt/512k_stddev             2030727    2033062          0
...
BM_StableSortInt/512k_mean        82004375   81999989          9
BM_StableSortInt/512k_stddev        197309     181453          0

第二个基准对 std::vector<S>512k 大小 (sizeof(Struct S) = 20) 进行排序。

[ g++ ]# benchmark_sorts --benchmark_repetitions=10
Run on (1 X 2500 MHz CPU )
2016-01-08 01:49:32
Benchmark                         Time(ns)    CPU(ns) Iterations
----------------------------------------------------------------
...
BM_BaseStruct/512k_mean           26485063   26410254         26
BM_BaseStruct/512k_stddev           270355     128200          0
...
BM_SortStruct/512k_mean           81844178   81833325          8
BM_SortStruct/512k_stddev           240868     204088          0
...
BM_StableSortStruct/512k_mean    106945879  106857114          7
BM_StableSortStruct/512k_stddev   10446119   10341548          0
[ clang++ ]# benchmark_sorts --benchmark_repetitions=10
Run on (1 X 2500 MHz CPU )
2016-01-08 01:53:01
Benchmark                         Time(ns)    CPU(ns) Iterations
----------------------------------------------------------------
...
BM_BaseStruct/512k_mean           27327329   27280000         25
BM_BaseStruct/512k_stddev           488318     333059          0 
...
BM_SortStruct/512k_mean           78611207   78407400          9
BM_SortStruct/512k_stddev           690207     372230          0 
...
BM_StableSortStruct/512k_mean    109477231  109333325          8
BM_StableSortStruct/512k_stddev   11697084   11506626          0

任何喜欢运行基准测试的人,这里是代码,

#include <vector>
#include <random>
#include <algorithm>

#include "benchmark/benchmark_api.h"

#define SIZE 1024 << 9

static void BM_BaseInt(benchmark::State &state) {
  std::random_device rd;
  std::mt19937 mt(rd());
  std::uniform_int_distribution<int> dist;

  while (state.KeepRunning()) {
    std::vector<int> v;
    v.reserve(state.range_x());
    for (int i = 0; i < state.range_x(); i++) {
      v.push_back(dist(mt));
    }
  }
}
BENCHMARK(BM_BaseInt)->Arg(SIZE);

static void BM_SortInt(benchmark::State &state) {
  std::random_device rd;
  std::mt19937 mt(rd());
  std::uniform_int_distribution<int> dist;

  while (state.KeepRunning()) {
    std::vector<int> v;
    v.reserve(state.range_x());
    for (int i = 0; i < state.range_x(); i++) {
      v.push_back(dist(mt));
    }

    std::sort(v.begin(), v.end());
  }
}
BENCHMARK(BM_SortInt)->Arg(SIZE);

static void BM_StableSortInt(benchmark::State &state) {
  std::random_device rd;
  std::mt19937 mt(rd());
  std::uniform_int_distribution<int> dist;

  while (state.KeepRunning()) {
    std::vector<int> v;
    v.reserve(state.range_x());
    for (int i = 0; i < state.range_x(); i++) {
      v.push_back(dist(mt));
    }

    std::stable_sort(v.begin(), v.end());
  }
}
BENCHMARK(BM_StableSortInt)->Arg(SIZE);


struct S {
  int key;
  int arr[4];
};

static void BM_BaseStruct(benchmark::State &state) {
  std::random_device rd;
  std::mt19937 mt(rd());
  std::uniform_int_distribution<int> dist;

  while (state.KeepRunning()) {
    std::vector<S> v;
    v.reserve(state.range_x());
    for (int i = 0; i < state.range_x(); i++) {
      v.push_back({dist(mt)});
    }
  }
}
BENCHMARK(BM_BaseStruct)->Arg(SIZE);

static void BM_SortStruct(benchmark::State &state) {
  std::random_device rd;
  std::mt19937 mt(rd());
  std::uniform_int_distribution<int> dist;

  while (state.KeepRunning()) {
    std::vector<S> v;
    v.reserve(state.range_x());
    for (int i = 0; i < state.range_x(); i++) {
      v.push_back({dist(mt)});
    }

    std::sort(v.begin(), v.end(),
              [](const S &a, const S &b) { return a.key < b.key; });
  }
}
BENCHMARK(BM_SortStruct)->Arg(SIZE);

static void BM_StableSortStruct(benchmark::State &state) {
  std::random_device rd;
  std::mt19937 mt(rd());
  std::uniform_int_distribution<int> dist;

  while (state.KeepRunning()) {
    std::vector<S> v;
    v.reserve(state.range_x());
    for (int i = 0; i < state.range_x(); i++) {
      v.push_back({dist(mt)});
    }

    std::stable_sort(v.begin(), v.end(),
                     [](const S &a, const S &b) { return a.key < b.key; });
  }
}
BENCHMARK(BM_StableSortStruct)->Arg(SIZE);


BENCHMARK_MAIN();

【讨论】:

  • +1 表示实际继续进行基准测试。你的工作看起来不错。但是,如果您以更容易理解的形式(例如绘制小图)来总结结果,结果会更容易获得。
【解决方案2】:

std::stable_sort 在有足够内存可用时执行 NlogN 比较。当可用内存不足时,它会降级为 N((logN)^2) 次比较。因此,当内存可用时,它与std::sort 的效率大致相同(在平均情况和最坏情况下执行 O(NlogN) 比较)。

对于感兴趣的人,sort() 使用 introsort(快速排序,当递归达到一定深度时切换到堆排序),而 stable_sort() 使用 merge sort

【讨论】:

  • LogN^2 通常不是表示 log(N^2) 而是表示 (log N)^2,尤其是当它以大 O 表示法出现时。这通常发生在采用 O(N log N) 步且每步工作 O(log N) 的算法中,反之亦然。所以,不,它不是一个常数 2。
  • 你说std::sort (which performs O(NlogN) comparisons in both average and worst case)。但是到 std::sort 的链接说: O(N·log(N)) 在所有情况下仅从 C++11 开始。在 C++11 之前,对于最坏的情况没有要求 O(Nlog(N))。
【解决方案3】:

足够大,足以保证一个单独的函数进行稳定排序,而不是让std::sort() 透明地执行它。

【讨论】:

  • 哈哈哈。好的,所以也许不是技术上最详细的答案,但肯定是我最喜欢的。
【解决方案4】:

有时需要 std::stable_sort() ,因为它保持相等元素的顺序。

传统的建议是,如果保持顺序不重要,则应使用 std::sort() 代替。

但是,它取决于上下文。即使您不需要维护顺序,也有很多数据最好使用稳定排序进行排序:

如果数据的枢轴点一直很差,快速排序很快就会变成最差的性能。

Burrows-Wheeler Transform 是一种算法,用作数据压缩的一部分,例如bzip2。它需要对文本的所有旋转进行排序。对于大多数文本数据,归并排序(通常由 std::stable_sort() 使用)比快速排序(通常由 std::sort() 使用)快得多。

bbb 是一个 BWT 实现,它指出了 std::stable_sort() 在此应用程序中优于 sort() 的优势。

【讨论】:

    【解决方案5】:

    性能差距有多大 实践?你有一些经验吗 关于那个?

    是的,但它没有按照您的预期进行。

    我采用了 Burrows-Wheeler 变换的 C 实现并对其进行了 C++ 化。结果比 C 代码慢很多(尽管代码更干净)。所以我把计时工具放在那里,看起来 qsort 的执行速度比 std::sort 快。这是在 VC6 中运行的。然后用 stable_sort 重新编译,测试运行速度比 C 版本快。其他优化设法使 C++ 版本比 C 版本快约 25%。我认为可以提高速度,但代码的清晰度正在消失。

    【讨论】:

    • 基本上我想说的是双向尝试。
    【解决方案6】:

    正在寻找类似的东西 - 但很惊讶没有人谈论辅助空间。

    我相信,stable_sort 和 sort 的实现都应该保证所有(最佳、平均和最差)情况的 O(NlogN)。

    但是,使用的辅助空间存在差异。 stable_sort 需要一个 O(N) 的辅助空间。

    性能上的差异可能在于获得该空间。 :)
    否则,理论上 - 它们应该具有相同的 w.r.t 性能。

    sort 应该做你需要的,除非你需要这个 -> stable_sort 保留具有等效值的元素的相对顺序。

    【讨论】:

      【解决方案7】:

      如果您对大量结构进行排序,则内存/磁盘的 IO 速度开始变得比渐近运行时间更重要。此外,还应考虑内存使用情况。

      我在 2Gb 的数据(64B 结构)上尝试了 std::stable_sort,但不知道 std::stable_sort 创建了数据的内部副本。随后的交换垃圾几乎锁定了我的电脑。

      使用不稳定的 std::sort 可将内存使用量减少 2 倍,这在对大型数组进行排序时很有用。我终止了 std::stable_sort,所以我无法确定它慢了多少。但是,如果不需要稳定排序,那么我认为最好使用不稳定的 std::sort。

      【讨论】:

      • 虽然这在现代计算机上可能太小了,但当您对非常大的数据集进行排序时,最好使用外部排序算法,该算法充分利用磁盘来存储中间结果;比如外部归并排序。
      • (有时排序指针比排序结构快得多 - 反之亦然,具体取决于结构的大小、可用 RAM 等)
      猜你喜欢
      • 2014-07-22
      • 2020-09-14
      • 2013-01-10
      • 2011-06-10
      • 1970-01-01
      • 2020-10-19
      • 1970-01-01
      • 1970-01-01
      • 2019-02-09
      相关资源
      最近更新 更多