リンクフリーを近日中にとりやめる予定です

すでにリンクを貼っていただいている方、ご一報頂きたくお願い申し上げます。


ごく少数ですが、リンクをお断りする場合があります



ブログ内 風景光景カテゴリー

続編記事などをご希望の方は こちらへどうぞ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[未掲載分] 車輪の再発明 (9)

元来、好ましい意味で用いられない「車輪の再発明」。



筆者は、そのような行為を肯定的に捉えている。
なぜならば、新しい技術や文化が芽生えるきっかけは、一見無駄と思われることへの挑戦である・・・


お知らせ
活動休止にともない、この記事を事前に予約投稿してあります。
トップ記事の固定を目的としています


この題目は2012年暮れ頃に掲載しようと下書きし、諸事情により掲載却下とした分です。
前話、車輪の再発明 (8)にひきつづき、利用環境を OS はWindows , CPU は Intel 製、いわゆるウィンテル機を前提に綴ります。

今回もサンプルコードを載せる。特に難しいところは無いので、サラっと次へ進みたいところ。だが、不慣れなヒトのために少々述べておく。

これを下書きした時点において、これからアプリを作ろうとするヒトが参考にするような書籍や解説系のサイト等は 32ビット環境を主軸に書かれている。
たしかに、Windows 95 が登場した頃からつい数年前までは 32ビット環境が主流であった。Windows Vista の登場により 64ビット環境の構築が容易になったが、店頭販売されていた PC のうち、80 ~ 90%は 32ビット版 の OS を搭載したモデルだったように記憶している。
64 ビット版 の OS が異端というワケではなく、既存のアプリとの互換性、つまり、「昨日まで使ってきたアプリを新しい PC でも使いたい」との声に応えたかったのであろう。
筆者の個人的な感覚で言えば、 Windows 7 の導入を機に 64ビット環境への移行が進んだように感じる。

OS が変わっても昨日までと同じ動作、結果を求めがちだ。既存のアプリとの互換性で言えば、昨日まで使ってきた 32ビット版のアプリがお行儀よく組まれたアプリであれば、64ビット版 の OS 上でほぼ動く。
「互換性が保たれる」という点から甘えが生じる。「このアプリはまだまだ使える」と思い込んでしまえば、新バージョンのアプリへ乗り換えを先送りしがちだ。仮に 32ビット版の新しい OS を選択しても、お行儀の悪いアプリは何かとトラブルのもと。OS を乗り換えたら、アプリも新規に買い揃えるのが無難だろう・・・

話が逸れてきたので戻ろう。
データ幅、レジスタの幅の意識を

CPU のデータ幅やアドレス幅などを論じだすと誤解が生じる可能性がある。ここで述べるのは、数値を扱える幅、データ型のこと。

Windows 用のアプリを作る場合、独特のデータ型の呼び名を覚えることになる。
今回はC/C ++ 言語でアプリを作る例として、DWORD32 や DWORD64 というデータの型を用いる。詳しくは windef.h や basetsd.h という名称のヘッダーファイル中で定義されている。

DWORD32 は32ビット幅の「符号なし整数」。通常は 「32ビット」と指定せずに DWORD と書くことが多いだろう。
C/C++ 言語の入門書等では unsigned int などが載っているハズ。

同様に、64ビット幅の符号なし整数を用いたいならば DWORD64 や DWORDLONG を用いる。
ほかにも、符号なし整数の仲間として UINT16 , UINT32 や UINT64 等のデータの型が定義されている。これらは固定幅の整数型である。

ならば、「16ビット幅の時はDWORD16 ???」
との声も聞こえてきそうだ。
DWORD のもともとの意味は Double WORD。WORD とは16ビット幅の符号なし整数、unsigned short つまり、DWORD は 16ビット幅の符号なし整数2つ分。
16ビット幅の「符号なし整数」をお望みならば 「D」「16」も付けないWORD。
アセンブリ言語では 16ビット幅の符号なし整数4つ分、64ビット幅という意味で QWORD もある。Quad WORD の略。既出の DWORD64 や DWORDLONG よりも QWORD の方が判りような・・・

ほかに「符号なし整数」としてunsigned long が紹介されている例もみかける。たいていは 32ビット幅を指していると思われるが、プログラミングモデルによって64ビット長の符号なし整数を指すケースもあるので要注意。こちらも固定幅の整数型として用いるならば ULONG32 や ULONG64。

前話、車輪の再発明 (8)で「32 ビット版の Windows と 64 ビット版の Windows の両方のポインタに合わせてサイズが変更される整数型」として、UINT_PTR という「符号なし整数」について触れた。
「_PTR」の付く整数型は流動的に翻訳される。ポインタの変換などを扱うのには便利。


