プログラミングコンテスト第1回「素数発見」競技の記録の伸びは飛躍的で、 コンテスト創設時に示した管理人の参考記録から10000倍の速度にまで達している。 同じコンピュータでありながらプログラムを変えただけで、これだけの性能アップが実現された秘密はどこにあるのだろうか。 この秘密について詳しく調べてみよう。 アルゴリズムの解説だから、プログラミング言語にはとらわれないつもりだが、 一部にプログラミングの話が出てくることは避けられないのでお許しを得たい。
影武者の正体を明かそう。名前の通り、管理人の「影武者」という意味であり、高田広志そのものでした(想像通りだった?)。
神戸高校生がどこまでプログラミング能力があるかを試して見たかったのがこのコンテスト企画だったのだ。
結論をストレートに述べよう。神戸高校生のプログラミング能力(人材として)は思ったほどではなかった。
なぜ、そのような結論に達したかを述べよう。
今から20年ほど前のことだ。当時、パソコンソフトのプログラム開発ではプロの世界でもBASICが使われていた。
当時はBASIC言語にアセンブラを併用するスタイルが開発現場での常識でだった。
このときに、C言語を学びたいので講習会を開いて欲しいという要望が筆者に寄せられた。
このC言語の講習会にはソフトハウスのプロのプログラマー(姫路、加古川、西脇などのソフトハウスから)数人が参加した。
C言語を使える人は非常に少なかった時代であった。
講習会に参加したプロの人は高い受講料を払っていたはずだが、
そのとき、番外扱いで、西脇工業生が1人参加していた。
当時は高校生がソフトハウスに部活動的発想で出入りしていた時代である。
「門前の小僧お経を読む」の感覚でBASIC言語を覚えていた生徒であった。
C言語の講習会で一番成績が良かったのが、その高校生だったのだ。
プログラミング能力は年齢には関係がない。中学生のほうが最初覚えるのが速い。
基本的なプログラミングレベルであれば年齢が若いうちの方が吸収力が良いためだ。
(なお、高度なプログラミングレベルであれば、周辺の知識の集積による総合力が必要になるため無理がある)。
今回のコンテストレベルであれば、高校生で十分に対応できる範囲内だから、彼くらいのレベルを期待したのだ。
彼に匹敵するレベルに思えてたのは、1人だけだった(それはだれか?秘密としましょう)。
もっと多くいると思っていたのだが...
長話はやめて、本論に入ろう。とりあえず、「影武者」さんの最新バージョンのソースリストを見てもらおう。
プログラムにはコメントを丁寧に書き加えていていること、
Pascalは、C言語と非常に似た言語であることから、
C言語を知っている人には説明しなくても、概ねプログラムの流れがつかめるだろう。
このプログラムの基本は「倍数除去」 アルゴリズムである。
単純な「倍数除去」 アルゴリズムのままでは、弱点を克服できないので、表のアルゴリズムにアレンジがなされている。
数字を載せる表の大きさは100万個分(100万ビット分)だけ取っている。
したがって、表のために使われるメモリー領域はたったの125キロバイト分しか使わない。
このサイズならプログラムがOSから簡単にメモリー領域を確保できてしまう。
そのため、スワップなどの障害が発生することはありえない。
では、これだけの表で素数が100億個の素数がなぜ求まるのか? の疑問が生じる。
そこに表の使い方の秘密の技が存在するのである。
表を使っているときのことを考えてみればよい。 1、2、3、4,5,6..など表は1から上限値まで続くが、表を使うのは、最初は始めの部分、後になるほど後ろの部分である。 例えば10000まで進んだとき、それまでの領域の数の除去はまったくないのだ。 すなわち、「最初の部分は、最初ときだけしか使わない」 ことに気付けばよい。
使い終わった表の部分を、これから使う部分として再利用すれば、大きな表は必要ではなくなる。
たったこれだけの技(テクニック)で巨大な表を小さな表に置き換えることが出来るのだ。
このような表の使い方を実現するためには、どのようなアルゴリズムにするかを考えればよいのだ。
思いつけばプログラムは簡単である。アルゴリズムを記述してみよう。
このアルゴリズムの 1 から 3 までの部分は、最大値100万までの素数を求めるプログラムと同様でよい。
したがって、素数限界値を100万とした「今までに作った素数発見プログラム」を利用できる。
新たにプログラムを作るより、今までのプログラムを再利用したほうががバグが発生しないため
トラブルが起きないのだ。
したがって、上のアルゴリズムの 4 以降を「表の再利用」を行う新たなアルゴリズムのプログラムとして作成するだけだ。
倍数を除去するときに注意することは、倍数に相当する部分がどこから始まるかが分かれば、
その数を足し算した位置にある数を順に繰り返し消してゆけばよいだけの処理になる。
では、再利用する表の場合、どの位置からその倍数が始まるかを知る方法が必要になる。
効率よくその位置を計算するためには、素数のリストが必要になる。
例えば、素数3では100万2が3の倍数だから2番目、素数5では100万5が5の倍数だから5番目、
素数7では100万6が7の倍数だから6番目である。
このように始まる位置はまちまちになる。
たびたびこれを計算すると、割り算を何回も行って余りを求める計算が必要になる。
計算時間がかかる処理が必要になるからそれは避けたい(割り算は少ない方が良い)。
そこで、前回の倍数除去のときにはみ出た量を覚えておく方法をとればよい。
例えば、最初の表の場合、素数3では99万9999で3の倍数が終わる。
次の3の倍数は100万2であり、その繰り越される数2を覚えておけばよい。
次の表での3の倍数の位置は、表の2番目、2+3番目、2+2+2番目、2+2+2+2番目、...と
最初の3の倍数の位置から3ずつ大きな数を順に消してゆけばよいだけだ。
他の倍数についても同様である。
では、素数をどれだけ覚えておけばよいかである。1億なら1億の平方根以下の整数まで、
10億なら10億の平方根の整数まで、100億なら100億の平方根の整数まででよい(前回までのアルゴリズムの説明で示されているチューニング技術)。
したがって、100億までの場合でも、10万までに含まれる素数を覚えるだけで済む(10万個の素数ではない!)からそれほど多くを覚える必要はない。
メモリー領域として必要となるのは、素数とあまりの数のセットになるから実際には約2万組程度(100億未満の素数発見の場合)である。
これに必要なメモリー領域は100キロバイト程度で済んでしまうことになる。
したがって、プルグラムに必要なメモリー領域は全てを合わせても1メガバイト程度あれば十分に処理できることになる。
プログラムは少し複雑にはなるが、このアルゴリズムを利用すると、素数の限界値は完全に突破できることになる(ページング・スワッピングによる壁について)。
この方法を取り入れたプログラムで、「影武者」さんは
前回のバージョンでチューニングを繰り返す苦労を積み上げ伸ばしてきた記録を、簡単に更新してしまうことになる。
メモリーのスワップがまったく無いので、素数発見の処理が一気に速くなったからだ。
更に、10億、20億、50億、100億と限界がなくなってしまったのだ。
素数発見プログラムのどの部分で時間を取られているかを分析できるようにプログラムを書き換えてみた。
測定した結果から、どの部分でプログラム実行に時間がかかるかを調べたところ、
プログラムの実行時間のほとんどがファイルの書き込みに使われていることが分かった。
アルゴリズムの改良では、素数を探す作業時間の短縮がほぼ限界に達し、
探した素数をハードディスクにファイルとして書き出すための作業時間が占めるようになったことになる。
これ以上の速度アップを目指すには、コンピュータのファイル書き込み速度を上げることに力を入れるしかない。
ファイル書き込み速度をあげる方法としては、次のような対策が考えられる。
以上の要求を満たすにはそれほど難しいことではない。
元々、「影武者」さんの現在の記録が弱小ノートパソコンで計測されたものだからだ。
このノートパソコンのスペックは、CPUが 1.1GHz(AMD ATHLON 1100+)、メモリー容量128MBである。
また、ハードディスクの回転数も4800回転と遅いものだ。
最近のノートパソコンでは5400回転のものがあり、ハードディスクも高速化している。
デスクトップでは、ハードディスクは7200回転が普通である。
したがって、この記録は最近のデスクトップパソコンなら簡単に記録更新できてしまうことになる。
(メモリーの量は速度改善にはあまり影響しないはず)。
それに、加えて、マイクロソフトやインテルの優秀なCコンパイラを使用してオブジェクトコードの最適化を行えば、
同じアルゴリズムを使ったとしても更に2倍を越える速度アップが可能ではないだろうか。
ソースリストの公開は、このような意図を含んだものである。
更に、CPUを高クロックのものを使う(Pen 3.4GHz)なども組み合わせると、
同じプログラムだとしても2倍以上の速度が可能ではないだろうか。
高性能のデスクトップをお持ちの方は挑戦してみてください。
なお、コンパイラが使えない人のために「物理の小道」コンテスト記録のページから、
影武者さんの最新バージョンの実行ファイルをダウンロードできるようにしている。
ダウンロードして新記録を報告してください(マシンの性能競争になるが)。
{********************************* 素数発見プログラム Ver. 8.10 バッファ多重再利用アルゴリズム 手続・関数・変数のローカル化 Date: 2004/03/24 Copyright by 高田 広志 Mail: tac_hiro@hotmail.com *********************************} program Project8; {$APPTYPE CONSOLE} uses SysUtils {標準ライブラリ} ,DateUtils; {日時関連ライブラリ(計時関係関数 MilliSecondOfTheDay関数 で使用)} const ProgName: string = '影武者・バージョン6'; SubComment: string ='(メモリー効率アップ版)'; {============ 特別コンパイル条件 =======================} { $ define DEBUG} {デバッグモード} {$define NonCall} {手続きインライン展開切り替え} {$define SepOut} {分散出力モード切り替え} {$define BitMode64} {64/32ビットモード切り替え} {$define MakeAllMask} {ビットマスク全生成化切り替え} {=======================================================} const {$ifdef BitMode64} BitMode: string = '64Bit:'; {$else} BitMode: string = '32Bit:'; {$endif} {素数フラグ・ブロックサイズ} BLOCK_RANGE = 1000000; {既定値(未最適化)] {素数フラグ・ブロックサイズ÷32 を設定する} BUFFER_SIZE = 31250; {=1,000,000/32} LIMIT_SIZE = BUFFER_SIZE * 64; {素数キャッシュ・サイズ} MAX_INDEX = 20000; {百億未満まで対応} LIMIT_VAL_CACSH = 200000; {キャッシュに入れる素数の最大値} {途中計時保存用のインデックス} StartOfProg=0; EndOfMemAlloc=1; EndOfInit=2; EndOf1st=3; EndOfNth=4; EndOfProg=5; type D=array[0..BUFFER_SIZE] of longword; {素数フラグエリア・ブロック型} P=^D; {======================} { グローバル変数定義 } {======================} var {システム主体関連} s: array[1..2] of string; {コマンドパラメーター解析バッファ} fp: text; {出力ファイルポインタ} {素数フラグ領域関連} Dp: P; {素数フラグ・ブロックへのポインタ} Dmx: array[0..31] of longword; {フラグセットマスク} {デバッグ関連} fDEBUG: boolean; {デバッグモードフラグ} TimeBox: array[0..10] of longword; {時間記録データバッファ(ミリ秒単位)} {フォントキャッシュ関連} SL: array[0..MAX_INDEX] of longword; {素数キャッシュ} SL0: array[0..MAX_INDEX] of longword; {繰越分保存} MaxSLidx: longword; {素数キャッシュデータ量} RangeOfSLidx: longword; {倍数チェック範囲} {$ifdef BitMode64} MaxRange: int64; {素数検索の限界値(64bit)} {$else} MaxRange: longword; {素数検索の限界値(32bit)} {$endif} {$ifdef MakeAllMask} var Dm: array[0..31] of longword; {ビットフラグ・クリア用マスク} procedure MakeBitMask; {フラグ操作用のビットマスク生成} var i: integer; begin Dmx[0]:=1; Dm[0]:=(not Dmx[0]); for i:=1 to 31 do begin Dmx[i]:=(Dmx[i-1] shl 1); Dm[i]:=(not Dmx[i]); end; end; {$else} const {フラグクリア用マスク} {フラグセットマスクはプログラムで作成} Dm : array[0..31] of longword =( $fffffffe,$fffffffd,$fffffffb,$fffffff7 ,$ffffffef,$ffffffdf,$ffffffbf,$ffffff7f ,$fffffeff,$fffffdff,$fffffbff,$fffff7ff ,$ffffefff,$ffffdfff,$ffffbfff,$ffff7fff ,$fffeffff,$fffdffff,$fffbffff,$fff7ffff ,$ffefffff,$ffdfffff,$ffbfffff,$ff7fffff ,$feffffff,$fdffffff,$fbffffff,$f7ffffff ,$efffffff,$dfffffff,$bfffffff,$7fffffff ); procedure MakeBitMask; var i: integer; begin for i:=0 to 31 do Dmx[i]:=(not Dm[i]); end; {$endif} {==========================================} { 素数フラグ領域のビット操作手続・関数群 } {==========================================} procedure BitSet(n: longword); {フラグ・セット手続} var n0,n1: longword; begin n0:=n and $0000001f; n1:=n shr 5; Dp^[n1]:=Dp^[n1] or Dmx[n0]; end; procedure BitSetAll; {全フラグ・セット手続} var i: longword; begin for i:=0 to BUFFER_SIZE do begin Dp^[i]:=$ffffffff; end; end; procedure BitClr(n: longword); {フラグ・リセット手続} var n0,n1: longword; begin n0:=n and $0000001f; n1:=n shr 5; Dp^[n1]:=Dp^[n1] and Dm[n0]; end; function BitChk(n: longword): boolean; {フラグ読出関数} var n0,n1: longword; begin n0:=n and $0000001f; n1:=n shr 5; BitChk:=(Dp^[n1] and Dmx[n0])<>0; end; procedure WriteBin(n: longword); {フラグ状態表示手続} var i: longword; begin for i:=0 to 31 do begin write(n and 1); n:=n shr 1; end; end; {====================================} { システム主体に関する手続・関数群 } {====================================} {致命的エラーで強制終了へ} procedure Abort(msg: string); begin writeln; writeln(msg); halt(1); end; {整数型平方根関数(切り上げ)} {$ifdef BitMode64} function isqrt(n: int64):int64; {64ビット版} var i,j: int64; {$else} function isqrt(n: longword):longword; {32ビット版} var i,j: longword; {$endif} begin i:=1; j:=1; while n > j do begin n:=n-j; inc(j,2); inc(i); end; isqrt:=i; end; {コマンドラインの解析(ファイル名、上限値等)} procedure AnalyzeComOpt; var i: integer; begin if ParamCount=0 then begin {コマンドラインのパラメータを取得する} writeln(ProgName); writeln('usage: [Exec-filename] [OutPutFileName] LimitCount'); write('出力ファイル名を入れてください:'); readln(s[1]); write('限界値を入れてください:'); readln(s[2]); end; for i:= 1 to ParamCount do s[i]:=ParamStr(i); end; {初期化処理手続} procedure Initialize; var i: longword; begin {セットマスクデータを作成} MakeBitMask; {上限値を設定する} Val(s[2],MaxRange,i); if i<>0 then MaxRange:=10000000; if MaxRange > 10000000000 then begin writeln('素数上限値が大きすぎるので中止します !'); halt(1); end; {素数キャッシュ初期化(キャッシュ初期化(最初は0個)} MaxSLidx:=0; {デバッグフラグの設定} if (s[1]='-d') or (s[1]='-D') then begin fDEBUG:=True; s[1]:='$sosuu$.txt'; end else begin fDEBUG:=False; end; if fDEBUG then begin writeln('Dm[i],Dmx[i] ----------------------'); for i:=0 to 31 do begin WriteBin(Dm[i]); write(' '); WriteBin(Dmx[i]); writeln; end; writeln('-Clr/Set test -------------------------'); for i:=0 to 31 do begin Dp^[1]:=$ffffffff; BitClr(i+32); WriteBin(Dp^[1]); write(' '); Dp^[1]:=0; BitSet(i+32); WriteBin(Dp^[1]); writeln; end; writeln('-Clr test------------------------------'); Dp^[1]:=$ffffffff; for i:=0 to 31 do begin BitClr(i+32); WriteBin(Dp^[1]); writeln; end; writeln('-Set test------------------------------'); for i:=0 to 31 do begin BitSet(63-i); WriteBin(Dp^[1]); writeln; end; writeln('----------------------------------------'); writeln; end; end; {================================} { デバッグに関する手続・関数群 } {================================} {素数ファイル内の素数総数を数えて返す} function linecount(fname:string):longword; var fp: text; n: longword; s: string; begin assign(fp,fname); reset(fp); n:=0; while not eof(fp) do begin readln(fp,s); inc(n); end; linecount:=n; end; procedure DisplayTimeBox; {プログラム処理経過時間分析を表示する} begin if fDEBUG then writeln('Line Count = ',linecount(s[1]),' lines'); writeln; writeln(' プログラム開始時刻 = ',TimeBox[StartOfProg],' ms'); write(' メモリー確保終了時刻 = ',TimeBox[EndOfMemAlloc],' ms'); writeln(' --> メモリ確保時間(',TimeBox[EndOfMemAlloc]-TimeBox[StartOfProg],' ms)'); write(' 初期化等終了時刻 = ',TimeBox[EndOfInit],' ms'); writeln(' --> 初期化処理時間(',TimeBox[EndOfInit]-TimeBox[EndOfMemAlloc],' ms)'); write(' 第1段階処理終了時刻 = ',TimeBox[EndOf1st],' ms'); writeln(' --> 第1段階の時間(',TimeBox[EndOf1st]-TimeBox[EndOfInit],' ms)'); write(' 第n段階処理終了時刻 = ',TimeBox[EndOfNth],' ms'); writeln(' --> 第n段階の時間(',TimeBox[EndOfNth]-TimeBox[EndOf1st],' ms)'); write(' プログラム終了時刻 = ',TimeBox[EndOfProg],' ms'); writeln(' --> メモリ解放時間(',TimeBox[EndOfProg]-TimeBox[EndOfNth],' ms)'); end; {====================================} { 素数発見処理に関する手続・関数群 } {====================================} {素数キャッシュ繰り上がり分設定} procedure SetCarry; var i,j,k: longword; begin k:=isqrt(MaxRange); for i:=0 to MaxSLidx-1 do begin if SL[i] < k then RangeOfSLidx:=i; j:=BLOCK_RANGE mod SL[i]; if j<>0 then j:=SL[i]-(BLOCK_RANGE mod SL[i]); SL0[i]:=j; end; end; {素数キャッシュをファイルに保存} procedure SaveSL; var i: longword; f: text; begin assign(f,s[1]+'.csh'); rewrite(f); for i:=0 to MaxSLidx-1 do begin writeln(f,SL[i],',',SL0[i]); end; close(f); end; {================================} { 素数の検索関連の手続・関数群 } {================================} {--------------------------------------------------------} {素数発見の最初のブロックの処理(素数キャッシュ作成含む)} {--------------------------------------------------------} procedure Search1stStep; var LimitOfSearch: longword; j,j2,k: longword; {作業変数} procedure AddList(n: longword); begin if MaxSLidx > MAX_INDEX then Abort('素数キャッシュ・オーバーフロー') else begin SL[MaxSLidx]:=n; SL0[MaxSLidx]:=0; inc(MaxSLidx); end; end; begin {倍数除去限界値} LimitOfSearch:=isqrt(BLOCK_RANGE); {素数2、3をファイルへ} writeln(fp,2); {素数2をファイルへ} writeln(fp,3); {素数3をファイルへ} {2、3を素数キャッシュへ} AddList(2); AddList(3); {3以上の奇数全てにフラグをセット} BitSetAll; {i:=3; while i <= BLOCK_RANGE do begin BitSet(i shr 1); inc(i,2); end; } {素数フラグエリア初期化終了時刻取得} TimeBox[EndOfInit]:=MilliSecondOfTheDay(Now); {$ifdef DEBUG} write('3-'); {$endif} {3の倍数は素数でないのでフラグをリセット} j:=3; j2:=j+j; k:=j+j2; while k <= BLOCK_RANGE do begin {$ifdef NonCall} Dp^[k shr 6]:=Dp^[k shr 6] and Dm[(k shr 1) and $0000001f]; {$else} BitClr(k shr 1); {$endif} k:=k+j2; end; {$ifdef DEBUG} write('5+'); {$endif} {5以上の素数でないものを探し、その倍数のフラグをリセットする} j:=5; while j <= LimitOfSearch do begin {$ifdef NonCall} if (Dp^[j shr 6] and Dmx[(j shr 1)and $0000001f])<>0 then {$else} if BitChk(j shr 1) then {素数なら、その倍数のフラグをリセット処理} {$endif} begin {$ifdef SepOut} writeln(fp,j); {素数をファイルへ} AddList(j); {素数をキャッシュへ} {$endif} {倍数は素数でないのでフラグをリセット} j2:=j+j; k:=j+j2; while k <= BLOCK_RANGE do begin {$ifdef NonCall} {手続きインライン展開方式} Dp^[k shr 6]:=Dp^[k shr 6] and Dm[(k shr 1) and $0000001f]; {$else} {手続き呼び出し方式} BitClr(k shr 1); {$endif} k:=k+j2; {次の倍数に移る} end; end; inc(j,2); {次の奇数に移る} {$ifdef DEBUG} if (j and $ff) = $ff then write('>'); {$endif} end; {$ifdef DEBUG} write('S-'); {$endif} {素数をファイルに出力する(SepOutのときは残りの素数をファイルへ)} {$ifndef SepOut} j:=5; {素数5から順に素数を見つけてファイルへ} {$endif} while j <= BLOCK_RANGE do begin {$ifdef NonCall} if (Dp^[j shr 6] and Dmx[(j shr 1)and $0000001f])<>0 then {$else} if BitChk(j shr 1) then {$endif} begin {素数をファイルへ} writeln(fp,j); {見つけた素数をキャッシュへ} if j < LIMIT_VAL_CACSH then AddList(j); end; inc(j,2); end; end; {------------------------------------------------------------------} {素数キャッシュを使って、続きのブロックを処理(最後のブロックまで)} {------------------------------------------------------------------} procedure SearchNthStep; var ProcessCounter: longword; {進行状況表示バーのカウンタ} {$ifdef BitMode64} base: int64; {$else} base: longword; {$endif} {進行状況表示手続} procedure DispProcessBar; {8回呼出し毎に表示} begin inc(ProcessCounter); if (ProcessCounter and $7) =0 then write('>'); end; {1ブロックに含まれる素数を探し、ファイルに保存} procedure Search_Block(BaseVal: longword); var i,j,j2,k: longword; begin {素数フラグを初期化} BitSetAll; {倍数を除く} for i:=0 to RangeOfSLidx do begin j:=SL[i]; j2:=j; k:=SL0[i]; while k < BLOCK_RANGE do begin BitClr(k); {@@@@@@} k:=k+j2; end; SL0[i]:=k-BLOCK_RANGE; end; {素数をファイルへ} for k:=0 to BLOCK_RANGE-1 do begin if BitChk(k) then writeln(fp,k+BaseVal); {@@@@@@} end; end; {第n段階処理の本体} begin base:=BLOCK_RANGE; {第1段階で終了済(1ブロック分)をセット} while base < MaxRange do begin Search_Block(base); base:=base+BLOCK_RANGE; {1ブロック分 base をずらし、次のブロックへ} DispProcessBar; end; writeln; end; {============================} { メインプログラム開始 } {============================} begin {コマンドラインオプション解析} AnalyzeComOpt; {プログラム開始時刻取得} TimeBox[StartOfProg]:=MilliSecondOfTheDay(Now); {$ifdef DEBUG} write('A-'); {$endif} {素数フラグエリア確保} Dp:=New(P); {素数フラグエリアメモリー取得終了時刻取得} TimeBox[EndOfMemAlloc]:=MilliSecondOfTheDay(Now); {$ifdef DEBUG} write('I-'); {$endif} {初期化処理} Initialize; {プログラムタイトル表示} writeln(ProgName,SubComment,'(',BitMode,BLOCK_RANGE,')'); writeln(' 開始 ',TimeBox[StartOfProg]); {出力ファイル名を設定しファイルを開く} assign(fp,s[1]); rewrite(fp); {素数発見の第1段階処理(最初のブロック(百万未満)を処理)} Search1stStep; {素数キャッシュに第1段階繰越分をセット} SetCarry; {$ifdef DEBUG} write('B-'); {素数キャッシュデータを保存} SaveSL; {$endif} {第1段階終了時刻取得} TimeBox[EndOf1st]:=MilliSecondOfTheDay(Now); {素数発見の第n段階処理(第2段階以降のブロックを最後まで処理)} SearchNthStep; {出力ファイルを閉じる} close(fp); {第n段階終了時刻取得} TimeBox[EndOfNth]:=MilliSecondOfTheDay(Now); {$ifdef DEBUG} write('D-'); {$endif} {素数フラグ領域解放} Dispose(Dp); {$ifdef DEBUG} writeln('#'); {$endif} {全終了時間取得} TimeBox[EndOfProg]:=MilliSecondOfTheDay(Now); {終了時間表示} writeln(' 終了 ',TimeBox[EndOfProg]); {処理時間表示} write('実行時間は'); write((TimeBox[EndOfProg]-TimeBox[StartOfProg])/1000.0:10:3); writeln(' 秒でした。'); writeln; {途中経過分析を表示} writeln(' 素数キャッシュ情報: ',RangeOfSLidx,'(',MaxSLidx,')',MAX_INDEX); DisplayTimeBox; halt(0); {正常終了です} end.