$ Incrementを$ Sequenceに既に変更しましたか?

これがTwitterの投稿であれば、「 CachéObjectScriptプログラマー! IDを生成するには、$ Incrementではなく$ Sequenceを使用します。 しかし、ここで、Habr、それは思考を広げる必要がある-猫の下で歓迎。







変更するものが何もない読者向けの小さな余談で、初めて$ Incrementという単語が表示されます。 $ Incrementは、 CachéObjectScriptの組み込み関数-アトミック操作です。 $ Increment関数の引数は、式ではなく変数のみです。 $ Incrementは 、変数を暗黙的にブロックし、その値を1増やし、変数のロックを解除し、新しい値を返します。 $ Incrementは 、タイプカウンターの数値識別子を新しいオブジェクトまたはレコードに割り当てる必要がある場合に広く使用されます。そのような場合、関数の引数はグローバルの名前です。 次のようになります。



for i = 1:1:10000 {

set id = $ Increment (^ Person)

set surname = ## class %PopulateUtils )。 LastName () ; ランダムな姓

セット = ##クラス %PopulateUtils )。 FirstName () ; ランダムな名前

セット ^ Person( id )= $ ListBuild surname name

}



$シーケンスとは何ですか? この関数は、バージョン2015.1で登場しました。 $ Incrementと同様に、アトミック操作を実行し、引数の値を1増やして返します。 $ Incrementとは異なり、 $ Sequenceへの引数はグローバル(ローカル変数ではない)のみです。 プロセスが特定のグローバルから$ Sequenceに初めてアクセスするとき、 $ Sequenceは返された値のセットをキャッシュし、その後の呼び出しでキャッシュから値を返します。 グローバル値は、キャッシュされた値の数だけ増加します。 キャッシュの値が終了すると、 $ Sequenceは新しいセットをキャッシュし、再びグローバルの値を増やします。 $シーケンスは、キャッシュする値の数を自動的に決定します。 プロセスが$ Sequenceに頻繁にアクセスするほど、より多くの値がキャッシュされます。



USER>set $Sequence(^myseq)="" USER>for i=1:1:15 {write "increment:",$Seq(^myseq)," allocated:",^myseq,! } increment:1 allocated:1 increment:2 allocated:2 increment:3 allocated:4 increment:4 allocated:4 increment:5 allocated:8 increment:6 allocated:8 increment:7 allocated:8 increment:8 allocated:8 increment:9 allocated:16 increment:10 allocated:16 increment:11 allocated:16 increment:12 allocated:16 increment:13 allocated:16 increment:14 allocated:16 increment:15 allocated:16
      
      





$ Sequence(^ myseq)が9を返したとき、次の8つの値(最大16)が現在のプロセスに対して既にキャッシュされていることがわかります。 $ Sequence(^ myseq)にアクセスする並列プロセスは、値17を取得します。



$ Sequenceは 、同じグローバル値を同時に増加させるプロセスで使用するためのものです。 $ Sequenceは値をバッチでキャッシュするため、プロセスがそれに割り当てられたすべての値を使用しなかった場合、識別子の順序にギャップが生じる可能性があります。 実際、 $ Sequence関数の主な目的は、一意のカウンター値を生成することです。 この意味でのインクリメントは、もう少し一般的な機能です。



$ Increment$ Sequenceを比較するには、小さな例を実行します。



クラスHabr.IncSeq.Test

{

ClassMethod 充填()

{

ロック + ^ P: "S"

ジョブを 設定 = $ job

for i = 1:1:200000 {

set id = $ Increment (^ Person)

set surname = ## class %PopulateUtils )。 ()

セット = ##クラス %PopulateUtils )。 FirstName ()

セット ^ Person( id )= $ ListBuild job surname name

}

ロック -^ P: "S"

}

ClassMethod run()

{

殺す ^人

設定 z1 = $ zhorolog

for i = 1:1:10 {

仕事 .. 充填 ()

}

ロック ^ P

セット z2 = $ zhorolog - z1

ロック

「done:」 z2 、!

}

}



runメソッドは10個のプロセスを開始し、それぞれが200,000エントリを^ Personグローバルに挿入します。 グローバル^ Pのロック 、親プロセスが子プロセスの動作を完了するのを待つためにのみ必要です。 したがって、彼はグローバル^ Pで排他ロックを取得しようとしますが、すべての子プロセスが作業を完了して共有ロックを削除した場合にのみ排他ロックを取得します。 その直後に、タイムカットオフ( $ zhorolog )を再度読み取り 、取得した^ Pのロックを削除して、レコードの挿入にかかった時間を確認します。 4コアのラップトップでは、runメソッドの実行に21秒かかりました(ボアの場合、これは同じメソッドの5回目の起動でした)



 USER>do ##class(Habr.IncSeq.Test).run() done:21.40948
      
      





