SSEを有効にして最適化する

Visual Studio 2005から標準でMMX、SSE、SSE2命令を有効にした最適化をおこなえるようになりました。これらマルチメディア拡張命令を使用することで高速化を試みてみましょう。プロジェクトのプロパティを開き、C/C++のコード生成で「拡張命令セットを有効にする」のオプションを変更します。
SHA-256のサンプルプログラムを実行してみました。以下はその実行速度です。
設定なし・・14103ms
SSE・・・・・13604ms(3.5%)
SSE2・・・・12932ms(8.3%)
SSE2を有効にしただけで、8%ほどパフォーマンスが改善されています。
VC++コンパイラのSSE対応は一部の演算命令をSSE2のものに置き換えているだけで、Intel製のC++コンパイラように自動でベクトルか化を行うものではありません。ただしSSE命令を使用することによって使用できるレジスタの値が増え、その結果メモリ転送量が減って高速化する場合があるという程度のようです。
SHA-256のサンプルプログラムをベクトル化してみました。以下はその実行速度です。
SSE2(ベクトル化)・・・・17532ms
むしろ遅くなっています。
SHA256アルゴリズムはもともとベクトル化にはあまり向いていません。4ブロック128ビット分を一度に演算していますが、ベクトル化できるのは途中までで、最後に手前のブロックとの演算結果との計算は単純に4ブロック分を一度に演算するわけにはいかないからです。それを別にしてもあまりにも遅くなっている気がします。
実はSSE命令にはいくつかパフォーマンス上の欠点があります。
一つ目は16バイト分のメモリに一度にアクセスするため、キャッシュにヒットしない場合の性能低下が大きくなるのです。
二つ目はアライメントの問題。16バイト境界にアライメントされていないメモリへのアクセスは非常に遅くなるのです。
ハッシュ値の計算もととなるバイト列は都合が良いようにはアライメントされていません。これを128bit変数にセットする部分のオーバーヘッドが大きくてベクトル化の恩恵を相殺してしまっています。
SHA-256のサンプルプログラムにSSEの_mm_prefetch命令を4行ほど追加してみました。
SSE2(プリフェッチ)・・・・11918ms(15.5%)
SSEにはCPU内部のメモリキャッシュ動作を制御する命令が追加されています。大量のデータにアクセスする場合、キャッシャ制御命令を使ってメモリバスが使われていない隙間の時間に、データをキャッシュメモリに読みだすことができます。1ブロック分の処理中に、次に使用するブロックをキャッシュに読み込むことで、6%以上高速化しています。
IntelのCPUではキャッシュメモリを32バイトごとに管理しています。_mm_prefetch命令を導入する場合には32バイト毎に、メモリアドレスを指定する必要があります。
AES暗号のサンプルプログラムで「拡張命令セットを有効にする」のオプションを同じように実行してみました。以下はその実行速度です。
設定なし・・36052ms
SSE・・・・・45021ms(-24.8%)
SSE2・・・・45817ms(-27.0%)
むしろ遅くなっています。最適化の成否を高度に判断するものではないようです。よって使用する場合にはソースごとに適用するか否かをプログラマがハンダする必要があるようです。

VisualStudio 2005 Remote Debugの設定

