以下の内容はhttps://tech.guitarrapc.com/entry/2017/12/09/061702より取得しました。


PowerShellの配列表現と生成処理時間

面白い記事があります。

http://blog.shibata.tech/entry/2017/12/09/000500

PowerShellにおいて配列生成は言語仕様上にある通りカンマ演算子,によって表現されるものであり、ASTでも満たされている。しかし、そこに言及がなく()$()@()で生成しているような表現を見かけるけど実は違うんだよ。ということが説明されています。とはいえ、@()で囲むことに意味はあるので注意なのですが。

http://tech.guitarrapc.com/entry/2015/09/05/012733

さて、結果としてみるとどの表現でも配列が生成されます。が、ASTを見てもそれぞれ違うことから「表現可能な方法が複数ある場合にどれを使うのがいいのか」を考えてみましょう。

PowerShell の構文木 AST を見る

もしPowerShellのASTが見たい場合は、ShowPSAstでモジュールを入れておくといいでしょう。

# 今のユーザーにのみ導入する
Install-Module ShowPSAst -Scope CurrentUser

https://github.com/lzybkr/ShowPSAst

配列の生成

今回は、ベンチマークでは単純に配列評価の時間を測定したいため、配列の生成は事前に文字列を起こしておきましょう。

以下のようにすると配列が生成できます。

https://gist.github.com/guitarrapc/1ba51e5ee9ba23dcc82bdaa3b5fe1c88

これで2種類の配列文字列が取得できたので準備okです。

1,2,3,4,5,....

1
,2
,3
,4
,5
....

ベンチマークの測定対象

それではベンチマークを測ってみましょう。

測定対象として選んだのは記事にあった表現とその派生です。

  1. ArrayLiteralAst : カンマ演算子,による配列の生成 = 最速の予定
    • 1,2,3
  2. ParenExpressionAst + ArrayLiteralAst : ()でカンマ演算子,による配列の生成のラップ
    • (1,2,3)
  3. ArrayExpressionAst + ArrayLiteralAst : @()でカンマ演算子,による配列の生成のラップ
    • @(1,2,3)
  4. SubExpressionAst + ArrayLiteralAst : $()でカンマ演算子,による配列の生成のラップ
    • $(1,2,3)
  5. (ArrayExpression + ArrayLiteralAst) * PipelineOutput : @()でカンマ演算子,による配列の生成のラップした結果をパイプラインでマップ
    • @(1,2,3) | % {$_}
  6. Constraints + ArrayLiteralAst : @()で生成した中身を前置カンマにしました
@(
1
,2
,3)

ベンチマーク結果

1000回実行したっけの平均/最大/最小を見ます。単位はmsです。

PowerShellでのベンチマークは、今回簡易にMeasure-Commandを用いました。

Code Target Count Average Maximum Minimum
1,2,3 ArrayLiteralAst 1000 6.72703 76.897 1.109
(1,2,3) ParenExpressionAst + ArrayLiteralAst 1000 6.70472 77.452 1.0702
@(1,2,3) ArrayExpressionAst + ArrayLiteralAst 1000 7.020254 185.7868 1.0828
$(1,2,3) SubExpressionAst + ArrayLiteralAst 1000 7.59060 85.0647 1.4674
前述参照 (ArrayExpression + ArrayLiteralAst) * PipelineOutput 1000 75.666 234.0299 52.0301
前述参照 Constraints + ArrayLiteralAst 1000 8.67313 195.6095 6.1331

いかがでしょうか? 予想通りですか?

ArrayLiteralAst

さすがにAverage / Maximum / Minimumのいずれにおいても安定して最速です。

ArrayLiteralAstだけの場合、次のAST評価となっています。

# AST  : {1,2,3} | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [ArrayLiteralAst] > ConstantExpressionAst(s)

ParenExpressionAst + ArrayLiteralAst

こちらもArrayLiteralAstのみと比較して、ParenExpressionAst + ArrayLiteralAstでは、()で括った分一段要素が増えます。一方で実行速度にはほとんど差がなく、()は評価の軽い要素であるのが明確です。

# AST  : {(1,2,3)} | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [ParenExpressionAst] > PipelineAst > CommandExpressionAst > [ArrayLiteralAst] > ConstantExpressionAst(s)

ArrayExpressionAst + ArrayLiteralAst

Maximum測定誤差がでたと考えられます。次のAST評価となっています。 ArrayExpressionAst + StatementBlock + CommandExpressionAstが増えていることからもそこそこ評価が増えてきました。が誤差レベルですね。

# AST  : {@(1,2,3)} | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [ArrayExpressionAst] > StatementBlockAst > PipelineAst > CommandExpressionAst > ArrayLiteralAst > ConstantExpressionAst(s)

SubExpressionAst + ArrayLiteralAst

こちらは、Minimumが少し大きいですが同様に誤差でしょう。

部分式は多用するのですが、AST評価を見ても[SubExpressionAst]> StatementBlockAst > PipelineAst > CommandExpressionAstとなっており、ArrayExpressionAst とだいたい同様ですね。こちらも気にしなくてよさそうです。

# AST  : {$(1,2,3)} | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [SubExpressionAst] > StatementBlockAst > PipelineAst > CommandExpressionAst > ArrayLiteralAst > ConstantExpressionAst(s)

(ArrayExpression + ArrayLiteralAst) * PipelineOutput

原因は明らかでパイプラインです。ASTを見ても明らかに要素数が多くなっています。パイプラインほんと重いんですよね。配列を生成するためにこの利用は避けましょう。

# AST  : {@(1,2,3) | % {$_}} | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [ArrayExpressionAst] > StatementBlockAst > PipelineAst > CommandExpressionAst > ArrayLiteralAst > ConstantExpressionAst(s)
#                                                             | > CommandAst
#                                                                         | > StringConstantExpressionAst
#                                                                         | > ScriptBlockExpressionAst > ScriptBlockAst > NamedBlockAst > PipelineAst > CommandExpressionAst > VariableExpressionAst

Constraints + ArrayLiteralAst

最初の要素1のみ速やかにConstantExpressionAstとして評価されています。しかし後続は前置のカンマによってシングル要素の配列とAST評価されてしまいArrayLiteralAstとついています。AST評価を見てみると明らかですね。

# AST  : {@(
# 1
# ,2
# ,3)
# } | Show-Ast
# Eval : ScriptBlockAst > NameBlockAst > PipelineAst > CommandExpressionAst > [ArrayExpressionAst] > StatementBlockAst > PipelineAst > CommandExpressionAst > [ConstantExpressionAst]
#                                                                                                                    | > PipelineAst(s) > CommandExpressionAst > [ArrayLiteralAst] > ConstantExpressionAst
#                                                                                                                    | > PipelineAst(s) > CommandExpressionAst > [ArrayLiteralAst] > ConstantExpressionAst

まとめ

特に制約がない時に書くなら、すなおに,でくくるのみにするか()で括るのが良さそうです。

$a = 1,2,3
$b = (1,2,3)

String Interporationのような文字列埋め込みに使う表現も悪くはなさそうです。

$c = "$(1,2,3)" // "1 2 3" となる

良く紹介される形も明らかな齟齬はなさそうです。

$d = @(1,2,3)

ただしパイプライン、お前はだめだ。

$e = @(1,2,3) | % {$_}

ベンチマークコード全体

コードを置いておきます。参考になれば幸いです。

https://gist.github.com/guitarrapc/8a89dc9438673871a71649ab8315e0e8




以上の内容はhttps://tech.guitarrapc.com/entry/2017/12/09/061702より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14