超はやめろ


 ソースがでかくなってきたから、スレッド間の受け渡し処理を整理しようと書き換えたら、何だか大体一秒ごとにカクッとするようになった。
 超意味わかんないしー超ムカツクーとか思って色々見てるうちに超うざくなってきて、放り出して書き直してやりたいっつーかマルチスレッドとか何で書いてんだよ俺ーみたいな超投げやりな気持ちになってきたが、ふと気付くと単に初期化処理の要求パケットを間違って一秒ごとに投げてたことに気付いたのであることだなあ。


普通のコードがどうコンパイルされるのかチラ見してみた


 どうコンパイルされるのか分かってねーとCとかC++とかは書きにくい気が、などとぬかしつつも現実には「詳細はよくわかんねーけど最適化してくれるはずだよね」って部分が9割だよな俺の場合、とも思ってた。
 で、実際に吐かれるコードをちゃんと見た記憶が遠くなりつつある今、volatile周辺の挙動も気になったので、かなーり久々にアセンブラ出力させてみた。

 とりあえず、問題のスレッド間通信用リングバッファのコードは、

template < typename T, size_t Size >
class t2t_pipe : noncopyable {
  T buf_[Size];
  volatile size_t head_, tail_;

  // リングバッファの次インデックスを取得する関数 //
  size_t next_index_of ( size_t index ) const {
    size_t next = index + 1;
    if ( next == Size ) {
      return 0;
    }
    return next;
  }

public:
  t2t_pipe () : head_(0), tail_(0) {
    assert( Size >= 2 );
    WMB(); // VC++2008でx86でvolatileだから記述不要?
  }

  bool empty () const {
    RMB(); // これも(これ以下も)全部不要?
    return head_ == tail_;
  }

  bool full () const {
    RMB();
    return head_ == next_index_of( tail_ );
  }

  T pop () {
    assert( ! empty() ); // 呼び出し側は先に必ずempty()見ろよ的設計
    T result;
    RMB();
    result.swap( buf_[head_] );
    head_ = next_index_of( head_ );
    WMB();
    return result;
  }

  void push ( const T& item ) {
    assert( ! full() );
    RMB();
    buf_[tail_] = item;
    tail_ = next_index_of( tail_ );
    WMB();
  }
};

こんな感じ。大丈夫なんだろうかこれ。特にbuf_がvolatileじゃない点とか。インデックス操作のメモリバリアさえ万全なら、関数が操作対象とする要素はvolatileな挙動をしていないはずだから、大丈夫そうな気はするけどいまいち自信が。つーか本当にどっか根本的に間違ってそうで不安だ。
 まあでもその辺は置いといて、実際にt2t_pipe< shared_ptr<foo_base_class>, 16384 > foo_pipe;にpop()を掛けてるところのコンパイル結果はこんな感じになっていた。

_TEXT SEGMENT
何たら$shared_ptr@何たら@@@boost@@どーのこーの PROC
  mov DWORD PTR [eax], 0
  mov DWORD PTR [eax+4], 0
  mov ecx, DWORD PTR 何たら$shared_ptr@何たら+131072
  lea ecx, DWORD PTR 何たら$shared_ptr@何たら[ecx*8]
  push esi
  cmp eax, ecx
  je SHORT $LN9@pop
  mov esi, DWORD PTR [ecx]
  mov edx, DWORD PTR [eax]
  mov DWORD PTR [eax], esi
  mov DWORD PTR [ecx], edx
$LN9@pop:
  mov edx, DWORD PTR [ecx+4]
  mov esi, DWORD PTR [eax+4]
  mov DWORD PTR [ecx+4], esi
  mov DWORD PTR [eax+4], edx
  mov ecx, DWORD PTR 何たら$shared_ptr@何たら+131072
  lea edx, DWORD PTR [ecx+1]
  mov ecx, edx
  sub ecx, 16384
  neg ecx
  sbb ecx, ecx
  and ecx, edx
  mov DWORD PTR 何たら$shared_ptr@何たら+131072, ecx
  pop esi
  ret 0
