みんなに優しく、解りやすくをモットーに開設しています。 以下のルールを守りみんなで助け合いましょう。
1.ファイルメーカーで解らない事があればここで質問して下さい。 何方でも、ご質問・ご回答お願いします。 (優しく回答しましょう)
You are not logged in.
再起式での評価の途中は、中間コードに置き換わって引き渡せれているのかも。それか、再起させたときに、一部をキャッシュできるような仕組みを持たせたとか。それだと短くなります。
> WhileがEvaluateの再帰式より遅いというのはちょっとがっかりな感じですね。
これは、評価する式の処理速度なんでしょうね。
再起式だと、Replace ( Replace ( Replace ( ..... Replace ( text ; 2 ; 0 ; "" ) ..... ) ) ) という文字列を作って、一挙に評価するのに対して、While() だと、Replace() を回数だけ処理するのですが、この Replace() のコンパイルが、引数が多いので遅いのでしょう。
Offline
どうでも良いテスト結果 32bitPC windows10(元はwindows7)一応メモリーは最大のを入れています。
テキスト文字数:1011712・・・本当は、10万台で試していたつもりだった
再帰式の処理時間
FM15 AD:492831ms
FM12 AD:781869ms
FM15はFM12よりだいぶ改善されてるようですね。
関数の呼び出し自体が時間を食うというんで、再帰式をSubstituteなしに変えてみたら、1割ぐらい早くなりました。
Char(32)はスペースに変えてもほとんど変わらないようでした。
Let ([
$str = recursive::text
;$fl = "Let ( [ $s="
;$fr = " ; n = Length($s) ] ;
Case ( n ≤ 1; $s ;
Let([
l=Left($s;Ceiling(n/2))
;r=Right($s;Floor(n/2))];
Evaluate($fl & Quote(l) & $fr ) & Char(32) & Evaluate($fl & Quote(r) & $fr ) ) ) )"
];
Evaluate($fl & Quote($str) & $fr)
)
当方の結果5回平均 単位ミリ秒
1000000:239996
while 再帰式 loop
1152: 10 278 22
53248:7556 12507 8246
106496:28713 25342 30352
そうそう、それなんです。
文字列が短ければ、確かに再帰式より純正の?ループ(Whileも関数でのループです)の方が速いですよね。
ところが、再帰式はだいたい長さに比例した時間なのですが、ループは長さの2乗した時間になっていて、長い文字列では逆転してしまってます。
一つには、ループの方はどっちもReplace関数を使ってるのが原因のようです。
1万文字のテキストの1文字目にスペースを挿入するより、1万文字目に挿入する方が2倍ぐらい時間がかかる
1万文字目に挿入する場合、1万文字のテキストより100万文字のテキストの方が8~9倍ぐらいかかる
(スクリプトで計測したので余分な処理が入ってあんまり正確ではない)
連結自体が速いわけではないようで、50万文字を2つ連結するのは、100万文字目に1文字挿入するのの半分ぐらいの時間でした。
処理の回数はスペースを挿入する数と同じなので、ループでも再帰でも同じですが、
再帰式は、再帰によって毎回半分の長さに分割されるので、すべて初期文字列より短い文字列(最終的には1文字)を処理
ループは、ループごとに1文字加えていくので、すべて初期文字列より長い文字列を処理
ということで、結果のようになってるのだと思います。
Evaluateの再帰式ですが、2文字と3文字の場合に再帰しないでその場で計算するようにしたら、だいたい倍速になりました。
(ついでに$sはsに変えたが、これはほぼ影響なし)
Let ([
$str = recursive::text
;$fl = "Let ( [ s="
;$fr = " ; n = Length(s) ] ;
Case ( n ≤ 1; s ;
n = 2 ; Left(s;1) & Char(32) & Right(s;1) ;
n = 3 ; Left(s;1) & Char(32) & Middle(s;2;1) & Char(32) & Right(s;1) ;
Let([
l=Left(s;Ceiling(n/2))
;r=Right(s;Floor(n/2))];
Evaluate($fl & Quote(l) & $fr ) & Char(32) & Evaluate($fl & Quote(r) & $fr ) ) ) )"
];
Evaluate($fl & Quote($str) & $fr)
)
Caseの条件が増えるのでどうかと思いましたが、試してみたら効果は抜群でした。
文字列を分割していくので例えば8文字の場合
8文字の処理を1回
4文字のを2回
2文字のを4回
1文字のを8回
という風に末端の処理の回数が多いので、短い文字列の処理を速くするのは影響が大きいわけでしょうね。
当方で再帰式V3の動作結果 5回平均 単位ミリ秒
1000000:167021
となりましたが
先日最後にスペースが付く方法をwhile関数で動かしたところ問題が生じました。延々と計算しているのかハングアップしているのか分からない状況に陥りました。・・これについては、別途で質問しましたhttps://fm-aid.com/bbs2/viewtopic.php?id=13358
途中までは問題なく動作したので一応
while_v2 再帰式_v3
1152: 1.8 182
53248:1141 7090
106496:3707 14176
こっちを先に書いてたのに投稿し忘れてた。
うちのPCだと、こんな感じでした。(ある1回の数字だが、何回かやってもそれほど大きな差はない)
while_v2 再帰式_v5(2文字と3文字の処理にはReplaceを使用したら、数%速度アップした感じ)
25600: 370 2535
51200:1252 5258
102400:3324 10430
204800:26506 22205
原因はよくわかりませんが、Whileの方は204800だけ急激に時間が増えてます。(102400までは文字数が2倍で時間は3倍ぐらいなのに、204800になると時間が8倍)
何かメモリ管理上効率が悪くなる限界に達してるんでしょうかね...
別スレッドに間違って投稿されたshinさんのwhileを使った式を頭にスペースが付いていたのでちょっと変えて試してみました。
] ;
Right ( t ; Length (recursive::a1)*2-1 )
);
Length ( recursive::a1)+1)
5回平均 単位ミリ秒
1152: 1.6
53248:1048
106496:2847
ここからは計測回数1回
262144:32250
300000:47288
500000:233437
1000000:880777
#31ので、2^xの長さの文字列だとn=3の条件が全く無駄になって他の場合よりだいぶ遅いのに気づきました。
n=4の条件も加えると、改善されます。
つまり再帰呼び出しはしなければしないほどいい(あたりまえか)
Replaceで後ろから挿入すると前からよりも32768文字の場合で2~3倍ぐらい速い。
800文字の場合が、Whileで後ろからReplaceするとEvaluateで2分割再帰するよりちょうど10倍ぐらい速かったので、条件を2文字とか3文字とか言わずに800文字以下ならWhileでループ処理するようにしてみたら、断然速くなりました。
800文字以上の場合は大体文字数に比例するので、100万文字で13秒ぐらい。
Let ([
//$str = recursive::text;
$fl = "Let ( [ s="
;$fr = " ; n = Length(s) ] ;
Case ( n ≤ 1; s ;
n ≤ 800 ; While ( [
t=s;
m=n-1]
; m > 0
; [
t = Replace(t;m;0;Char(32));
m=m-1
] ;
t
) ;
Let([
l=Left(s;Ceiling(n/2))
;r=Right(s;Floor(n/2))];
Evaluate($fl & Quote(l) & $fr ) & Char(32) & Evaluate($fl & Quote(r) & $fr ) ) ) )"
];
Evaluate($fl & Quote($str) & $fr)
)
Let ([
//$str = recursive::text;
を
Let ([
$str = recursive::a1;
に変え、12文字で試した結果
「 f g h l k k k p o t xm」
頭にスペースが付、最後のスペースが抜ける
ああ、挿入位置が1つずれてました。
m=n]
; m > 1
ですね。
$str=がコメントだったのは、100万文字のテストだけスクリプト内で生成してたせいです。(古い計算フィールドが残ってるのでレコードに入れるとえらい時間がかかりそうで)
5回平均 単位ミリ秒
1152: 1.6
53248:514
106496:1048
262144:2656
300000:3160
500000:5791
1000000:12308
全然違う方法。末尾に1個余分にスペースがつくが
SetRecursion (
While (
[
$text = recursive::text ;
t2 = $text
] ;
t2 <> "" ;
[
c = Left ( t2 ; 1 ) ;
$text = Substitute ( $text ; c ; c & " " ) ;
t2 = Substitute ( t2 ; c ; "" )
] ;
$text
);
Length ( $text ))
文字種の多さに依存するので、0~9しか使ってないサンプルデータでは30万文字でも90msとかすごい速度になる。
コピペでテスト
結果:?
ああ、失敗。元の文字列にスペースが含まれる場合は、それを最初に置換しておかないとだめでした。
SetRecursion (
While (
[
t1 = Substitute ( $text ; " " ; " " ) ;
t2 = Substitute ( $text ; " " ; "" )
] ;
t2 <> "" ;
[
c = Left ( t2 ; 1 ) ;
t1 = Substitute ( t1 ; c ; c & " " ) ;
t2 = Substitute ( t2 ; c ; "" )
] ;
t1
);
Length ( $text ))
なんか不完全な・・・・
何故か条件
t2 <> ""
が成立しないのか理由が分かりませんが「?」が返るためカウンターに変えて下記の式で試してみました。
SetRecursion (
While (
[
$text = recursive::a1;
t1 = Substitute ( $text ; " " ; " " ) ;
t2 = Substitute ( $text ; " " ; "" );
i = 1];
i < Length ( $text );
[
c = Left ( t2 ; 1 ) ;
t1 = Substitute ( t1 ; c ; c & " " ) ;
t2 = Substitute ( t2 ; c ; "" );
i = i + 1
] ;
t1
);
Length ( recursive::a1 ))
文字数
300000:12344ms
やはりカウンターを追加した分だけ処理時間が掛かっているのか?
t1とt2を同じにした状態から始めて、
t2の先頭の文字をt1ですべて後ろにスペースを付ける、t2からは削除
を繰り返して、すべての種類の文字を実行するとt2がなくなって終了、
なので、同じ文字は一度に済ませるのがコンセプトなので、元の文字数でカウントしたら意味がありません。
例:
aaabbb
1回目
t1=a a a bbb
t2=b
2回目
t1=a a a b b b
t2=
6文字でも2回ループで終了
ロジックは時間が掛かったがなんとなく理解できたが、如何せん条件が成立しない。?が返る問題点を探すためにもロジックが正しいことを見るためカウンターを利用しただけです。
何か論理に破綻があるのかなあ?
カウンタを変えて時間が長くなったとして、結果は合ってますか?
元のテキストにスペースがあると結果のチェックがしづらい(なければ、スペースを除去して元のと比べるだけでいい)
ので、スペースを除去しようと10万文字ぐらい入ったフィールドで「検索/置換」をやったら、一向に終わらなくなって(レコードを処理しています、のメーターが何度も最初に戻る)強制終了する羽目に...
これはバグっぽいなあ。「現在のレコード」「現在のフィールド」でやったんですが...
結果の文字数は600000でエディタにコピペしてみても最後にスペースが付いているので問題はないかと・・
私の場合元のテキストにはスペースはありませんそこまで考えてデモテキストを作っていませんから。
t2が何も文字列を示さなければ終了の条件は理解できるが、しかし条件に「t2 <> "" 」を使えば結果として「?」が返り苦し紛れでロジックの理解に使ったカウンターで書き込みました。
追加で
i < 30
137ms
再度元に戻り下記の式で試すと成立する。ますます理由が分からないコピペして「$text = recursive::a1;」を付け加えて?が返りそのあとも何回も条件を「t2 <> ""」に戻したりしていても?で今回エディタに保存していた「t2 <> ""」の部分だけコピペし直してみたらOK・・・スペースが入っていた結果だったら笑えない。
SetRecursion (
While (
[
$text = recursive::a1;
t1 = Substitute ( $text ; " " ; " " ) ;
t2 = Substitute ( $text ; " " ; "" )];
t2 <> "";
[
c = Left ( t2 ; 1 ) ;
t1 = Substitute ( t1 ; c ; c & " " ) ;
t2 = Substitute ( t2 ; c ; "" )
];
t1
);
Length ( recursive::a1 ) )
文字数
300000:133ms
お騒がせしました。
うまくいってよかったです。
#44の例が間違ってましたね。1回目の処理後は
t1=a a a bbb
t2=bbb
でした。
普通の日本語なら使ってる文字はせいぜい3000種類とかだろうから、長い文字列では有効かもしれない。
[ Generated in 0.011 seconds, 8 queries executed - Memory usage: 575.38 KiB (Peak: 611.91 KiB) ]