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

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


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



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

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

スポンサーサイト

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

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

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



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


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


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

ここから筆者の雑感 ---

今回の内容もソースコードを載せ、解説へのリンクを張れば済みそうな話。
筆者が相談を受けた内容から、端折らずに載せた方が良いだろうと判断した。
質問される事は苦ではない。たいていは「説明書を読んだ?」で解決しそうなケースだが、稀に「たずねる側は筆者のことを家庭教師か何かと勘違いしているのではないか!?」と疑いたくなるケースさえある。

今回のようなテーマならば、C99 や( 2011年版の ) C11規格 といったC 言語仕様、MSDN ライブラリ、Visual Studio のヘルプ機能等から欲する情報にたどりつける。

能動的に「己から積極的に学ぶ」ことができるヒトがいる、一方、誰かに教えてもらう受動的な傾向のヒトもいる。何事も不慣れな段階においては他を頼りたくなるものだ。、己の力としたいならば己で探りだせるようになってほしい・・・

--- ここまで筆者の雑感

前回までで、アプリのフォームにボタンを着け、そのボタンをクリックすることで新しく設けた関数を呼び出せるようになっている。今回は、前回までの流れの続きとして、
・ メモリの確保
・+ 開始時刻
・++ 処理
・+ 終了時刻の取得
・+ 処理にかかった時間、開始時刻、終了時刻の差を表示
・ 確保したメモリを解放
と進む。

(3-1) 関数の戻り値を変更

関数の戻り値と表現すると堅苦しいかもしれない。おおざっぱに言えば、「作業は成功?それとも失敗?」を確認することだ。
前回までで作成した関数は、単純に成功を意味する 「NO_ERROR」を返すようになっている。状況に応じた返答ができるように変更する。

LONG execBench(HWND hOwner)
{
    LONG lRet = S_FALSE;
    // ここに(3-2) 以降のコードを追加する
    return lRet;
}

のような感じ。この関数を呼び出した場合、失敗を意味する「S_FALSE」が戻る。

変数 lRet には、エラーコードが入る。途中の段階で何らかの問題が生じた場合、エラーコードを格納。
処理が全て成功した段階で 変数 lRet に「S_OK」もしくは「NO_ERROR」を代入する。

「S_OK」と「NO_ERROR」は同じ 0 を意味する。「S_FALSE」や「S_OK」は HRESULT型の戻り値として使われる。
Windows 上で動作するアプリを作るうえで HRESULT型 が頻繁に登場する。
HRESULT の定義は、ヘッダーファイル「winnt.h」の中で行われており、
typedef LONG HRESULT;
となっている。実質的には long を指している。なぜならば、大文字で記される LONG 型 も
typedef long LONG;
と定義されていることから、LONG は long の別名と解釈できる。
筆者は LONG 型のまま話を進めるが、気になるヒトは LONG の部分を HRESULT に置き換えると良いだろう・・・


※ これは2013年2月18日に掲載した記事の画像です

予め述べておきます。
C/C++ 言語において、{} で囲まれた部分はブロックと呼ばれている。
一般的に数学などでは、
e - [d -{c - (a + b)}] のように、深さによって囲む記号・符号が変わる。
一方、C/C++ 言語 では、
e - {d -{c - {a + b}}} のように、深さに関係なく同じ波括弧の記号で囲む。

この後の内容は、入れ子(ネスト)、つまり、{}の内側に{}が入る状態が深めのまま載せてあります。出口を揃える意図。

C/C++ 言語 でも他の言語と同様、無条件分岐、つまり、任意の場所に直接ジャンプすることを意味する goto が用意されている。
教本等では goto はなるべく使わないなどと記されている。中には goto を 禁止、厳禁と記しているモノまである。
エドガーダイクストラ (Edsger W. Dijkstra) によって「構造化プログラミング」が唱えられた頃から、無条件分岐等を使わないスタイルが浸透してきた。
※ 厳密に言えば、「構造化プログラミング」と「無条件分岐の排除」は異なる。

C/C++ 言語 に不慣れな段階ではそれに従うのが良いだろう。なぜならば、無条件分岐を多用することで、複数の出口を設けてしまうミスに繋がる可能性がある。
「入り口に対する出口が複数の場合、後日、メンテナンスの際に苦労する」というは、多くの先人達が経験してきたことだ。