何たら$shared_ptr@何たら@@@boost@@どーのこーの ENDP
_TEXT ENDS

 うーん。期待以上のとこと以下のとこがあるなあ…。

 とりあえず、いきなりeaxを使ってる辺りfastcall系っぽくて呼び出し規約がまず分からないんだが、最後にreturnしてるからインラインではないよな。0を書き込んでる辺り、T result;の処理かな。その後でswapっぽいこともしてるしな。つーか全くアトミックに見えないんだけど大丈夫なのか(笑)。shared_ptrそのもののコピー関連はスレッドセーフらしいけど。ポイントした先の操作は普通にスレッドセーフじゃないけど。
 メモリバリア周辺での異常行動は無いように見えるっつーか、その辺を見たいならempty()の待機ループとかも見ないと意味無いか。えーと見てきました。大丈夫に見えました。一見。
 つーかぶっちゃけ前半よく分かんねえ(笑)。呼び出し規約が分からないのと、shared_ptrの実装が分からないのとで。
 next_index_of()がインライン展開された後半の辺りは、subしてnegしてsbbしてand、という手法で分岐を抑制していた。確か例の電卓で全く同じ最適化を無駄にやってた気がする。
 だが、こいつはテンプレートクラスで、Sizeはコンパイル時点で16384の定数だから、後半部分に本当に期待していたのはandのマスクだったんだが。
 まあ、人間の手でこう書けばいいんだよな…。

  size_t next_index_of ( size_t index ) const {
    size_t next = index + 1;
    if ( is_power_of_2( Size ) ) {
      return next & Size-1;
    }
    if ( next == Size ) {
      return 0;
    }
    return next;
  }

 つーかこれ、is_power_of_2()のとこがインライン展開で定数化されないと逆に遅いよな。どうなるんだろう。うまく行ったとしても、条件式が定数だよとか、死んでるコードパスがあるよとか、余計な警告も出そうだよな。SFINAEを利用したりすればどうにかなるのだろうか。テンプレートは初歩的な使い方しか理解してないからさっぱり分からんが。
 で、どうでもいいところに不要な最適化を書くな、って話になるんだが(笑)。分かってます。大丈夫です。今捨てますから。コンストラクタにassert(is_power_of_2(Size))とかも書きませんから。…いや別にそっちでいい気もするな。つーかstatic_assert早くくれ。Boostのは例によってIntellisenseが死ぬし。
 メモリに厳しい世界なら頑張り甲斐もあるんだろうなあ。でもすぐ飽きるんだろうなあ俺(笑)。

 まあ、よほどのところ以外はコンパイラ任せで大体平気っぽいよな、とは思いました。
 そんなことより他に考えることが山ほどあるよな、とも思いました。とりあえずnewしまくりは大丈夫なんだろうかとか。でも最近はアロケータも結構賢いらしいけど。まー駄目そうなら「設計失敗しましたー、次行ってみよう」でいいか(笑)。別に今回のは遅くても困らないのに、無駄にマルチスレッドにして遊んでるようなもんだし(笑)。
 派生クラスのインスタンスをnewして、ベースクラス型のshared_ptrに突っ込んでスレッドまたいで渡してる辺りなんかも、どんなコードが生成されてるのか理解してないんだよなあ。ちゃんと派生クラスのdeleteが掛かる仕様らしいけど。

 つーか、Express Editionだとプロファイラが無いのがなあ…。MS謹製のスタンドアロン版は試してみようとしてめんどくさくて気力が尽きたし。タダだから文句は言えないが、こんなとこでケチらなくてもって気はしなくもなく。


C++0xをチラ見してみた


 えーと…。何だこれ(笑)。さすがC++(笑)。
 存在意義を維持したまま、ここまで変態化するもんなのか。まあC++は生まれた時からそういう性格か。Zero-overhead principleとか前面に掲げちゃう言語だしな。
 見た目も仕様も色々とキチガイじみてることだけが難点だよなあ(笑)。罠が多くなるのは仕方ないんだろうけど。Cのポインタが難しい、ってのと同質だとは思う。パフォーマンス落とさない為にちょっと分かりにくくなったけど、脳内でアセンブラに置き換えれば別に難解じゃないから構わんだろー、みたいな。Cからの伝統で、どうコンパイルされるかが見えてないと書きにくい言語というか。

 あと、動作が軽いものは、それだけで使われるんだよなあ。PHPなんかもそうだと思うけど。
 クライアントPCあたりは最近パワーあんまり要らないんじゃね的になりつつあったけど、省電力路線のPCも出てきちゃったし、シングルスレッドのパフォーマンス向上も微妙にきつくなってるし。
 ただ、ネイティブでマルチスレッドアプリ書いてると思うけど、もしも「VMの方がうまく勝手に分散してくれるよ」なんてことになるならVM大勝利かもなあ。仮にそうなったら俺どの言語使うんだろ。まあその時に流行ってる奴か(笑)。
 ぶっちゃけ現状だと、Windowsで同じアプリの.NET版とネイティブ版とついでにJava版があったら、俺ならネイティブ版使うしな。これが「普通.NET版だろ」ってなったら、俺にとって言語の乗り換え時になるんだろう。いや、俺だけじゃないだろうけど。

 で、C++0xだが。
 相変わらずの超高級アセンブラマクロ言語なんだけど、より一層の謎言語になっていた。
