私は自分のアプリケーションでたくさんの並べ替えをしていますが、パフォーマンスのボトルネックは大きな文字列配列の並べ替えにあることがわかりました。私はそのようなソートをする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;
}
}
そしてプログラムの出力は
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の恩恵を受けるには違いがなくなることも説明しています。
概念実証: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;
}();
関連記事
- バブルソートを使用して構造体の配列をソートする - 構造体メンバの交換を高速化する方法
- 同じ関数を使って類似の構造体を初期化する方法
- 構造体呼び出しごとに1回使用される構造体への関数ポインター
- python - ctypesを使って構造体へのポインタを使って関数を呼び出す
- Cで動的に作成された構造体の配列の長さを取得する方法?
- MATLABのsubstruct関数を使用して、「end」を使用する参照を表す構造体を作成する方法を教えてください。
- Haskell FFI - 構造体へのポインタの代わりに構造体を受け取るか返すC関数を処理する方法?
- 後で使用するために値を返すために関数内の構造体にメモリを正しく割り当てる方法、および構造体をポインタにキャストする方法
転載記事の出典を記入してください: Boost.Sortのstring_sort関数を使ってC構造体を高速に動作させる方法 - コードログ