この21秒が何をしたかを知るのは興味深いです。 ^%SYS.MONLBL起動すると (ちなみに、ハブに関する記事がありました)、次の図が表示されます。



  ; ** Source for Method 'filling' ** 1 10 .000433 lock +^P:"S" 2 10 .000013 set job = $job 3 10 .000038 for i=1:1:200000 { 4 1999991 13.222959 set id = $Increment(^Person) 5 1997246 7.029486 set surname = ##class(%PopulateUtils).LastName() 6 1995420 4.766967 set name = ##class(%PopulateUtils).FirstName() 7 1999680 208.226093 set ^Person(id) = $ListBuild(job, surname, name) 8 1999790 1.69106 } 9 10 .000205 lock -^P:"S" ; ** End of source for Method 'filling' ** ; ; ** Source for Method 'run' ** 1 1 .01005 kill ^Person 2 1 .000003 set z1 = $zhorolog 3 1 .000004 for i=1:1:10 { 4 10 .056381 job ..filling() 5 0 0 } 6 1 26.244814 lock ^P 7 1 .000003 set z2 = $zhorolog - z1 8 1 .000006 lock 9 1 .000009 write "done:",z2,! ; ** End of source for Method 'run' **
      
      





レポートの最初の列^%SYS.MONLBLはメソッドの行番号、2番目はこの行の実行数、3番目はこの行が実行された秒数です。



IDの取得に合計13.2秒かかりました。 13.2をプロセス数で割ると、それぞれが新しいIdを取得するのに1.32秒、名前と姓を計算するのに1.1秒、グローバルにデータを書き込むのに20.8秒かかったことがわかります。 プロファイラーにより、合計時間(26.24)は5秒長くなりました。



テスト(つまり、 filling()メソッド)で$ Increment(^ Person)$ Sequence(^ Person)に置き換えて、テストを再度実行しましょう。



 USER>do ##class(Habr.IncSeq.Test).run() done:3.324123
      
      





結果は素晴らしいです。 IDを取得する時間を$ Sequenceで短縮できますが、20.8秒でデータを書き込むのはどこですか? ^%SYS.MONLBLの結果を参照してください。



  ; ** Source for Method 'filling' ** 1 10 .000523 lock +^P:"S" 2 10 .000017 set job = $job 3 10 .000048 for i=1:1:200000 { 4 1911382 1.69533 set id = $Sequence(^Person) 5 1753050 3.783609 set surname = ##class(%PopulateUtils).LastName() 6 1830006 3.407867 set name = ##class(%PopulateUtils).FirstName() 7 1827874 21.544164 set ^Person(id) = $ListBuild(job, surname, name) 8 1879819 .843424 } 9 10 .00023 lock -^P:"S" ; ** End of source for Method 'filling' ** ; ; ** Source for Method 'run' ** 1 1 .010926 kill ^Person 2 1 .000004 set z1 = $zhorolog 3 1 .000004 for i=1:1:10 { 4 10 .049543 job ..filling() 5 0 0 } 6 1 5.090719 lock ^P 7 1 .000003 set z2 = $zhorolog - z1 8 1 .000007 lock 9 1 .00001 write "done:",z2,! ; ** End of source for Method 'run' **
      
      





各プロセスは、1.32の代わりにIDを取得するために0.17秒かかります。 しかし、なぜデータベースに書き込むのに2.15秒かかるのでしょうか? これはどのようにできますか? 実際には、グローバルは(通常)各8キロバイトのブロックに格納されます。 グローバルを変更する前の各プロセス( set ^ Person(id)= ...など )は、ブロックの内部ロックを取得します。 複数のプロセスが同じブロックを変更しようとすると、1つのプロセスが他のプロセスがブロックを解放するまで待機します。 そのようなプロセスが10個ある場合、9個が1つを待っています。 $ incrementで作成されたグローバル^ Personを見ると、1つのプロセスで2つの隣接するレコードが作成されることはほとんどないことがわかります。



 1: ^Person(100000) = $lb("12950","Kelvin","Lydia") 2: ^Person(100001) = $lb("12943","Umansky","Agnes") 3: ^Person(100002) = $lb("12945","Frost","Natasha") 4: ^Person(100003) = $lb("12942","Loveluck","Terry") 5: ^Person(100004) = $lb("12951","Russell","Debra") 6: ^Person(100005) = $lb("12947","Wells","Chad") 7: ^Person(100006) = $lb("12946","Geoffrion","Susan") 8: ^Person(100007) = $lb("12945","Lennon","Roberta") 9: ^Person(100008) = $lb("12944","Beatty","Mark") 10: ^Person(100009) = $lb("12946","Kovalev","Nataliya") 11: ^Person(100010) = $lb("12947","Klingman","Olga") 12: ^Person(100011) = $lb("12942","Schultz","Alice") 13: ^Person(100012) = $lb("12949","Young","Filomena") 14: ^Person(100013) = $lb("12947","Klausner","James") 15: ^Person(100014) = $lb("12945","Ximines","Christine") 16: ^Person(100015) = $lb("12948","Quine","Mary") 17: ^Person(100016) = $lb("12948","Rogers","Sally") 18: ^Person(100017) = $lb("12950","Ueckert","Thelma") 19: ^Person(100018) = $lb("12944","Xander","Kim") 20: ^Person(100019) = $lb("12948","Ubertini","Juanita")
      
      





並列プロセスは同じブロックにブレークスルーしようとし、実際にデータを変更するよりも、ブロックに書き込む順番を長く待ちました。 $ Sequenceの場合、Idは大きなピースで発行され、異なるプロセスを異なるブロックに分散します。



 1: ^Person(100000) = $lb("12963","Yezek","Amanda") // 351     12963 353: ^Person(100352) = $lb("12963","Young","Lola") 354: ^Person(100353) = $lb("12967","Roentgen","Barb")
      
      





「これはすべて素晴らしい」と読者は言いますが、オブジェクトとSQLのアクセスでは、Cachéは$ Incrementを使用して新しいIDを生成します。 $ Sequenceの使用方法 バージョン2015.1以降、IDFunctionストレージパラメーターは、IDを生成する関数を定義します。 デフォルトでは、「increment」と同じですが、「sequence」に変更できます(Studioインスペクターで、[ストレージ]> [デフォルト]> [IDFunction]を選択します)



結論として:

ここに書かれていることを信じないでください。 コンピュータの特性と、このテストを実行したCachéインスタンスの設定を具体的に書き留めていません。自分で実行する方が良いです。



ボーナス



別のテストとして、ラップトップのデータベースサーバーとこのラップトップ内の仮想マシンのアプリケーションサーバーを使用して、小さなECP構成を作成しました。 ^ Personグローバルのリモート(リモート、削除されていない)データベースへのマッピングを設定しました。 このテストの代表性に疑問の余地はありません。 ECPを使用した$ Increment慎重に使用する必要があります。 ただし、結果は次のとおりです。



$インクリメント



 USER>do ##class(Habr.IncSeq.Test).run() done:163.781288
      
      





^%SYS.MONLBL:



  ; ** Source for Method 'filling' ** 1 10 .000503 lock +^P:"S" 2 10 .000016 set job = $job 3 10 .000044 for i=1:1:200000 { 4 1843745 1546.57015 set id = $Increment(^Person) 5 1880231 6.818051 set surname = ##class(%PopulateUtils).LastName() 6 1944594 3.520858 set name = ##class(%PopulateUtils).FirstName() 7 1816896 16.576452 set ^Person(id) = $ListBuild(job, surname, name) 8 1933736 .895912 } 9 10 .000279 lock -^P:"S" ; ** End of source for Method 'filling' **
      
      





$シーケンス



 USER>do ##class(Habr.IncSeq.Test).run() done:13.826716
      
      





^%SYS.MONLBL:



  ; ** Source for Method 'filling' ** 1 10 .000434 lock +^P:"S" 2 10 .000014 set job = $job 3 10 .000033 for i=1:1:200000 { 4 1838247 98.491738 set id = $Sequence(^Person) 5 1712000 3.979588 set surname = ##class(%PopulateUtils).LastName() 6 1809643 3.522974 set name = ##class(%PopulateUtils).FirstName() 7 1787612 16.157567 set ^Person(id) = $ListBuild(job, surname, name) 8 1862728 .825769 } 9 10 .000255 lock -^P:"S" ; ** End of source for Method 'filling' **
      
      





$ Sequence関数にはいくつかの制限があります-使用する前にドキュメントを確認してください。



ご清聴ありがとうございました!



All Articles