[c++] 文字列のソーティング
qsort と STL の sort では STL の方が速いよ、という事を以下の記事で読んで知ってました。たしかに整数の配列(正確には vector
C の qsort と STL の sort の速度比較 - bkブログ
最近になって文字列のソーティングをする必要に迫られ、最初は考えずにえいやっと std::sort を使っていたのですが、気になってベンチマークをとって見ると qsort の方が100倍速いという結果になりました。ついでに multikey-quicksort と比べてみると、qsort と同じか少し速いようです*1。
ソート方法 | g++4.3.1 |
---|---|
qsort | 1.10秒 |
stl-sort-func | 115.91秒 |
stl-sort-functor | 115.80秒 |
mulikey-quicksort | 0.80秒 |
ベンチマークには 1MB のテキストファイルを使いました。具体的には、手元にあった XML ファイルを head -c 1m
したものです。
参照の局所性だとかテキストデータの性質とか考慮すべき点はたくさんありますが、これだけの差は容易にひっくり返らないのでは、と思います。
うーーん。どっか間違えているのかな。
#include <cassert> #include <cstdlib> #include <ctime> #include <cstring> #include <string> #include <vector> #include <algorithm> #include "mmap_file.hpp" typedef char Char; typedef const Char* String; void swap(String& a, String& b) { if( &a == &b ) { return; } const String temp = a; a = b; b = temp; } int qsort_cmp(const void* p1, const void* p2) { const String& s1 = *(const String *)p1; const String& s2 = *(const String *)p2; return strcmp(s1, s2); } bool stl_sort_func_cmp(const String& s1, const String& s2) { return strcmp(s1, s2) < 0; } struct stl_sort_functor_cmp { bool operator()(const String& s1, const String& s2) const { return strcmp(s\ 1, s2) < 0; } }; void do_qsort(String* v, const int v_num) { qsort(&v[0], v_num, sizeof(&v[0]), qsort_cmp); } void do_stl_sort_func(String* v, const int v_num) { std::sort(&v[0], &v[v_num], stl_sort_func_cmp); } void do_stl_sort_functor(String* v, const int v_num) { std::sort(&v[0], &v[v_num], stl_sort_functor_cmp()); } void multikey_quicksort(String* begin_, String* end_, int offset) { if( begin_ >= end_ ) { // do nothing } else if( end_ - begin_ < 8 ) { qsort(&begin_[0], end_ - begin_, sizeof(&begin_[0]), qsort_cmp); } else { assert( begin_ < end_ ); const String& pivot = *(begin_ + (end_ - begin_) / 2); const char pivot_char = pivot[offset]; String* p1 = begin_; String* p2 = end_ - 1; while( p1 < p2 ) { if( (*p1)[offset] < pivot_char ) { ++p1; } else if( (*p2)[offset] >= pivot_char ) { --p2; } else { swap(*p1, *p2); ++p1; } } p2 = p1; for( String* p3 = p1; p3 != end_; ++p3 ) { if( (*p3)[offset] == pivot_char ) { swap(*p2, *p3); ++p2; } } if( pivot_char ) { multikey_quicksort(begin_, p1, offset ); multikey_quicksort(p1, p2, offset+1); } multikey_quicksort(p2, end_, offset ); } } void do_multikey_quicksort(String* v, const int v_num) { multikey_quicksort(&v[0], &v[v_num], 0); } typedef void (*SortFunc)(String* v, const int v_num); void benchmark(const char* name, SortFunc func, const String text, const int text_size) { assert( text[text_size] == '\0' ); const int v_num = text_size + 1; String* v = new String[v_num]; for( int i = 0; i < v_num; ++i ) { v[i] = &text[i]; } const double start_time = clock(); func(&v[0], v_num); const double elapsed = clock() - start_time; printf("%-20s:\t%.2f\t[%d %d %d]\n", name, elapsed / CLOCKS_PER_SEC, v[v_num/4] - text, v[v_num/2] - text, v[v_num-1] - text); } int main(int argc, const char** argv) { assert( argc == 2 ); const char* filename = argv[1]; const MmapFile mmap(filename); const String text = mmap.data(); assert( text ); const int text_size = mmap.size(); assert( text[text_size] == '\0' ); benchmark("qsort", do_qsort, text, text_size); benchmark("stl-sort-func", do_stl_sort_func, text, text_size); benchmark("stl-sort-functor", do_stl_sort_functor, text, text_size); benchmark("mulikey-quicksort", do_multikey_quicksort, text, text_size); return 0; }
*1:というか、「ついで」と書いておいてなんですが、この mulkey-quicksort の実用上の性能を知りたいというのが本来の動機だったので、「ついで」でも何でもなくこれが本命だということをいま思い出しました。これも yak shaving なのでしょうか。