« 昼間の仕事世界に復帰 | メイン | 昨日の続き »

2010年04月24日

アセンブラまで降りるとわからない

 なぜだか、エンディアンを操作する処理をクラステンプレートを使用したメタプログラミングでやる、というのを楽しんでいる。が、その話は後だ。読んで楽しいと思う人が少ないだろうからだ。それよりも、自分が悩んでいることを先に書いて助けを請うことにしよう。

 何で悩んでいるかというと、エンディアンを little ─ big 間で相互に変換する際の処理で一番早いのがどれか、自分では判断がつかないのだ。候補として扱う条件は、分岐やループなど、遅くなる要因がないことや、可能な限り局所変数の数や演算の量が少ないこと。扱うのは 32 ビット変数のみと(ひとまずは)しておく。最初は std::reverse() で片付けようかと思ったが、ループやスワップが多いので論外。で、ひとまずイケそうなのは以下の2つと考えた。無論、int が 32 ビット幅だという前提はあるものとする。

int endian_invert( int n ) {
    return ( ( n & 0xFF000000 ) >> 24 ) |
           ( ( n & 0x00FF0000 ) >>  8 ) |
           ( ( n & 0x0000FF00 ) <<  8 ) |
           ( ( n & 0x000000FF ) << 24 );
};
int endian_invert( int n ) {
    union { int val; char ch[4]; } wk;
    wk.ch[3] = ((char*)&n)[0];
    wk.ch[2] = ((char*)&n)[1];
    wk.ch[1] = ((char*)&n)[2];
    wk.ch[0] = ((char*)&n)[3];
    return wk.val;
};

 正直言って、どちらが速いのか自分にはわからない。アセンブルリストを出してみた限りでは、後者の方が短いようだ。自分の無根拠な直観も、後者の方が速いのかな‥‥‥と思ってはいる。しかし、アセンブルリスト上で1行でも、命令によって実際に実行するのにかかる処理コストは異なるという話を聞いたことがある。しかし、そもそも自分はアセンブラを読めない、で困っているというわけだ。こういうときの判断のためのガイドラインとか知っている方、教えて下さい。

投稿者 kagelow : 2010年04月24日 20:30

コメント

エンディアン変換ですか。
アセンブラなら恐らく1命令で実行可能なんですけどね(インテル系ならbswapだったかな?)。
恐らく命令自体は1クロックで処理されると思いますが、C→アセンブラへの移行時にレジスタデータの退避などオーバーヘッドがあるだろうから、1行しかないアセンブラソースだとかえって遅くなるかもね(僕も詳しくは分かりませんが)。

あと、メモリアクセスよりもレジスタ操作の方が高速な筈ですから、ポインタ操作よりビットシフトの方が高速じゃないかと思います。
もっとも、この辺はコンパイラがどのように解釈するかで変化するところですから、実行時間を計測するしか無いでしょうね。
とりあえず、以下のような方法で時間を計測して一番効率が良さそうなのを選ぶと良いのでは?

int i;
printf( "START=%f\n", clock() );
for(i=0; i<99999999; i++ )
{
inv32 = endian_invert( inv32 );
inv32 = endian_invert( inv32 );
inv32 = endian_invert( inv32 );
inv32 = endian_invert( inv32 );
inv32 = endian_invert( inv32 );
}
printf( "END =%f\n", (float)clock() );

ループの中のendian_invertファンクションを5つ列挙しているのは、時間稼ぎの理由もありますが、forループの比重を下げるのが主な目的です。

※最近はCとか触っていないのでこのソースで動くかイマイチ自信ないのですが、雰囲気は分かりますよね?(苦笑)

投稿者 いなあも : 2010年04月25日 00:03

> いなあも さん

 ありがとうございます。やっぱり、アセンブラソースの長さだけじゃなんとも言い切れないですよね。

 もともと、big と little の他に、いわゆる middle endian (PDP エンディアンとかその他変態的なやつ)もイジれるようにしようとしたのが発端なんですが、それを可能な限りコンパイル時点でバインドしたいな、と。で書いているうちにこのエントリのような疑問が出てしまったわけです。

 middle endian なんてまず使うことは無いんですが、やり始めちゃった以上仕方ないかなと。そんなわけで、実際シフトを使うのは little - big 間の相互変換だけなんでしょうけれど、これはとりあえず時間計測してみます。

投稿者 kagelow : 2010年04月25日 00:34

> いなあもさん

 計ってみました。1億回まわすループの中で endian_invert( ) を10回連続してコールするという試行で、ビットシフトの方は4秒、char 配列解釈の転写では10秒かかりました。やっぱりビットシフトの方が速いんですね。

投稿者 kagelow : 2010年04月25日 01:26

コメントがあればどうぞ




保存しますか?




kagelow home へ