・OSの設定
アプリケーションを実行するコンピュータにユーザアカウントを追加します。開発環境を動作させているコンピュータにログインする時に使用している同じアカウント名とパスワードで管理者ユーザを作成します。
コントロールパネルの管理ツールにあるローカルセキュリティポリシーを起動して、ローカルポリシーのセキュリティオプションにある「ローカルアカウントの共有とセキュリティモデル」の設定を「クラシック」に変更します。
ファイアウォールが有効になっている場合にはリモートデバッグを実行できません。必ず無効にしてください。
・Remote Debuggerのインストール
アプリケーションを実行するコンピュータに対してVisual Studio 2005インストールCDのvsRemote Debugger\x86\rdbgsetup.exeを実行してリモートデバッガをインストールしてください。
・デバッグ用ランタイムDLLのインストール
VC++で作成したネィテブアプリケーションのデバッグにはランタイムDLLが必要です。ランタイムDLLの導入を行わないと、デバッグ版のアプリケーションを実行することができません。以下のフォルダにあるデバッグ用ランタイムDLLを、デバッグするアプリケーションの実行ファイルと同じ場所にコピーします。このときDLLファイルだけではなく、マニュフェストファイルも一緒にコピーしてください。
C:\Program Files\Microsoft Visual Studio 8\VC\redist\Debug_NonRedist\x86\Micorosft.VC80.DebugOpenMP
C:\Program Files\Microsoft Visual Studio 8\VC\redist\Debug_NonRedist\x86\Microsoft.VC80.DebugCRT
C:\Program Files\Microsoft Visual Studio 8\VC\redist\Debug_NonRedist\x86\Microsoft.VC80.DebugMFC
・デバッグの手順
アプリケーションを実行するコンピュータにて「VisualStudio 2005 リモートデバッガ」を起動します。
その後、VisualStudioの開発環境からデバッグのプロセスにアタッチを実行してください。修飾子にアプリケーションを実行するコンピュータのコンピュータ名を指定すると、実行されているプロセスの一覧が表示されるので、デバッグするプロセスを選択します。
サービスのデバッグを行いた場合には、「VisualStudio 2005 リモートデバッガ構成ウィザード」を起動してリモートデバッガがサービスとして動作するように設定します。
・デバッガをサービスとして動作させる
ログイン前のアプリケーションの動作や、サービスとして登録したアプリケーションをデバッグするには、デバッガをサービスとして動作させる必要があります。プログラムメニューから「Visual Studio 2005 リモートデバッガ構成ウィザード」を実行することでサービスとして登録できます。ですが、これだけではまだサービスとしてリモートデバッガを利用することはできません。
リモートデバッガをサービスとして動作させるには、コントロールパネルの管理ツールからサービスを起動してください。Visual Studio 2005 Remote Debuggerというサービスが登録されています。サービスのプロパティを開いて自動起動に変更してください。同時にサービスを実行するアカウントに管理者権限を持つユーザアカウントを指定します。このあと再起動すると、リモートデバッガはサービスとして起動します。

メモリリーク個所の特定

VisualC++に付属しているCランタイムライブラリにはヒープメモリのリークを検出する機能が備わっています。その機能をもちいて、メモリリーク個所を特定してみましょう。
メモリリークの検出
メモリリークを検出するには_CrtDumpMemoryLeaksを呼び出します。このとき未解放のメモリがあればデバッグコンソールにそのメモリアドレスと先頭16バイトのダンプが表示されます。

Dumping objects ->
{108} normal block at 0x00143FC0, 256 bytes long.
Data: 48 65 6C 6C 6F 20 57 6F 72 6C 64 00 CD CD CD CD
Object dump complete.

この中の{}で囲まれた数字は、起動後に何番目に確保さえたメモリかを示します。
メモリリーク個所の特定
メモリリーク箇所を特定するには_CrtSetBreakAllocを使用します。_CrtSetBreakAllocに先ほどの{}で囲まれた数字を指定します。これによりリークしたメモリを確保した個所でアプリケーションを中断することができます。
しかしこの方法ではアプリケーションの起動後、メモリを確保する順番が変動しないことが保障されていないと場所を特定できません。
そういった場合には_CrtMemCheckpointと_CrtMemDumpAllObjectsSinceを組み合わせて使用します。

int breakCnt;
_CrtMemState stateOld;
_CrtMemCheckpoint(&stateOld);
//ここで意図的にメモリを確保してリークさせる
_CrtMemDumpAllObjectsSince(&stateOld); // (1)
_CrtSetBreakAlloc(breakCnt);
//ここでデバッグ対象となる処理をおこなう。
_CrtMemDumpAllObjectsSince(&stateOld); // (2)

