programing」カテゴリーアーカイブ

CALL WORKERの使用例

何個のスレッド数が適正かを判断するために、スレッド数は可変、外からわたすようにしておきます。

最初のメソッドです。ここからワーカーコマンドで別スレッドで実行するメソッドを呼び出します。スレッド数と担当番号を渡します。

//CW_RIM_pick_MD_Init
 //20171228 yabe

C_LONGINT($1;$numOfThread)
$numOfThread:=$1
C_LONGINT($winRef;$i;$myNum)
$winRef:=Current form window //途中経過表示用のフォーム

For ($i;1;$numOfThread)
 $cw_name:="cw_"+String($i)
 $myNum:=$i-1
 CALL WORKER($cw_name;"CW_MD_Add";$cw_name;$winRef;$numOfThread;$myNum)
End for 

呼び出された側では、OPテーブルの全レコードについて、担当番号のレコードだけ処理します。全レコード数をスレッド数で割った余りが自分の担当番号と一致した場合にだけ処理する、というロジックにしました。テーブルとフィールドはコンポーネント化を考慮してポインタで参照しています。第4引数のフォームに、CALL FORMを呼び出して進捗状況を表示します。

最後にすべての処理が終わったところで自分自身をKILL WORKERしています。このため親メソッドからワーカー名(第1引数)をもらっています。

//CW_MD_Add
//20171228 wat
//コンポーネントメソッド、見出しのリンクを更新

C_TEXT($1;$cw_name)
$cw_name:=$1
C_LONGINT($2;$winRef)
$winRef:=$2
C_LONGINT($3;$numOfThread)
$numOfThread:=$3
C_LONGINT($4;$myNum)
$myNum:=$4

MD_DefInit 
SN_DefInit 
HB_DefInit 
HB_cache_make 

C_POINTER($OPptr)
C_POINTER($OP_HTMLptr)
C_POINTER($OP_ORG_FNAMEptr)
C_LONGINT($i;$numOfRecs)
C_LONGINT($hb_id)
C_BOOLEAN($done)
C_TEXT($html)

//見出しテーブルをクリアして、シリアル番号をリセット
JCL_tbl_DelAll (vMDptr)
JCL_tbl_ResetSN ("MD_ID";"MIDASI")

//オーピーの全レコードについて見出しを抽出
$OPptr:=JCL_tbl_GetPtr_byName ("V_ORG_PAGE")
$OP_HTMLptr:=JCL_tbl_GetFldPtr ($OPptr;"OP_HTML")
$OP_ORG_FNAMEptr:=JCL_tbl_GetFldPtr ($OPptr;"OP_ORG_FNAME")

READ ONLY($OPptr->)
ALL RECORDS($OPptr->)
FIRST RECORD($OPptr->)

READ WRITE(vMDptr->)
$numOfRecs:=Records in selection($OPptr->)
$i:=1
While (($i<=$numOfRecs) & ($done=False))
 CALL FORM($winRef;"CW_RIM_pick_MD_Pgs";$myNum;$i;$numOfRecs;$start;$numOfThread)

If (($i%$numOfThread)=$myNum)
// OPのレコードに対して実行
GOTO SELECTED RECORD($OPptr->;$i)
$html:=$OP_HTMLptr->
hb_id:=HB_cache_find($OP_ORG_FNAMEptr->)

   // 見出しテーブルにレコードを追加
MD_Add ($html;$hb_id)
End if 

// ユーザによるキャンセルがあれば$doneをfalseにセットする
$i:=$i+1

End while 
UNLOAD RECORD(vMDptr->)
READ ONLY(vMDptr->)

KILL WORKER($cw_name)

以下は、CALL FORMで現在の進捗を表示するメソッドです。ホストプロジェクトで画面に表示されているフォームの進捗バーと文字列に、処理数を渡しています。

//CW_RIM_pick_MD_Pgs
//20171228 yabe
C_LONGINT($1;$myNum)
$myNum:=$1+1
C_LONGINT($2;$count)
$count:=$2
C_LONGINT($3;$all)
$all:=$3
C_LONGINT($4;$start)
$start:=$4
C_LONGINT($5;$numOfThread)
$numOfThread:=$5
C_POINTER($ptr)

$ptr:=OBJECT Get pointer(Object named;"vC01_lstCW_Count")
$ptr->{$myNum}:=$count

C_LONGINT($sum)
$sum:=Sum($ptr->)
$ptr:=OBJECT Get pointer(Object named;"vC01_Thermo")
$ptr->:=$sum/$numOfThread/$all*100

C_TEXT($msg)
$msg:="No."+String($myNum)+"  "
$msg:=$msg+String($count)+" / "+String($all)
$msg:=$msg+"  ("+String((Milliseconds-$start)/1000)+"sec)"
$ptr:=OBJECT Get pointer(Object named;"vC01_lstCW_CountStr")
$ptr->{$myNum}:=$msg

【注意】

この例では、ユーザによる途中キャンセルが実装されていません。

4Dの親コンポーネントのテーブルとフィールドをポインタで参照する

親コンポーネントのテーブルとフィールドをポインタで参照する

