本連載 第63回で「PowerShell 7.0.0 Preview3」のリリースとインストール方法について説明したが、今回はその続きとして、注目の新機能をご紹介しよう。
PowerShell 7.0.0 Preview3 新機能
次期PowerShell Coreとなる「PowerShell 7」は現在プレビューの段階にあり、執筆段階では「PowerShell 7.0.0 Preview 3」が公開されている※。
基本的にはWindows PowerShellとの互換性向上とバグ修正が続けられているのだが、新機能の追加や実験的な機能の追加なども行われている。
新機能の中でプレビュー3で特に注目されるのは、ForEach-Objectコマンドレットに新しく「-Parallel」というパラメータが追加されたことだ。この機能は名前のとおり、ForEach-Objectの処理を並列処理に変更するための指定になっている。詳しい使い方は次のドキュメントにわかりやすくまとめられている。
PowerShell ForEach-Object Parallel Feature|PowerShell
PowerShell Coreは便利なシェルではあるのだが、UNIX系のシェルと比べると並列処理が弱く、処理速度が遅いという課題もある。今回ForEach-Objectコマンドレットに追加された-Parallelパラメータは万能の機能ではないものの、これまでPowerShell Coreが提供してきた並列処理機能と比べるとかなり書き方が楽になる。オーバーヘッドが多く、さらに使い方を間違えると逆効果になるが、うまく使えば処理速度の圧倒的な高速化が可能になる。今回はこの機能について取り上げようと思う。
ForEach-Object -Parallelの使い方サンプル
まず、次のサンプルを見てほしい。ForEach-Objectを使った簡単なサンプルで、送られてくる10個のデータに対してそれぞれ1秒づつ待つという処理を行っている。開始から終了までの時間を計測すれば、当然この場合には10秒の時間がかかることになる。
これに対して-Parallelを指定してみよう。次のように処理時間が2秒に縮まっていることを確認できる。
なぜ10秒から2秒に時間が短縮されたかというと、-Parallelを指定するとForEach-Objectコマンドレットがパイプラインで流れてくるデータを最大5個まで並列処理するようになるためだ。5個同時並列して、そのあとまた5個並列処理している。このため、総合でかかる時間は2秒になったわけだ。
ForEach-Objectコマンドレットには-ThrottleLimitというパラメータが用意されており、並列して実行するスレッドの最大数を指定できるようになっている。このデフォルト値が5だ。この値を変更すると並列処理数を増やすことができる。たとえば、先程の処理に対して次のように-ThrottleLimit 10を指定すると、処理時間が2秒から1秒まで短縮されることを確認できる。
今回の機能で特に注目しておきたいのは、-Parallelパラメータを指定することでブロック内のスクリプトを簡単に並列処理できる点にある。それぞれ別のスレッドとしてインスタンスが生成され実行されている。
ForEach-Object -Parallelの課題は
ForEach-Object -Parallelの登場でPowerShellスクリプト内部でスレッドの並列利用が簡単になる。シンプルに記述できるというのが最大の利点ではないかと思う。反面、この機能は期待しているほどのパフォーマンスが出ない点に注意が必要となる。
ForEach-Object -Parallelは実行環境を新しく生成し、そこでスクリプトを実行するという処理を行う。実行環境を生成する処理は比較的重たい処理だ。このため、なにも考えずに-Parallelを指定すると、逆に処理速度が遅くなる。さらに、-Parallelを指定すると処理は並列で実行されるため順番に処理が行われるということはない。上記サンプルではたまたま順番に値が出力されているが、必ずしもこうなるという保証はなく、バラバラに実行されるというのが実際の処理だ。
ではどういった場合にForEach-Object -Parallelが利用できるのかという話になるが、比較的長い処理、たとえば計算系の処理を行うとか待ち時間が長い処理を行うといった場合には、ForEach-Object -Parallelを効果的に使うことができる。環境を用意するための処理よりも十分に長い時間がかかる処理であれば、並列化した方が高速化が期待できるわけである。
UNIXシェルとPowerShellの並列化の違い
UNIXシェルではパイプラインでコマンドを接続すると、基本的にコマンドは個別のプロセスとして動作し、個別のコアで並列処理される。明示的にバックグラウンドプロセスとして起動して並列処理させる方法もあるが、パイプラインで挟む方が何も考えずに並列処理になるので便利だ。カーネルはパイプラインによるプロセス間通信に対してチューニングを実施していることが多く、処理は高速である。
一方、PowerShellのパイプラインはUNIXシェルのパイプラインのように見えるが、実質はまったく別物だ。パイプラインはオブジェクトを渡すための機能であり、すべての処理が終わってから次のコマンドにオブジェクトを渡すものだ。このため、処理は高速にはならない。PowerShellはオブジェクト指向の機能を備えた強力なプログラミング言語である反面、UNIXシェルが持っているシンプルで高速な並列処理性は失っているのである。
どちらがすごいということではなく、特性としてそうした違いがある。しかし、macOSやLinuxでも利用できるソフトウェアになったことで、こうした面についても改善を望む声が多くなるものとみられる。ForEach-Objectの-Parallelパラメータはこうしたユーザの声に応えるひとつの機能になっていくものとみられる。
foraechとForEach-Objectは異なる機能
ForEach-Objectに-Parallelパラメータが追加されたということは、foreachでも同じ機能が使えると考えるかもしれないが、これは別の機能だ。foreachにはまだ並列処理機能は実装されておらず、-Parallelを指定しても次のようにエラーになる。
ただし、エラーメッセージからわかるとおり、foreachに対して-Parallelパラメータは予約語として確保されている。どの段階になるかは不透明だが、こちらのもいずれは並列機能の追加が予定されているようだ。登場を楽しみだ。