処理を行う前のメモリステータスをCrtMemCheckpoint関数で保存し、_CrtMemCheckpoint以降に確保したメモリのうち解放されていない分が_CrtDumpMemoryLeaksと同じように出力されます。
(1)で出力された{00}の番号と、(2)で出力された{00}の番号の差分を算出しておき、次回実行時に(1)の出力を確認したうえでデバッガから_CrtSetBreakAllocのパラメータを設定します。

アプリケーション側からデバッガを呼び出す。

プロセスを起動してからデバッガでプロセスにアタッチする方法では、プロセスの初期処理部分のデバッグを行えません。そのような場合にはDebugBreak APIを使用します。アプリケーション内部でDebugBreak APIを呼び出すと、DebugBreak APIを呼び出した個所でデバッガがアタッチするまで待たせることができます。
マネージドコードアプリケーションの場合には、DebugBreakの代わりにDebugger.Launch()を使用します。

マップファイルを使用したデバッグ

リリース後のアプリケーションにおいて不正なメモリアクセスなどのエラーが起こった場合には、マップファイルを用いてデバッグをおこないます。

マップファイルの作成

プロジェクトプロパティを開き、リンカのデバッグにあるマップファイルの生成を「はい」にしてください。これでビルド時にマップファイルが生成されます。

実行位置の特定

Windows Vistaでアプリケーション実行中に下のような画面が表示されます。詳細をクリックして詳しい情報を参照してください。
crash_vista.png
下記は実際に発生したエラーの詳細です。例外コードは例外の種類を表します。C00000005はWrite Protection Errorです。次の例外オフセットが例外が発生したコードの場所を示しています。マップファイルと照らし合わせて見てみます。

問題の署名:
問題イベント名: APPCRASH
アプリケーション名: crashTest.exe
アプリケーションのバージョン: 0.0.0.0
アプリケーションのタイムスタンプ: 498a7b3c
障害モジュールの名前: crashTest.exe
障害モジュールのバージョン: 0.0.0.0
障害モジュールのタイムスタンプ: 498a7b3c
例外コード: c0000005
例外オフセット: 00001064
OS バージョン: 6.0.6000.2.0.0.256.1
ロケール ID: 1041
追加情報 1: 1172
追加情報 2: 7912d3cce3b67ed49e553844317cb1a3
追加情報 3: c6b1
追加情報 4: 3dc30d5ca3a80f458b372b9da0535a3a

マップファイルには下記のような内容が含まれています。Publics by Valueが関数名。Rva+Baseがコードのアドレスです。ベースアドレス00400000+オフセットアドレス00001064=00401064が問題のおこったコードアドレスになります。

Address Publics by Value Rva+Base Lib:Object

0000:00000001 ___safe_se_handler_count 00000001
0000:00000000 ___ImageBase 00400000
0001:00000000 _wmain 00401000 f crashTest.obj
0001:00000020 ?funcA@@YAHXZ 00401020 f crashTest.obj
0001:00000050 ?funcB@@YAHH@Z 00401050 f crashTest.obj
0001:00000090 ?funcC@@YAXXZ 00401090 f crashTest.obj

Rva+Baseと照らし合わせると、00401064は「?funcB@@YAHH@Z」と「?funcC@@YAXXZ」の間にあることがわかります。Rva+Baseは関数の先頭を示すアドレスですから、00401064は「?funcB@@YAHH@Z」の内部だということがわかります。
ひとつだけ注意しなければならない点があります。コンパイラの最適化により関数がインラインに展開されたりした場合には、呼び出し元関数内でエラーが発生しているように表示されます。たとえば今回の場合「funcB」がインライン展開されていた場合、funcBの呼び出し元である「_tmain」がエラーの発生個所となります。

参考資料

KB196755 特定の場所、障害が発生コード内で表示されるエラー メッセージでアドレスを使用する方法

マルチコアCPUのための最適化-スレッド間の連携

