Boost.Sortのstring_sort関数を使ってC構造体を高速に動作させる方法

私はポインタの長さとstd :: stringのペアと比較してstd :: stringオブジェクトをソートするときに非常に大きなパフォーマンスの違いがあることがわかりました。

私は自分のアプリケーションでたくさんの並べ替えをしていますが、パフォーマンスのボトルネックは大きな文字列配列の並べ替えにあることがわかりました。私はそのようなソートをする2つの良い方法を知っています – std :: sortとBoost.sort関数を使うこと。ポインタと文字列長の情報を使用して大きなファイルの一部をソートしています

私は自分のパフォーマンスをstd :: stringオブジェクトをソートすることと比較しようとしました、そして私の単純なポインタ長さ構造はずっと遅いです。私は想像できない – なぜ?
sizeof(std :: string)は32、sizeof(my_struct)は16バイトです。どちらも内部比較のために:: memcmp関数を使っています

問題を説明するために、std :: stringと構造化オブジェクトのパフォーマンスの違いを示すことができる小さなアプリケーションを作成しました。

#include <iostream>
#include <vector>
#include <chrono>
#include <algorithm>
#include <boost/sort/sort.hpp>

using namespace std;

#define LEN   4
#define COUNT 1000000
#define USE_BOOST_SORT

// simple structure that i think to be identical to std::string
struct test
{
    inline bool operator<(const test& a) const  noexcept
    {
        size_t minsize = len < a.len ? len : a.len;
        int res = ::memcmp(ptr, a.ptr, minsize);
        return res < 0 || (res == 0 && len < a.len);
    }
    inline size_t size() const noexcept
    {return len;}
    inline const char* data() const noexcept
    {return ptr;}
    inline const char& operator[](size_t index) const noexcept
    {return ptr[index];}

    const char* ptr = nullptr;
    size_t len = 0;
};


int main(int,char**)
{
    // stage 1.a - initialize string sorting data with randoms
    vector<string> strings;
    strings.resize(COUNT);
    int counter = 0;
    for (string& s : strings)
    {
        s.resize(LEN);
        for (char& c : s)
            c = (counter++) % 256;
    }
    // stage 1.b - make the copy of data to get deterministic results and initialize tests array
    vector<string> strings_copy = strings;
    vector<test> tests;
    tests.resize(strings_copy.size());
    for (size_t i = 0; i < tests.size(); ++i)
    {
        tests[i].ptr = strings_copy[i].data();
        tests[i].len = strings_copy[i].size();
    }

    //  stage 2. sorting
    for (size_t i = 0; i < 10; ++i)
    {
        // make the copy of data to keep order unchanged
        vector<string> to_be_sorted = strings;
        chrono::high_resolution_clock::time_point t1 = chrono::high_resolution_clock::now();
#ifdef USE_BOOST_SORT
        boost::sort::spreadsort::string_sort(to_be_sorted.begin(), to_be_sorted.end());
#else
        sort(to_be_sorted.begin(), to_be_sorted.end());
#endif
        chrono::high_resolution_clock::time_point t2 = chrono::high_resolution_clock::now();
        to_be_sorted.clear();
        // make the copy of tests for sorting
        vector<test> tests_for_sort = tests;
        chrono::high_resolution_clock::time_point t3 = chrono::high_resolution_clock::now();
#ifdef USE_BOOST_SORT
        boost::sort::spreadsort::string_sort(tests_for_sort.begin(), tests_for_sort.end());
#else
        sort(tests_for_sort.begin(), tests_for_sort.end());
#endif
        chrono::high_resolution_clock::time_point t4 = chrono::high_resolution_clock::now();

        cout << "String sort time: " << chrono::duration_cast<chrono::milliseconds>(t2-t1).count()
             << " msec" << endl;
        cout << "Test sort time: " << chrono::duration_cast<chrono::milliseconds>(t4-t3).count()
             << " msec" << endl;
    }
}

Here is same code at IDE One

そしてプログラムの出力は

String sort time: 57 msec
Test sort time: 134 msec
String sort time: 51 msec
Test sort time: 130 msec
String sort time: 49 msec
Test sort time: 131 msec
String sort time: 51 msec
Test sort time: 130 msec
String sort time: 49 msec
Test sort time: 129 msec
String sort time: 51 msec
Test sort time: 130 msec
String sort time: 49 msec
Test sort time: 130 msec
String sort time: 51 msec
Test sort time: 132 msec
String sort time: 50 msec
Test sort time: 130 msec
String sort time: 50 msec
Test sort time: 131 msec