とはいえ、あくまでも「goto を絶対使うな」というのではなく、「なるべく使わない」「できるだけ使わない」である。
何らかのプログラミング言語を習得していた場合、goto を使いたくなるのも当然である。
C/C++ 言語を前提に考えるならば、反復処理はwhile 文 for 文 などで代行、条件判断は、if 文の入れ子構造を少し深くしたり、switch文 を用いることで goto に頼らずに済む。
仮に goto を用いるとしても、「流れを遡るような分岐は使わない」「複数の出口を設けない」など、一定のコンセプトに基づいていれば問題も少く抑えられるのでは。

なお、人間が直感的に判断し易いネストの深さは3~4層くらいと言われている。幾重もの深さになってしまうと読みやすさが損なわれる。そのような際は、外部に別の関数を設けるなど工夫すれば読み易くなるハズ・・・

(3-2) 作業領域を確保

メモリを確保する大きさを定義する。この関数を通して
「メモリー間の転送速度を向上できるのか!?」
メモリ - メモリ間の転送速度を調べたいワケです。なぜ調べたいのかは、過去の記事車輪の再発明 (2)をご覧あれ。

16MB や 32MB くらいの領域 ( 区間の長さ )ではキャッシュが効いてしまい、欲する値とかけ離れるた結果となってしまう。そこで、キャッシュに乗り切らないようなサイズ 128MB もしくは 256 MB を定義する。

冒頭のほうに
#define __SIZE_DUPLICATE (1024*1024*128)

もしくは
#define __SIZE_DUPLICATE (1024*1024*256)

と定義を追加。
#define MAX_LOADSTRING 100
の後あたりに追加するのが良いだろう。コピー元とコピー先を2箇別々の空間として扱うので、定義した2倍サイズの空きメモリが必要。

メモリの確保や解放を行う関数

