4Dのマルチスレッドを使って処理時間を短縮する

テーブルに1万件を超えるHTMLファイルがあって、それらの中から所定のタグを抽出して別のテーブルに保存する、ような課題がありました。インタープリタモードでXeonプロセッサでも2時間以上かかっていました。同時にマルチタスクで実行すれば、使っているXeonが4コアなので1/4の処理時間、つまり30分で終わるようになるはずです。
4D Serverのモニタ画面で見ていると、CPUは25%くらいしか使われていません。4Dプロセスはインタープリターモードでは1つのコアしか使わないのです。コンパイルしてプリエンプティブ(Preemptive)モードで実行すると、「プロセス管理はシステムへと委任され、マルチコアのマシンではシステムはプロセスをそれぞれのCPUへと個別に割り当てる」とされています。
http://doc.4d.com/4Dv16/4D/16.3/Preemptive-4D-processes.300-3651705.ja.html#2821655
果たしてCPUは100%使われるのでしょうか。やってみましょう。
プリエンプティブマルチスレッドで実行するためには3つの敷居があります。マルチプロセス、プリエンプティブ、コンパイルの3つです。マルチプロセスで動くように記述されたメソッドを、スレッドセーフ(プリエンプティブモード)で、コンパイルして実行する必要があります。3つの敷居を乗り越えるように元のソースを修正するには少々手間がかかります。コンパイルするだけで早くなるかもと、次の順に試していきました。

■ コンパイル

1.【デザイン】→【コンパイル開始】
2.【実行】→【コンパイル済み再起動】
これだけです。20%ほど速くなります。ただメニューを実行するだけですので簡単です。しかしメソッドやフォームを編集しているときは【インタープリタで再起動】しなくてはなりません。特に4D Serverで複数の開発者が編集中の場合は恩恵をうけることができません。そこで問題の処理をコンポーネント化することにしました。遅い処理をコンポーネントに記述してコンポーネントだけをコンパイルしておき、ホストプロジェクトは編集可能なインタープリターで運用する、という作戦です。
コンポーネント化に伴う親テーブルをポインタ参照

■ マルチプロセス(マルチスレッド)

手始めにプリエンプティブではなく、コオペラティブ(Cooperative)プロセスで、マルチスレッドを実行してみました。検討した4Dコマンドは、New ProcessとCall Workerです。New processは以前からあって使ったことがあります。Call Workerはv15から使えるようになったコマンドです。今回はCall Workerを試してみます。 Call Workerの使用例

プリエンプティブでなくてもマルチプロセス化するだけで20%ほど速くなります。おそらくデータベースをクエリしたり保存したりするときに、ハードディスクのファイルI/OなどでCPUに待ち時間が発生していると考えられます。

■ プリエンプティブマルチスレッド

スレッドセーフという敷居があります。プリエンプティブマルチスレッドで実行するためには対象部分のメソッドがスレッドセーフでコンパイルされている必要があります。ここでは明示的にメソッドプロパティで「プリエンプティブプロセスで実行可能」をチェックします。呼び出しているメソッドすべてにこの設定をします。この設定をしておくと、メソッド内部でスレッドアンセーフなコマンドを呼び出しているとコンパイルエラーになって便利です。4Dコマンドにもスレッドアンセーフなコマンドがあります。【コンパイル開始】するとスレッドセーフなメソッドができます。
http://doc.4d.com/4Dv16/4D/16.3/Preemptive-4D-processes.300-3651705.ja.html#2821655

■ 実行結果
MacBookPro(2016)、macOS Sierraで試しました。Core i7の2コアで、4スレッド使えるようです。アクティビティモニタでCPU使用率を確認すると、コオペラティブモードでは97%くらいでしたが、これが390%とか400%近い数値になります。これでプリエンプティブになっていることがわかります。速度は4倍より遅いと感じましたが、元のよりも十分に速いです。
WindowsはOSがWindow 7で32 bitのため、プリエンプティブにしても恩恵が無いにもかかわらず、コンパイルとマルチプロセス化でかなり速くなりました。