PowerShellでどのようなやり方がいいかを少し考えてみます。
「1から100の偶数の和を求めるワンライナー」まとめ - Qiita
というのがあり、Twitterでつぶやいたのですが、一応まとめておきます。
guitarrapc_tech (@guitarrapc_tech) August 13, 2019
概要
リスト作ってもいいなら、メソッド方式で。作りたくないならパイプラインで。 bitwiseやシフト演算が最速と思いきや、普通に% (剰余) がいいです。
PowerShellでSIMD活用ってどうやるといいのかが気になります。
算出方法
どれでもどうぞ。
https://gist.github.com/guitarrapc/2c5049cc71699f2d6bb3b5006afbc6c3
いずれも求められますが、大きな違いは2つです。(8つあるのは、2 x 4通りです)
- フィルター方法がパイプラインorメソッド
- 偶数の算出が剰余(modulo) or除算(division) orビット演算(bitwise) orシフト演算(shift)
シーケンスの生成
フィルター方法の選択でメモリと実行速度に違いがでます。
- パイプライン
|を使うことで、1~100のメモリ域を確保しないので使用メモリが減る一方で、実行速度は落ちる - メソッド
(シーケンス).Where{}を使うことで、1~100のメモリ域を確保するため使用メモリが増える一方で、実行速度は上がる
リスト作る必要ないならパイプラインがいいですね。
算出方法とベンチマーク
偶数の算出は、どれを選ぶかで実行速度に違いが出ます。
- modulo
PowerShellでも算術演算子%を使えます。奇数は剰余が1、偶数は0です。
よく書きます。
- bitwise
8ビットで考えます。奇数は20が常に1なので1とand(論理積)を演算すれば常に1になります。偶数なら0です。 こっちのほうが早い時には使います。
00000001
00000001 (00000001 is 1)
&
--------
00000001
C系の(x & 1) == 0をPowerShellに翻訳すると($_ -band 1) -eq 0になります。
- division
残術演算子/を使って2で割って、intで小数点を破棄してかけなおすと元に戻るかです。
明らかに無駄なので普段書きません。
- shift
偶数の1ビット目が0であるため、右シフトして桁を落として左シフトで0を入れた時に元の値になれば偶数、そうでなく1少なくなれば奇数です。
00000011 (3) 00000001 (>> 1) 00000010 (<< 1) -------- 00000010 (2)
C系の((3 >> 1) << 1) == 3をPowerShellに翻訳すると(3 -shr 1) -shl 1 -eq 3となります。
速度を見てみましょう。計算回数が少なければ早いので、オペレータのコストとJITでの最適化がかかるかがポイントです。
PowerShellは1回1回のベンチマークのずれが激しいので、10000回実行した算術平均をとって1回当たりの実行速度を見てみます。*1
| BenchMark(Method) | Times | Avg(ms) |
|---|---|---|
| bitwise | 1000 | 0.542 |
| division | 1000 | 0.521 |
| modulo | 1000 | 0.489 |
| shift | 1000 | 0.520 |
| Benchmark(Pipeline) | Times | Avg(ms) |
|---|---|---|
| bitwise | 1000 | 1.353 |
| division | 1000 | 1.486 |
| modulo | 1000 | 1.330 |
| shift | 1000 | 1.359 |
https://gist.github.com/guitarrapc/04df00c23ca58f276fd694b41b87b27a
余談 : クラス構文
PowerShellでは、同じ処理でもクラス構文にすると、dllからの呼び出しになるため高速化する傾向があります。
といっても、偶数判定だけクラス構文にすると遅くなります。
| Benchmark(Method) | Times | Avg(ms) |
|---|---|---|
| shift | 1000 | 1.287 |
| class | 1000 | 1.088 |
https://gist.github.com/guitarrapc/07809ddbefadbbb266570bddaa291d56
全体をクラス構文にして、インスタンスメソッド、スタティックメソッドでどうなるか見てみると早くなっていないことがわかります。
| Benchmark(Class) | Times | Avg(ms) |
|---|---|---|
| bitwise | 1000 | 0.536 |
| division | 1000 | 0.562 |
| modulo | 1000 | 0.533 |
| shift | 1000 | 0.559 |
| Benchmark(Static) | Times | Avg(ms) |
|---|---|---|
| bitwise | 1000 | 0.538 |
| division | 1000 | 0.553 |
| modulo | 1000 | 0.521 |
| shift | 1000 | 0.544 |
https://gist.github.com/guitarrapc/2eb9005aa441b217a2d99f6e90ddacb9
この程度だと速度差つかないですね。(PowerShell 5.1 / 6.2)
*1:これでも差が出るのでウォームアアップがあるとよりいいですね