C#で配列に対してforとforeachどっちが速いの?　～実際試してみた～

# TL;DR

[前回の続き](https://qiita.com/Tokeiya/items/7918e1ff2c02fa9f9fd5)と言う名の沼

少なくとも、**.Net Core**の上で動かすのなら、`for`でも`foreach`でも好きな方使えばいいんじゃないかな?

# 検証に先立つ追記(教えて頂いた知見)
記事公開後に[はぇ～☆](https://twitter.com/haxe)先生から、ecxのコピーの件、

<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">ループカウンタは32bit、アドレスは64bitだから別レジスタで管理せざるを得ないのでは</p>&mdash; はぇ～☆ (@haxe) <a href="https://twitter.com/haxe/status/1140423949890666502?ref_src=twsrc%5Etfw">2019年6月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

とのご指摘を頂いた。
確かに`ecx`は32bit[^1]、`r~~`は64bitなのでこちらの方が理由としては大きいと考えた。

また、その後

<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">ちょっと調べました。Ivy以降であればmovsx r9, ecxはレジスタリネーミングで処理されるので実行コストがかからないっぽい。</p>&mdash; はぇ～☆ (@haxe) <a href="https://twitter.com/haxe/status/1140465661237293056?ref_src=twsrc%5Etfw">2019年6月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">あとSandy以降だとrax+r9*4+10hのような３入力演算はport1という特定ALUでしか実行できないとのこと。他の演算でport1が使用中の場合、ストールするので実行時間がバラけるかも。Span&lt;T&gt;を使えばrax+r9*4とかの２入力演算にできるはずで、安定するようになるかも？</p>&mdash; はぇ～☆ (@haxe) <a href="https://twitter.com/haxe/status/1140466223609630720?ref_src=twsrc%5Etfw">2019年6月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">load+opがmicro-fusionで結合されるから、mov+addと直接addに違いはなくなりそう。ちなみにcmp+jgを結合するのはmacro-fusion。</p>&mdash; はぇ～☆ (@haxe) <a href="https://twitter.com/haxe/status/1140470168524509184?ref_src=twsrc%5Etfw">2019年6月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


と、教えて頂いた。

実際のCPUがどのようにAssemblyを解釈して実行してるのか全く知らなかったのでとても参考になりました。

教えて頂きありがとうございます。

# ベンチマークはどのように実行されたのか

ベンチマークは概ね以下のプロセスで実行される。

詳細は[How it works](<https://benchmarkdotnet.org/articles/guides/how-it-works.html>)を参照してください。

1. ベンチマークコードを独立したプロジェクトとして生成する
2. イテレーションカウントの計測
3. 関係ない部分のオーバーヘッドタイムの計測
4. ウォーミングアップでベンチマークコードを実行
5. 実計測(20個)



ウォーミングアップは、Managed heapのサイズを安定させるために行ってる。

また、実計測が20回では無く、20個なのは、明らかな外れ値を弾いて再実行しているため。

# 検証したデータ及び、呼称
以下の結果は、先に述べたとおり、様々な方から様々なCPUの実行結果の提供を受けた。
実データは[github](<https://github.com/Tokeiya/ArrayAccessSurvey/issues>)にまとまっている。

また、検証に使用した配列のサイズが900,000,000個は流石に多すぎると思い、サンプルは少ないが、配列のサイズが100,000個の場合もベンチマークを取っている。
この先、ArraySize=900,000,000を**900M**、ArraySize=100,000を**100K**と表現する。

#AMDの場合
AMD製のCPU/APUでこのベンチマークを実行した結果を以下に示す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9662/db9a51be-a7df-1959-70a0-79a212566112.png)
----
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9662/e95dc09c-0dba-4cfa-cd01-7fc385fbc3b5.png)


どちらの結果も、`for`を使ったループ操作を行った方が高速であることがわかる。

#Intel

こっちはかなり複雑というか、割と凄いことになる。
まずは結果を示す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9662/03b88dca-e422-b615-8c59-75156580a4c1.png)
----
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9662/fb0639f0-e746-795c-01dd-27084194798c.png)

グラフからわかるとおり、forが高速なパターンと、foreachが高速なパターンが存在する。
そこで、世代毎に並べた上で、どちらが高速なのかその比率を図示化してみた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9662/d910b397-555f-3896-458e-a330c7bdf6ea.png)
----
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9662/7c94169a-092d-1447-714a-33b0c0383f78.png)

`ratio`の計算式は`1-<速い方>/<遅い方>`とし、`for`が速い場合は負値、`foreach`が速い場合は正値で表した結果となる。

## ここから見えてくること

