Delphi の高速化手法（小手先編）

# Delphi の高速化手法

Delphi コンパイラの最適化はそこまで頭が良くないため、速度を出すには小手先のテクニックが必要です。
僕がコードを書くに当たって読みづらくならない程度に気をつけているポイントをご紹介します。

#1.プロパティを複数回参照するなら代入する

プロパティは内部で計算を行っている場合があります。
次のようなクラスを考えてみます。

```pascal:TFooクラス
type
  TFoo = class
  private 
    function GetBar: Integer;
  public
    property Bar: Integer read GetBar;
  end;

function TFoo.GetBar: Integer;
begin
  Result := 0;
  for var i := 0 to 9999 do // 1万回繰り返す凶悪なルーチン
    Result := Result + 1;
end;
```

そのまま Bar プロパティを呼ぶと Bar プロパティの実装である GetBar メソッドが３回実行されてしまいます。

```pascal:TFooクラスを使う関数
procedure Baz;
begin
  var Foo := TFoo.Create;
  var C := Foo.Bar + Foo.Bar + Foo.Bar; // ここで GetBar が３回呼ばれる
  Writeln(C);
end;
```

なので、呼び出し毎に値が変わらない場合は、変数に代入しておいて使い回すと良いでしょう。

```pascal:TFooクラスを使う関数改
procedure Baz;
begin
  var Foo := TFoo.Create;
  var B := Foo.Bar;
  var C := B + B + B;
  Writeln(C);
end;
```

#2.加減算は Inc/Dec を使う