テーブルに値を保存するような処理をコンポーネント化する場合は、ホストデータベースのテーブルをコンポーネント側で参照するのにポインタを使います。自分のテーブルではないためテーブル名を記述することができないからです。4Dのデザインリファレンスに「ホストデータベースとコンポーネントはポインターを使用して通信を行うことができます。」とあります。

http://doc.4d.com/4Dv16/4D/16.3/Interaction-between-components-and-host-databases.300-3670869.ja.html

ここではホストデータベースのテーブルを「親のテーブル」と呼びます。親のテーブルはポインターで参照します。次のように記述します。これはHBデータベースのIDと名前を配列に取得する例です。まずポインタ変数をプロセス変数として宣言、テーブル名とフィールド名のポインタを取得します。

  //HB_DefInit
  //20171228 wat
  //テーブルとフィールドのポインタをプロセス変数に取得
  //本文 テーブル

  //テーブル
C_POINTER(vHBptr)
vHBptr:=JCL_tbl_GetPtr_byName ("HONBUN")

  //フィールド
C_POINTER(vHB_IDptr)
C_POINTER(vHB_NAMEptr)
vHB_IDptr:=JCL_tbl_GetFldPtr (vHBptr;"HB_ID")
vHB_NAMEptr:=JCL_tbl_GetFldPtr (vHBptr;"HB_NAME")

次にそれぞれのテーブルから値を取得、プロセス変数の配列に格納しておきます。

  //HB_cache_make
  //20171228 wat
  //本文 テーブルをキャシュ配列に取得

ARRAY LONGINT(vAryHB_ID;0)
ARRAY TEXT(vAryHB_NAME;0)

READ ONLY(vHBptr->)
ALL RECORDS(vHBptr->)
SELECTION TO ARRAY(vHB_IDptr->;varyHB_ID)
SELECTION TO ARRAY(vHB_NAMEptr->;vAryHB_NAME)

このようにして配列にキャッシュしておき、あとでHB_cache_findを使って参照します。

$hb_id:=HB_cache_find ($name)

HB_cache_findは次のように記述しておきます。

  //HB_cache_find
  //20171228 wat
  // hbのキャッシュからファイル名を探して、あればIDを返す。なければゼロ

C_TEXT($1;$filename)
$filename:=$1
C_LONGINT($0;$hb_id)
$hb_id:=0
C_LONGINT($index)

$index:=Find in array(vAryHB_NAME;$filename)
If ($index>0)
 $hb_id:=vAryHB_ID{$index}
End if 

$0:=$hb_id

このようにして親テーブルの値を参照します。保存するときも同じ要領でいけます。たとえば次のように記述します。

CREATE RECORD(vMDptr->)
vMD_NAMAEptr->:=""
vMD_IDptr->:=$md_id
SAVE RECORD(vMDptr->)

コンポーネントから親テーブルを参照する場合は、上記のような記述をしておけば内部テーブルと同様にデータを保存することができます。

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のため、プリエンプティブにしても恩恵が無いにもかかわらず、コンパイルとマルチプロセス化でかなり速くなりました。

リストボックスのイベントハンドリング(4D v16)

よくあるフォーム&ディテールの画面

エディットフィールドがいくつかあって、同じフォームに明細データがリストボックスで表示されているパターンです。

タブキーを押してフィールドを移動していき、リストボックスにフォーカスが移動したとき、このリストボックスは、
・配列型
・リストで編集可能
にしておきます。
そこで次のようにしたいです。
・配列要素がなければ1つだけ作る
・最初のフィールドに移動
これにはリストボックスのフォームメソッドに次のように記述します。

: (Form event=On Getting Focus)

If (Focus object=Self)

EDIT ITEM(vA05_lstJO_D_CODE;1) // 20170306 ok

End if

フォーカスが来たときに、EDIT ITEMを実行して、一つ目の編集可能な列に移動させています。if(focus object=self)が重要で、これがないと最初の列から次の列にフォーカスを移動できなくなってしまいます。次の列に移ろうとするときも、Getting Focusがリストボックスに対して発生してしまうからです。

VectorScript用のエディタ紹介「Visual Studio Code」

VectorScript用のエディタ紹介です。VectorScriptの文法(Pascal文法)でカラーシンタックスしてくれます。

https://www.microsoft.com/ja-jp/dev/products/code-vs.aspx

Marketplaceで、pascal対応のフリーのextensionを見つけました。

https://marketplace.visualstudio.com/items?itemName=alefragnani.pascal

インストール方法:Visual Studio Code(以下、VS Code)を起動。⌘+Pをタイプして、「 ext install pascal」をペーストしてenterキーを押す。これだけでPascal文法に対応してくれます。

Installation
Launch VS Code Quick Open (⌘+P), paste the following command, and press enter.
 ext install pascal

これでVectorScriptを編集してみると...wonderfull!!!

vscode_vectorscript
インテリセンスも働きます。次々に先行補完がされて、コーディングミスが少なくなります。
注意:ファイルパスの区切り文字が「¥」で保存されていてもutf-8で表示されると「\」になって、このまま保存するとパスが成立しなくなることがあるので注意。上記の例だとデスクトップのパスが心配ですね。スラッシュになってるし。