オブジェクトの数を数える時によく使われるものといえば?
Measure-Object
でしょうか? System.Linq.Enumerable
でしょうか?
今回はさらっとちょっとだけ見てみましょう。
目次
個数のカウント
よく使うのはどれでしょうか?
.Length
プロパティ?.Count
プロパティ?Measure-Object
コマンドレット?System.Linq.Enumerable
の.Count
メソッド?
.Length
プロパティは .NET 通りなのでいいでしょう。
ここではPowerShell で奇妙に感じる .Count
プロパティ と Cmdlet であるMeasure-Object
を見てみます。
.Count
プロパティって気持ち悪いけど便利
PowerShell のオブジェクトは、そこかしこで .Count プロパティを使って個数を数えることができます。*1
そう、たとえ配列でなくてもです。
気になるのが文字列System.String
に対してですが、「インテリセンス上は Length しかでず」これは文字長を取得します。*2
しかし、インテリセンスになくとも .Count
が使える不思議。これはSystem.String
であれば、必ず 1になります。空文字""
であってもです。
まぁ、便利は便利です。ICollection インターフェイスの.Count
プロパティ同様に取得コストがほぼないので。
しかし、配列でもはいのに要素数が.Count
で取れるのは気持ち悪いことこの上ない。
.Count
プロパティの利用は罠がある
PowerShell 6.1 から PSCustomObject の Countプロパティが機能するようになっています。
以下の内容はPowerShell 6.0 以下のバージョンにおいて該当します。
大体のオブジェクトに対して.Count
で個数が取れるのですが、数少ない例外が[PSCustomObject]
です。
この型に対しては、.Count
プロパティでの個数取得できません。
となった時に、.Length
プロパティが使えるかというと同様にnull が返ってきます。
これを回避するには、いったん配列@()
で包んでしまいます。
Measure-Object を使って個数を数える
ここでようやく、Measure-Object
Cmdlet が出てきます。
プロパティを使って取得できるなら、可能な限りそっちを使うのがふつーなのですが、取れないなら数えるしかないと。
よくあるパターンで PSCustomObject
型を生成して個数を数えるとこうでしょうか?
Measure-Object の罠
正確には、パイプライン |
の罠です。
PowerShell のパイプラインは、基本的にはオブジェクトを1つずつ渡します。*3
ということは? パイプラインに 1000000 オブジェクト渡すと、1000000 オブジェクト渡しきるのを待たないといけないということです。これはひどい。
そのため、以下のように書いた日には18秒ぐらいぽけ~(())と待つことになります。やめましょう。
(1000000..1 | measure).Count
個数カウントの方法とベンチマーク
楽をしたいので、C# で良くやるように PowerShell にも持ち込みます。
PowerShell の長所でもあり短所でもあるのが、パイプラインです。コストがパイプラインにかかっているので、避ければいいのです。
ということで、System.Linq.Enumerable
を使えばいいでしょう。それぞれのベンチをとってみます。
方法 | 1回目 | 2 回目 | 3回目 | 平均(ms) |
---|---|---|---|---|
.Count | 261.2029 | 155.8287 | 145.4331 | 187.488 |
Measure-Object | 19268.5511 | 19558.6135 | 19934.1167 | 19587.093 |
Linq.Enumerable.Count() | 405.0513 | 357.8473 | 389.6365 | 384.18 |
当然の結果ですが、想定通りですね。
Min / Max の取得
この件がもっと影響を及ぼすのが、Min / Max です。
Measure-Object
はとても便利な Cmdlet で、Min/Max/Average/Count をプロパティを指定して計測できます。
しかし、パイプライン越しの利用が主体になるため、オブジェクト個数に応じて時間がかかります。(1-1000程度ならmsで完了するのでいいのですが)
そういう意味では、System.Linq.Enumerable
は、PowerShell からも使いやすいので是非使えばいいでしょう。
Min
方法 | 1回目 | 2 回目 | 3回目 | 平均(ms) |
---|---|---|---|---|
Measure-Object | 22628.2811 | 22705.8116 | 23010.3391 | 22781.477 |
Linq.Enumerable.Min() | 451.5155 | 431.6266 | 566.3141 | 483.152 |
Max
方法 | 1回目 | 2 回目 | 3回目 | 平均(ms) |
---|---|---|---|---|
Measure-Object | 23102.14 | 22624.6367 | 22535.7175 | 22754.164 |
Linq.Enumerable.Max() | 460.4299 | 548.7585 | 390.1181 | 466.435 |
Average
方法 | 1回目 | 2 回目 | 3回目 | 平均(ms) |
---|---|---|---|---|
Measure-Object | 20982.5092 | 21166.3688 | 20576.0699 | 20908.315 |
Linq.Enumerable.Agerage() | 382.4254 | 370.6433 | 360.0512 | 371.03 |
まとめ
.Count
プロパティが使える時は素直に使いましょう。
Measure-Object
は量が十分抑えられる時には便利ですね。
Linq.Enumerable
くぁわいい。