在实践中std :: sort和std :: stable_sort之间的性能差距有多大?

时间:2009-05-01 10:42:52

标签: c++ sorting stl

两者都应该在O(n log n)中运行,但一般排序比stable_sort快。实践中的性能差距有多大?你对此有一些经验吗?

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

7 个答案:

答案 0 :(得分:16)

当有足够的内存可用时,

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

对于那些感兴趣的人,sort()使用introsort(当递归到达某个深度时切换到heapsort的quicksort)和stable_sort()使用merge sort

答案 1 :(得分:16)

有理由比较算法的好答案。出于好奇,我将std::sortstd::stable_sortgoogle/benchmark进行了基准比较。

提前指出是很有用的;

  • 基准测试机具有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();

答案 2 :(得分:10)

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

答案 3 :(得分:9)

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

传统的建议是,如果维护订单不重要,则应使用std :: sort()代替。

但是,它依赖于上下文。即使您不需要维护订单,也有大量数据可以通过稳定排序进行最佳排序:

如果数据的枢轴点一直很差,Quicksort会迅速成为最糟糕的表现。

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

bbb是一个BWT实现,它注意到std :: stable_sort()优于此应用程序的sort()的优点。

答案 4 :(得分:2)

  

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

是的,但它并没有像你期望的那样。

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

答案 5 :(得分:1)

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

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

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

答案 6 :(得分:1)

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

我相信 - stable_sort和sort的实现应该保证所有(Best,Average和Worst)案例的O(NlogN)。

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

可能性能上的差异在于获得那个空间。 :)
否则,从理论上讲 - 它们应该与w.r.t表现相同。

除非你需要,否则

sort应该做你需要的 - &gt;     stable_sort保留具有等效值的元素的相对顺序。