次に、メモリの確保、それと対になるようにメモリ解放の関数を追加する。
追加する位置は、execBench( 関数の LONG lRet = S_FALSE; と return lRet; の間。

    LPVOID lpTemp[2] = { NULL , NULL };

    if ((lpTemp[0] = ::VirtualAlloc(NULL , __SIZE_DUPLICATE , MEM_COMMIT , PAGE_READWRITE) ) == NULL){

        lRet = ERROR_OUTOFMEMORY;

    } else {

        if ((lpTemp[1] = ::VirtualAlloc(NULL , __SIZE_DUPLICATE , MEM_COMMIT , PAGE_READWRITE) ) == NULL){

            /* if (lpTemp[0]) */ ::VirtualFree(lpTemp[0] , 0 , MEM_RELEASE);

            lRet = ERROR_OUTOFMEMORY;

        } else {

            // ここに(3-3) 以降のコードを追加する

            /* if (lpTemp[1]) */ ::VirtualFree(lpTemp[1] , 0 , MEM_RELEASE);
            /* if (lpTemp[0]) */ ::VirtualFree(lpTemp[0] , 0 , MEM_RELEASE);

            lRet = S_OK;

        }

    }

古典的な教本に従えば、メモリの確保と解放はmalloc とfree。現状では、HeapAlloc と HeapFree の組み合わせ、もしくは new と delete を用いるのが一般的。

Windows に限って言えば、16ビット時代から残っている LocalAlloc や GlobalAlloc といった関数もある。32ビット OS が登場してからLocalAlloc や GlobalAlloc の利用は非推奨、HeapAlloc や VirtualAlloc への切り替えが唱えられてきた。
64ビット OS が登場した昨今、教本やサンプルで GlobalAlloc を軸に載せている例もある。クリップボードの操作やBSTR 系の文字列操作で GlobalAlloc が必要とされている。
なお、GlobalAlloc 関数で割り当てられる限界は256MB 程度。

ここでは、Windows に特化した話なので、VirtualAlloc と VirtualFree を用いたコードを載せた。
※ 正しくは、VirtualAlloc で確保した空間をメモリへ結びつけるには VirtualLock 関数を用いる。今回載せた分では抜けている。メモリ空間を Lock する際の副作用、弊害の話は長くなりそうなので省く・・・

Virtual ~~ を用いる理由は、巨大なメモリ空間を確保したいから。
今回試したい内容に関しては、小さな空間、つまり短距離間の動作速度を測っても意味が無い。
VirtualAlloc と比べ HeapAlloc は短距離向けのイメージ。実のところ、Virtual ~~ と Heap ~~を分ける明確な基準はない。個人的な感覚で言えば 500 KB 程度のメモリを確保するケースならば HeapAlloc で良いだろう。

アライメントの手間、つまり割り当てられるメモリのアドレスと実際に使うアドレスの微調整が必要となるケースも出てくる。
その手間を考えると VirtualAlloc ならば、64KB ( 65536 バイト) の境目に揃うので楽。

ほかにも、HeapAlloc の前に HeapCreate が必要。もちろん、HeapCreate でヒープを作成した際は、対になる HeapDestroy 関数、ヒープの破棄も必要。HeapDestroy を付け忘れれば、メモリリークの要因となる。

GetProcessHeap 関数を用いて ヒープを作成を省くこともできる。その際「一般的なアプリのデフォルトスタックサイズが 1MB」という点に注意が必要。
※ 「スタックサイズが 1MB」というのは32ビット版のWindows が登場した頃からの情報。

しばし、char 変数名[256];などといったコードも見かける。
練習段階ではこのようなコードも参考になるだろう。筆者としては悪い習慣にならないように願うばかりである。
幸いなことに、Visual Studio でアプリ ( ウィンテル機向け ) を組むと、このようなコードも通る。エラーにならない。
文字変数の一文字あたりが1バイトであれ2バイトであれ、 変数名[256] は 「スタックサイズが 1MB」のおかげでエラー無しで通る。「スタックサイズが 1MB」 を認識した上で用いるのは結構だが、ほかの環境でのスタックサイズは???である・・・

不慣れなヒトにとって、「デフォルトスタックサイズ」 というのは判り難い???
「デフォルト」は初期状態。「スタックサイズ」は、アプリを表舞台と例えるならば、「舞台そで」「舞台裏」「楽屋」の広さといったところ。

スタックサイズが足りないならば、割り当ての変更は可能 。



Visual Studio を使っているならば、ツリービュー、[リンカ] -> [システム] と辿り、スタックやヒープの設定を変更できる。
「スタックやヒープの設定を変更しなければ動かないアプリ」を作ってしまうよりも、設定の変更せずとも動くようにアプリの構造を見直したいものだ・・・

古典的なメモリ割り当て系の関数と比べ、Windows で用いるメモリ割り当て系の関数は「割り当てたいサイズ」だけでなく「割り当ての方法」や「割り当てのタイプ」を指定する。VirtualAlloc 関数を呼び出す際のオプションは MEM_COMMIT とした。
MSDN ライブラリによれば
「メモリ内またはディスクのページングファイル内に ~~ 割り当てます。」
との記述がある。
筆者の環境 ( Windows7 64ビット版 ) では物理メモリ上に割り当てられた。ここで陥りがちなのは、自の環境で起きた事象は他の環境でも同様に起きると錯覚すること。常にメモリ上に割り当てられると想定すべきではない。結果は実行する環境に左右されるのだ。
PC に搭載されているメモリが少ない場合は、ディスクのページングファイル内、つまり仮想ファイル上に割り当てられることもある。
ファイル上に割り当てられてしまうと、物理メモリへの読み書きではなく、ディスクの読み書きへ置き換わる。
ちなみに、メモリとディスクとの速度差は10万倍以上とも言われている。

この題目のきっかけとなったブロガーさんは、Windows XP を利用しているとのこと。
32ビット版のWindows では利用できるメモリ容量との絡みでディスク上に割り当てられてしまう可能性もある。
「物理メモリ上に割り当て」が譲れない、MEM_PHYSICAL オプションを指定すると良いだろう。
32ビット版のOSでも AWE ( Address Windowing Extensions ) がサポートされている環境という条件を満たせば、より多くのメモリ空間を扱える。とはいえ、今後は AWE を期待すべきではない。例えば、SQL Server でも 2008 までのバージョンは AWE を大きな空間に頼ることができた。2012年のバージョンから「AWEのサポートは廃止」とアナウンスされている。

ほか、Windows Vista 以降を対象に MEM_LARGE_PAGES オプションなどが用意されている。
「Windows Vista ,7 以降は VirtualAlloc を呼び出す際に、MEM_LARGE_PAGESやMEM_4MB_PAGES オプションを追加する」ことで大きい規模の領域を効率的に確保できるとのこと・・・

if ((lpTemp[0] = ::VirtualAlloc( ~~ ) ) == NULL){
} else {
}

の部分が読み辛いでしょうか。ここは、

lpTemp[0] = ::VirtualAlloc( ~~ );
if (lpTemp[0] == NULL){
} else {
}

と書いても意味は同じ。

if (○○== NULLではなくif (!○○と書く方がスマートなのでは!?」などの声が届きそう。
if 文が「単純な比較」の意味なら if (!○○ と書く方がスマート。ここでのif 文は「代入してから評価する」の意味。

VirtualAlloc で失敗した場合、NULL が返答される。もし、VirtualAlloc で確保できない場合は、最初の{} 内側のブロック、確保できた場合 は else {}の内側のブロックへと進む。
「メモリ確保のたびに確認が必要なの???」
との声も聞こえてきそう。
たしかに、教材やサンプルコードでは条件判断、「成功もしくは失敗」の確認を省いている例もたくさん見かけます。
規模の小さなモノやテスト的なコードの段階では、失敗せずに進んだのでしょう。それを前提に確認を省いているのか、あるいは、サンプルを載せたヒトが無頓着なのかもしれません。
今回のようなサイズのメモリ空間を一度に確保することは「お行儀の悪い」と揶揄されるものです。が、画像や音声を扱うアプリを組むとして、高速さを求めるならば「巨大なメモリ空間を扱いたい」と考えるのも当然。
巨大なメモリ空間を確保する際、成功を期待しないのが賢明。時には確保に失敗します。失敗したにも関わらず ( 強引に ) 次に進むことは、実生活の場ではお風呂の空焚きするようなトラブルのもと。
今回の例では、lRet = ERROR_OUTOFMEMORY;が失敗した時の対処。エラーコードを代入して次の作業へ到達しないようになっている。
トラブルを避けるために、ただ命令を羅列して闇雲に進むのではなく、「もし△△が成功したら次へ」のように安全確認しながら進むようにしたいものだ・・・
※ メモリ確保、解放を繰り返すごとにメモリの断片化が起こり、やがて確保に失敗するとの見解が多い。

VirtualAlloc 関数で確保した空間は VirtualFree 関数で解放する。
「ポインタが NULL でないことを確認してからメモリ解放関数を呼ぶ」
を守っていればトラブルを減らせる。VirtualFree 関数を呼び出す前に if (ポインタ変数) もしくは if (ポインタ変数 != NULL) の条件判断を行うのが良い。
今回は確保直後に NULL で無いことを確認してある。失敗を意味する NULL ならば解放する関数の部分に到達しない。よって省略可能。上記の例ではif 文による条件判断をコメントアウトしてあります。

参考までに HeapAlloc を用いた例。

    HANDLE hHeap;
    LPVOID lpTemp[2] = { NULL , NULL};

    if ((hHeap = HeapCreate( 0 , 0 , 0) ) != NULL){

        lRet = ERROR_INVALID_HANDLE;

    } else {

        if ((lpTemp[0] = (LPVOID) HeapAlloc(hHeap , HEAP_ZERO_MEMORY , __SIZE_DUPLICATE) ) != NULL){

            lRet = ERROR_OUTOFMEMORY;

        } else {

            if ((lpTemp[1] = (LPVOID) HeapAlloc(hHeap , HEAP_ZERO_MEMORY , __SIZE_DUPLICATE)) != NULL){

                lRet = ERROR_OUTOFMEMORY;

            } else {
                // ここに(3-3) 以降のコードを追加する

                lRet = S_OK;
            }

        }

        if (hHeap){
            ::HeapDestroy( hHeap );
        }

    }


※ VirtualAlloc の例と同様、HeapLock を抜いたコードとなっている。

HeapAlloc でメモリ確保する前、hHeap = HeapCreate( ~~ の部分でヒープを作成。
あまり大きな空間でないならば、GetProcessHeap 関数を用いて HeapCreate を省くことも可能。今回確保したいメモリのサイズはそれよりもはるかに大きいため、新しくヒープを作成する。

ほかにも、鋭いヒトから、
「HeapFree 関数が抜けているよ~!」などの声が届きそう。
「借りたモノは確実に返す」に従い malloc 関数で確保したメモリ空間は、free 関数 で解放する。先ほどの VirtualAlloc 関数で確保したメモリ空間は VirtualFree で解放。それと同様に HeapAlloc 関数 に対しても HeapFree 関数 を使ったほうが丁寧。
筆者の手元にある MSDN ライブラリによれば、
「Processes can call HeapDestroy without first calling the HeapFree function to free memory allocated from the heap.」
日本語版では
「ヒープから割り当て済みのメモリを解放する場合、最初に HeapFree 関数を呼び出すことなく、直接 HeapDestroy 関数を呼び出すこともできます。」
と記載されている。これが、HeapFree 関数 を省いても通用しそうな根拠。言い換えれば、
「ヒープを作成し確保したメモリ空間であれば、HeapDestroy 関数でヒープを破棄する際に全て解放される」
よって、解放系の関数をあれこれ設置せず、HeapDestroy 関数 に任せてしまうのが楽。

HeapDestroy 関数を用いる際も、「ヒープハンドルが NULL でないことを確認」すべき。アプリが異常終了する事態を追跡すると、HeapDestroy で 二重解放、多重解放が原因となるケースも多い・・・

(3-3) 開始と終了の時刻を取得

DWORD dwTm[2];

Sleep( 5000 );

timeBeginPeriod(1);
dwTm[0] = timeGetTime();

// ここに(3-5) 以降のコードを追加する

dwTm[1] = timeGetTime();
timeEndPeriod(1);

// ここに(3-4) 以降のコードを追加する

Sleep 関数は、アプリが安定状態になるまで待つ。単位はミリ秒、1/1000 秒。例えば1秒待たせたいならば 1000 を渡す。
昨今のIntel 製 CPU ではターボ状態が30秒ほど続くことがある。ターボ状態の最中に実行すると結果がバラつく。バラつき避けたいならば、Sleep 関数へ渡す値を 40000 程度、40 秒程度に設定すると良いのかも・・・

Windows 上で時刻を得る関数として挙げるとすれば、GetTickCount、timeGetTime、QueryPerformanceCounter や QueryPerformanceFrequency などなど。
ほかにも、タイムスタンプカウンタ を読み出す方法がある。タイムスタンプカウンタ を読み出したい場合、アセンブリ言語 の RDTSC 命令を使うのだが、少々古めの Visual Studio ではインラインアセンブラを使うことになる。Visual Studio 2005 より新しいバージョンならば __rdtsc() という関数が用意されており、アセンブリ言語に頼らなくとも可。
今回のようなテスト用途であればインラインアセンブラも差し支えないだろうが、ひとつのアプリを作るとなると、全般的な最適化が妨げられることがある。アセンブリ言語 、インラインアセンブラ を含んだコードは 32ビット向けアプリとしてならばビルド可能であるが、64ビット向けのアプリとしてビルドに失敗する。
タイムスタンプカウンタ を読み出す方法 は CPU の周波数が一定で動作していた時代には有効であった。昨今の省電力機能が搭載された CPU では、緩急に応じて周波数が変動し、得られる値にバラつきが生じる。よって却下。
GetTickCount や QueryPerformance もクセが強い。などなどの理由から今回はtimeGetTime 関数を選択。

測りたい処理の開始前、処理終了の2箇所の timeGetTime 関数を呼ぶ。走る競技のスタートとゴールの瞬間ストップウォッチのボタンを押すようなイメージだ。終了時の値から開始時の値を引けば経過時間となる。

timeGetTime 関数の性質上、開始時の値が終了時の値を上回ることもある。当然、結果が異常な数値となる。そのような場合は計測結果を標本から外せは済むこと・・・

timeGetTime 関数 を覆うように timeBeginPeriod や timeEndPeriod 関数を追加。
これらの関数は最小タイマ分解能を高める役割がある。反面、timeBeginPeriod は副作用があるのも有名。ここでの副作用というのは、キビキビ動くのと引き換えに省電力機能が効き難くなる。
メモリの alloc と free が対になるように、timeBeginPeriod を用いたならば timeEndPeriod を忘れずに・・・

筆者が過去に遭遇したケースの中に、「複数ソケットのマシンにおいて、timeGetTime で得られる値が、実際の経過時間の 2倍や 4倍 になってしまう」という現象があった。複数ソケットのマシン複数コアのマシンを誤解するヒトもいる。


※ これは2012年7月25日に掲載した記事の画像です

SMP (Symmetric Multiprocessing) 、「対称型マルチプロセッサ」や 「対称型マルチプロセシング」と訳される。過去記事の画像にあるように複数のCPU が 搭載されている状態。
現在主流のCore i7 , i5 , i3 や Core 2 Duo ,Quad などの CPU はひとつのソケットで複数のコアを持っている。当時はひとつのソケットでひとつのプロセッサ、ひとつのプロセッサでひとつのコアというのが主流であった。

通常は 1秒イコール1000ms。2つの timeGetTime の差を得る場合、例えば60秒ならば 60000 となることを期待する。ところが、挿しているプロセッサの数を掛けた数値が返ってきた。
「搭載されている CPU コア数を取得して割り算すれば良いのでは!?」
などの意見も出そうだ。
そのマシンで10000 を指定して Sleep 関数を呼び出したところ、きっちり10秒間休憩することが判った。ということで、Sleep 関数を timeGetTime で挟み、Sleep した時間と timeGetTime で得られる値との比率で割る策を処した・・・

(3-4) 経過時間を確認

いまだ、C 言語の教本では printf や puts でコンソール画面に出力する等が載っているのではないだろうか。Windows 用アプリならば、wsprintf で文字列を書式化し、MessagoBox を表示するのが単純。
※ wsprintf と 似た wprintf も存在するのでお間違えなく。

wsprintf((LPTSTR) lpTemp[0] , _TEXT("time = %d \0") , (dwTm[1] - dwTm[0]) );
MessageBox(hOwner , (LPCTSTR) lpTemp[0] , _TEXT("job done.\0") , MB_OK);

文字列を書式化したいなら、古くからの sprintf 関数 などがある。ただし、Visual Studio でこれらの古い関数を用いるとたくさんの警告が出る。すでに、車輪の再発明 (3)で触れたとおり、旧来からの関数を「_s」付きの別の関数、sprintf 関数 ならば sprintf_s 関数に置き換えれることで警告を減らせます。

MessagoBox 関数でダイアログボックスを表示する。
PC を長く使っているヒトに何か相談された際、「ダイアログボックス」という単語が通じない場面に遭遇する。そもそもダイアログボックスが何を指すか判っていないヒトもいれば、ダイアログボックスとウィンドウ・フォームを混同しているヒトもいる。



おおざっぱに言えば、意思確認のために表示されるモノを指す。
例えば、メモ帳で何か入力したとしよう。メモ帳を終了しようとすれば、保存するか否かの確認される。

MessagoBox 関数 の戻り値を通して、ユーザがクリックしたボタンの値を知ることができる。今回は [ OK ] ボタンのみなので、どのボタンをクリックしたかの確認は不要。

「ダイアログボックスを表示させるのは面倒」というのなら、MessagoBox( ~ の部分を
::SetWindowText(hOwner , (LPCTSTR) lpTemp[0]);
に置き換えることで、フォームのタイトルバー上に結果が表示されるハズ。

ところで、MessagoBox や wsprintf の引数に lpTemp を指定している部分が気になるヒトもいることだろう。何を意図したかといえば、すでに動的に確保したメモリを使いまわしているに過ぎない。
たしかに、出回っているサンプルコードでは
TCHAR szMsg[256];
等と文字列変数を定義し、文字列を作成する作業域に指定する例も多い。
重複するが、AMD や Intel 製 CPU 、Windows 上で動かすアプリを想定しているなら構い。スタック、つまり、自由に使える作業域が 1MB なので通用する。
昨今はそれ以外のプロセッサでも Windows が搭載されるようになってきた。AMD や Intel 製 CPU 以外のプロセッサではスタックが小さい場合があり、エラーとなるかもしれない・・・

(3-5) 測りたい部分
2箇所の timeGetTime を置き、その間に速度を測りたい部分を設ける。

ここで処理したいのは メモリ - メモリ間のデータ転送。ひとまず、古くからのコードを設けておく。

CopyMemory(lpTemp[1] , lpTemp[0] , __SIZE_DUPLICATE);

データ転送を行う関数はいくつかある。C/C++ 言語の教本に登場する memcpy 関数、Windows 上のアプリと限定すれば CopyMemory 関数。今回は CopyMemory を選択。memcpy と CopyMemory 関数との違いは戻り値が無い点。

PC は時代とともに高速化されてきた。CopyMemory 関数を一回実行するだけでは、一瞬で完了し、結果を把握し辛いかも。1回実行するだけでなく、10回なり、100回なり繰り返して結果を見るのがよいだろう。例えば 10回くり返したいなら
SIZE_T i;
for(i = 10 ; i ; --i){

    CopyMemory( ~ );
}
のような感じ。for 文 や while 文で繰り返す。カウンタをマイナスする意は別の機会に触れたい。
くれぐれも、繰り返し回数を増やしすぎないように。Windows 7 やそれ以降のOSでは一定の時間アプリが反応しないと異常と判定されることがある。

ちなみに、筆者がアプリを組む際、( 反応無しになるのを防ぐことを目的として ) 長時間要すると判っている特定の箇所にメッセージをポンプする機能を埋め込むことがある。それはVisual Basic や C# の DoEventsメソッドのようなモノで、PeekMessage と DispatchMessage 関数の使い方が判ればそれほど難しくない。
今回の例で言えば、メッセージをポンプする機能は _tWinMain( 関数内の後半に含まれているメッセージループのコードがそれに近い。メッセージループとは、while( 文で始まり、GetMessage( ~~ ) とその内側に TranslateMessage や DispatchMessage が含まれている部分。GetMessage 関数は何かイベントが発生するまで待ち続け、while 文でイベント待ちやディスパッチを繰り返す。
DoEvents 相当の関数を考えるならば、if (PeekMessage( ~~ ) ){}の形に変え、{}の内側は メッセージループと同じに構えればOK。メッセージループと異なるのは、2番目の引数として指定するウィンドウのハンドル値で、これが NULL と変わる点。ハンドル値を特定せずに NULL としておくことで、ほかのアプリやプロセスへメッセージが届くようになる。

この辺でビルドして実行してみよう。アプリのフォーム上にある、[ 開始 ]ボタンをクリックして計測開始。



画像は 141msとなっている。ビルド直後に実行すると若干遅れが生じる。何度か繰り返すと値が安定する。
Windows に用意されている CopyMemory 関数は 124ms。旧来からの memcpy 関数は 127 ms。ほぼ誤差の範囲。

ここまでで、 段階(3) の古典的な方法とその速度を測る部分が出来た。次のステップは、速度向上を目指し、データ転送部分を加工してゆく・・・

ところで、速度を調べる前に確認しておくべきことがある。ベンチマーク系のアプリを使うヒトにとっては常識的なコトであるが、一応記す。
作業を全力で実行できる状況で測るようにしたい。その場合、余計な割り込みや負荷を減らすことが重要。
例えば、タスクマネージャを見て不要と思われるプロセスを停止する。特にネットへのアクセスは割り込みが増える。LANケーブルを抜くだけではなく、デバイスマネージャからネットワークアダプタを停止すると良い。
さらに、省電力機能をoff にする。そうしないと、全力で実行されない可能性があり、得られる結果にバラつきが生じ易い。
コントロールパネルから、省電力設定を解除しただけでは省電力の仕組みがoffにならないこともある。
Intel 製 CPU を搭載したPC ならば、UEFI や BIOS の設定画面 でEIST や C3/C6ステートを無効、AMD 製 CPU を搭載したPC ならば Cool'n'Quiet が無効になっているか確認。

長くなりましたので続きはまた後日・・・

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

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

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

コメントの投稿

非公開コメント

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

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

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

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

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

最新記事
カレンダー
05 | 2017/06 | 07
- - - - 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 -
カテゴリ
ランキング
いつも応援いただきありがとうございました。ただいま休養中につきランキングへ参加していません・・・

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

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

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