sort( x.begin(), x.end(), []( const auto& a, const auto& b )->bool { return a.id() < b.id(); } );
みたいな気持ち悪い文が書けそう。こういう気持ち悪いコードは是非書きたい。というか何これ別言語?
 autoはこの書き方でもいいのかなあ。前方参照してそうだけど。でもそれ出来ないとautoの存在意義ねーか。
 つーかむしろ、
for ( auto& c : x ) { c.chomp(); }
こんなコードの方が早く使ってみたい。これでコンパイル通るかは知らんけど。こんなん見ちゃうと、
for ( int i = 0; i < sizeof(x)/sizeof(x[0]); i++ ) { x[i].chomp(); }
とかもう書きたくねー。sizeofをマクロにしろよとかは置いといてですね。たぶんこれコンパイルしたら同じコードになるよなあ。使いたいなあ。
 昔は空気だったのに、やたら派手になって帰ってきたautoも凄いよな。昔と同じ扱いをすると多分怒られるんだろうな。コンパイラに(笑)。

 まあ何か他にも色々面白そうでした。リテラルが自分で定義出来るとか何に使うんだ(笑)。constexprとかコンパイルタイム向けの仕様もだいぶ増えてるのはメタプログラミング向けなんだろうか。普通に使う分にも便利そうだけど。


一発で動く不安感


 とりあえずクライアントとサービスにあたるスレッドを一本ずつ作って、すんげー簡素な一対一片方向通信専用のパイプを自作して、リクエストを
shared_ptr<req_base_class>( new req_derived_sample_class( arg1, arg2... ) )
みたいなのに包んで渡す形で動くところまでは書けた。一見。
 ただ、パイプのempty()やfull()の時、たぶん真面目にWaitForSingleObjectとかすべきなんだろうけど、テストコードは激しくスピンとかSleep()とかしつつポーリングである。まあ、めんどくせーけど次はここ調べるか…。

 んで、通信パイプは単純にshared_ptrの配列をリングバッファとして扱ってるんだが、VC++2005以降の仕様が「volatileの読み書きはメモリバリア考慮してるから平気ですよ、x86ならCPUの方も心配無しさ」とのことで、インデックス変数をvolatileにしただけでメモリバリアもInterlocked関数も一つも書かずに普通に動いてるんだけど、こんなんで大丈夫なんだろうか本当に。shared_ptr受け渡しのリークとかも怖いし。ほとんど一発で動いたから、逆にすげー心配なんだけど(笑)。


無類の地雷原好き


 いやまあ本物の地雷原は全力で大嫌いだけど。

 ついさっき、普通のテキストエディタで書いた自作クラスを丸ごとIDEのエディタにコピペしてコンパイル掛けたら、コンストラクタの行で「C2514だぜーなんたらクラスにコンストラクタが定義されていません。」「コンパイルされたクラスのテンプレートのインスタンス化なんたらの参照を確認してください」「誤った式になっています。」「型指定子がありま略」「構文エ略」その他合計52個出て止まった。よし、100個は行かなかったな。
 どう見てもコンストラクタに問題が見付からないし、前後におかしな構文も全然見当たらない。何だこりゃー。
 コンストラクタをコメントアウトすると、次のbool f()な関数の行で「C2062だぜーboolとか型イラネーから」「あと他にも8個くらいエラーな」とさらに謎展開。

 結局オチは何なのかというと、
正 public:
誤 pubic:
とかそんな感じでした。エラー内容から類推できねー。まあよくある。
 プログラミングって楽しいですね。あとC++も好きなんですよ。いや本当に。