Delphiで処理速度を計測する
今作ってるプログラムで、ちょっとレスポンスが悪いプロシージャがあったので
プロシージャ内部のどの処理が時間を食ってるのか調べることにした。
何パターンかやり方があるけど、とりあえず簡単なのを。
timeGetTime関数
uses MMSystem; procedure TimeCheck; var time : DWORD; begin time := timeGetTime; { 測定したい処理 } time := timeGetTime - time; end;
使い方としてはざっくりこんな感じ。
timeGetTime関数とは、Windowsのシステム時刻をミリ秒単位で取得する関数。
戻り値はDWORDで返ってきます。
上記のソースは、測定したい処理の直前の時刻を取得しておき、処理終了後の時刻を再度取得してその差分を計算するという単純なもの。
uses句にMMSystemを追加すれば使えます。
精度としては1msec程度の分解能。WindowsNTに限っていうとマシンによっては5msec以上になることもあるそうな。
DWORD型ということで戻り値は、0~2^32[msec]の間を循環します。
2^32[msec]は49.71日に相当するので、これ以上の時間は測定できません。
50日近く終わらない処理なんて測定することはしないでしょう(
変数timeに最終的な結果が代入されているので、なんかしらの形でこれを出力してやれば結果を見れます。
デバッグ画面から追っていってもいいんじゃないかと。
上記リンク、MSDNを見てもらえれば分かる通り今回はDelphi向けの書き方をしてますが、当然C#やC++でも使えます。
文法をそれに合わせるだけ。
基本的には1msec程度の分解能でも十分使えるんだけども、これ以上に細かいところを見たいとなると別の手法になってきます。
QueryPerformanceCounter関数とQueryPerformanceFrequency関数を使うことになるのですが、今回は使わなかったのでまたいずれ。
Sublime Text 2のインストール後に3をインストールした場合に、既定のプログラムとして登録できない問題
最初、Sublime Text 2を導入してたんですがいろいろと使いたいパッケージがあったため後からSublime Text 3をインストールしました。
当然2の方はアンインストールしたわけなんですが、txtファイル等よく開く拡張子を関連付けしようとしたところ
どうも設定上2の方を選んだことにされるようで、アンインストールしているので当然開こうとするとエラーを吐きます。
なんでやろかなーと思ってたら
forum.sublimetext.com
フォーラムに解決策でてましたね。
レジストリから書き換えが必要らしいです。
レジストリエディタを開いて
\HKEY_CLASSES_ROOT\Applications\sublime_text.exe\shell\open\command
の階層下にあるレジストリを編集。
僕の場合
"C:\Program Files\Sublime Text 2\sublime_text.exe""%1"
となってたのを、2→3に書き換えただけです。
これで関連付けができるようになります。
RIFFフォーマットファイルの読み込み
DelphiでWaveファイルを扱うためにRIFFフォーマットのファイルを読み込む必要がでてきたんで色々とメモ書き。
まず必要なものの説明。
MMSystemライブラリ
マルチメディア関連のAPIが各種はいっている。
uses句にこれを追加する必要あり。
MMRESULT型
MMSystem内の関数は一部、戻り値をこの型で返してくる。
中身はエラーコード。
TWaveFormatEx構造体
Wave形式のオーディオデータフォーマットが定義されている構造体。
この構造体のメンバは
メンバ名 | データ型 | 詳細 |
---|---|---|
wFromatTag | WORD | ウェーブフォームオーディオフォーマットのタイプ |
nChannel | WORD | チャンネル数、モノラルなら1Ch、ステレオなら2Ch | |
nSamplesPerSec | DWORD | サンプリング周波数、標本化周波数とも |
nAvgBytesPerSec | DWORD | フォーマットタグで必要な平均データ転送速度 |
nBlockAlign | WORD | ブロックアライメント、フォーマットタイプのデータの最小単位 |
wBitesPerSample | WORD | 1サンプリングあたりのビット数 |
cbSize | WORD | WaveFormatEx構造体の後ろに追加されるフォーマット情報のサイズ |
hMMIO型
オープンされているファイルのハンドルを格納するためのもの。
TMMCKINFO構造体
RIFFファイル内のチャンクに関する情報が定義されている。
TMMCKINFO構造体のメンバは
メンバ名 | 型 | 概要 |
---|---|---|
ckid | FOURCC | チャンク識別子 |
cksize | DWORD | チャンクのサイズ |
fccType | FOURCC | フォームのタイプ |
dwDataOffset | DWORD | Discend、Ascendを行ったあとのファイル上での位置 |
dwFlags | DWORD | 原則0が格納 |
mmioOpen関数
入出力をバッファリングしてファイルを開くための関数。
パラメーターは全部で3つ。
mmioOpen(szFilename,lpmmioinfo,dwOpenFlags);
パラメータ | データ型 | 概要 |
---|---|---|
szFilename | LPSTR | 開くファイルのファイル名がはいった文字列のアドレスを指定 |
lpmmioinfo | LPMMIOINFO | ファイルを開くためにインストールされていないI/Oプロシージャを指定する場合以外はnil指定 |
dwOpenGlags | DWORD | オープン操作のためのフラグを指定 MMIO_ALLOCBUF : ファイルを不バッファリングされた入出力用に開く MMIO_COMPAT : ファイルを互換モードで開き、指定されたマシン上の全てのプロセスでそのファイルを何度も開くことができるようにする MMIO_CREATE : 新規ファイルを作成する MMIO_DELETE : ファイルを削除する MMIO_DENYONE : 他のプロセスからのファイルの読み書きを拒否せず開く MMIO_DENYWRITE : ファイルに対する他のプロセスからの読み取りを拒否して開く MMIO_EXCLUSIVE : ファイルに対する他のプロセスからの読み書きアクセスを拒否して開く MMIO_EXIST : 指定されたファイルが存在するかどうかを調べ、szFilenameパラメータで指定したパスからファイル名を作成 MMIO_GETTEMP : szFilenameパラメータに渡されるパラメータを暫定的に使いテンポラリファイル名を作成 MMIO_PARSE : MMIO_EXISTに近いが存在するかどうかを調べない MMIO_READ : ファイルを読み取り専用として開く MMIO_READWRITE : ファイルを読み書き用に開く MMIO_WRITE : ファイルを書き込み専用として開く |
関数が成功すると、開いたファイルのハンドルが返ってくる。
mmioStringToFOURCC関数
NULLで終わる文字列を4文字コードに変換する。
第1引数に変換したい文字列、第2引数はフラグを指定するが「MMIO_TOUPPER」の1種のみ。
フラグを指定するとすべての文字が大文字に変換される。変換が必要ない場合、パラメータは0でよい。
mmioAscend関数
mmioDescend関数で侵入、またはmmioCreateChunk関数で作成したRIFFファイルのチャンクから退出する関数。
mmioAscend(hmmio,lpck,wFlags);
パラメータ | データ型 | 概要 |
---|---|---|
hmmio | HMMIO | 開いてるRIFFファイルのハンドルを指定 |
lpck | LPMMCKINFO | mmioDescend関数またはmmioCreateChunk関数で値が書き込まれている、アプリケーション定義のMMCKINFO構造体のアドレスを指定 |
wFlags | UNIT | 予約されているので0を指定 |
関数が成功するとMMSYSERR_NOERRORが返る。
mmioDescend関数
mmioOpen関数で開いたRIFFファイルのチャンクへ進入する。または指定されたチャンクを検索する。
mmioDescend(hmmio,lpck,lpckParent,wFlags);
パラメータ | データ型 | 概要 |
---|---|---|
hmmio | HMMIO | 開いてるRIFFファイルのハンドルを指定 |
lpck | LPMMCKINFO | アプリケーション定義のMMCKINFO構造体のアドレスを指定 |
lpckParent | LPMMCKINFO | 検索するチャンクの親を識別するためのMMCKINFO構造体のアドレスを指定 親チャンクが指定されてない場合nil指定 |
wFlags | UNIT | 検索フラグを指定する MMIO_FINDCHUNK : 指定されたチャンク識別子のチャンクを検索 MMIO_FINDLIST : チャンク識別子がLISTで指定されたフォームタイプのチャンクを検索 MMIO_FINDRIFF : チャンク識別子がRIFFで指定されたフォームタイプのチャンクを検索 |
関数が成功するとMMSYSERR_NOERRORが返る。
mmioRead関数
mmioOpen関数で開いたファイルから指定されたバイト数を読み取る。
mmioRead(hmmio,pch,cch);
パラメータ | データ型 | 概要 |
---|---|---|
hmmio | HMMIO | 読み取るファイルのハンドルを指定 |
pch | HPSTR | ファイルから読み取られたデータが入るバッファのアドレスを指定 |
cch | LONG | ファイルから読み取るバイト数を指定 |
と、この辺を使えば読み込みが可能になる。
関数などのパラメータ等はかなり簡略化してまとめてある。
詳細はMSDNなどで関数名を調べれば出てくる。C系の書き方がしてあるが、書式を変えればそのまんまDelphiでも使える。
今書いてるソースの中から該当部分だけをピックアップするとこんな具合。
uses MMSystem; //==============================// // FileOpenDialog呼び出し // //==============================// procedure TForm1.BtOpenClick(Sender: TObject); begin if OpenDialog1.Execute then begin wav_open(OpenDialog1.FileName); end; end; //========================================// // waveファイル読み込みプロシージャ // //========================================// procedure TForm1.wav_open(name:string); var x : integer; mmres : MMRESULT; PCM : TWaveFormatEx; hIO : hMMIO; RIFF_INFO : TMMCKINFO; DATA_INFO : TMMCKINFO; FMT_INFO : TMMCKINFO; buf : array[0 .. (1024 -1)] of Smallint; sz : DWORD; remain : DWORD; read_size : DWORD; total_size : DWORD; limit : DWORD; WaveData1 : array of Smallint; WaveData2 : array of Smallint; begin Ch1Index := 0; Ch2Index := 0; // wavファイル読み込み hIO := mmioOpen(PWideChar(name),nil,MMIO_READ); try RIFF_INFO.fccType := mmioStringToFOURCC('WAVE',0); mmres := mmioDescend(hIO, @RIFF_INFO, nil, MMIO_FINDRIFF); if(mmres <> MMSYSERR_NOERROR) then begin Memo1.Lines.Add('ERROR'); Exit; end; FMT_INFO.ckid := mmioStringTOFOURCC('fmt', 0); mmres := mmioDescend(hIO, @FMT_INFO, @RIFF_INFO, MMIO_FINDCHUNK); if(mmres <> MMSYSERR_NOERROR) then begin Memo1.Lines.Add('FMT CHUNK ERROR'); Exit; end; sz := mmioRead(hIO, @PCM, FMT_INFO.cksize); if sz <> FMT_INFO.cksize then begin Memo1.Lines.Add('Wave Format ERROR'); Exit; end; DATA_INFO.ckid := mmioStringTOFOURCC('data',0); mmres := mmioDescend(hIO, @DATA_INFO, @RIFF_INFO, MMIO_FINDCHUNK); if (mmres <> MMSYSERR_NOERROR) then begin Memo1.Lines.Add('Data Chunk Error'); exit; end; remain := DATA_INFO.cksize; // waveファイルのデータ数 read_size := Length(buf) * sizeof(Smallint); total_size := 0; // 読み込んだデータを格納するための配列の長さを決定 // 2chあるためデータ総数div2 SetLength(WaveData1,DATA_INFO.cksize div 2); SetLength(WaveData2,DATA_INFO.cksize div 2); while remain > 0 do begin if read_size > remain then read_size := remain; mmioRead(hIO, @buf, read_size); for x := 0 to (read_size div (sizeof(Smallint))) - 1 do begin if (x mod 2) = 0 then begin WaveData1[Ch1Index] := buf[x]; inc(Ch1Index); end else begin WaveData2[Ch2Index] := buf[x]; inc(Ch2Index); end; end; inc(total_size, read_size); dec(remain, read_size); if limit > 0 then begin if total_size >= limit then Break; end; end; mmioAscend(hIO, @DATA_INFO, 0); mmioAscend(hIO, @RIFF_INFO, 0); finally mmioClose(hIO, 0); end; end;
ざっくり流れとしては
1.ファイル展開
2.チャンクを検索してWaveファイルかどうか調べる
3.チャンクを検索してフォーマットが正しいか調べる
3.チャンクを検索してデータフォーマットが正しいか調べる
4.配列の長さをデータ数に合わせる
5.チャンネルごとにデータを分けて格納していく
6.チャンクから退出
7.ファイルを閉じる
という感じ。
これだとWaveファイルをただ開いてデータを取り込むだけなので、追加で色々実装する必要はあり。
TWaveFormatEx構造体のメンバは結構良く使う。
TImageのリサイズ時に気をつけること
Delphiやりはじめて初日ですどうも。早速躓きまくってるので色々メモ書きをガンガン残していきます。
DelphiでTImageコンポーネントを、フォームのサイズに合わせてリサイズしようとしていたのだが、どうにもうまくリサイズされない。
通常のコンポーネントは、プロパティのAlignとAnchorsを設定すればフォームの大きさに合わせて可変する。
しかし、TImageさんはフォーム起動時の大きさを維持したまま動かない。
なぜ??
最初に書いたソースの該当部分はこれ。
procedure TForm1.FormResize(Sender: TObject); var //X軸Y軸開始座標格納 XAxis, YAxis : Integer; begin XAxis := 0; YAXis := 0; //キャンバスの各種描画 with Image1.Canvas do begin //キャンバスの塗りつぶし brush.Color := clblack; fillrect(Image1.Canvas.ClipRect); //軸の描画 Pen.Color := clLime; pen.Width := 3; XAxis := Image1.Height div 2; moveto(0,XAxis); lineto(Image1.Width,XAxis); end;
フォームのリサイズイベントハンドラにImage1.Canvasの塗りつぶしと描画を発生させている。
しかし、フォームを可変させようとコンポーネントのサイズ自体が変化しないのであればなんの意味もない。
なんでやろかと調べてたところ、TImageのCanvasプロパティは、他のコンポーネントのそれとはちょいと違うらしい。
TImageのCanvasプロパティを利用して描画をしてから表示されるまでの流れは
1.Canvasプロパティを使って描画を行おうとすると、PictureGraphicに代入されてる「TBitmap」に描画がはじまる
2.TBitmapでOnChangeイベント発生 → TImageのイベントハンドラ呼び出し
3.イベントハンドラでTImageの表示領域をInvalidateメソッドで無効化し、Windowsに再描画を要求
4.Updateメソッドで強制的に再描画。しかしWindowsからの再描画要求によってPaintメソッドが呼ばれ、Picture.Graphicに代入されているTBitmapがTImageの表示面にStretchDrawで描画される。
というメンドクサイ手順を追ってるようだ。
もともとTImageというものがPicture.Graphicに代入されている画像を表示するコンポーネントらしい。
暗黙的にTImageはTBitmapを抱えているようだ。
抱き合わせ販売とか詐欺まがいなことはやめてほしい()
つまりTBitmapのサイズをフォームに合わせてリサイズしてやり、そのサイズにあわせてTImageをリサイズさせる。
という段取りを経る必要があるようだ。
よって、上記のソースはこうなる。
procedure TForm1.FormResize(Sender: TObject); var //Image_AというTbitMapダミーのコンポーネントを宣言 //実際には表示されない Image_A : TBitMap; //X軸Y軸開始座標格納 XAxis, YAxis : Integer; begin XAxis := 0; YAXis := 0; //Image_Aを生成 Image_A := TBitMap.Create; //Image_AのWidthとHeightをForm1に合わせる Image_A.Height := Form1.Height; Image_A.Width := Form1.Width; //Image1をImage_Aにあわせる Image1.Picture.Bitmap := Image_A; //オブジェクトの解放 Image_A.Free; Image_A := nil; //キャンバスの各種描画 with Image1.Canvas do begin //キャンバスの塗りつぶし brush.Color := clblack; fillrect(Image1.Canvas.ClipRect); //軸の描画 Pen.Color := clLime; pen.Width := 3; XAxis := Image1.Height div 2; moveto(0,XAxis); lineto(Image1.Width,XAxis); end; end;
とまぁ、これでうまく動く。
注意しなければならないのは
Image_A.Free;
Image_A := nil;
この記述を忘れると、リサイズを繰り返すうちにメモリを食い潰すことになる。(普通にフリーズする)
Image_Aというポインタがリサイズされるたびに生成され続けることになる。
TImageのリサイズが終わったタイミングで必ず開放すること。
DelphiのエディターをMonokai風にアレンジ
仕事の都合上、Delphiデビューすることになりました。
エディターが初期設定だとかなり見づらいので、カラーチェンジの設定をメモ書き程度に書いていきます。
SublimeTextの方でカラースキーマはMonokaiを使ってるので、感覚的にはこれに近づけていく感じでいじりました。
参考までにどうぞ。
要素 | 前景色 | 背景色 |
---|---|---|
基本の背景色 | 39,40,34 | |
ホワイトスペース | 銀色 | 39,40,34 |
コメント | 255,189,123 | 39,40,34 |
予約語 | 102,217,239 | 39,40,34 |
識別子 | 166,226,46 | 39,40,34 |
シンボル | 白色 | 39,40,34 |
文字列 | 230,219,116 | 39,40,34 |
文字 | 230,219,116 | 39,40,34 |
数値 | 217,130,208 | 39,40,34 |
実数 | 217,130,208 | 39,40,34 |
8進数 | 217,130,208 | 39,40,34 |
16進数 | 217,130,208 | 39,40,34 |
アセンブラ | 255,128,64 | 39,40,34 |
検索一致 | 黒色 | 127,170,255 |
マークされたブロック | 黒色 | 127,170,255 |
不正なブレークポイント | 238,226,204 | 90,90,90 |
有効なブレークポイント | 黒色 | 186,186,254 |
無効なブレークポイント | 白色 | 灰色 |
実行ポイント | 黒色 | 189,255,189 |
エラー行 | 白色 | 255,108,108 |
行番号 | 灰色 | 39,40,34 |
このほか細かいところは好みに合わせて調整すればいいかと思います。