みんなに優しく、解りやすくをモットーに開設しています。 以下のルールを守りみんなで助け合いましょう。
1.ファイルメーカーで解らない事があればここで質問して下さい。 何方でも、ご質問・ご回答お願いします。 (優しく回答しましょう)
You are not logged in.
いつもお世話になっております。
「全ての文字の間にスペースを挿入したい」https://fm-aid.com/bbs2/viewtopic.php?id=13333
の続きみたいなものですが、表題と違う質問となりますので新たに質問させてください。
下記のような計算式で実行したところ
SetRecursion (
While (
[
text = recursive::a1 ;
cnt = 1;
textS= ""
] ;
cnt < Length ( text )+1 ;
[
textS = textS & Middle ( text ; cnt ; 1) & " " ;
cnt = cnt + 1
] ;
Left ( textS ;cnt*2-3)
);
Length ( recursive::a1 )+1)
テキスト文字数が265000では「応答なし」となり結果が得られません、260000では20342msで結果は得られます。その分岐点がどの数値なのかまでは把握していません。これをloop処理スクリプトで実行すれば31496msで問題なく結果が得られます。前の質問に合ったWhile関数の結果より途中まで処理時間が短かったのにこの時点で行き詰まりました。
私が示した計算式のどこに問題があるのかご教授お願いいたします。
Windows10 fm19.5.2.201環境です。
While の最大試行回数は環境に依存するのでは?
26500文字はこちらのテスト環境では計算されます。(Mac M1 Pro / 32GB)
Offline
Moz様
26500文字はこちらのテスト環境では計算されます。(Mac M1 Pro / 32GB)
とありますが265000文字のタイプミス?
すんません。26500だと読み違えてますた。
いま265000で試行中です。まだ返ってきません。むりかも知れません。
追記)
265000文字(半角英数)で結果でました。
Last edited by Moz (2022-08-06 19:32:34)
Offline
「応答なし」は、関数の計算中には中断できないので結構すぐに目にしますが、通常は計算が終われば何事もなく復帰します。(ユーザが不安になるので、OSの要求?に応答するようなオプション=計算にかかる時間はその分長くなるわけですが=があるといい気がしますが)
Whileじゃないのですが、スクリプトで再帰カスタム関数の100万文字の計時実験をしたら、大量にメモリを食いつくして他のアプリ(Firefoxですが)がクラッシュしたり画面が真っ暗になったりしたので、その後はやってみてません...(他の事と「ながら」なので困る。最低限ブラウザでこのサイトは開いてるわけで、テスト用にもう1台PC用意しないと...)
こうなるとFMというかWin10自体のバグの感じです。(なんで他のアプリの動作で影響してしまうのか)
ヘルプのメモは、「非末尾再帰を使用するカスタム関数」についてのものです。(ただし、以下が日本語だと紛らわしいが)
>非末尾再帰を使用するカスタム関数は、SetRecursion によって指定された最大繰り返しの影響も受けます。ただし、SetRecursion に関係なく、メモリ内で使用できるスタックの容量が少なすぎる場合、終了して「?」を返します。
この場合、増やす方向にSetしても無効な場合が多いんでしょう。
Whileは末尾再帰のカスタム関数と同じで、SetRecursionが有効に働いてもらわないと困りますよね。26万ならOKで26万5000でエラーになる理由は特にないはずです。(繰り返しごとに持ち越すデータがごく限られてるので)
MacではOKだったというし。
繰り返し内で生成する文字列が毎回別の領域を必要としたりとかで「スタック」を消費しているのかなあ?
関係がないと思って記しませんでしたが 、下記のloop処理スクリプトで1000000文字では、1143846ms でした。・・・但し1回限り
変数を設定 [ $text; 値:recursive::a1 ]
変数を設定 [ $textS; 値:"" ]
変数を設定 [ $count; 値:1 ]
Loop
Exit Loop If [ Let ( [ $textS = $textS & Middle ( $text ; $count ; 1) & " " ; $count = $count + 1 ] ; $count > Length ( $text ) ) ]
End Loop
フィールド設定 [ recursive::b1; Left ( $textS ;$count*2-3) ]
これを元にWhile関数の計算式を組みました。
私の場合もFirefoxを起動して動作させましたがあまり影響は受けていないように思いますが(テスト時は大体ブラウザは閉じていますが)、Excelを開いたときにhimadaneeさんが別の質問で示された最新の再帰式で2倍強の処理時間が1度だけあったことも事実です。一般的に32bitのPCの場合セキュリティソフトがメモリを大きく食い潰すので動作不良の問題は多々ありましたが64bitではあまり問題は今までありませんでした。
「260000では20342ms」までは、大体「文字数2倍に付き時間3倍」で処理できてますね。
うちのPCでは(102400までは文字数が2倍で時間は3倍ぐらいなのに、204800になると時間が8倍)だったので、もう少し早く異常な状態になってる感じです。
「分岐点」より後の処理に異常に長時間かかってるとすると(例えば204800の最後の800回に102400の5倍相当の時間)、26万から5000増えただけで結果が出なくなるということもありえなくはないか??
追記)
265000文字(半角英数)で結果でました。
私も再挑戦です。2361124msで結果が出ました。
あとこれも関係ないと思いますが今回は環境設定のメモリを256MBに上げてみました。
環境設定のはファイルキャッシュなので関係ないはずですが
ああ、そういえば512KBが524288バイトですから、262144文字相当ですね。何かのメモリがこのサイズで、2つ目が必要になると極端に遅くなるのかな?
この処理はスペース挿入して約2倍の長さにするので、途中から結果の文字列が1MBを超えるということですね。
計算式を
SetRecursion (
While (
);
Length ( recursive::a1 ))
に変えて確認・・ブラウザを閉じ忘れていました
262143: 28986
262144:2337339
参戦する気はなかったんだけど、別のアルゴリズムを使うとかなり早くなったので報告だけ。要するに、Replace() で扱うテキストがある程度大きくなると階乗的に非常に遅くなる、ということのようです。
n は、text の長さの平方根あたりがいいようです。
While (
[
t = text ;
n = 100 ;
i = Div ( Length ( t ) ; n ) * n + 2
] ;
i ≥ 0 ;
[
t1 = While (
[
t0 = Middle ( t ; i ; n ) ;
i0 = Length ( t0 )
] ;
i0 > 0 ;
[
t0 = Replace ( t0 ; i0 ; 0 ; " " ) ;
i0 = i0 - 1
] ;
t0
) & t1 ;
i = i - n
] ;
Left ( t ; 1 ) & t1
)
検証途中での式をあげてしまったので、修正
Last edited by Shin (2022-08-10 11:54:33)
Offline
そこまでぴったりした長さが分岐点でしたか。。。
多分、FMをプログラムするのに使ってる言語の仕様で標準のテキスト型は1MBまでしか扱えないんでしょうね。
そうなると、私のPCでもう少し短いテキストで既に遅さが出てたのは何だったのかな...
260000では20342ms
262143: 28986
一応、ここでも多少遅くなってきてはいるのかな?誤差?(1%長くなってるだけで時間は4割増しになっている?)
結局、SetRecursion とか While の問題というわけではないということかな。
Shinさんのは、こっちじゃなくて元ネタの
https://fm-aid.com/bbs2/viewtopic.php?id=13333
の方ですね。
こっちは、最初からReplaceは使ってません。ちょっと違う話題です。
よく分かりませんが素人目に見れば、再帰式でもshinさんのWhile関数使った方法やLoop処理スクリプトでもこんなに極端な処理時間の変遷はないのでなんか変なドツボの設定に嵌まったような気がする。
あぁ、スレッド間違った。
Offline
FMの技術仕様にあるカスタム関数の再帰限界
「テール再帰は、テールコールによってコールスタックのサイズが大きくならないように適切に最適化されます。」
またあるサイトに
「末尾再帰は、末尾から「ループ」させるか、先頭から「ループ」させるかには関係ない。重要なのは、連続した関数呼び出しによって値がどのように蓄積され、受け渡されるかです(特に、関数が動作中に一時的なメモリの「スタック」を介して値を供給する必要があるかどうかです)。最も簡単な言葉で言えば、スタックに依存しないように構成された再帰的関数は、末尾再帰的である。"ということだ。
つまり、局所変数を作らずに、式の結果をすべてパラメータとして次の繰り返しに渡すと、末尾再帰的なのです。」(翻訳ソフトで変換)
ともあり、内容が全く理解できないが・・・今回のWhile問題とは関係ないと思うがもしかして再帰の問題がと思い引用しました。
他にも
https://filemakerhacks-com.translate.go … _pto=op,sc
https://blog-beezwax-net.translate.goog … _pto=op,sc
等があったがWhileや再帰は難しすぎる。
う~ん、文字列が1MBを超えたからすぐ遅くなるという単純な話でもなかったようで、長い文字列を作るだけのテストでこんなことをやってみましたが数msで結果が出ました。
While ( [
s="1" ]
; Length(s) < 512*1024 * 4
; [
s = s & s
] ;
s
)
Whileの場合は、「ループ」なので今回の問題のように変数値がどんどん長くなるとかループ内で新たに変数を定義したりしなければ、末尾再帰と同様でスタックの問題=回数の限界は出ないはずです。
再帰関数の場合は、自分自身を呼び出すときに「式の結果をすべてパラメータとして次の繰り返しに渡すと、末尾再帰」とあるように、
function(param);
であれば、現在の状況は破棄して呼び出せるので、SetRecursiveの制限しか出ないはずで、
function(param) & " ";
みたいに呼び出すと、呼び出した結果を現在の呼び出しレベルで受け取る必要があって、「現在の呼び出しレベル」の情報をすべて保存しておく必要があるから「スタック」を消費してしまいます。
今回の例で言えば、inspaceTail(text;len)というカスタム関数を定義して
Case ( len < 2 ; text ;
inspaceTail ( Replace ( text ; len ; 0 ; " " ) ; len - 1 )
)
とすると、末尾再帰になってSetRecursionが有効になります。
1回だけの実験結果
12800:446ms
51200:6672
102400:27031
204800:116565
131072:43180
262143:197192
262144:201513
300000:277455
500000:846435
100万:3617425
大体長さ2倍で時間は4倍になってて、262144のところにはっきりした分岐点は見えない感じです。
While 関数は SetRecursion 関数で上限を拡張しなければ50000回が上限となります。
Offline
そうです、末尾再帰と同様です。
末尾再帰でないカスタム関数は、上限を拡張してもそこまで計算できるとは限らない、ということです。
今回質問されてる状況は、結局計算時間が長いだけで、スタックがオーバーして「?」になるケースとは別のようです。
原因がWhileなのか、Middleなのか、Lengthなのか???
Whileの場合は、「ループ」なので今回の問題のように変数値がどんどん長くなるとかループ内で新たに変数を定義したりしなければ、末尾再帰と同様でスタックの問題=回数の限界は出ないはずです。
とありますが、この変数の長さのの部分だけ捉えると前回の投稿時
himadaneeさんのテキスト増加式と同じような方法で前に試したことがあります。
単純に変数宣言を下記のようにすると
text=text&text
さすがに10億文字数を超えるとフィールドの限度を超えるので結果は?となりますがその半分の5億超では大した処理時間は要しませんでした。
この時は、繰り返し回数が300以下なので問題外の事だと思っていました。
となると、こんな極端な変数の長さでなくそれなりの長さの変数が繰り返しで一時的なメモリの「スタック」に生成されると本来?となるべき上限(スタックの最大値)を超えた場合SetRecursiveが有効なため別の領域で計算され処理時間が掛かる・・・と素人の解釈してみたが
「スタックに依存しないように構成された再帰的関数は、末尾再帰的である。」とはどんな式を指すのか見当が付かなかったし、そもそもwhileとカスタム関数の制限条件に関係性があるのかも疑問でした。
あれ~?
怖いのでやってみてなかったのですが、さっきカスタム関数の方で1時間ぐらいかかったので思い切って#1の計算式をやってみたら、
800:9ms
3200:36
12800:157
51200:1088
102400:3622
204800:16068
262143:35741
262144:34872
通過してしまいました。(1回だけの計測なので逆転しています)
300000:455315
しかし、ここはどう見ても分岐点を過ぎている感じです。(とはいえまだ#11よりは1桁短い。)
再度下記の式で実行・・今回はブラウザを閉じています。
SetRecursion (
While (
[
text = recursive::a1 ;
cnt = 1;
textS= ""
] ;
cnt < Length ( text )+1 ;
[
textS = textS & Middle ( text ; cnt ; 1) & " " ;
cnt = cnt + 1
] ;
Left ( textS ;cnt*2-3)
);
Length ( recursive::a1 )+1)
262144:2202205ms
300000:2984500
今まで" "をchar(32)等に代えたりしてみたが多少の時間差はあるもののほぼ同じ結果でした。私のPC環境のせいかしら?
他に計算式を作り試してみる
SetRecursion (
While (
[
text = recursive::a1 ;
i = 1;
result=""
] ;
i < 5101 ;
[
result = Middle ( text ; i ; 1) ;
i = i + 1
] ;
result
);
5100)
文字数
262143:134
262144:24463
やはりこの時点で処理時間が大きく変異する
この前に下記でも試してみた。
result = List (result ; Middle ( text ; i ; 1)):2262189
result = Middle ( text ; i ; 1):2179938
Middle ( text ; i ; 1)に加えLength ( text )を262144の実数にかえ:2014509
多少の違いがあるが大きな変化はない。
追試・・関数をMiddleから Left に変えても
SetRecursion (
While (
[
text = recursive::a1 ;
i = 1;
result=""
] ;
i < 5101 ;
[
result = Left ( text ; i ) ;
i = i + 1
] ;
result
);
5100)
文字数
262143:31
262144:33344
[ Generated in 0.009 seconds, 8 queries executed - Memory usage: 583.68 KiB (Peak: 620.59 KiB) ]