_SECURE_SCL
VC++用のまともなプロファイラが手元にないので、結果に影響ない範囲で同じ関数を二度呼ぶ手法で所要時間の概算を調査しています。
例えばnew/deleteの時間を測るには、
#include <new> #include <cstdlib> void* operator new(std::size_t size) throw(std::bad_alloc) { // dummy std::free(std::malloc(size)); return std::malloc(size); } void operator delete(void* ptr) throw() { std::free(ptr); }
みたいな事をやってます。
まぁ、予想通り、new/deleteは結構時間を食っているのが分かったので、短文字列最適化の効かないサイズのstd::stringが多いのかなぁと思って、サイズ毎の統計を取ってみました。
#include <new> #include <cstdlib> const std::size_t count_size = 128; std::size_t new_counts[count_size]; void* operator new(std::size_t size) throw(std::bad_alloc) { if (size < count_size) new_counts[size]++; else new_counts[count_size-1]++; return std::malloc(size); } void operator delete(void* ptr) throw() { std::free(ptr); } // ... #include <iostream> int main() { // .... for (std::size_t i = 0; i < count_size; ++i) std::cout << new_counts[i] << std::endl; }
なんと4バイトの要求が圧倒的に多く、29471595回も呼んでました。
今度は犯人探しです。
#include <new> #include <cstdlib> #include <intrin.h> const std::size_t count_size = 128; struct count_info { void* addr; unsigned count; }; count_info count_data[count_size]; void* operator new(std::size_t size) throw(std::bad_alloc) { // VC++コンパイラ擬似関数 void* addr = _ReturnAddress(); for (std::size_t i = 0; i < count_size; ++i) { count_info& info = count_data[i]; if (info.addr == addr) { unsigned n = info.count++; for (std::size_t j = i; j > 0; --j) { count_info& prev = count_data[j-1]; if (prev.count < n) { count_info& cur = count_data[j]; cur.addr = prev.addr; cur.count = prev.count; prev.addr = addr; prev.count = n; } } break; } else if (info.addr == 0) { info.addr = addr; info.count++; break; } } return std::malloc(size); } void operator delete(void* ptr) throw() { std::free(ptr); } // ... int main() { // ... for (std::size_t i = 0; i < 16; ++i) std::cout << count_data[i].addr << "\t" << count_data[i].count << "\n"; }
トップは構文木のvectorのコピーコンストラクタでした。
これだけだと「なんだ、vectorのコピーなんてするヤツが悪いじゃん」となりそうですが、問題のnew呼び出しはサイズ=4固定になっており、要素のコピーそのものに時間がかかっているわけではありませんでした。
ソースを追った結果、xutilityヘッダの_Container_base_aux_alloc_realに辿りつきました。
これは反復子の正当性チェック用のクラスらしいです。しかし、リリースビルドでも使われるのは問題です。
一応、マクロ_SECURE_SCLを0に定義すれば無効に出来るんですが、ABIが変わってしまうので、user-config.jamで
using msvc : 9.0release : C:/Program\ Files/Microsoft\ Visual\ Studio\ 9.0/VC/bin/cl.exe : <cxxflags>-D_SECURE_SCL=0 ;
みたいなツールセットを追加して、Boost共々リビルドしました。
ver | 時間 |
---|---|
v1 | 87.500秒 |
v2 | 70.734秒 |
no_node_d | 56.609秒 |
cache | 47.657秒 |
no-dump | 38.313秒 |
_SECURE_SCL=0 | 30.219秒 |
書き忘れましたが、bjam_dumpのダンプ処理は計測の邪魔なので、今回からコメントアウトして試しています。
no-dumpがcache版からダンプを省いたバージョンで、_SECURE_SCLの処理だけで8.094秒ほど消費していた計算になります。
で、何でそんなにvectorのコピーをしていたかというと、自分的には「空のvectorが軽量」という前提があって、
typedef std::vector<big_data> vec_type; void foo(std::list<vec_type>& list) { std::vector<big_data> vec; // ここでvecに要素を追加 list.push_back(vec_type()); list.back().swap(vec); }
みたいなことをよくやっているからです。
mapの値にコンテナを使う場合も同様の問題が発生します。
まぁ、現行の規格では「空のコンテナは動的メモリを使わない」と決めているわけでもないので仕方ないのですが、パフォーマンス的にはダメダメですね。