[Inc 手続き](http://docwiki.embarcadero.com/Libraries/Rio/ja/System.Inc)の説明には以下のように書いてあります([Dec 手続き](http://docwiki.embarcadero.com/Libraries/Rio/ja/System.Dec)についても同様)。

> 一部のプラットフォームでは，Inc は最適化されたコードを生成するので，特にループで使用すると有効です。

恐らく Delphi の古いバージョンの Intel のプラットフォームでは最適化されたコードが生成されると思われます。
また、たとえ**現在**最適化されないとしてもコンパイラもバージョンアップされていきますし未来永劫吐き出されるコードが変わらないわけではいことに注意してください。
つまり、ドキュメントに書いてあるとおり最適化が施される可能性もあるので、現時点においても Inc/Dec と書いておいて損はありません。

#3.値を変更しない引数には const を付ける

DocWiki の[定数パラメータ](http://docwiki.embarcadero.com/RADStudio/Rio/ja/%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%EF%BC%88Delphi%EF%BC%89#.E5.AE.9A.E6.95.B0.E3.83.91.E3.83.A9.E3.83.A1.E3.83.BC.E3.82.BF) には以下のように書いてあります。

> 構造化型パラメータや文字列型パラメータに対して const を指定すると、コンパイラではコードを最適化できます

Integer といった単純型の場合、値がそのまま引数に渡されますが、構造化型や文字列型は参照渡しにしないと値がコピーされるため速度が低下します。
そこで、値の変更が無い場合 const を付けてあげるとコンパイラが適宜判断して[^1]参照渡しにしてくれます。

#4.if 文はショートサーキット評価を考慮する

if 文を書くとき、例えば、条件Aと条件Bがあり

* 条件A：めちゃくちゃ重い処理
* 条件B：軽い処理。あまり値は変化しない（例えば Property によるフラグなど）

と言うとき

```pascal
if (条件A) and (条件B) then
  // 何か処理
else
  // 何か処理
```

と書いてしまうと、if 文で条件Aが評価されてしまうため、重い処理が必ず走ってしまいます。

これを下記の様に改めた場合、条件Bの[ショートサーキット評価](http://docwiki.embarcadero.com/RADStudio/Rio/ja/%E5%BC%8F%EF%BC%88Delphi%EF%BC%89#.E5.AE.8C.E5.85.A8.E8.AB.96.E7.90.86.E8.A9.95.E4.BE.A1.E3.81.A8.E7.9F.AD.E7.B5.A1.E8.AB.96.E7.90.86.E8.A9.95.E4.BE.A1)で条件Aを見る頻度が減り、重い処理が走る頻度がへります。

```pascal
if (条件B) and (条件A) then
  // 何か処理
else
  // 何か処理
```

#5. 2の冪乗計算にはシフト演算を使う

2, 4, 8 といった 2の冪乗を使った乗除算はシフト演算で代替できます。

```pascal:乗算
  var A = 1;
  var B = A * 2;
  var C = A * 8;
  var D = C div 4
```

```pascal:シフト演算
  var A = 1;
  var B = A shl 1;
  var C = A shl 3;
  var D = C shr 2
```

乗除算に比べるとシフト演算のほうが高速なのは間違いありませんが、非常に読みづらくなります。
また、最近の CPU は乗除算も高速です。
ですので、「ほんのちょっとの速度向上」と「メンテナンス性の高さ」のどちらを取るかは適宜判断が必要です。

なお、最適化によって乗除算がシフト演算になるコンパイラもありますが、ならないコンパイラ(AARM64等)もあるのでマルチプラットフォーム開発をするなら考慮すべきでしょう。

#6.論理的に区切りを付けるための関数には inline を付ける

メソッドや関数・手続きを書いている時に、長くなってきたし論理的なまとまりが２つ以上あるなと感じた時に、僕は関数を分けています。

例えば（実際はこんな単純な事はありませんが）↓のような事です。

```pascal:別の処理が１つの手続きの中
var A, B: Integer;

procedure Foo;
begin
  for var i := 0 to 10000 do
  begin
    // A に関する処理
    A := 0;
    for var j = 0 to 100 do
      Inc(A);

    // B に関する処理
    B := 9999;
    for var j = 0 to 100 do
      Dec(B);
  end;
end;
```

```pascal:処理を別の手続きに分ける
var A, B: Integer;

procedure ProcA; // A に関する処理
begin
  A := 0;
  for var i = 0 to 100 do
    Inc(A);
end;

procedure ProcB;// B に関する処理
begin
  B := 9999;
  for var i = 0 to 100 do
    Dec(B);
end;

procedure Foo;
begin
  for var i := 0 to 10000 do
  begin
    ProcA;
    ProcB;
  end;
end;
```

このように分けると論理的まとまり毎にコードが整理されるため見やすくなります。
ただ、手続きやメソッドにしてしまうと、それを呼び出すコストが掛かります。
特に上記のようなループ中となるとバカに出来ません。
そのため、そもそも「関数を分けない」という選択肢もあります。
それでも分けた方が良さそうだという時には [inline 指令](http://docwiki.embarcadero.com/RADStudio/Rio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E3%81%A8%E9%96%A2%E6%95%B0%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%EF%BC%88Delphi%EF%BC%89#inline_.E6.8C.87.E4.BB.A4.E3.81.AE.E5.88.A9.E7.94.A8)を指定すると関数を呼び出す代わりに直接コードを生成してくれます。

```pascal:
procedure ProcA; inline
begin
  // 処理A
end;

procedure ProcB; inline;
begin
  // 処理B
end;

procedure Foo;
begin
  for var i := 0 to 10000 do
  begin
    ProcA; // 処理Aが直接生成される
    ProcB; // 処理Bが直接生成される
  end;
end;
```

ただし、inline 指令は必ず inline 化されるわけではなく DocWiki の説明にあるように「コンパイラへの提案」です。
必ずインライン化されるわけではない所に注意してください。

# まとめ
個々の処理では微々たるものですが「塵も積もれば山となる」と言うように、特にループ処理では無視できない高速化に繋がる事もあります。
顕著なのが画像処理関係でした…でしたと過去形なのは最近は GPU による並列演算が可能なため、CPU 側で処理するより圧倒的に速いんですよね…
とはいえ、高速で実行されることにデメリットはほとんど無いので、小手先のテクニックとして知っておいても損は無いと思います。


[^1]: ※ただし、コンパイラによっては const を指定しても値渡しになる場合があるみたいです