GoodTimes.mesa, some simple timing tests
Russ Atkinson, November 22, 1983 12:32 pm
DIRECTORY
BasicTime USING [GetClockPulses, GMT, Now, Pulses, PulsesToMicroseconds],
Commander USING [CommandProc, Register],
FS USING [StreamOpen],
IO USING [Close, Flush, PutChar, Put, PutF, PutRope, STREAM],
Process USING [Detach, GetPriority, MsecToTicks, Pause, SetPriority],
Random USING [Init, Choose],
Rope USING [
Balance, Concat, Equal, Fetch, FetchType, FromProc, InlineFetch, Length, MakeRope, Map, MaxLen,
PieceActionType, PieceMap, Replace, ROPE, Size, Substr, VerifyStructure],
SafeStorage USING [
ReclaimCollectibleObjects, SetCollectionInterval, WaitForCollectorDone],
ViewerIO USING [CreateViewerStreams],
VMStatistics USING [pageFaults];
GoodTimes: CEDAR MONITOR
IMPORTS
BasicTime, Commander, FS, IO, Process, Random, Rope, SafeStorage, ViewerIO, VMStatistics
= BEGIN
CARD: TYPE = LONG CARDINAL;
Microseconds: TYPE = LONG CARDINAL;
PTR: TYPE = LONG POINTER;
Pulses: TYPE = BasicTime.Pulses;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
pause: INTEGER ← 0;
faults: INT ← 0;
pi: REAL ← 3.14159;
int: INT ← 31416;
cardinal: CARDINAL ← 31416;
stream: IO.STREAMNIL;
WritePageMark: PROC = TRUSTED {
WriteChar[14C];
WriteChar[15C];
IO.Flush[stream];
};
WriteChar: PROC [c: CHAR] = TRUSTED {
IO.PutChar[stream, c];
IF abort THEN {IO.Flush[stream]; ERROR ABORTED};
};
WriteRope: PROC [r: ROPE] = TRUSTED {
IF r # NIL THEN {
IO.PutRope[stream, r];
};
IF abort THEN {IO.Flush[stream]; ERROR ABORTED};
};
WriteDecimal: PROC [x: INT] = {
IO.Put[stream, [integer[x]]];
};
WriteReal: PROC [x: REAL] = {
IO.Put[stream, [real[x]]];
};
WriteLine: PROC [s: ROPE] = TRUSTED {
WriteRope[s];
WriteChar[15C];
};
Put: PROC [c: CHAR] RETURNS [BOOL] = TRUSTED {
IF c = '" THEN WriteChar['\\];
WriteChar[c]; RETURN [FALSE];
};
WriteRopeLit: PROC [r: ROPE] = TRUSTED {
WriteChar['"];
WriteRope[r];
WriteChar['"];
};
Show: PROC [r: ROPE, pieces: BOOLTRUE, label: ROPENIL]
RETURNS [ROPE] = TRUSTED {
EachPiece: Rope.PieceActionType = TRUSTED {
WriteRope[" "];
WriteChar['"];
[] ← Rope.Map[base, start, len, Put];
WriteChar['"];
WriteChar[15C];
RETURN [FALSE];
};
IF label # NIL THEN {WriteRope[label]; WriteRope[": "]};
WriteRopeLit[r]; WriteChar[15C];
IF pieces THEN [] ← Rope.PieceMap[r, 0, Rope.Size[r], EachPiece];
RETURN [r]};
FillData: TYPE = RECORD [c: CHAR];
FetchProc: Rope.FetchType = {
WITH data SELECT FROM
rf: REF FillData => RETURN [rf.c];
ENDCASE => ERROR};
MakeFill: PROC [fill: CHAR, size: INT] RETURNS [ROPE] = {
RETURN [Rope.MakeRope[NEW[FillData ← [fill]], size, FetchProc]]};
ItsShowTime: PROC
[lastMark: Microseconds, msg: ROPE, lead: ROPENIL, useCR: BOOLTRUE] = TRUSTED {
delta: Microseconds ← GetMark[] - lastMark;
places: CARDINAL
SELECT delta FROM
< 100 => 6,
< 1000 => 5,
< 10000 => 4,
< 100000 => 3,
< 1000000 => 2,
ENDCASE => 1;
IF lead # NIL THEN WriteRope[lead];
WriteReal[delta / 1E6];
WriteRope[" secs "];
faults ← VMStatistics.pageFaults - faults;
IF faults # 0 THEN {
WriteRope["("];
WriteDecimal[faults];
IF faults = 1
THEN WriteRope[" fault) "]
ELSE WriteRope[" faults) "]};
WriteRope[msg];
IF useCR THEN WriteChar[15C];
faults ← VMStatistics.pageFaults;
};
offset: Microseconds ← 0;
lastDelta: Microseconds ← 0;
RandomRange: PROC [r: ROPE] RETURNS [start,len: INT] = {
size: INT ← r.Size[];
start ← Random.Choose[0, size];
len ← Random.Choose[0, size];
};
ShowNumber: PROC [str: ROPE, number: INT] = {
WriteRope[str];
WriteDecimal[number];
};
CountPieces: PROC [r: ROPE] RETURNS [CARDINAL] = {
count: CARDINAL ← 0;
EachPiece: Rope.PieceActionType = {
count ← count + 1; RETURN [FALSE]};
[] ← Rope.PieceMap[r, 0, Rope.MaxLen, EachPiece];
RETURN [count];
};
ShowGC: PROC = {
gcMark: Microseconds ← 0;
delta: Microseconds ← 0;
afterObjs,afterWords: LONG INTEGER ← 0;
gcMark ← GetMark[];
IF useTraceAndSweep
THEN {
[] ← SafeStorage.ReclaimCollectibleObjects[FALSE, TRUE];
[incarnation: , reason: , wordsReclaimed: afterWords, objectsReclaimed: afterObjs] ←
SafeStorage.WaitForCollectorDone[];
delta ← GetMark[] - gcMark;
ItsShowTime[gcMark, "to collect (trace and sweep)", " "]}
ELSE {
[] ← SafeStorage.ReclaimCollectibleObjects[FALSE, FALSE];
[incarnation: , reason: , wordsReclaimed: afterWords, objectsReclaimed: afterObjs] ←
SafeStorage.WaitForCollectorDone[];
delta ← GetMark[] - gcMark;
ItsShowTime[gcMark, "to collect (normal)", " "]};
ShowNumber[" objs collected: ", afterObjs];
ShowNumber[", words collected: ", afterWords];
WriteChar[15C];
IF afterObjs > 0 THEN {
adj: INT ← afterWords + afterObjs/2;
ShowNumber[" words/obj: ", adj/afterObjs];
IF afterObjs > 0 THEN
ShowNumber[", usecs/obj: ", delta/afterObjs];
IF afterObjs > 0 THEN
ShowNumber[", usecs/word: ", delta/afterWords];
WriteChar[15C]};
WriteChar[15C];
};
ShowCurrentTime: PROC = TRUSTED {
IO.PutF[stream, "At the time, the tone will be %g\n", [time[BasicTime.Now[]]]];
};
GetMark: PROC RETURNS [Microseconds] = TRUSTED {
RETURN [BasicTime.PulsesToMicroseconds[BasicTime.GetClockPulses[]]];
};
startTime: BasicTime.GMT;
SetStartTime: PROC = TRUSTED {
startTime ← BasicTime.Now[];
};
ShowStartTime: PROC = TRUSTED {
IO.PutF[stream, "This test started at %g\n", [time[BasicTime.Now[]]]];
};
AppendString: PROC [dst,src: LONG STRING] = TRUSTED {
pos: CARDINAL = dst.length;
IF src = NIL THEN RETURN;
FOR i: CARDINAL IN [0..src.length) DO
dst[i+pos] ← src[i];
ENDLOOP;
dst.length ← pos + dst.length;
};
NullAction: PROC [c: CHAR] RETURNS [BOOL] = {RETURN [FALSE]};
NullProc: PROC = {};
nullProc: PROC ← NullProc;
NullEntryProc: ENTRY PROC = {};
nullEntryProc: PROC ← NullEntryProc;
dummyREF: REFNIL;
dummyPtr: LONG POINTERNIL;
Dummy: TYPE = RECORD [a,b: INT];
DummyLink: TYPE = RECORD[next: Link ← NIL, a,b: CARDINAL];
Link: TYPE = LONG POINTER TO DummyLink;
sky: Sky ← Sky[red[4]];
dummyREF1: ROPE ← "dummyREF1";
dummyPtr1: Link ← LOOPHOLE[dummyREF1];
Sky: TYPE = RECORD [opt: SELECT tag: * FROM
red => [red: INT],
blue => [blue: INT]
ENDCASE];
RandomPiece: PROC [size: NAT] RETURNS [ROPE] = TRUSTED {
fill: PROC RETURNS [CHAR] = TRUSTED {
nat: NAT ← Random.Choose[40B, 'z-0C];
RETURN [nat + 0C]};
RETURN [Rope.FromProc[Random.Choose[1, size], fill]]};
RandomRope: PROC [maxTotal: NAT ← 0, maxPiece: NAT ← 0]
RETURNS [ROPE] = TRUSTED {
IF maxTotal = 0 THEN maxTotal ← Random.Choose[1, 1000];
IF maxPiece = 0 OR maxPiece > maxTotal THEN maxPiece ← maxTotal;
{size: NAT ← Random.Choose[1, maxTotal];
ref: ROPE ← RandomPiece[maxPiece];
DO
rsize: INT ← Rope.Size[ref];
IF rsize >= size THEN {
ref ← Rope.Substr[ref, Random.Choose[0, size/2], size];
RETURN [ref]};
IF rsize > 0 THEN rsize ← rsize - 1;
ref ← Rope.Replace[ref,
Random.Choose[0, rsize],
Random.Choose[0, rsize/2],
RandomPiece[maxPiece]];
ENDLOOP};
};
CheckReplace: PROC [x: ROPE, start: INT, len: INT, y: ROPE] = TRUSTED {
size: INT ← Rope.Size[x];
pos: INT ← start + len;
IF start > size THEN start ← size;
IF pos < start THEN pos ← start;
IF pos > size THEN pos ← size;
{left: ROPE ← Rope.Replace[x, start, len, y];
r1: ROPE ← Rope.Substr[x, 0, start];
r2: ROPE ← y;
r3: ROPE ← Rope.Substr[x, pos];
c1: ROPE ← Rope.Concat[r1, r2];
right: ROPE ← Rope.Concat[c1, r3];
IF NOT Rope.Equal[left, right] THEN {
left ← Show[left, FALSE, " left"];
r1 ← Show[r1, FALSE, " r1"];
r2 ← Show[r2, FALSE, " r2"];
r3 ← Show[r3, FALSE, " r3"];
c1 ← Show[c1, FALSE, " c1"];
right ← Show[right, FALSE, "right"];
SIGNAL CheckReplaceFailed}};
[] ← Rope.VerifyStructure[x];
};
CheckReplaceFailed: SIGNAL = CODE;
CauseError: PROC = {ERROR MyError};
MyError: ERROR = CODE;
maxBestIndex: NAT = 64;
best: ARRAY [0..maxBestIndex] OF REALALL[0.0];
GopherIt: PROC = TRUSTED {
head: LONG STRING ← "Starting major loop ...";
header: ROPE ← "Starting major loop ...";
headSize: CARDINAL ← head.length;
dummy: REFNIL;
start: INT ← 0;
c: CHAR ← 0C;
operationIndex: NAT ← 0;
str: STRING ← [20];
mark: Pulses ← BasicTime.GetClockPulses[];
InitMark: PROC = TRUSTED {
mark ← BasicTime.GetClockPulses[];
faults ← VMStatistics.pageFaults;
};
TestOne: PROC
[inner: PROC, operations: INT ← 100, dummyProcVar: PROCNIL]
RETURNS [timePerOperation: REAL ← 0.0] = TRUSTED {
elapsed: Microseconds ← 0;
overhead: Microseconds ← 0;
temp: Pulses ← 0;
divisor: REAL ← 1.0;
IF dummyProcVar = NIL THEN dummyProcVar ← dummyProc;
make sure that the IO system is pretty quiet
IO.Flush[stream];
try the initial flush of things into our cache
dummyProcVar[];
inner[];
higher priority makes our numbers somewhat sharper
Process.SetPriority[Process.GetPriority[]+2];
WHILE elapsed < 1000 DO
divisor ← operations;
try to flush the dummyProc into the caches
dummyProcVar[];
dummyProcVar[];
calc the overhead time
temp ← BasicTime.GetClockPulses[];
temp ← BasicTime.GetClockPulses[];
FOR i: INT IN [0..operations) DO
dummyProcVar[];
ENDLOOP;
temp ← BasicTime.GetClockPulses[] - temp;
overhead ← BasicTime.PulsesToMicroseconds[temp];
calc the operation time
IF operations > 3 THEN {
inner[]; -- do once to get some 1st-time effects over
inner[]}; -- do once more to get some 2nd-time effects over
temp ← BasicTime.GetClockPulses[];
temp ← BasicTime.GetClockPulses[];
FOR i: INT IN [0..operations) DO
inner[];
ENDLOOP;
temp ← BasicTime.GetClockPulses[] - temp;
elapsed ← BasicTime.PulsesToMicroseconds[temp];
IF elapsed > overhead THEN elapsed ← elapsed - overhead ELSE elapsed ← 0;
IF operations > 10000 AND elapsed = 0 THEN ERROR;
increase the number of iterations
operations ← operations * 10;
ENDLOOP;
priority is less important now...
Process.SetPriority[Process.GetPriority[]-2];
timePerOperation ← LOOPHOLE[elapsed, INT];
timePerOperation ← timePerOperation / divisor;
};
Report: PROC
[inner: PROC, msg: ROPE, operations: INT ← 100, dummyProcVar: PROCNIL]
= TRUSTED {
faults: INT ← VMStatistics.pageFaults;
timePerOperation: REAL ← TestOne[inner, operations, dummyProcVar];
bestTime: REAL ← best[operationIndex];
IF bestTime > timePerOperation THEN best[operationIndex] ← bestTime ← timePerOperation;
operationIndex ← operationIndex + 1;
faults ← VMStatistics.pageFaults - faults;
WriteRope[" "]; WriteReal[timePerOperation];
WriteRope[" usecs ["]; WriteReal[bestTime]; WriteRope["] "];
IF faults > 0 THEN {
WriteRope[" ("]; WriteDecimal[faults];
IF faults = 1 THEN WriteRope[" fault) "] ELSE WriteRope[" faults) "]};
WriteLine[msg];
};
dummyProc: PROC = TRUSTED {i: INT ← int; {}}; -- one up-level access
dummyProc0: PROC = TRUSTED {}; -- really nothing!!!
innerBool1: PROC = TRUSTED {
b: BOOLTRUE;
IF b THEN {b ← TRUE};
};
innerBool2: PROC = TRUSTED {
b: BOOLFALSE;
IF b THEN {b ← TRUE};
};
innerNarrow: PROC = TRUSTED {
ref: REF ← header;
r: ROPENARROW[ref];
};
innerRefSelect1: PROC = TRUSTED {
ref: REF ← header;
WITH ref SELECT FROM
x: ROPE => {};
ENDCASE => ERROR;
};
innerRefSelect2: PROC = TRUSTED {
ref: REF ← header;
WITH ref SELECT FROM
a: ATOM => {};
x: ROPE => {};
ENDCASE => ERROR;
};
innerRefSelect3: PROC = TRUSTED {
ref: REF ← header;
WITH ref SELECT FROM
a: ATOM => {};
l: LIST OF REF => {};
x: ROPE => {};
ENDCASE => ERROR;
};
innerVariantSelect: PROC = TRUSTED {
ptr: LONG POINTER TO Sky ← @sky;
WITH x: ptr SELECT FROM
red => {};
ENDCASE => ERROR;
};
dummyStringFetch: PROC = TRUSTED {
str: LONG STRING ← head;
index: CARDINAL ← start; -- to allow for narrowing
c ← 0C};
innerStringFetch: PROC = TRUSTED {
c ← head[start];
};
innerRopeInlineFetch: PROC = TRUSTED {
c ← Rope.InlineFetch[header, start];
};
innerRopeFetch: PROC = TRUSTED {
c ← Rope.Fetch[header, start];
};
innerRopeLength: PROC = TRUSTED {
[] ← Rope.Length[header];
};
innerNew: PROC = TRUSTED {
dummyREF ← NEW[Dummy];
};
innerDoublePointerAssign: PROC = TRUSTED {
dummyPtr ← dummyPtr1;
dummyPtr ← NIL;
};
innerDoubleRefAssign: PROC = TRUSTED {
dummyREF ← dummyREF1;
dummyREF ← NIL;
};
innerAppendString: PROC = TRUSTED {
str.length ← 2;
AppendString[str, "bb"];
};
innerRopeConcat: PROC = TRUSTED {
[] ← Rope.Concat["aa", "bb"];
};
dummyCardinalAdd: PROC = TRUSTED {
card: CARDINAL ← cardinal;
card ← cardinal;
};
innerCardinalAdd: PROC = TRUSTED {
card: CARDINAL ← cardinal;
card ← card + cardinal;
};
innerCardinalMult: PROC = TRUSTED {
card: CARDINAL ← cardinal;
card ← card * cardinal;
};
innerCardinalDiv: PROC = TRUSTED {
card: CARDINAL ← cardinal;
card ← cardinal / 100;
};
dummyIntAdd: PROC = TRUSTED {
li: INT ← int;
li ← int;
};
innerIntAdd: PROC = TRUSTED {
li: INT ← int;
li ← li + int;
};
innerIntMult: PROC = TRUSTED {
li: INT ← int;
li ← li * int;
};
innerIntDiv: PROC = TRUSTED {
li: INT ← int;
li ← li / 100;
};
dummyRealAdd: PROC = TRUSTED {
r: REAL ← pi;
r ← pi;
};
innerRealAdd: PROC = TRUSTED {
r: REAL;
r ← pi + pi;
};
innerRealMult: PROC = TRUSTED {
r: REAL;
r ← pi * pi;
};
innerRealDiv: PROC = TRUSTED {
r: REAL;
r ← pi / 100.0;
};
innerNullProc: PROC = TRUSTED {
NullProc[];
};
innerNullProcVar: PROC = TRUSTED {
nullProc[];
};
innerNullEntryProc: PROC = TRUSTED {
NullEntryProc[];
};
innerNullEntryProcVar: PROC = TRUSTED {
nullEntryProc[];
};
innerProcAndCatch: PROC = TRUSTED {
CauseError[! MyError => CONTINUE];
};
innerRandom: PROC = TRUSTED {
[] ← Random.Choose[0, 1000];
};
innerWriteToFile: PROC = TRUSTED {
st: IO.STREAMFS.StreamOpen["GoodTimes.test", create];
FOR i: NAT IN [0..1000) DO
j: CARDINAL ← i MOD headSize;
IO.PutChar[st, 15C];
ENDLOOP;
IO.Close[st];
};
run some timing tests
Report[innerBool1, "{b: BOOL ← TRUE; IF b THEN {b ← TRUE}}",
1000, dummyProc0];
Report[innerBool2, "{b: BOOL ← FALSE; IF b THEN {b ← TRUE}}",
1000, dummyProc0];
Report[innerNarrow, "NARROW", 1000];
Report[innerRefSelect1, "REF SELECT", 1000];
Report[innerRefSelect2, "REF SELECT (2nd case)", 1000];
Report[innerRefSelect3, "REF SELECT (3rd case)", 1000];
Report[innerVariantSelect, "variant record SELECT", 1000];
Report[innerStringFetch, "LONG STRING fetch", 1000, dummyStringFetch];
Report[innerRopeInlineFetch, "Rope.InlineFetch (flat rope)", 1000, dummyStringFetch];
Report[innerRopeFetch, "Rope.Fetch (flat rope)", 1000, dummyStringFetch];
Report[innerRopeLength, "Rope.Length (flat rope)", 1000];
Report[innerNew, "NEW (4-word object)"];
Report[innerDoublePointerAssign, "double long pointer assign (g1 ← g2; g1 ← NIL)",
10000, dummyProc0];
Report[innerDoubleRefAssign, "double AssignRefs (g1 ← g2; g1 ← NIL)",
1000, dummyProc0];
Report[innerAppendString, "AppendString[str, 'bb'] (str = 'aa')"];
Report[innerRopeConcat, "Rope.Concat['aa', 'bb']"];
Report[innerCardinalAdd, "CARDINAL add (card ← 31416 + 31416)",
10000, dummyCardinalAdd];
Report[innerCardinalMult, "CARDINAL multiply (card ← 31416 * 31416)",
10000, dummyCardinalAdd];
Report[innerCardinalDiv, "CARDINAL divide (card ← 31416 / 100)",
10000, dummyCardinalAdd];
Report[innerIntAdd, "INT add (li ← 31416 + 31416)", 1000, dummyIntAdd];
Report[innerIntMult, "INT multiply (li ← 31416 * 31416)", 1000, dummyIntAdd];
Report[innerIntDiv, "INT divide (li ← 31416 / 100)", 1000, dummyIntAdd];
Report[innerRealAdd, "REAL add (li ← 3.1416 + 3.1416)", 1000, dummyRealAdd];
Report[innerRealMult, "REAL multiply (li ← 3.1416 * 3.1416)", 1000, dummyRealAdd];
Report[innerRealDiv, "REAL divide (li ← 3.1416 / 100.0)", 1000, dummyRealAdd];
Report[innerNullProc, "local proc call", 1000, dummyProc0];
Report[innerNullProcVar, "local proc var call", 1000, dummyProc0];
Report[innerNullEntryProc, "local ENTRY proc call", 1000, dummyProc0];
Report[innerNullEntryProcVar, "local ENTRY proc var call", 1000, dummyProc0];
Report[innerProcAndCatch, "proc call & caught ERROR", 100, dummyProc0];
Report[innerRandom, "[] ← Random.Choose[0, 1000]", 100, dummyProc0];
Report[innerWriteToFile, "for FileIO.Open, 1000 IO.PutChars, IO.Close", 10];
WriteRope["Checking consistency of Rope operations... "];
{-- perform some random consistency checks on ropes
last: ROPENIL;
localMark: Microseconds ← GetMark[];
offset ← 0;
FOR i: NAT IN [0..100) DO
ref: ROPE ← RandomRope[100, 40];
size: INT ← ref.Size[];
start: INT ← Random.Choose[0,size];
len: INT ← Random.Choose[0,size];
rep: ROPE ← Rope.Substr[ref, Random.Choose[0,size], Random.Choose[0,size]];
ref ← Rope.Replace[ref, start, len, rep];
CheckReplace[ref, start, len, rep];
last ← ref;
ENDLOOP;
ItsShowTime[localMark, "to check."];
};
ShowGC[];
now try to exercise ropes and the allocator
IF showRopes THEN {
WritePageMark[];
FOR i: CARDINAL IN [0..4) DO
ShowNumber["Round #", i];
{base: ROPE ← Rope.Concat[header, MakeFill['$, 100]];
replSize: CARDINAL ← Random.Choose[1, 6];
rep: ROPE ← MakeFill['*, replSize];
subMark: Microseconds ← GetMark[];
size: INT ← base.Size[];
FOR j: CARDINAL IN [0..10) DO
start ← Random.Choose[0, size];
FOR k: CARDINAL IN [0..100) DO
base ← base.Replace[start, 0, rep];
size ← size + replSize;
start ← start + replSize;
ENDLOOP;
ENDLOOP;
ItsShowTime[subMark, "for 1000 insertions", ", "];
{leaves,nodes,maxDepth: INT ← 0;
[leaves,nodes,maxDepth] ← base.VerifyStructure[];
ShowNumber[" size: ", size];
ShowNumber[", leaves: ", leaves];
ShowNumber[", nodes: ", nodes];
ShowNumber[", depth: ", maxDepth];
{mapTime: Microseconds ← GetMark[];
[] ← Rope.Map[base, 0, Rope.MaxLen, NullAction];
ItsShowTime[mapTime, "to map", "; "];
mapTime ← GetMark[];
base ← base.Balance[];
ItsShowTime[mapTime, "to balance", " ", FALSE];
[leaves,nodes,maxDepth] ← base.VerifyStructure[];
IF size # base.Size[] THEN ERROR; -- Balance failed!
ShowNumber[", leaves: ", leaves];
ShowNumber[", nodes: ", nodes];
ShowNumber[", depth: ", maxDepth];
WriteChar[15C]}}};
ENDLOOP;
ShowGC[];
};
IF swapTraceAndSweep THEN useTraceAndSweep ← NOT useTraceAndSweep;
IF pause >= 0 THEN Process.Pause[Process.MsecToTicks[pause]];
};
InnerRun: PROC [majors: NAT ← 8] = TRUSTED {
oldInterval: INT ← SafeStorage.SetCollectionInterval[1000000];
in: STREAM;
[in,stream] ← ViewerIO.CreateViewerStreams["GoodTimes.ts", NIL, "GoodTimes.ts", FALSE];
SetStartTime[];
[] ← Random.Init[]; -- setup for full range
FOR i: NAT IN [0..maxBestIndex] DO
best[i]← 1.0E9;
ENDLOOP;
FOR major: NAT IN [0..majors) WHILE gopher DO
WriteChar[15C];
ShowStartTime[];
ShowCurrentTime[];
WriteRope["Major loop #"];
WriteDecimal[major];
WriteChar[15C];
GopherIt[];
ShowCurrentTime[];
WritePageMark[];
ENDLOOP;
IO.Close[in];
IO.Close[stream];
[] ← SafeStorage.SetCollectionInterval[oldInterval];
};
Run: PROC [majors: NAT ← 8] = {
InnerRun[majors ! ABORTED => CONTINUE];
};
Test: PROC [majors: NAT ← 8, fork: BOOLTRUE] = TRUSTED {
for living with others peacefully
IF fork
THEN Process.Detach[FORK Run[majors]]
ELSE Run[majors];
};
GoodTimesCommand: Commander.CommandProc = {
Test[];
};
gopher: BOOLTRUE; -- ← FALSE stops the test at the next clean point
abort: BOOLFALSE; -- ← TRUE aborts the test at the next output
showRopes: BOOLTRUE; -- used to determine rope operations printing
useTraceAndSweep: BOOLFALSE; -- determines use of T&S collector
swapTraceAndSweep: BOOLFALSE; -- determines alternate use of T&S
Commander.Register["GoodTimes", GoodTimesCommand, "Some simple timing tests."];
END.