PHPのタイプヒンティングはどのくらいコストフリーではないのか

# はじめに

CakePHP のコーディング規約を見ていて、気になる一文があったため、実際にどのくらいコストがかかるのかを確認します。

> オブジェクトや配列を期待する引数はタイプヒンティングを指定することができます。しかしながらタイプヒンティングはコストフリーではないので、public メソッドにだけ指定します。

https://book.cakephp.org/4/ja/contributing/cakephp-coding-conventions.html#id12

# 結論

:::note info
タイプヒンティングすることで処理時間が約 110% となる
:::

# バージョン

- PHP
  - 8.2.1
- Docker Desktop
  - 4.16.2

# 確認

## 確認したリポジトリ

https://github.com/q23isline/study_php

## 検証プログラム

<details><summary>タイプヒンティングありのメソッドを 100万回実行するプログラム</summary><div>

```php:src/Shell/typeHintingPerformanceEnableShell.php
<?php

declare(strict_types=1);

define('MAXIMUM_LOOP_COUNT', 1000000);

$start = hrtime(true);

$character = 'A';
for ($number = 0; $number < MAXIMUM_LOOP_COUNT; $number++) {
    getCellAddress($number, $character++);
}

$end = hrtime(true);

echo '処理時間: ' . ($end - $start) . "ナノ秒\n";

/**
 * タイプヒンティングあり
 *
 * @param int $number
 * @param string $character
 * @return string
 */
function getCellAddress(int $number, string $character): string
{
    return "{$character}{$number}";
}
```

</div></details>

<details><summary>タイプヒンティングなしのメソッドを 100万回実行するプログラム</summary><div>

```php:src/Shell/typeHintingPerformanceDisableShell.php
<?php

declare(strict_types=1);

define('MAXIMUM_LOOP_COUNT', 1000000);

$start = hrtime(true);

$character = 'A';
for ($number = 0; $number < MAXIMUM_LOOP_COUNT; $number++) {
    getCellAddress($number, $character++);
}

$end = hrtime(true);

echo '処理時間: ' . ($end - $start) . "ナノ秒\n";

/**
 * タイプヒンティングなし
 *
 * @param int $number
 * @param string $character
 * @return string
 */
function getCellAddress($number, $character)
{
    return "{$character}{$number}";
}
```

</div></details>

## 検証コマンド

```bash
docker exec -it app php src/Shell/typeHintingPerformanceEnableShell.php
```

## 検証結果

| -             | タイプヒンティングあり（ミリ秒） | タイプヒンティングなし（ミリ秒） | あり / なし * 100 |
|---------------|------------------|------------------|---------------|
| 100万回実行の平均時間  | 177.55           | 151.51           | 117.19%       |
| 1000万回実行の平均時間 | 1,919.52         | 1,746.58         | 109.90%       |
| 1億回実行の平均時間    | 18,253.01        | 16,691.26        | 109.36%       |

<details><summary>速度計測エビデンス</summary><div>

| 100万回試行回数 | タイプヒンティングあり（ミリ秒） | タイプヒンティングなし（ミリ秒） |
|-----------|------------------|------------------|
| 1         | 176.07           | 152.15           |
| 2         | 183.05           | 149.79           |
| 3         | 172.41           | 150.44           |
| 4         | 174.32           | 151.14           |
| 5         | 181.92           | 154.05           |

| 1000万回試行回数 | タイプヒンティングあり（ミリ秒） | タイプヒンティングなし（ミリ秒） |
|------------|------------------|------------------|
| 1          | 1,794.38         | 1,946.96         |
| 2          | 1,757.58         | 1,917.64         |
| 3          | 1,880.40         | 1,836.29         |
| 4          | 2,096.76         | 1,505.15         |
| 5          | 2,068.50         | 1,526.87         |

| 1億回試行回数 | タイプヒンティングあり（ミリ秒） | タイプヒンティングなし（ミリ秒） |
|---------|------------------|------------------|
| 1       | 18,204.54        | 17,053.92        |
| 2       | 19,668.12        | 16,582.56        |
| 3       | 18,631.36        | 17,161.73        |
| 4       | 17,385.31        | 15,423.06        |
| 5       | 17,375.74        | 17,235.03        |

</div></details>

# まとめ

コストは確かにあることがわかりました。  
ただし、100万回呼び出されても 20ミリ秒しか差がないことから、可読性・保守性を考えるとタイプヒンティングを積極的に利用すべきのように感じます。  
（性能が悪いからとタイプヒンティングを外すのではなく、もっと根本的な問題が別にあると思うのでそこを対応すべき）  
この記事が他のエンジニアの助けになれば幸いです。