面白い記事があります。
PowerShellにおいて配列生成は言語仕様上にある通りカンマ演算子,によって表現されるものであり、ASTでも満たされている。しかし、そこに言及がなく()や$()や@()で生成しているような表現を見かけるけど実は違うんだよ。ということが説明されています。とはいえ、@()で囲むことに意味はあるので注意なのですが。
さて、結果としてみるとどの表現でも配列が生成されます。が、ASTを見てもそれぞれ違うことから「表現可能な方法が複数ある場合にどれを使うのがいいのか」を考えてみましょう。
PowerShell の構文木 AST を見る
もしPowerShellのASTが見たい場合は、ShowPSAstでモジュールを入れておくといいでしょう。
# 今のユーザーにのみ導入する Install-Module ShowPSAst -Scope CurrentUser
配列の生成
今回は、ベンチマークでは単純に配列評価の時間を測定したいため、配列の生成は事前に文字列を起こしておきましょう。
以下のようにすると配列が生成できます。
https://gist.github.com/guitarrapc/1ba51e5ee9ba23dcc82bdaa3b5fe1c88
これで2種類の配列文字列が取得できたので準備okです。
1,2,3,4,5,....
と
1 ,2 ,3 ,4 ,5 ....
ベンチマークの測定対象
それではベンチマークを測ってみましょう。
測定対象として選んだのは記事にあった表現とその派生です。
- ArrayLiteralAst : カンマ演算子
,による配列の生成 = 最速の予定1,2,3
- ParenExpressionAst + ArrayLiteralAst :
()でカンマ演算子,による配列の生成のラップ(1,2,3)
- ArrayExpressionAst + ArrayLiteralAst :
@()でカンマ演算子,による配列の生成のラップ@(1,2,3)
- SubExpressionAst + ArrayLiteralAst :
$()でカンマ演算子,による配列の生成のラップ$(1,2,3)
- (ArrayExpression + ArrayLiteralAst) * PipelineOutput :
@()でカンマ演算子,による配列の生成のラップした結果をパイプラインでマップ@(1,2,3) | % {$_}
- 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