はじめに
リストの中身を順に取得する方法として、ついついこんなロジックを記載しがちです。
f pos=1:1:$ll(list) s val = $li(list,pos)
ただこの方法は、ドキュメントを読むと悪手とされています。
リストを順に取得するのであれば、$listnext()を使用する方がより高速に取得できると記載しています。
今回は、$listNext()の処理速度を検証してみたいと思います。
$listNext()の使い方
set list = $listBuild("日本", "平安時代", "藤原道長", "紫式部")
set (point,pos) = 0
while ($listNext(list, point, val)){
s pos = pos + 1
w !,val
}
変数「point」は、リスト内の次の要素に対するポインタになるので、リストの位置が格納される訳ではありません。
また、whileのループが完了しリストの最後まで到達したら、変数「point」は0に戻ります。
次のリストを回す時、再度0クリアを行う必要はありません。
変数「point」がリストの位置では無い為、リストの位置を把握する材料がありません。
リストの位置を知りたいのであれば変数「pos」の様に、内部でカウントを取る必要があります。
リストの位置を知る必要が無ければ、変数「pos」は不要です。
速度検証実施
ClassMethod checkListSpeed(listCnt As %Integer, cnt As %Integer = 10000)
{
f pos=1:1:listCnt s $li(list, * + 1)=pos // リストを作成
s start = $zh
, len = $ll(list)
f roop=1:1:cnt f pos=1:1:len s val = $li(list, pos)
w !,"list:"_($zh-start)
s start = $zh
, point = 0
f roop=1:1:cnt while($listNext(list, point, val)){}
w !,"next:"_($zh-start)
}
リストの中身は、引数「listCnt」分作成します。
中身は全て1~listCntの数値になっています。
forと$listの組み合わせでの取得方法と、whileと$listNextの組み合わせでの取得方法で速度差を計測します。
ただ、両処理の処理時間が早いと想定して、さらにループする形で引数「cnt」を与えています。
リスト10個、ループ10,000回で実行
d ##class(developer.Sample).checkListSpeed(10,10000)
リストの個数が10個程度だと、あまり速度差が出ていませんが、謳われている通り$listNextの方が若干早い事が分かります。
ただ、1万回の処理で0.024秒差なので、両者の差はほぼないと言えます。
リスト50個、ループ10,000回で実行
d ##class(developer.Sample).checkListSpeed(50,10000)
リストの内容が増えれば増える程、両者に速度差が出てきているようです。
ここまで来ると、すこし見過ごせないレベルになってきます。
この先、リストの数が増える毎に、両者の差が開いていくと考えられます。
数値では味気ないので、文字列を入れてみる
プログラムを少し修正して、リストの中身を文字列にしてみます。
「%PopulateUtils」を使用して、人名をリストの中身に格納します。
ClassMethod checkListSpeed(listCnt As %Integer, cnt As %Integer = 10000)
{
f pos=1:1:listCnt s $li(list, * + 1)=##class(%PopulateUtils).Name()
s start = $zh
, len = $ll(list)
f roop=1:1:cnt f pos=1:1:len s val = $li(list, pos)
w !,"list:"_($zh-start)
s start = $zh
, point = 0
f roop=1:1:cnt while($listNext(list, point, val)){}
w !,"next:"_($zh-start)
}
一先ずリスト50個、ループ10,000回で検証してみましょう。
d ##class(developer.Sample).checkListSpeed(50,10000)
リストの各項目の文字数が増えたのが要因か、数値で計測した結果よりも処理速度が若干落ちたのが確認できます。
リスト100個、ループ10,000回
d ##class(developer.Sample).checkListSpeed(100,10000)
やはり、$listNextでの取得が早い事が分かります。
オシャレ$listGetは早いのか?
では、オシャレ$listGetと比較してみたいと思います。
かなり現実的なロジックではないですが、あくまでも処理速度検証の名目なので大目に見て下さい。
ClassMethod checkListSpeed(listCnt As %Integer, cnt As %Integer = 10000)
{
f pos=1:1:listCnt s $li(list, * + 1)=##class(%PopulateUtils).Name()
s start = $zh
, len = $ll(list)
f roop=1:1:cnt f pos=1:1:len s val = $li(list, pos)
w !,"list:"_($zh-start)
s start = $zh
, point = 0
f roop=1:1:cnt while($listNext(list, point, val)){}
w !,"next:"_($zh-start)
s start = $zh
, point = 0
f roop=1:1:cnt s $lg(v1, ~略~, v50)=list
w !,"オシャ :"_($zh-start)
}
変数を50個用意したので、リスト50個、ループ10,000回で実行してみます。
d ##class(developer.Sample).checkListSpeed(50,10000)
リストを一つ一つ取得していないので、やっぱりオシャレ$listGetは早いですね~
終わりに
ドキュメントに記載している通り、$listNextの方が処理速度が速い事が分かりました。
両者は、1回での実行は微々たる差ですが、何度も繰り返し実行すると大きな差を生んでしまう事になります。
リストの値を取得する際は、今後の参考にしていただけると助かります。