std::deque の実装 其の弐
いくつかのコンパイラで std::deque の push_back() のパフォーマンス特性を実測してみました。規格上、std::deque の push_back() は定数時間内に終わらなければならないことになっています。が、予想通り、「稀に多大な時間がかかり且つ既存の要素数が多ければ多いほど多くの時間がかかる」という実測結果が得られました。
前回の調査結果でも予想通りの実装方法だったわけですが、これでは厳密にはいずれのコンパイラに付属している STL はどれも規格を満たしていないことになります。
これって私が見落としているだけで、実は規格に「稀に極端な時間がかかってもいいよ」的なことがどこかに記述されてるんでしょうか? あるいはまだ規格に std::deque の実装方法としてこれこれと言ったものを想定している等の記述があればそこから「まぁ、このアルゴリズムなら稀に時間がかかるのは仕方ないよね」と行間を読めるものなのですが。
それとも、実は実装が面倒なだけで規格の要求を満たす実装方法があるんだけど、どこのコンパイラ(STL)ベンダーも面倒くさがって簡易な実装で逃げてるだけ?
以下に、今回、測定に利用したコードとその結果を残しておきます。
測定に利用したコード
#include <deque> #include <stdexcept> #include <string> #include <stdio.h> #include <stdlib.h> #if defined(__GNUC__) # include <sys/time.h> # include <stdint.h> #else # include <windows.h> #endif #if !defined(__GNUC__) class win32_error :public std::runtime_error { DWORD code; public: win32_error(DWORD X_code = GetLastError()) :std::runtime_error(win32_error::make_error_message(X_code)), code(X_code) { } static const std::string make_error_message(DWORD X_code = GetLastError()); DWORD get_error_code() const { return code; } const char * get_error_message() const { return what(); } }; const std::string win32_error::make_error_message(DWORD X_code) { LPVOID lpMsgBuf; FormatMessageA ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, X_code, MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), (LPSTR)&lpMsgBuf, 0, NULL ); const std::string result = (const char *)lpMsgBuf; LocalFree(lpMsgBuf); return result; } #endif class performance_time { public: #if defined(__GNUC__) typedef int64_t value_type; #else typedef __int64 value_type; #endif typedef performance_time this_type; value_type value; performance_time(value_type a_value) :value(a_value) { } performance_time(const this_type & a) :value(a.value) { } static performance_time get_current_tick() { value_type result; #if defined(__GNUC__) struct timeval tv; gettimeofday(&tv, NULL); result = tv.tv_sec; result *= 1000000; result += tv.tv_usec; #else if (!QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&result))) { throw win32_error(GetLastError()); } #endif return result; } static value_type get_tick_resolution() { #if defined(__GNUC__) return 1000000; #else value_type result; if (!QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER *>(&result))) { throw win32_error(GetLastError()); } return result; #endif } value_type get_sec() const { return value /get_tick_resolution(); } value_type get_usec() const { const value_type scale = 1000000; return performance_time(value *scale).get_sec() -get_sec() *scale; } this_type operator = (const this_type & a) { value = a.value; return *this; } this_type operator += (const this_type & a) { value += a.value; return *this; } this_type operator -= (const this_type & a) { value -= a.value; return *this; } this_type operator + (const this_type & a) const { return value + a.value; } this_type operator - (const this_type & a) const { return value - a.value; } }; inline bool operator == (const performance_time & a, const performance_time & b) { return a.value == b.value; } inline bool operator != (const performance_time & a, const performance_time & b) { return a.value != b.value; } inline bool operator < (const performance_time & a, const performance_time & b) { return a.value < b.value; } inline bool operator <= (const performance_time & a, const performance_time & b) { return a.value <= b.value; } inline bool operator > (const performance_time & a, const performance_time & b) { return a.value > b.value; } inline bool operator >= (const performance_time & a, const performance_time & b) { return a.value >= b.value; } int main(int argc, char *args[]) { argc, args; try { std::deque<int> int_deque; performance_time max_tick = 0; performance_time total_tick = 0; const int try_size = 1024 *1024; for(int i = 0; i < try_size; ++i) { performance_time begin = performance_time::get_current_tick(); int_deque.push_back(i); performance_time end = performance_time::get_current_tick(); performance_time current_tick = end -begin; if (max_tick < current_tick) { max_tick = current_tick; printf ( "%7d:%2d.%06d\n", i, static_cast<int>(max_tick.get_sec()), static_cast<int>(max_tick.get_usec()) ); } total_tick += current_tick; } printf ( " total:%2d.%06d\n", static_cast<int>(total_tick.get_sec()), static_cast<int>(total_tick.get_usec()) ); performance_time average_tick = total_tick.value /try_size; printf ( "average:%2d.%06d\n", static_cast<int>(average_tick.get_sec()), static_cast<int>(average_tick.get_usec()) ); } catch(const std::exception & a) { fputs(a.what(), stderr); } catch(...) { fputs("予期せぬエラーが発生しました。", stderr); } return EXIT_SUCCESS; }
Borland C++ 5.6.4 での結果
既存の要素数 | 時間(秒) |
---|---|
0 | 0.000002 |
1 | 0.000002 |
4 | 0.000003 |
11 | 0.000003 |
25 | 0.000003 |
186 | 0.000006 |
378 | 0.000006 |
425 | 0.000008 |
852 | 0.000014 |
920 | 0.000248 |
27305 | 0.000342 |
39703 | 0.000605 |
109225 | 0.000723 |
218452 | 0.001533 |
303583 | 0.004909 |
873812 | 0.005564 |
total | 1.825859 |
average | 0.000001 |
Microsoft C++ 8 での結果
既存の要素数 | 時間(秒) |
---|---|
0 | 0.000007 |
44 | 0.000137 |
9178 | 0.000691 |
32214 | 0.000726 |
144712 | 0.000739 |
315576 | 0.001084 |
473364 | 0.001556 |
710048 | 0.002382 |
total | 1.855766 |
average | 0.000001 |
Metroweks C/C++ Compiler 3.0.1 での結果
既存の要素数 | 時間(秒) |
---|---|
0 | 0.000042 |
94 | 0.002739 |
114367 | 0.006732 |
575119 | 0.021710 |
total | 1.898673 |
average | 0.000001 |
g++ 3.4.4 での結果
cygwin上での調査結果の為、タイマーの精度が思わしくなく正常に測定できていませんが、他と同様のパフォーマンス特性が見受けられます。
既存の要素数 | 時間(秒) |
---|---|
3885 | 0.015000 |
12762 | 0.016000 |
total | 0.654000 |
average | 0.000000 |
追記
コメント欄でもご指摘頂いてますが、規格上、このパフォーマンス特性でよいようです。
cf. http://ml.tietew.jp/cppll/cppll/thread_articles/13428