マルチコアの性能を十分に引き出すためには、処理を複数のスレッドに分割すると共に、それぞれのスレッドが並列して動作するようにスレッドの独立性を高める必要があります。いくつか基本となるスレッド間の連携方法を紹介します。
コマンダー
ほぼどのような形の処理にも適用することができます。
ひとつの制御スレッドと、複数の作業スレッドからなります。制御スレッドからキューを通して処理の要求を作業スレッドに送ります。各作業スレッドはキューから作業内容を取得し、その結果を制御スレッドに返します。
CPUコア数と同じ数の作業スレッドを作ることでCPUを有効的に使用することができます。作業スレッドの数が増えると制御スレッドの負荷が高くなり、制御スレッドの動作速度が律速となります。
パイプライン
マルチメディアなどストリームデータの処理に適しています。
一連の処理を、前段、中段、後段のように複数の段階に分割し、それぞれの段階をスレッドにします。前段の処理が終了したら処理結果を中段に送り、前段は次のデータの処理を開始します。中段も同様に処理が終了したら処理結果を後段におくり、前段から次のデータを受け取って処理を開始します。
適当な処理単位で旨く分割することができれば、CPUコア数の増加に応じて処理速度を高めていくことができます。しかし処理を均等に分割することが難しく、もっとも負荷の大きい段階が律速となります。

マルチコアCPUのための最適化-データ構造の最適化

マルチコアCPU向けに最適化する場合、CPUキャッシュが複数ある事を踏まえて、データ構造を工夫する必要があります。
・CPUキャッシュメモリの動作
CPUにはキャッシュメモリがあり、プログラムの使用するデータがキャッシュされています。複数のCPUコアがある場合、CPUコアごとにキャッシュメモリを持っています。もしCPU Aがメモリにデータを書き込んだ時に、同じメモリをCPU Bがキャッシュしていた場合、そのままではCPU AとCPU Bがそれぞれ持つキャッシュメモリの一貫性(キャッシュコヒーレンシ)が失われてしまいます。そこで多くのCPUではキャッシュメモリと実データの一貫性が失われた事を他のCPUに通知し、キャッシュデータを破棄して再取得するための仕組みが用意されています。しかしその為には低速なCPU外部バスにアクセスしなければならず、プログラムの実行速度を大きくそこないます。マルチコアCPUの性能を引き出すためには、メモリの一貫性を破壊しないようにデータ構造を最適化しておく必要があるのです。
原則はスレッド毎の独立性を高めることです。各スレッド間で交換しなければならないデータ量を減らします。その上で各スレッドから変更する可能性のある変数が、同一メモリブロックに配置されないように、なおかつ、最小で済むようにデータ構造を工夫します。多くのCPUではキャッシュメモリを64バイト~128バイト程度のメモリブロック(キャッシュライン)に分けて管理していますから、このサイズも意識する必要があります。
注意が必要なデータ構造
・変数の共有
同一の変数を複数のスレッドから参照するのは、もっとも容易で安易な方法です。ですが変数を参照する箇所が少なかったり、あるいは更新頻度が少ない場合には必要十分な方法です。
ただし、その変数の前後で宣言されている変数が同一のキャッシュメモリブロックに配置される可能性が高い事を考慮する必要があります。もし変数に頻繁に書き込みをおこなえば、その前後で宣言された参照しかしていない変数も同時に影響を受けることになります。
影響を防ぐにはスレッドの初期処理でスタック変数に値をコピーし、スレッド内部ではコピーした値を使用します。スタックは各スレッドごとに用意され、他のスレッドと同じキャッシュメモリに配置される可能性はありません。これにより書き換えられる変数と同一のキャッシュメモリブロックに配置される可能性がなくなり、キャッシュのミスマッチに伴う動作速度の低下をなくせます。
・配列の使用
各スレッドが配列の異なる要素を参照している場合です。異なるデータに対して同じ計算を繰返しおこなう場合、データの管理や状況の管理が容易なので良く行われる方法です。
ただし配列の要素サイズが小さい場合、前後の要素が同じキャッシュメモリブロックに属する可能性があります。もし要素1の内容を書き換えた場合、他のスレッドで参照していた要素2でのキャッシュミスマッチが発生してしまいます。
メモリに余裕がある場合には配列の要素サイズがキャッシュメモリブロックの整数倍になるようにパディングをおこないます。キャッシュメモリブロックのサイズはCPUの種類ごとに異なるため、128バイトか256バイト程度の切の良いサイズにすることになります。
メモリに余裕が無い場合にはリンクリストなどを使い、要素ごとにヒープメモリ領域に格納します。コンパイラのメモリマネジメントにより同一メモリブロックに配置されてしまう可能性もありますが、回避できない要因として無視します。

