TL;DR
前回の続きと言う名の沼
少なくとも、.Net Coreの上で動かすのなら、for
でもforeach
でも好きな方使えばいいんじゃないかな?
検証に先立つ追記(教えて頂いた知見)
記事公開後にはぇ~☆先生から、ecxのコピーの件、
ループカウンタは32bit、アドレスは64bitだから別レジスタで管理せざるを得ないのでは
— はぇ~☆ (@haxe) 2019年6月17日
とのご指摘を頂いた。
確かにecx
は32bit[^1]、r~~
は64bitなのでこちらの方が理由としては大きいと考えた。
また、その後
ちょっと調べました。Ivy以降であればmovsx r9, ecxはレジスタリネーミングで処理されるので実行コストがかからないっぽい。
— はぇ~☆ (@haxe) 2019年6月17日
あとSandy以降だとrax+r9*4+10hのような3入力演算はport1という特定ALUでしか実行できないとのこと。他の演算でport1が使用中の場合、ストールするので実行時間がバラけるかも。Span<T>を使えばrax+r9*4とかの2入力演算にできるはずで、安定するようになるかも?
— はぇ~☆ (@haxe) 2019年6月17日
load+opがmicro-fusionで結合されるから、mov+addと直接addに違いはなくなりそう。ちなみにcmp+jgを結合するのはmacro-fusion。
— はぇ~☆ (@haxe) 2019年6月17日
と、教えて頂いた。
実際のCPUがどのようにAssemblyを解釈して実行してるのか全く知らなかったのでとても参考になりました。
教えて頂きありがとうございます。
ベンチマークはどのように実行されたのか
ベンチマークは概ね以下のプロセスで実行される。
詳細はHow it worksを参照してください。
- ベンチマークコードを独立したプロジェクトとして生成する
- イテレーションカウントの計測
- 関係ない部分のオーバーヘッドタイムの計測
- ウォーミングアップでベンチマークコードを実行
- 実計測(20個)
ウォーミングアップは、Managed heapのサイズを安定させるために行ってる。
また、実計測が20回では無く、20個なのは、明らかな外れ値を弾いて再実行しているため。
検証したデータ及び、呼称
以下の結果は、先に述べたとおり、様々な方から様々なCPUの実行結果の提供を受けた。
実データはgithubにまとまっている。
また、検証に使用した配列のサイズが900,000,000個は流石に多すぎると思い、サンプルは少ないが、配列のサイズが100,000個の場合もベンチマークを取っている。
この先、ArraySize=900,000,000を900M、ArraySize=100,000を100Kと表現する。
#AMDの場合
AMD製のCPU/APUでこのベンチマークを実行した結果を以下に示す。
どちらの結果も、for
を使ったループ操作を行った方が高速であることがわかる。
#Intel
こっちはかなり複雑というか、割と凄いことになる。
まずは結果を示す。
グラフからわかるとおり、forが高速なパターンと、foreachが高速なパターンが存在する。
そこで、世代毎に並べた上で、どちらが高速なのかその比率を図示化してみた。
ratio
の計算式は1-<速い方>/<遅い方>
とし、for
が速い場合は負値、foreach
が速い場合は正値で表した結果となる。
ここから見えてくること
AMDとは異なり、画一的にどちらが速いと結論づけることが出来ない結果となっている。
具体的にはHaswellを境として、古いCPUはfor
が高速であり、それ以降のCPUはforeach
が高速となっている。
また、全体的な傾向として、Haswell世代の比率が最も大きくなり、時代が下るに従ってその差は小さくなっており、2019年6月16日現在最新のCoffee Lake
ではforeachが速いとは言え、その差は微少になっていることがわかる。
まとめ ~結論だけが残った~
おいらは、CPUのハードウェア的な知識が皆無なので、実験した結果をまとめるに留めておく。
- AMDのCPUは世代に関係なく常に
for
の方が高速。 - intelのCPUはHaswellを境として、それ以前のCPUなら
for
が優位であり、それ以降ではforeach
が優位である。 - 但し、その差は小さくなっている。
全体的に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 |