qsort vs std :: sort的表现?

Cha*_*han 70 c++ sorting performance stl

根据Scott Meyers的说法,在他的Effective 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";
}
Run Code Online (Sandbox Code Playgroud)

这是我的结果:

C quick-sort time elapsed: 0.061
C++ quick-sort time elapsed: 0.086
Press any key to continue . . .
Run Code Online (Sandbox Code Playgroud)

更新

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

最好的祝福,

Pup*_*ppy 95

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

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

  • @DeadMG:谢谢!我更改为发布模式,得到了类似的结果。我真的很喜欢斯科特·迈耶斯,我相信他的话;) (2认同)
  • @Oo Tiib:输出文本并不意味着它不会同时输出。如果缓冲区比第一个缓冲区大但比第二个缓冲区小怎么办?现在它必须在第二次调用之前刷新 - 但在第一次调用时没有刷新。哦亲爱的。但我不太高兴,因为我解决了上述所有问题,并且 qsort 现在快了很多。:( (2认同)

ras*_*mus 18

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

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

尝试反转测试,即从运行std :: sort开始.

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

  • 我很惊讶没有人提到任何衡量代码实际有效性的策略.您可以编写一个小程序,对几百个元素进行排序,将所有内容加载到L1缓存中,并在记录时间内完成,但这绝不会反映您在具有几百个其他系统的系统上运行的实际程序活动进程,执行上下文切换,因为你是计算限制的,调度程序讨厌你,同时按照新泽西州的大小排序数据集.使您的基准测试看起来很像真正的应用程序. (5认同)

tem*_*def 12

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

  • @Chan:切换到使用"发布"版本.另外,请确保不要使用Visual Studio中的whithin程序运行程序 - 调试器之类的东西会改变程序的时间特性. (3认同)

Zan*_*ynx 10

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

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


650*_*502 5

在我的机器上添加一些肉类(将数组变成1000万个元素并在数据部分中移动它)并进行编译

g++ -Wall -O2 -osortspeed sortspeed.cpp
Run Code Online (Sandbox Code Playgroud)

我得到的结果

C quick-sort time elapsed: 3.48
C++ quick-sort time elapsed: 1.26
Run Code Online (Sandbox Code Playgroud)

还应注意可能配置为根据系统负载以可变速度运行的现代“绿色” CPU。进行基准测试时,这种行为会使您发疯(在我的计算机上,我有一个小的脚本,用于修复进行速度测试时使用的CPU时钟)。

  • 如果您使用性能计数器,“绿色” CPU无关紧要(因为您应该这样做才能获得有意义的基准测试结果) (6认同)
  • @ 6502:您已经颠倒了。性能计数器是每个进程的,时钟是全局的。 (2认同)

Mr *_*ley 5

编写准确的基准测试很困难,所以让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;

    });
});
Run Code Online (Sandbox Code Playgroud)

使用 Apple Clang 7.3.0 编译,

$ clang++ -std=c++14 -stdlib=libc++ -O3 -march=native sort.cpp -o sort
$ ./sort
Run Code Online (Sandbox Code Playgroud)

并在我的 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
Run Code Online (Sandbox Code Playgroud)

因此std::sort,没有内联的速度大约比qsort(可能是由于不同的排序算法)快 1.7 倍,而内联的颠簸速度大约快 2.4 倍。当然是令人印象深刻的加速,但远低于 670%。