今回は 旧来の4バイトずつ転送する例と16バイトずつ転送する例を比べたい。よって、「32ビット幅の~~」と明確に意識したコードを書く。1バイトは 8 ビット、ゆえに 32ビット幅は4バイト幅を指す。

ところで、入門者向けの書籍や解説系のサイト等で扱われている整数のデータ型でもっとも多いのは int。冒頭で述べた、「32ビット環境を主軸に」言い換えれば「32ビット環境を前提に」とも言えるが、モノによっては int は 32ビット幅と決めつけているような例もある。
「int は 固定幅の整数型ではない」という考え方は筆者の思い込みかもしれないので、手元にある K & R 本を確認してみた。それによれば データ型の説明として
・「通常、特定の計算機に自然な大きさ」
・「short は int より長くてはならず」
・「int はlong より長くてはいけない」の3点が挙げられているが、int は 16ビット幅 ( 2バイト ) とも 32ビット幅 ( 4バイト ) とも明言されていない・・・

コードと説明を載せる前にもうひとつ。
データ型を確認する方法を載せておきたい。
データ型を知りたい状況になると、windef.h や basetsd.h などのヘッダーファイルを探し開く。



上の図は Windows 7 でのファイル検索の例。
エクスプローラー右上の検索ボックスを使えば多数のファイルを探し回るよりも楽。



同じような名称のファイルが複数列挙される。更新日時が目安になるかもしれないが、どれがアクティブなのか迷うのも当然。
開発環境として Visula Studio を使っているならばもっと楽になる。

例えば 、先ほど触れた DWORD32 を確認したいとしよう。



ソースコードの調べたい語句をダブルクリックすると選択された状態になる。反転表示される。
ここでキーボードの[F12]ボタンを押せばOK。

マウス主体に操作している時や[F12]ボタンが効かないこともありそうなので加えておこう。
反転表示された真ん中周りへマウスのポインタを移動し右クリック。
キーボードから操作する場合は、[Shift]ボタンを押しながら[F10]を押す、もしくはアプリケーションキーを押す。
※ アプリケーションキーとは右 Ctrl ボタンの近くにあり、メニューとマウスポインタのような矢印が描かれたボタン。



コンテクストメニューの [定義へ移動] もしくは [宣言へ移動] をクリック。



DWORD32unsigned int であることを確認できるハズ。



タブをみると、BaseTsd.h を開いていることが判る。
確認が済み次第ファイルを閉じる。くれぐれも、ヘッダーファイルに変更を加え保存しないように。

ファイルを閉じるにはタブを右クリックしてコンテクストメニューの[閉じる]を選択するか、キーボードの[Ctrl]ボタンを押しながら[F4]を押す。
キーボード操作で間違えやすいのは[Alt]ボタンを押しながら[F4]を押すことによるアプリの終了。ここで言うアプリの終了とは ウィンドウ右上の[閉じる]をクリックするのと同等、Visual Studio の終了・・・

前話の終りで 「CopyMemory 関数 を書き換えてみる」 と述べた。正しくは「自分で書いてみる」という方針。
「書き換え」という語句は API フックを指すこともある。フックとは釣鐘ではなく、関数を横取りすること。筆者もその手法を用いて作ることもあるのだが奥が深く、ここに載せるには長い。そもそも、記事のタイトルと逸れてしまう・・・

ということで、CopyMemory 関数のようなコードを自分で書いてみる。

3話前の車輪の再発明 (6)で触れた
SIZE_T i;
for(i = 10 ; i ; --i){

CopyMemory( ~ );
}

この4行のうち、for( ~ ){} の間、CopyMemory( ~ );を変更。

最初は簡単で手軽に考えてみる。仕組みとしては、あらかじめ決めた回数だけロードとストアを繰り返す。
4バイトずつ転送するので繰り返す回数は __SIZE_DUPLICATE として定義した数の1/4。
DWORD32 *pdwd , *pdws;
pdws = (DWORD32 *)(lpTemp[2]);
pdwd = (DWORD32 *)(lpTemp[3]);
SIZE_T j;
for(j = 0 ; j < (__SIZE_DUPLICATE / sizeof(DWORD32) ) ; j++){pdwd[j] = pdws[j];}
for 文による反復を使ってこんな感じだろうか・・・

#if __MYAPP_LEVEL == 2
// 第2番目のパターンのコード
#elif __MYAPP_LEVEL == 3

2話前の車輪の再発明 (7)で触れた #if ディレクティブ ~~ #else ディレクティブ ~~ #endif ディレクティブを活かし、第2パターンのところにコードを追加する。

