web-dev-qa-db-ja.com

qsortとstd :: sortのパフォーマンス?

Scott Meyersによると、彼のEffective STL book-item 46で、彼は std::sort はインラインの事実により std::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 3rd Edition(2001)
第7章STLを使用したプログラミング
項目46:関数ではなく関数オブジェクトをアルゴリズムパラメーターとして検討します。

宜しくお願いします、

68
Chan

std :: clock()は実行可能なタイミングクロックではありません。 Windows High Performance Timerなど、プラットフォーム固有の高解像度タイマーを使用する必要があります。それ以上に、clock()を呼び出す方法は、最初にテキストがコンソールに出力され、それが時刻に含まれることです。これは間違いなくテストを無効にします。さらに、すべての最適化を使用してコンパイルしたことを確認してください。

最後に、コードをコピーして貼り付け、qsortで0.016、std :: sortで0.008を取得しました。

94
Puppy

キャッシュに言及している人がいないことに驚いています。

コードでは、aryと* ary_copy *をタッチすることから始めて、qsortの時点でキャッシュに常駐するようにします。 qsortの間に、* ary_copy *が削除される場合があります。 std :: sortの時点で、要素はメモリまたはより大きな(読み取りslower)キャッシュレベルからフェッチする必要があります。もちろんこれはキャッシュサイズに依存します。

テストを逆にしようとします。つまり、std :: sortを実行して開始します。

一部の人々が指摘したように。配列を大きくすると、テストがより公平になります。その理由は、大きな配列はキャッシュに収まらない可能性が高いためです。

18
rasmus

最適化が有効になっていない2つの並べ替えアルゴリズムは、同等のパフォーマンスを備えているはずです。 C++ sortqsortをかなり上回る傾向があるのは、コンパイラーが比較の実行に使用されている関数に関する型情報を持っているため、行われている比較をインライン化できるためです。最適化を有効にしてこれらのテストを実行しましたか?そうでない場合は、オンにしてこのテストを再度実行してください。

12
templatetypedef

Qsortが予想よりもはるかに優れたパフォーマンスを発揮するもう1つの理由は、新しいコンパイラーが関数ポインターを介してインライン化および最適化できることです。

Cヘッダーがライブラリ内に実装する代わりにqsortのインライン実装を定義し、コンパイラが間接的な関数のインライン化をサポートする場合、qsortはstd :: sortと同じくらい高速になります。

10
Zan Lynx

私のマシンでいくつかの肉を追加して(配列を1000万個作成し、データセクションで移動)、次のようにコンパイルします。

g++ -Wall -O2 -osortspeed sortspeed.cpp

結果として得られます

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

また、システムの負荷に応じて可変速度で動作するように構成されている可能性のある最新の「グリーン」CPUにも注意してください。この種の動作のベンチマークを行うと、夢中になります(私のマシンでは、速度テストを行うときに使用できる2つの小さなスクリプトnormalfastをセットアップしました)。

4
6502

正確なベンチマークを書くのは難しいので、 Nonius を取得してみましょう。 100万個のランダムな整数のベクトルで、インライン化なしの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%未満です。

3
Mr Bingley