AMDとは異なり、画一的にどちらが速いと結論づけることが出来ない結果となっている。
具体的にはHaswellを境として、古いCPUは`for`が高速であり、それ以降のCPUは`foreach`が高速となっている。
また、全体的な傾向として、Haswell世代の比率が最も大きくなり、時代が下るに従ってその差は小さくなっており、2019年6月16日現在最新の`Coffee Lake`ではforeachが速いとは言え、その差は微少になっていることがわかる。

# まとめ　～結論だけが残った～

おいらは、CPUのハードウェア的な知識が皆無なので、実験した結果をまとめるに留めておく。

1. AMDのCPUは世代に関係なく常に`for`の方が高速。
2. intelのCPUはHaswellを境として、それ以前のCPUなら`for`が優位であり、それ以降では`foreach`が優位である。
3. 但し、その差は小さくなっている。

全体的にintel製のCPUが優勢な状況なので、どちらを使うべきかと言えば多分`foreach`の方だと考えられる。
しかしながら、その差は小さくなってきており、またAMD製のCPUでは`for`の方が優位なので、TL;DRで述べたとおり、好きな方を使えば良いのではないかなと言う割と身も蓋もない結論となった。

# 補遺

コメントを頂いて、CPU以外の環境を明示していなかったので補遺としてリストしておきます。

|cpu                                             |os                                                        |sdk_version             |runtime                                                                   |
|:-----------------------------------------------|:---------------------------------------------------------|:-----------------------|:-------------------------------------------------------------------------|
|AMD Ryzen 7 1700X                               |Windows 10.0.17763.529 (1809/October2018Update/Redstone5) |2.2.300                 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|Intel Core i7-3770K CPU 3.50GHz (Ivy Bridge)    |Windows 10.0.17763.557 (1809/October2018Update/Redstone5) |2.2.300                 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|Intel Pentium CPU 4415Y 1.60GHz                 |Windows 10.0.17134.753 (1803/April2018Update/Redstone4)   |2.2.300                 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|Intel Core i7-6700 CPU 3.40GHz (Skylake)        |Windows 10.0.17134.765 (1803/April2018Update/Redstone4)   |3.0.100-preview5-011568 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|Intel Core i7-9700K CPU 3.60GHz (Coffee Lake)   |debian 9                                                  |2.2.300                 |.NET Core ? (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT     |
|Intel Core i7-8850H CPU 2.60GHz (Coffee Lake)   |macOS Mojave 10.14.5 (18F203) [Darwin 18.6.0]             |3.0.100-preview5-011568 |.NET Core 2.2.0 (CoreCLR 4.6.27110.04, CoreFX 4.6.27110.04), 64bit RyuJIT |
|Intel Core i5-3210M CPU 2.50GHz (Ivy Bridge)    |macOS Mojave 10.14.5 (18F132) [Darwin 18.6.0]             |2.2.107                 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|Intel Core i5-4200U CPU 1.60GHz (Haswell)       |Windows 10.0.17763.557 (1809/October2018Update/Redstone5) |2.2.300                 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|Intel Core i5-3337U CPU 1.80GHz (Ivy Bridge)    |Windows 10.0.17763.529 (1809/October2018Update/Redstone5) |2.2.300                 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|AMD Ryzen 5 PRO 2500U w/ Radeon Vega Mobile Gfx |Windows 10.0.18362                                        |3.0.100-preview5-011568 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|Intel Core i7-2620M CPU 2.70GHz (Sandy Bridge)  |macOS High Sierra 10.13.6 (17G7024) [Darwin 17.7.0]       |2.2.107                 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|Intel Core i5-7600 CPU 3.50GHz (Kaby Lake)      |Windows 10.0.17763.503 (1809/October2018Update/Redstone5) |3.0.100-preview-009812  |.NET Core 2.2.3 (CoreCLR 4.6.27414.05, CoreFX 4.6.27414.05), 64bit RyuJIT |
|Intel Core i5-6267U CPU 2.90GHz (Skylake)       |macOS High Sierra 10.13.4 (17E199) [Darwin 17.5.0]        |2.2.107                 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|AMD A10-6800K APU with Radeon(tm) HD Graphics   |Windows 10.0.17763.348 (1809/October2018Update/Redstone5) |2.2.300                 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |
|Intel Core i5-4300U CPU 1.90GHz (Haswell)       |Windows 10.0.17763.529 (1809/October2018Update/Redstone5) |3.0.100-preview4-011223 |.NET Core 2.2.5 (CoreCLR 4.6.27617.05, CoreFX 4.6.27618.01), 64bit RyuJIT |