Boost.Sortの代わりにstd :: sortを使用しても問題は解決しません。

しかし、LENパラメータを16以上に変更しようとすると、構造体は同じ速度でソートを開始します。

それで私の質問 – 小さな文字列を使うときstd :: stringオブジェクトと同じくらい速くソートするように私のコードをどのように改良することができますか?

私の主なコンパイラはMSVC 2015 update 3 / Win64です。 IDEの1つは内部的にGCCを使用しているので、これはコンパイラの問題ではないかもしれません

Boost.Sortを使用するときのもう1つのオプションは、構造をラップして必要なインターフェースを実装する「Functor」オブジェクトを作成することです。しかしこのようにすると、今よりも遅くなります。

charの代わりにunsigned charデータ型を使用しても何も変わりません。

ベストアンサー
ライブラリが文字列の実装にSSO(Small String Optimization)を使用しているかどうかを確認してください。

もしそうであれば、参照の局所性の増加は容易に違いを説明することができます。また、文字列が大きくなりすぎてSSOの恩恵を受けるには違いがなくなることも説明しています。

概念実証:killSSO

このベンチマークをkillSSO行で実行したところ、プリントがコメントアウトされました:Live On Coliru

String std::sort time:  193.334 msec
View std::sort time:    419.458 msec
String boost sort time: 63.2888 msec
View boost sort time:   154.191 msec
...

行のコメントを解除するstd :: for_each(c.begin()、c.end()、kill_SSO {});プリント:Live On Coliru

String std::sort time:  548.243 msec
View std::sort time:    422.26 msec
String boost sort time: 156.891 msec
View boost sort time:   154.163 msec

ノニウスベンチマーク

Nonius micro-benchmark frameworkを使うと、

#include <algorithm>
#include <boost/sort/sort.hpp>
#include <boost/utility/string_view.hpp>
#include <vector>
#define NONIUS_RUNNER
#include <nonius/benchmark.h++>
#include <nonius/main.h++>

extern std::vector<std::string> const testdata;

struct kill_SSO {
    void operator()(std::string& s) const { s.reserve(20); }
    template <typename Other> void operator()(Other&&) const   {} // not needed
};

struct std_sort          { template <typename It> static void run(It b, It e) { std::sort(b,                            e); } };
struct boost_spread_sort { template <typename It> static void run(It b, It e) { boost::sort::spreadsort::string_sort(b, e); } };

template <typename C, typename Sort, bool Kill = false> void bench(nonius::chronometer& cm) {
    C c {testdata.begin(), testdata.end()};
    if (Kill) std::for_each(c.begin(), c.end(), kill_SSO{});

    cm.measure([&]{ Sort::run(c.begin(), c.end()); });
}

using view = boost::string_view; // std::string_view, boost::string_ref, gsl::span etc.
NONIUS_BENCHMARK("SSO std::sort time:  ",    [](nonius::chronometer cm) { bench<std::vector<std::string>, std_sort, false>(cm); })
NONIUS_BENCHMARK("SSO boost sort time: ",    [](nonius::chronometer cm) { bench<std::vector<std::string>, boost_spread_sort, false>(cm); })
NONIUS_BENCHMARK("String std::sort time:  ", [](nonius::chronometer cm) { bench<std::vector<std::string>, std_sort, true>(cm); })
NONIUS_BENCHMARK("String boost sort time: ", [](nonius::chronometer cm) { bench<std::vector<std::string>, boost_spread_sort, true>(cm); })
NONIUS_BENCHMARK("View std::sort time:    ", [](nonius::chronometer cm) { bench<std::vector<view>       , std_sort>(cm); })
NONIUS_BENCHMARK("View boost sort time:   ", [](nonius::chronometer cm) { bench<std::vector<view>       , boost_spread_sort>(cm); })

std::vector<std::string> const testdata = [] {
    std::vector<std::string> generated(1000000);
    auto genchar = [count=0]() mutable { return static_cast<char>(static_cast<uint8_t>(count++ % 256)); };
    std::generate(generated.begin(), generated.end(), [&] { return std::string {genchar(), genchar(), genchar(), genchar()}; });
    return generated;
}();

結果Interactive On Plot.ly

enter image description here

転載記事の出典を記入してください: Boost.Sortのstring_sort関数を使ってC構造体を高速に動作させる方法 - コードログ