鳥の巣箱

ネトゲしたり、機械いじったり、ソフト書いたり、山篭ったり、ギャンブルしたりする人

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

このほか細かいところは好みに合わせて調整すればいいかと思います。