慣れないヒトにとっては変数名が気になることだろう。
ハンガリアン記法ハンガリー表記法と呼ばれ、古くから Windows でプログラミングする際に用いられてきた。
プレフィックス、接頭辞に特徴があり、ポインタであれば「p」、データ型が DWORD ならば「dw」といった具合。
※ 「用いられてきた」と過去形で表現は、昨今の MSDN ライブラリなど、変数や関数の名前付けガイドラインに「ハンガリー表記法は使用しないでください」と記述があるから。

DWORD32 *pdwd , *pdws;
の部分は「32ビット幅の符号なし整数へのポインタを使う」
pdwd[j] = pdws[j];の部分で
ポインタ pdws の指すアドレス (= 場所) にある数値を4バイト分(= 32ビット分) 読み出し、ポインタ pdwd の指すアドレスを4バイト分書き込む。
それを for 文で繰り返す。転送するバイト数は既に __SIZE_DUPLICATE で定義している。この数を 4バイト分 で割った回数繰り返す。

本来、for 文や while 文、さらには if 文において単一の命令を繰り返すならば、命令を {} で囲む必要はない。
入門用のサンプルでは略されることが多く、{} を省いた方が見た目がシンプルである。
実際には繰り返し文の中に複数の命令コードを設けることも多いので{} で囲む。
あまりにも深いネストは直感的な読み易さを損なう。
Visual Studio では最初の { を入力すると閉じ括弧が強調表示されるようになっている。

鋭いヒトはお気づきの通り、何かが抜けている。略してあるのは端数の転送処理。
今回はたまたま、転送するバイト数が4の倍数と判りきっているので不要だ。しかし、実際には4の倍数以外を転送する、つまり、4で割って余りが出るようなケースも想定すべきなのだ。
余りはゼロから3の間。その場合は残りを1バイトずつ転送すれば良いだろう。最大3回までで済む。
ちなみに、数年前までのIntel 製 CPU では4バイト転送の直後に2バイト単位のアクセスを行うとペナルティ、つまり、遅くなると言われていた。Core 2 Duo , Quad 以降では改善されているようで、2バイトアクセスによる遅延を気にしないでもよさそうだ・・・

このままでもコンパイラが良好なコードを出力してくれる可能性が高い。
あえて、変更を加えてみよう。

SIZE_T j;
j = (__SIZE_DUPLICATE / sizeof(DWORD32) );
while(--j){
    *pdwd++ = *pdws++;
}

インクリメント演算子 (加算演算子) 、デクリメント演算子が読み辛いかも。
演算子を加算後に代入、減算後に代入に置き換えてみると以下のようになる

while(j){
    *pdwd = *pdws;
    pdws += 1;
    pdwd += 1;
    j -= 1;
}

さらに読み辛いヒトは△-=1△ = △-1と置き換えてみればわかるでしょうか・・・

最初の例と比べ for 文だった部分が while 文 へと変更。
最初の例でデータの読み書き部分がポインタと配列の組み合わせだったのに対し、ポインタ値での読み書きとなったため、自前でポインタ値を加算するように変更。
さらに、「特定の数に達したら終了」から「残り数を1つずつ減らし、ゼロになったら終了」へと変更。

for 文 と while 文の違いで速度に影響することはほぼありません。
比較の部分は「ゼロになったら終了」が速いとされてきました。というよりも、遅くならないと言い換えた方が適切かも。詳しくは結果検証のところで・・・

次に 64ビット幅、8バイトずつ転送する案を考えてみる。

DWORD64 *pdwd , *pdws;
pdws = (DWORD64 *)(lpTemp[2]);
pdwd = (DWORD64 *)(lpTemp[3]);
SIZE_T j;
j = (__SIZE_DUPLICATE / sizeof(DWORD64) );
while(--j){
    *pdwd++ = *pdws++;
}

32ビット幅ずつ転送するのに比べ異なる点は 、DWORD32 から DWORD64 への変更。
一度に読み書きできる幅が2倍になった分、読み書き作業の総数も半分となる。繰り返す回数は __SIZE_DUPLICATE として定義した数の1/8。

なお、筆者の環境は64ビット版の Windows 7 である。
32ビット版の OS 上で64ビットコードを転送するコードは返って速度向上は期待できない。なぜならば、32ビット環境では汎用レジスタが4バイト分であり、2回転送するようなコードが生成される可能性が高い。
実際に出力されたアセンブリ言語のファイルで確認するのが手っ取り早い。アセンブリファイルを出力する方法については車輪の再発明 (7)で触れた。

まず、64ビット版 のプラットフォーム向けに出力されたアセンブリファイルのコードは以下の通り。



64ビット幅を持つ1本のレジスタへ読み込み、そのレジスタの値を書き出す。

続いて32ビット版 のプラットフォーム向けに出力されたアセンブリファイル。



32ビット幅を持つ1本のレジスタへ読み込み、そのレジスタの値を書き出す。それを2回行った後ポインタの加算、残りがゼロか比較している。
やはり2回転送するようなコードが生成された・・・

32ビット幅で2度の転送を避けられないならば以下のように翻訳されるならば結果が良くなりそうだ。



32ビット幅を持つ別々の2本のレジスタへ読み込み、ポインタの加算、そのレジスタの値を書き出す。
どのようにC/C++ 言語のコードを組み変えたのか気になるヒトもいるでしょう。64ビット幅のデータ転送が最終目的ならじっくり扱いところですが、あしからず・・・

C/C++ 言語で SSE2 を使わない転送の話はここまで・・・の予定でしたが少々脱線・・・
条件は Intel 製 CPU 、アセンブリ言語、32ビット環境に限られるが、もっと手短かに転送する例として以下のように考えられる。
DWORD dwEcx , dwEsi , dwEdi;
dwEcx = __SIZE_DUPLICATE / sizeof(DWORD);
dwEsi = (DWORD)(lpTemp[2]);
dwEdi = (DWORD)(lpTemp[3]);
__asm{
    mov ecx , dwEcx
    mov esi , dwEsi
    mov edi , dwEdi
    rep movsd
}

上記の例において、__asm{ ~~ }のブロックでインラインアセンブラを使っている。
64ビット環境を前提に C/C++ 言語 で組む場合、インラインアセンブラが廃止されたことによりアセンブリ言語は利用できない。厳密に言えば、Visual Studio でもアセンブリ言語を扱えるのだが、利用の際は厳しい制限がある。
ほかにも、64ビット環境とインラインアセンブラの関係だけでなく最適化が効かない、致命的なエラーを発生させ易いなど気をつけるべき点もあり、「お手軽」とは言えない。

現在、MOVSD は複数の意味があり、ここでは Move data from String to string 「ストリングの移動」を指す。実際には、基のデータを消去しない。よって、移動ではなく複写。
もうひとつは、SSE2 機能を搭載したプロセッサで Move Scalar Double 、「倍精度浮動小数点値 の移動」を指す。
前者の MOVSD は、 ESI の指す番地にあるデータを4バイト読み出し(ロード)、EDI の指す番地に書き込む(ストア)。その後、ESI 、EDI 両レジスタに格納されている値は4加算。
ただし、ステータスレジスタ、Intel 製 CPU では EFLAGS レジスタのディレクションフラグ が 1に設定されているならば、ESI 、EDI 両レジスタともに4減算。

MOVSD の前に REP が付くのは Repeat の略で、「繰り返し」を指す。「ECX の値がゼロになるまで繰り返す」という意味になる。
C/C++ 言語において、for 文や while 文を用いて「残り数がゼロになるまで繰り返す」のと同じようなものだ。

「ストリングの移動」を指す MOVSD 命令は古くから搭載されており、速くないとされてきた。
筆者の個人的な見解かもしれないが「速くない」という表現より、「転送の条件によっては遅れが生じ易い」と言い換えた方が実際の状況に近い。

さてさて、 C/C++ 言語で SSE2 を使った例・・・と続けたいところですが・・・

長くなりましたので続きはまた後日・・・
スポンサーサイト

本日も最後までご覧いただきありがとうございます。

「つまらなかった」「判り辛った」という方もご遠慮なくコメント欄へどうぞ

テーマ : プログラミング
ジャンル : コンピュータ

検索サイトからお越しの方へ
検索サイトからお越しの方は、ブラウザのアドレス欄vitalaboloveおよび、fc2.comが含まれているかご確認ください。
含まれていない場合、偽サイトを閲覧なされている可能性があります。

偽サイトは、当ブログの文字部分や画像部分が有害サイトへのバナーと置き換わっているようです。
プロフィール

Author:Vitalabolove
ご訪問ありがとうございます。
店長を任されておりますVitalaboloveです。

コメントはお気軽に。
今のところリンクフリーですが、あと数日でとりやめます。

画像データ、文言の引用は事前連絡くださるようお願い申し上げます。事前連絡の際は、左下、メールフォームを経由をご利用ください。

最新記事
カレンダー
06 | 2014/07 | 08
- - 1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 - -
カテゴリ
ランキング
いつも応援いただきありがとうございました。ただいま休養中につきランキングへ参加していません・・・

フリーエリア
内緒話などはおきてがみをご利用ください。
月別アーカイブ
メールフォーム
掲載された記事について、ご不明な点はここからお問い合わせください

名前:
メール:
件名:
本文:

最新コメント
最新トラックバック
スパムと思われるトラックバックは削除しました
QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。