BTreeTestImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Taft, December 10, 1983 2:52 pm
Russ Atkinson (RRA) March 11, 1985 10:41:00 pm PST
DIRECTORY
Basics,
BasicTime,
BTree,
BTreeTestOps,
BTreeVM,
FS,
FSBackdoor,
PrincOps,
PrincOpsUtils,
Random,
Real,
Rope;
BTreeTestImpl:
PROGRAM
IMPORTS Basics, BasicTime, BTree, BTreeVM, FS, FSBackdoor, Random, PrincOpsUtils, Real
EXPORTS BTreeTestOps
= { OPEN BTreeTestOps;
ROPE: TYPE = Rope.ROPE;
Representation primitives
myRepPrim: BTree.RepresentationPrimitives = [
compare: Compare,
compareEntries: CompareEntries,
entrySize: EntrySize,
entryFromRecord: EntryFromRecord,
newRecordFromEntry: NewRecordFromEntry];
Key: TYPE = REF CARDINAL;
Record: TYPE = REF EntryObject;
Entry: TYPE = LONG POINTER TO EntryObject;
EntryObject:
TYPE =
RECORD [
copies: EntSize, -- number of copies of the key
keys: SEQUENCE COMPUTED EntSize OF KeyIndex];
EntSize: TYPE = [0..LAST[BTree.PageSize]-BTree.reservedWordsPerPage];
EntryTable:
TYPE =
RECORD [
count: KeyIndex,
exists: PACKED SEQUENCE maxEntries: KeyIndex OF BOOL];
Compare:
PROC [key: BTree.Key, entry: Entry]
RETURNS [BTree.Comparison] = {
e: CARDINAL = entry.keys[0];
RETURN [
SELECT
NARROW[key, Key]^
FROM
<e => less,
=e => equal,
ENDCASE => greater];
};
CompareEntries:
PROC [entry1, entry2: Entry]
RETURNS [BTree.Comparison] = {
e1: CARDINAL = entry1.keys[0];
e2: CARDINAL = entry2.keys[0];
RETURN [
SELECT e1
FROM
<e2 => less,
=e2 => equal,
ENDCASE => greater];
};
EntrySize:
PROC [entry: Entry]
RETURNS [words: BTree.EntSize] = {
RETURN [SIZE[EntryObject[entry.copies]]] };
EntryFromRecord:
PROC [record: BTree.Record]
RETURNS [entry: Entry] = {
RETURN [LOOPHOLE[record]] };
NewRecordFromEntry:
PROC [entry: Entry]
RETURNS [record: Record] = {
record ← NEW[EntryObject[entry.copies]];
PrincOpsUtils.LongCopy[to: LOOPHOLE[record], from: entry, nwords: SIZE[EntryObject[entry.copies]]];
};
myTree: BTree.Tree;
myStorage: BTreeVM.Handle;
key: Key = NEW[CARDINAL];
record: Record;
entry: Entry;
maxEntSize: EntSize ← FS.WordsForPages[1]/8; -- 1/8 minimum-size page
entryTable: REF EntryTable;
Main procedures callable from interpreter
useFixedSeed: BOOL ← TRUE;
markov: Random.RandomStream;
validateEveryUpdate: BOOL ← FALSE;
millisecondsPerPulse: REAL = 1000.0 / Real.Float[BasicTime.MicrosecondsToPulses[1000000]];
Time:
PROC [iterations:
LONG
CARDINAL]
RETURNS [msPerOp:
ARRAY Operation
OF
REAL] = {
counts: ARRAY Operation OF LONG CARDINAL ← ALL [0];
times: ARRAY Operation OF LONG CARDINAL ← ALL [0];
entriesEnumerated: LONG CARDINAL ← 0;
markov ← Random.Create[0, IF useFixedSeed THEN 0 ELSE -1];
IF myTree=NIL THEN [] ← Create[];
WHILE iterations#0
DO
operation: Operation;
k: KeyIndex;
then: BasicTime.Pulses ← BasicTime.GetClockPulses[];
[operation: operation, key: k] ← PerformRandomOperation[];
times[operation] ← times[operation] + (BasicTime.GetClockPulses[] - then);
counts[operation] ← counts[operation]+(IF operation=validate THEN myTree.GetState[].entryCount ELSE 1);
IF validateEveryUpdate AND operation IN UpdateOperation THEN myTree.Validate[];
iterations ← iterations-1;
ENDLOOP;
FOR operation: Operation
IN Operation
DO
msPerOp[operation] ← (Real.Float[times[operation]]/Real.Float[MAX[counts[operation], 1]]) * millisecondsPerPulse;
ENDLOOP;
};
VMStats:
PROC
RETURNS [refs:
LONG
CARDINAL, hitPercent:
REAL, reads, writes:
LONG
CARDINAL, msPerReadWrite, avgChainLength:
REAL] = {
hits, misses, cumChainLength, cumReadWriteTime: LONG CARDINAL;
[hits: hits, misses: misses, reads: reads, writes: writes, cumChainLength: cumChainLength, cumReadWriteTime: cumReadWriteTime] ← BTreeVM.GetStats[myStorage];
refs ← hits+misses;
hitPercent ← Real.Float[hits]*100.0 / Real.Float[MAX[refs, 1]];
msPerReadWrite ← (Real.Float[cumReadWriteTime] / Real.Float[MAX[reads+writes, 1]]) * millisecondsPerPulse;
avgChainLength ← Real.Float[cumChainLength] / Real.Float[MAX[refs, 1]];
};
MissingEntry: SIGNAL [k: KeyIndex] = CODE;
EnumerateFailedToTerminate: SIGNAL = CODE;
FindMissingEntries:
PROC = {
FOR k: KeyIndex
IN [0..entryTable.maxEntries)
DO
IF entryTable.exists[k] AND ~Lookup[k] THEN SIGNAL MissingEntry[k];
ENDLOOP;
};
EnumerateBackward:
PROC [usePathStk:
BOOL ←
FALSE] = {
EntryProc:
PROC [entry: Entry] = {
found ← entry#NIL;
IF found THEN foundKey ← entry.keys[0];
};
found: BOOL;
foundKey: KeyIndex ← entryTable.maxEntries;
pathStk: BTree.PathStk ← IF usePathStk THEN BTree.NewPathStk[] ELSE NIL;
FOR k: KeyIndex
DECREASING
IN [0..entryTable.maxEntries)
DO
IF entryTable.exists[k]
THEN {
key^ ← foundKey;
myTree.ReadEntry[key: key, Proc: EntryProc, relation: less, pathStk: pathStk, useExistingPath: usePathStk];
IF ~found OR foundKey#k THEN SIGNAL MissingEntry[k];
};
ENDLOOP;
key^ ← foundKey;
myTree.ReadEntry[key: key, Proc: EntryProc, relation: less, pathStk: pathStk, useExistingPath: usePathStk];
IF found THEN SIGNAL EnumerateFailedToTerminate;
};
Subsidiary procedures
file: FS.OpenFile ← FS.nullOpenFile;
Create:
PUBLIC
SAFE
PROC [cacheSize: BTreeVM.CacheSize ← 20, initialize:
BOOL ←
TRUE, initialFileSize:
INT ← 20, filePagesPerPage: BTreeVM.FilePagesPerPage ← 1, maxEntries: KeyIndex ← 10000]
RETURNS [tree: BTree.Tree, storage: BTreeVM.Handle] =
TRUSTED {
IF initialize AND file#FS.nullOpenFile AND file.GetInfo[].pages<initialFileSize THEN Destroy[];
IF file=
FS.nullOpenFile
THEN {
file ← FS.Create[name: "///Temp/BTreeTest.tree", pages: initialFileSize];
initialize ← TRUE;
};
storage ← BTreeVM.Open[file: FSBackdoor.GetFileHandle[file], filePagesPerPage: filePagesPerPage, cacheSize: cacheSize];
tree ← BTree.New[repPrim: myRepPrim, storPrim: [BTreeVM.ReferencePage, BTreeVM.ReleasePage], minEntrySize: 2];
tree.Open[storage: storage, pageSize: FS.WordsForPages[filePagesPerPage], initialize: initialize];
IF initialize
THEN {
record ← NEW[EntryObject[maxEntSize]];
entry ← LOOPHOLE[record];
entryTable ← NEW [EntryTable[maxEntries]];
entryTable.count ← 0;
FOR i: KeyIndex
IN [0..entryTable.maxEntries)
DO
entryTable.exists[i] ← FALSE;
ENDLOOP;
};
myTree ← tree;
myStorage ← storage;
};
Destroy:
PUBLIC
SAFE
PROC =
TRUSTED {
IF file#
FS.nullOpenFile
THEN {
fullFName: ROPE = file.GetName[].fullFName;
file.Close[];
FS.Delete[fullFName ! FS.Error => CONTINUE];
file ← FS.nullOpenFile;
};
myTree ← NIL;
myStorage ← NIL;
};
keyOfInterest: CARDINAL ← LAST[CARDINAL];
EncounteredKeyOfInterest: SIGNAL = CODE;
PerformRandomOperation:
PUBLIC
SAFE
PROC
RETURNS [operation: Operation, key: KeyIndex] =
TRUSTED {
SELECT Random.ChooseInt[markov, 0, 250+entryTable.count/2]
FROM
0 => {
occasionally validate the tree. The frequency of this decreases in proportion to the size of the tree, since each enumeration takes longer as the tree gets larger.
myTree.Validate[];
operation ← validate;
};
ENDCASE => {
otherwise, choose a random key and perform some operation
key ← Random.ChooseInt[markov, 0, entryTable.maxEntries-1];
IF key=keyOfInterest THEN SIGNAL EncounteredKeyOfInterest;
IF entryTable.exists[key]
THEN {
key exists. 25% of the time verify that looking it up succeeds, 25% of the time delete it, and 50% of the time replace it with a new entry of a different size.
SELECT Random.ChooseInt[markov, 0, 3]
FROM
0 => {
operation ← lookup;
IF ~Lookup[key] THEN ERROR; -- failed to find existing key
};
1 => {
operation ← delete;
IF ~Delete[key] THEN ERROR; -- failed to find existing key
};
ENDCASE => {
operation ← replace;
Insert[key];
};
}
ELSE {
key does not exist. 10% of the time verify that looking it up fails, and 90% of the time insert an entry with this key.
SELECT Random.ChooseInt[markov, 0, 9]
FROM
0 => {
operation ← lookup;
IF Lookup[key] THEN ERROR; -- found key that it shouldn't have
};
ENDCASE => {
operation ← insert;
Insert[key];
};
};
};
};
GetEntryCount:
PUBLIC
SAFE
PROC
RETURNS [count:
LONG
CARDINAL] =
CHECKED {
RETURN [entryTable.count];
};
Lookup:
PROC [keyVal: KeyIndex]
RETURNS [found:
BOOL] = {
EntryProc:
PROC [entry: Entry] = {
found ← entry#NIL;
IF found
THEN
FOR i:
CARDINAL
IN [0..entry.copies)
DO
IF entry.keys[i]#keyVal THEN ERROR; -- entry has wrong contents
ENDLOOP;
};
key^ ← keyVal;
myTree.ReadEntry[key: key, Proc: EntryProc];
};
Insert:
PROC [keyVal: KeyIndex] = {
ProduceEntry:
PROC [bEntry: BTree.Entry] = {
PrincOpsUtils.LongCopy[to: bEntry, from: entry, nwords: words];
};
copies: CARDINAL = Random.ChooseInt[markov, 1, maxEntSize];
words: EntSize = SIZE[EntryObject[copies]];
entry.copies ← copies;
FOR i:
CARDINAL
IN [0..copies)
DO
entry.keys[i] ← keyVal;
ENDLOOP;
key^ ← keyVal;
myTree.UpdateEntry[key: key, words: words, Proc: ProduceEntry];
IF ~entryTable.exists[keyVal]
THEN {
entryTable.exists[keyVal] ← TRUE;
entryTable.count ← entryTable.count+1;
};
};
Delete:
PROC [keyVal: KeyIndex]
RETURNS [found:
BOOL] = {
key^ ← keyVal;
found ← myTree.DeleteKey[key: key];
IF found
THEN {
entryTable.count ← entryTable.count-1;
entryTable.exists[keyVal] ← FALSE;
};
};
Yucky arithmetic lifted from SystemImpl (in Nucleus some day?)
MultThenDiv:
PROC [m1:
LONG
CARDINAL, m2:
CARDINAL, dv:
CARDINAL]
RETURNS [result:
LONG
CARDINAL] = {
OPEN Basics, mm1: LOOPHOLE[m1, Basics.LongNumber.num];
t:
MACHINE
DEPENDENT
RECORD [
SELECT
OVERLAID *
FROM
separate => [low, mid, high: CARDINAL],
lower => [lowlong: LONG CARDINAL, junk: CARDINAL],
higher => [junk: CARDINAL, highlong: LONG CARDINAL],
ENDCASE];
t.lowlong ← LongMult[mm1.lowbits, m2];
IF mm1.highbits # 0
THEN
{
t.highlong ← LongMult[mm1.highbits, m2] + t.mid;
IF t.high # 0
THEN
{ OPEN q: LOOPHOLE[result, Basics.LongNumber.num];
have to do triple divide
IF t.high >= dv THEN t.high ← t.high MOD dv; -- overflow; lowbits will be right
[quotient: q.highbits, remainder: t.mid] ← LongDivMod[t.highlong, dv];
q.lowbits ← LongDiv[t.lowlong, dv];
RETURN;
};
};
t.high is 0, so let mesa do the work...
RETURN[t.lowlong/LONG[dv]];
};
}.