tech.guitarrapc.cóm

Technical updates

PowerShell で オブジェクトの数を計測するんです?

オブジェクトの数を数える時によく使われるものといえば?

Measure-Object でしょうか? System.Linq.Enumerable でしょうか?

今回はさらっとちょっとだけ見てみましょう。

目次

個数のカウント

よく使うのはどれでしょうか?

  • .Lengthプロパティ?
  • .Countプロパティ?
  • Measure-Object コマンドレット?
  • System.Linq.Enumerable.Countメソッド?

.Lengthプロパティは .NET 通りなのでいいでしょう。

ここではPowerShell で奇妙に感じる .Countプロパティ と Cmdlet であるMeasure-Object を見てみます。

.Countプロパティって気持ち悪いけど便利

PowerShell のオブジェクトは、そこかしこで .Count プロパティを使って個数を数えることができます。*1

f:id:guitarrapc_tech:20150117061617p:plain

そう、たとえ配列でなくてもです。

気になるのが文字列System.String に対してですが、「インテリセンス上は Length しかでず」これは文字長を取得します。*2

f:id:guitarrapc_tech:20150117062036p:plain

しかし、インテリセンスになくとも .Countが使える不思議。これはSystem.Stringであれば、必ず 1になります。空文字""であってもです。

まぁ、便利は便利です。ICollection インターフェイスの.Countプロパティ同様に取得コストがほぼないので。

しかし、配列でもはいのに要素数が.Count で取れるのは気持ち悪いことこの上ない。

.Countプロパティの利用は罠がある

PowerShell 6.1 から PSCustomObject の Countプロパティが機能するようになっています。

blog.shibata.tech

以下の内容はPowerShell 6.0 以下のバージョンにおいて該当します。

大体のオブジェクトに対して.Count で個数が取れるのですが、数少ない例外が[PSCustomObject] です。

この型に対しては、.Countプロパティでの個数取得できません。

f:id:guitarrapc_tech:20150117062601p:plain

となった時に、.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くぁわいい。

*1:実態は .Length プロパティです

*2:この場合は4が取得できます

*3:より正確にはひと塊ずつです。デフォルト1塊り、1オブジェクトなので一見1つずつになります。