次の要件を設定します。 既存のコードに進行状況を追加することはできません:
- 既存の関数とメソッドのプロトタイプを変更します。
- 関数内に新しい変数を追加します。
- 現在の進行状況の自明でない計算を含めるには(たとえば、
100 * $i / $n
すでに自明ではないと見なされます)。 - この高価な操作に時間を浪費するために、進行状況インジケーターを更新する必要があるかどうかを理解するために、タイマーを調整するか、反復をカウントします。
プロセスをサブプロセスに分割する
簡単な例は次のようになります。
ここで、それぞれの半分が、進行状況情報を提供できる長い関数への挑戦であると仮定します。 ただし、どのコンテキストで呼び出されたか、およびその実装に割り当てられた一般的な進捗インジケータの範囲はわかりません。 自然な実装は次のようになります。init_progress ;
#
do_first_half ;
update_progress 50 ;
#
do_last_half ;
update_progress 100 ;
つまり、進行状況に関する情報を報告し、希望する範囲(この場合は0〜50%)で誰かに表示させます。 ここで、3次元座標のアフィン変換が4×4マトリックスで記述され、変換のシーケンスがスタックに配置されるOpenGLマトリックススタックとの類推を思い付きました。特定のオブジェクトの頂点を指定する場合は、計算なしで特定の数値を示します。 OpenGL自体が座標を変換し、特定のマトリックスを乗算します。 ここでは、実際には、進行状況インジケーターの座標もあり、1次元のみです。 アフィン変換は、転送とスケーリングの2つの数値で記述されます。 変換をスタックに配置し、sub do_first_half ( ) {
#
update_progress 33 ;
#
update_progress 66 ;
#
update_progress 100 ;
}
update_progress
関数
update_progress
必要な変換
update_progress
実行し、既に変換された座標をレンダラーに渡します。
次に# [, ]
my @stack = ( [ 1 , 0 ] ) ;
sub update_progress ( $ ) {
my $percent = shift ;
$percent = $stack [ - 1 ] [ 0 ] * $percent + $stack [ - 1 ] [ 1 ] ;
renderer ( $percent ) ;
}
push_progress
および
pop_progress
追加し
pop_progress
。 使いやすさのために、スケーリングと
push_progress
に転送するのではなく、後続のパーセンテージが表示される範囲を転送します。 もちろん、何らかの種類の変換が既に有効になっている場合は、
push_progress
パラメーターも変換する
push_progress
あります。
今では、関数呼び出しsub push_progress ( $$ ) {
#
my ( $s , $e ) = @_ ;
#
( $s , $e ) = map { $stack [ - 1 ] [ 0 ] * $_ + $stack [ - 1 ] [ 1 ] } ( $s , $e ) ;
#
push @stack , [ ( $e - $s ) / 100 , $s ] ;
}
sub pop_progress ( ) {
pop @stack ;
}
do_first_half
と
do_last_half
を角かっこ
push_progress/pop_progress
ラップするだけ
push_progress/pop_progress
:
すでに悪くない。 残念ながら、各push_progress 0 , 50 ;
do_first_half ;
pop_progress ;
push_progress 50 , 100 ;
do_last_half ;
pop_progress ;
push_progress
がペアの
pop_progress
対応することを確認する必要があります。 ただし、
push_progress
と
pop_progress
間のコードフラグメントをブロックにラップして、
sub_progress
関数に渡すことが
sub_progress
ます。
次に、メインコードが簡略化されます。sub sub_progress ( & $$ ) {
my ( $code , $s , $e ) = @_ ;
push_progress $s , $e ;
my @retval = & { $code } ( ) ;
update_progress 100 ;
pop_progress ;
return @retval ;
}
sub_progress { do_first_half } 0 , 50 ;
sub_progress { do_last_half } 50 , 100 ;
pop_progress
前に、ブロックがこれを行うのを忘れた場合に備えて、
update_progress(100)
を呼び出しました。 これで、
$s
パラメーターが不要であることが明らかになりました。代わりに、進行状況インジケーターの最後に表示された値を使用できます。
サイクル
次に、ループで何ができるかを見てみましょう。 サイクルのすべての反復がほぼ同じ時間であり、反復回数がわかっていると仮定します。 これは
for ( $i = 1 ; $i < = 1024 ; $i*= 2 )
のようなループでは動作しませんが、任意の
foreach
ループで動作します(ところで、上記のループは
foreach
:
for ( map { 2 **$_ } 0. .10 )
))。
for_progress
は、反復ごとにこのアクションチェーンを実行します。スタックの範囲
[ $i / $n * 100 , ( $i + 1 ) / $n * 100 ]
を
for_progress
します。ここで、$ iは反復数、$ nは要素の数ですリスト、現在の要素を$ _にロード、コードブロックを実行、
update_progress(100)
呼び出し、スタックから最後の要素を抽出します。 次に、既存のループで
for
を
for_progress
に置き換え
for
、リストを最後までドラッグし(
map
)、別の変数を使用した場合は変数に$ _を割り当てます。 内部
for_progress
for
for_progress
レギュラーである
for
、
next
と
last
引き続き機能します(
for_progress
ますが)。 最も簡単なテストは次のようになります。
init_progress ;
for_progress { sleep ( 1 ) } 1. .10 ;
update_progress
はブロックの最後で自動的に呼び出されるため、ループからまったく
update_progress
できます。 ただし、各反復が長い場合は、現在の反復の完了率を示すことで使用できます。 もちろん、
sub_progress
内で
for_progress
を使用してネストされたループが機能し、その逆も同様です。 以下に簡単な例を示します。
現代のプログラミングは、言葉のsub A {
for_progress {
sleep ( 1 ) ;
} 1. .4 ;
}
sub B {
sleep ( 1 ) ;
update_progress 10 ;
sub_progress { A } 50 ;
sleep ( 1 ) ;
update_progress 60 ;
sleep ( 2 ) ;
update_progress 80 ;
sleep ( 2 ) ;
}
init_progress ;
sub_progress { A } 25 ;
sub_progress { A } 50 ;
sub_progress { B } 100 ;
map
と
reduce
なしでは想像しにくいです。
map_progress
および
reduce_progress
ラッパーもそれらに書き込まれます。
ここでは、もちろん、生産性の問題が発生します。反復が短すぎ、毎回進行状況インジケーターを更新するための呼び出しにより、プロセスが大幅に遅くなります。init_progress ;
print " \n Sum of cubes from 1 to 1000000 = " .
reduce_progress { $a + $b * $b * $b } 1. .1000000 ;
update_progress
はこれを考慮し、毎回レンダラーを呼び出しませんが、それが必要であると考えた場合のみ:パーセンテージが100に達した場合、最後の更新からかなりの時間が経過したか、十分な時間が経過した(すべてが
init_progress
パラメーターで構成されます) さらに、追加の最適化が行われました。その結果、
reduce_progress
を使用した私の例は、
List::Util::reduce
場合よりも4.5倍遅いだけです。 非常に短い反復では、慎重に使用してください。
入手先
Progress::Stack
モジュールの最初のバージョンをCPANに配置しました。 これまで、名前空間のアプリケーションは承認されていませんが、パッケージはCPANのWebサイトからダウンロードできます。 ここで説明した機能に加えて、オブジェクトインターフェイス(特に必要ではありません)や、
while ( <FH> ) { }
に似たテキストファイルを処理するための
file_progress
関数など、
file_progress
ものがあります。 ドキュメントには、詳細な説明と例があります。
コメントや提案を歓迎します:-)