Maintain a pool of empty symbol tables that can be reused.

The motivation for this is that allocating and clearing symbol tables is a common operation, especially with C99+, where a construct like "if (...) { ... }" involves three levels of scope with their own symbol tables. In some tests, it could take an appreciable fraction of total execution time (sometimes ~10%).

This patch allows symbol tables that have already been allocated and cleared to be reused for a subsequent scope, as long as they are still empty. It does this by maintaining a pool of empty symbol tables and taking one from there rather than allocating a new one when possible.

We impose a somewhat arbitrary limit of MaxBlock/150000 on the number of symbol tables we keep, to avoid filling up memory with them. It would probably be better to use purgeable handles here, but that would be a little more work, and this should be good enough for now.
This commit is contained in:
Stephen Heumann 2022-12-13 21:05:55 -06:00
parent 705c9d36a2
commit 09fbfb1905
3 changed files with 57 additions and 10 deletions

View File

@ -74,6 +74,7 @@ interface
const
{hashsize appears in CCOMMON.ASM}
hashSize = 876; {# hash buckets - 1}
{NOTE: hashsize2 is used in Symbol.asm}
hashSize2 = 1753; {# hash buckets * 2 - 1}
maxLine = 255; {max length of a line}
maxPath = 255; {max length of a path name}

View File

@ -9,11 +9,12 @@
****************************************************************
*
ClearTable private cc
tableSize equ 7026 sizeof(symbolTable)
hashSize2 equ 1753 # hash buckets * 2 - 1
sizeofBuckets equ 4*(hashSize2+1) sizeof(symbolTable.buckets)
subroutine (4:table),0
ldy #tableSize-2
ldy #sizeofBuckets-2
lda #0
lb1 sta [table],Y
dey

View File

@ -75,6 +75,7 @@ type
buckets: array[0..hashSize2] of identPtr; {hash buckets}
next: symbolTablePtr; {next symbol table}
staticNum: packed array[1..6] of char; {staticNum at start of table}
isEmpty: boolean; {is the pool empty (nothing in buckets)?}
end;
var
@ -310,6 +311,9 @@ type
var
staticNum: packed array[1..6] of char; {static variable number}
tablePool: symbolTablePtr; {pool of reusable empty symbol tables}
tablePoolSize: 0..maxint; {number of tables in pool}
tablePoolMaxSize: 0..maxint; {max number of tables in pool}
{- Imported from expression.pas --------------------------------}
@ -414,6 +418,10 @@ procedure Purge; extern;
{ write any constant bytes to the output buffer }
{- Imported from IIGS Memory Manager ---------------------------}
function MaxBlock: longint; tool ($02, $1C);
{---------------------------------------------------------------}
procedure ClearTable (table: symbolTable); extern;
@ -698,6 +706,22 @@ procedure DoGlobals;
{ declare the ~globals and ~arrays segments }
procedure FreeTablePool;
{ free the symbol table pool }
var
tPtr: symbolTablePtr;
begin {FreeTablePool}
while tablePool <> nil do begin
tPtr := tablePool;
tablePool := tPtr^.next;
dispose(tPtr);
end;
end; {FreeTablePool}
procedure StaticInit (variable: identPtr);
{ statically initialize a variable }
@ -1053,6 +1077,8 @@ begin {DoGlobals}
{if printSymbols then {debug}
{ PrintTable(globalTable); {debug}
FreeTablePool; {dispose of unneeded symbol tables}
{declare the ~globals segment, which holds non-array data types}
if smallMemoryModel then
currentSegment := ' '
@ -1677,8 +1703,12 @@ var
begin {InitSymbol}
staticNum := '~0000'; {no functions processed}
table := nil; {initialize the global symbol table}
tablePool := nil; {table pool is initially empty}
tablePoolSize := 0;
tablePoolMaxSize := ord(MaxBlock div 150000); {limit size of pool based on RAM}
PushTable;
globalTable := table;
globalTable^.isEmpty := false; {global table is never treated as empty}
functionTable := nil;
{declare base types}
new(sCharPtr); {signed char}
@ -2348,12 +2378,13 @@ if needSymbol then begin
{p^.used := false;} {unused for now}
if space <> fieldListSpace then {insert the symbol in the hash bucket}
begin
if itype = nil then
hashPtr := pointer(ord4(table)+Hash(name))
else if isGlobal then
hashPtr := pointer(ord4(globalTable)+Hash(name))
else
if (itype = nil) or not isGlobal then begin
hashPtr := pointer(ord4(table)+Hash(name));
table^.isEmpty := false;
end {if}
else begin
hashPtr := pointer(ord4(globalTable)+Hash(name));
end; {else}
if space = tagSpace then
hashPtr := pointer(ord4(hashPtr) + 4*(hashSize+1));
p^.next := hashPtr^;
@ -2452,7 +2483,13 @@ if (lint & lintUnused) <> 0 then
CheckUnused(tPtr);
if tPtr^.next <> nil then begin
table := table^.next;
dispose(tPtr);
if not tPtr^.isEmpty or (tablePoolSize = tablePoolMaxSize) then
dispose(tPtr)
else begin
tPtr^.next := tablePool;
tablePool := tPtr;
tablePoolSize := tablePoolSize + 1;
end; {else}
end; {if}
end; {PopTable}
@ -2480,8 +2517,16 @@ repeat
done := i = 1;
end; {if}
until done;
new(tPtr); {create a new symbol table}
ClearTable(tPtr^);
if tablePool <> nil then begin {use existing empty table if available}
tPtr := tablePool;
tablePool := tPtr^.next;
tablePoolSize := tablePoolSize - 1;
end {if}
else begin
new(tPtr); {...or create a new symbol table}
ClearTable(tPtr^);
tPtr^.isEmpty := true;
end; {else}
tPtr^.next := table;
table := tPtr;
tPtr^.staticNum := staticNum; {record the static symbol table number}