qsort vs std :: sort的表现?

时间:2011-01-16 21:15:22

标签: c++ performance sorting stl

根据Scott Meyers的有效STL书 - 第46项。他声称由于内联事实,std::sortstd::qsort快约670%。我测试了自己,我看到qsort更快:(!有人能帮助我解释这种奇怪的行为吗?

#include <iostream>
#include <vector>
#include <algorithm>

#include <cstdlib>
#include <ctime>
#include <cstdio>

const size_t LARGE_SIZE = 100000;

struct rnd {
    int operator()() {
        return rand() % LARGE_SIZE;
    }
};

int comp( const void* a, const void* b ) {
    return ( *( int* )a - *( int* )b );
}

int main() {
    int ary[LARGE_SIZE];
    int ary_copy[LARGE_SIZE];
    // generate random data
    std::generate( ary, ary + LARGE_SIZE, rnd() );
    std::copy( ary, ary + LARGE_SIZE, ary_copy );
    // get time
    std::time_t start = std::clock();
    // perform quick sort C using function pointer
    std::qsort( ary, LARGE_SIZE, sizeof( int ), comp );
    std::cout << "C quick-sort time elapsed: " << static_cast<double>( clock() - start ) / CLOCKS_PER_SEC << "\n";
    // get time again
    start = std::clock();
    // perform quick sort C++ using function object
    std::sort( ary_copy, ary_copy + LARGE_SIZE );
    std::cout << "C++ quick-sort time elapsed: " << static_cast<double>( clock() - start ) / CLOCKS_PER_SEC << "\n";
}

这是我的结果:

C quick-sort time elapsed: 0.061
C++ quick-sort time elapsed: 0.086
Press any key to continue . . .

更新

有效STL第3版(2001)
第7章使用STL编程
第46项:考虑函数对象而不是函数作为算法参数。

致以最诚挚的问候,

7 个答案:

答案 0 :(得分:94)

std :: clock()不是可行的时钟。您应该使用特定于平台的更高分辨率计时器,例如Windows高性能计时器。更重要的是,调用clock()的方式是首先将文本输出到控制台,该控制台包含在时间中。这肯定会使测试无效。此外,请确保使用所有优化进行编译。

最后,我复制并粘贴了你的代码,qsort为0.016,std :: sort为0.008。

答案 1 :(得分:18)

我很惊讶没有人提到缓存。

在您的代码中,您首先触摸 ary 和* ary_copy *,以便它们在 qsort 时驻留在缓存中。在 qsort 期间,* ary_copy *可能会被驱逐。在 std :: sort 时,必须从内存或更大的(读取更慢的)缓存级别获取元素。这当然取决于您的缓存大小。

尝试撤消测试,即首先运行 std :: sort

有些人指出;使阵列更大将使测试更公平。原因是大型数组不太可能适合缓存。

答案 2 :(得分:12)

未启用优化的两种排序算法应具有相当的性能。 C ++ sort倾向于明显优于qsort的原因是编译器可以内联进行比较,因为编译器具有关于用于执行比较的函数的类型信息。您是否在启用优化的情况下运行这些测试如果没有,请尝试将其打开并再次运行此测试。

答案 3 :(得分:10)

qsort可能比预期表现更好的另一个原因是较新的编译器可以通过函数指针进行内联和优化。

如果C头定义了qsort的内联实现而不是在库中实现它并且编译器支持间接函数内联,那么qsort可以和std :: sort一样快。

答案 4 :(得分:4)

在我的机器上添加一些肉(制作数组1000万个元素并在数据部分中移动它)并使用

进行编译
g++ -Wall -O2 -osortspeed sortspeed.cpp

我得到了结果

C quick-sort time elapsed: 3.48
C++ quick-sort time elapsed: 1.26

还要注意可配置为以可变速度运行的现代“绿色”CPU,具体取决于系统的负载。在对这种行为进行基准测试时可能会让你发疯(在我的机器上我设置了两个小脚本normalfast,我可以在进行速度测试时使用它。)

答案 5 :(得分:3)

编写准确的基准测试很困难,所以让我们Nonius为我们做!让我们在没有内联的情况下测试qsortstd::sort,在一百万个随机整数的向量上测试std::sort内联(默认值)。

// sort.cpp
#define NONIUS_RUNNER
#include <nonius.h++>
#include <random>
#include <algorithm>

// qsort
int comp(const void* a, const void* b) {
    const int arg1 = *static_cast<const int*>(a);
    const int arg2 = *static_cast<const int*>(b);

    // we can't simply return a - b, because that might under/overflow
    return (arg1 > arg2) - (arg1 < arg2);
}

// std::sort with no inlining
struct compare_noinline {
    __attribute__((noinline)) bool operator()(const int a, const int b) {
        return a < b;
    }
};

// std::sort with inlining
struct compare {
    // the compiler will automatically inline this
    bool operator()(const int a, const int b) {
        return a < b;
    }
};

std::vector<int> gen_random_vector(const size_t size) {

    std::random_device seed;
    std::default_random_engine engine{seed()};
    std::uniform_int_distribution<int> dist{std::numeric_limits<int>::min(), std::numeric_limits<int>::max()};

    std::vector<int> vec;
    for (size_t i = 0; i < size; i += 1) {
        const int rand_int = dist(engine);
        vec.push_back(rand_int);
    }

    return vec;
}

// generate a vector of a million random integers
constexpr size_t size = 1'000'000;
static const std::vector<int> rand_vec = gen_random_vector(size);

NONIUS_BENCHMARK("qsort", [](nonius::chronometer meter) {

    // Nonius does multiple runs of the benchmark, and each one needs a new
    // copy of the original vector, otherwise we'd just be sorting the same
    // one over and over
    const size_t runs = static_cast<size_t>(meter.runs());
    std::vector<std::vector<int>> vectors{runs};
    std::fill(vectors.begin(), vectors.end(), rand_vec);

    meter.measure([&](const size_t run) {

        std::vector<int>& current_vec = vectors[run];

        std::qsort(current_vec.data(), current_vec.size(), sizeof(int), comp);

        return current_vec;
    });
});

NONIUS_BENCHMARK("std::sort noinline", [](nonius::chronometer meter) {

    const size_t runs = static_cast<size_t>(meter.runs());
    std::vector<std::vector<int>> vectors{runs};
    std::fill(vectors.begin(), vectors.end(), rand_vec);

    meter.measure([&](const size_t run) {

        std::vector<int>& current_vec = vectors[run];

        std::sort(current_vec.begin(), current_vec.end(), compare_noinline{});

        return current_vec;

    });
});

NONIUS_BENCHMARK("std::sort inline", [](nonius::chronometer meter) {

    const size_t runs = static_cast<size_t>(meter.runs());
    std::vector<std::vector<int>> vectors{runs};
    std::fill(vectors.begin(), vectors.end(), rand_vec);

    meter.measure([&](const size_t run) {

        std::vector<int>& current_vec = vectors[run];

        std::sort(current_vec.begin(), current_vec.end(), compare{});

        return current_vec;

    });
});

使用Apple Clang 7.3.0进行编译,

$ clang++ -std=c++14 -stdlib=libc++ -O3 -march=native sort.cpp -o sort
$ ./sort

并在我的1.7 GHz i5 Macbook Air上运行,我们得到了

qsort                211 ms +/- 6 ms
std::sort noinline   127 ms +/- 5 ms
std::sort inline      87 ms +/- 4 ms

所以没有内联的std::sortqsort快1.7倍(可能是由于不同的排序算法),并且内联凸起的速度提高了约2.4倍。当然是一个令人印象深刻的加速,但远低于670%。

答案 6 :(得分:-2)

不知道几年前std :: sort是如何实现的。 但是std :: sort可以快得多,因为std :: sort是一个带有堆排序回退的快速排序。 Heapsort是一种线性的alghoritm,这意味着如果你有两倍的排序数据,排序时间会翻倍。实际上它不仅仅是双倍的,因为它不是完全线性的,但是,qsort可以是二次的,因此需要指数更多的时间来对输入进行两次排序。