typedef struct{
DWORD val1;
DWORD val2;
BYTE padding[xxx]; // 要素サイズが128バイトになるようにパディング
}PARAM;

int target = 16;
int param1;
int param2;
PARAM data[16];

void ThreadA(void)
{
int localParam1 = param1; // 変数をスタックに複製
int localParam2 = param2; //
int localTarget;
while (target > 0)
{
localTarget = target – 1;
target –;
data[localTarget].xxxx = ….;
}
}

VisualStudio 2003 Remote Debugの設定

・OSの設定
アプリケーションを実行するコンピュータにユーザアカウントを追加します。開発環境を動作させているコンピュータにログインする時に使用している同じアカウント名とパスワードで管理者ユーザを作成します。
コントロールパネルの管理ツールにあるローカルセキュリティポリシーを起動して、ローカルポリシーのセキュリティオプションにある「ローカルアカウントの共有とセキュリティモデル」の設定を「クラシック」に変更します。
ファイアウォールが有効になっている場合にはリモートデバッグを実行できません。必ず無効にしてください。
・Remote Debuggerのインストール
Visual Studio 2003のインストーラを起動し、Visual Studio .Net Setupの画面から「リモートコンポーネントセットアップ」をクリックします。表示されるHTMLにリモートデバッグコンポーネントの導入手順が書かれているので、それに従ってインストールしてください。

・デバッグの手順
リモートデバッガはサービスとして起動しているので、別途起動する必要はありません。
VisualStudioの開発環境からデバッグのプロセスにアタッチを実行してください。修飾子にアプリケーションを実行するコンピュータのコンピュータ名を指定すると、実行されているプロセスの一覧が表示されるので、デバッグするプロセスを選択します。

LED Lamp Bulb

LEDライトの個人輸入の話。
日本国内で見かけるLEDライトは懐中電灯だったり、自動車用であったり、装飾用であったりして家庭用のLED電球を見かけることはほとんど無い。検索すればいくつか見つけることはできるが、価格が高く到底エコノミーではありません。唯一簡単に手に入るのは常夜灯に使う小さな電球だけという状況です。
ヨーロッパの通販サイトを見ると、LED電球の内外価格差に驚きます。照明にも使える3W~5W程度のものが、安いものは500円程度で売られています。LED特有の冷たい明かりではなく、昼光色もあります。これが日本だと1000円以上。いつの間に、こんな価格差がついたんでしょう。500円以下なら電球型蛍光灯並みの価格帯、LEDに切り替えても元を取れるというものです。
個人輸入~と思ったのだけど、残念なことに欧州は240Vなんですよ。日本とほぼ同じ125V米国に期待したのだけれど、125VのLED電球はほとんどなく価格も高かったです。
日本でも同じ価格帯で商ってくれる商店が現れてくれるとうれしいな。

Degital Photo Frame

某掲示板でAmazon UKでDVDを購入するのが、ちょっとした祭りになっていた。その時にAmazonをみてちょっと気になったGoodsがある。1インチ前後の画面を備えたDegital Photo Frameだ。同様の商品を日本国内で目にした記憶はない。
キーホルダーサイズで1インチ前後の画面を備えたDegital Photo Frameが、1000円~3000円程度の価格で多種類扱われていて興味を引いた。日本でよく売られている5~7インチのDegital Photo Frameでは携行には絶えないし、仕事机の上に置くにもちょっと大きい。でもこのサイズなら気楽に持ち運べるし、仕事机の中にも気軽においておける。仕事の疲れをいやすグッズとして、ひとつどうだろうか?