bring back id table algorithm instead of std table [backport:2.2] (#24930)

refs #24929, partially reverts #23403

Instead of using `Table[ItemId, T]`, the old algorithm is brought back
into `TIdTable[T]` to prevent a performance regression. The inheritance
removal from #23403 still holds, only `ItemId`s are stored.

(cherry picked from commit 82553384d1)
This commit is contained in:
metagn
2025-05-06 10:36:20 +03:00
committed by narimiran
parent 39757d421e
commit c385fcb6be
7 changed files with 111 additions and 26 deletions

View File

@@ -796,6 +796,15 @@ type
TPairSeq* = seq[TPair] TPairSeq* = seq[TPair]
TIdPair*[T] = object
key*: ItemId
val*: T
TIdPairSeq*[T] = seq[TIdPair[T]]
TIdTable*[T] = object
counter*: int
data*: TIdPairSeq[T]
TNodePair* = object TNodePair* = object
h*: Hash # because it is expensive to compute! h*: Hash # because it is expensive to compute!
key*: PNode key*: PNode
@@ -940,9 +949,11 @@ proc getPIdent*(a: PNode): PIdent {.inline.} =
const const
moduleShift = when defined(cpu32): 20 else: 24 moduleShift = when defined(cpu32): 20 else: 24
template id*(a: PType | PSym): int = template toId*(a: ItemId): int =
let x = a let x = a
(x.itemId.module.int shl moduleShift) + x.itemId.item.int (x.module.int shl moduleShift) + x.item.int
template id*(a: PType | PSym): int = toId(a.itemId)
type type
IdGenerator* = ref object # unfortunately, we really need the 'shared mutable' aspect here. IdGenerator* = ref object # unfortunately, we really need the 'shared mutable' aspect here.
@@ -1269,6 +1280,11 @@ proc copyStrTable*(dest: var TStrTable, src: TStrTable) =
setLen(dest.data, src.data.len) setLen(dest.data, src.data.len)
for i in 0..high(src.data): dest.data[i] = src.data[i] for i in 0..high(src.data): dest.data[i] = src.data[i]
proc copyIdTable*[T](dest: var TIdTable[T], src: TIdTable[T]) =
dest.counter = src.counter
newSeq(dest.data, src.data.len)
for i in 0..high(src.data): dest.data[i] = src.data[i]
proc copyObjectSet*(dest: var TObjectSet, src: TObjectSet) = proc copyObjectSet*(dest: var TObjectSet, src: TObjectSet) =
dest.counter = src.counter dest.counter = src.counter
setLen(dest.data, src.data.len) setLen(dest.data, src.data.len)
@@ -1607,6 +1623,16 @@ proc initStrTable*(): TStrTable =
result = TStrTable(counter: 0) result = TStrTable(counter: 0)
newSeq(result.data, StartSize) newSeq(result.data, StartSize)
proc initIdTable*[T](): TIdTable[T] =
result = TIdTable[T](counter: 0)
newSeq(result.data, StartSize)
proc resetIdTable*[T](x: var TIdTable[T]) =
x.counter = 0
# clear and set to old initial size:
setLen(x.data, 0)
setLen(x.data, StartSize)
proc initObjectSet*(): TObjectSet = proc initObjectSet*(): TObjectSet =
result = TObjectSet(counter: 0) result = TObjectSet(counter: 0)
newSeq(result.data, StartSize) newSeq(result.data, StartSize)
@@ -2135,14 +2161,8 @@ proc isTrue*(n: PNode): bool =
n.kind == nkIntLit and n.intVal != 0 n.kind == nkIntLit and n.intVal != 0
type type
TypeMapping* = Table[ItemId, PType] TypeMapping* = TIdTable[PType]
SymMapping* = Table[ItemId, PSym] SymMapping* = TIdTable[PSym]
template idTableGet*(tab: typed; key: PSym | PType): untyped = tab.getOrDefault(key.itemId) template initSymMapping*(): SymMapping = initIdTable[PSym]()
template idTablePut*(tab: typed; key, val: PSym | PType) = tab[key.itemId] = val template initTypeMapping*(): TypeMapping = initIdTable[PType]()
template initSymMapping*(): Table[ItemId, PSym] = initTable[ItemId, PSym]()
template initTypeMapping*(): Table[ItemId, PType] = initTable[ItemId, PType]()
template resetIdTable*(tab: Table[ItemId, PSym]) = tab.clear()
template resetIdTable*(tab: Table[ItemId, PType]) = tab.clear()

View File

@@ -713,6 +713,70 @@ iterator items*(tab: TStrTable): PSym =
yield s yield s
s = nextIter(it, tab) s = nextIter(it, tab)
proc isNil(x: ItemId): bool {.inline.} =
x.module == 0 and x.item == 0
proc hasEmptySlot[T](data: TIdPairSeq[T]): bool =
for h in 0..high(data):
if isNil(data[h].key):
return true
result = false
proc idTableRawGet[T](t: TIdTable[T], key: int): int =
var h: Hash
h = key and high(t.data) # start with real hash value
while not isNil(t.data[h].key):
if toId(t.data[h].key) == key:
return h
h = nextTry(h, high(t.data))
result = - 1
proc getOrDefault*[T](t: TIdTable[T], key: ItemId): T =
var index = idTableRawGet(t, toId(key))
if index >= 0: result = t.data[index].val
else: result = default(T)
template idTableGet*[T](t: TIdTable[T], key: PType | PSym): T =
getOrDefault(t, key.itemId)
proc idTableRawInsert[T](data: var TIdPairSeq[T], key: ItemId, val: T) =
var h: Hash
let keyId = toId(key)
h = keyId and high(data)
while not isNil(data[h].key):
assert(toId(data[h].key) != keyId)
h = nextTry(h, high(data))
assert(isNil(data[h].key))
data[h].key = key
data[h].val = val
proc `[]=`*[T](t: var TIdTable[T], key: ItemId, val: T) =
var
index: int
n: TIdPairSeq[T]
index = idTableRawGet(t, toId(key))
if index >= 0:
assert(not isNil(t.data[index].key))
t.data[index].val = val
else:
if mustRehash(t.data.len, t.counter):
newSeq(n, t.data.len * GrowthFactor)
for i in 0..high(t.data):
if not isNil(t.data[i].key):
idTableRawInsert(n, t.data[i].key, t.data[i].val)
assert(hasEmptySlot(n))
swap(t.data, n)
idTableRawInsert(t.data, key, val)
inc(t.counter)
template idTablePut*[T](t: var TIdTable[T], key: PType | PSym, val: T) =
t[key.itemId] = val
iterator idTablePairs*[T](t: TIdTable[T]): tuple[key: ItemId, val: T] =
for i in 0..high(t.data):
if not isNil(t.data[i].key):
yield (t.data[i].key, t.data[i].val)
proc initIITable(x: var TIITable) = proc initIITable(x: var TIITable) =
x.counter = 0 x.counter = 0
newSeq(x.data, StartSize) newSeq(x.data, StartSize)

View File

@@ -1,5 +1,5 @@
import std/[tables] import std/[tables]
import ast import ast, astalgo
type type
LayeredIdTableObj* {.acyclic.} = object LayeredIdTableObj* {.acyclic.} = object
@@ -28,14 +28,15 @@ proc shallowCopy*(pt: LayeredIdTable): LayeredIdTable {.inline.} =
## copies only the type bindings of the current layer, but not any parent layers, ## copies only the type bindings of the current layer, but not any parent layers,
## useful for write-only bindings ## useful for write-only bindings
result = LayeredIdTable(topLayer: pt.topLayer, nextLayer: pt.nextLayer, previousLen: pt.previousLen) result = LayeredIdTable(topLayer: pt.topLayer, nextLayer: pt.nextLayer, previousLen: pt.previousLen)
#copyIdTable(result.topLayer, pt.topLayer)
proc currentLen*(pt: LayeredIdTable): int = proc currentLen*(pt: LayeredIdTable): int =
## the sum of the cached total binding count of the parents and ## the sum of the cached total binding count of the parents and
## the current binding count, just used to track if bindings were added ## the current binding count, just used to track if bindings were added
pt.previousLen + pt.topLayer.len pt.previousLen + pt.topLayer.counter
proc newTypeMapLayer*(pt: LayeredIdTable): LayeredIdTable = proc newTypeMapLayer*(pt: LayeredIdTable): LayeredIdTable =
result = LayeredIdTable(topLayer: initTable[ItemId, PType](), previousLen: pt.currentLen) result = LayeredIdTable(topLayer: initTypeMapping(), previousLen: pt.currentLen)
when useRef: when useRef:
result.nextLayer = pt result.nextLayer = pt
else: else:
@@ -56,7 +57,7 @@ proc setToPreviousLayer*(pt: var LayeredIdTable) {.inline.} =
iterator pairs*(pt: LayeredIdTable): (ItemId, PType) = iterator pairs*(pt: LayeredIdTable): (ItemId, PType) =
var tm = pt var tm = pt
while true: while true:
for (k, v) in pairs(tm.topLayer): for (k, v) in idTablePairs(tm.topLayer):
yield (k, v) yield (k, v)
if tm.nextLayer == nil: if tm.nextLayer == nil:
break break

View File

@@ -909,15 +909,15 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode,
if c.inGenericContext > 0 and c.matchedConcept == nil: if c.inGenericContext > 0 and c.matchedConcept == nil:
result = semGenericStmt(c, n) result = semGenericStmt(c, n)
result.typ() = makeTypeFromExpr(c, result.copyTree) result.typ() = makeTypeFromExpr(c, result.copyTree)
elif efNoUndeclared in flags:
result = nil
elif efExplain notin flags: elif efExplain notin flags:
# repeat the overload resolution, # repeat the overload resolution,
# this time enabling all the diagnostic output (this should fail again) # this time enabling all the diagnostic output (this should fail again)
result = semOverloadedCall(c, n, nOrig, filter, flags + {efExplain}) result = semOverloadedCall(c, n, nOrig, filter, flags + {efExplain})
elif efNoUndeclared notin flags:
result = nil
notFoundError(c, n, errors)
else: else:
result = nil result = nil
notFoundError(c, n, errors)
proc explicitGenericInstError(c: PContext; n: PNode): PNode = proc explicitGenericInstError(c: PContext; n: PNode): PNode =
localError(c.config, getCallLineInfo(n), errCannotInstantiateX % renderTree(n)) localError(c.config, getCallLineInfo(n), errCannotInstantiateX % renderTree(n))

View File

@@ -17,7 +17,7 @@ when defined(nimPreviewSlimSystem):
import import
options, ast, msgs, idents, renderer, options, ast, msgs, idents, renderer,
magicsys, vmdef, modulegraphs, lineinfos, pathutils, layeredtable, magicsys, vmdef, modulegraphs, lineinfos, pathutils, layeredtable,
types, lowerings, trees, parampatterns types, lowerings, trees, parampatterns, astalgo
import ic / ic import ic / ic
@@ -42,7 +42,7 @@ type
breakInLoop*: bool # whether we are in a loop without block breakInLoop*: bool # whether we are in a loop without block
next*: PProcCon # used for stacking procedure contexts next*: PProcCon # used for stacking procedure contexts
mappingExists*: bool mappingExists*: bool
mapping*: Table[ItemId, PSym] mapping*: SymMapping
caseContext*: seq[tuple[n: PNode, idx: int]] caseContext*: seq[tuple[n: PNode, idx: int]]
localBindStmts*: seq[PNode] localBindStmts*: seq[PNode]
@@ -258,7 +258,7 @@ proc popProcCon*(c: PContext) {.inline.} = c.p = c.p.next
proc put*(p: PProcCon; key, val: PSym) = proc put*(p: PProcCon; key, val: PSym) =
if not p.mappingExists: if not p.mappingExists:
p.mapping = initTable[ItemId, PSym]() p.mapping = initSymMapping()
p.mappingExists = true p.mappingExists = true
#echo "put into table ", key.info #echo "put into table ", key.info
p.mapping[key.itemId] = val p.mapping[key.itemId] = val

View File

@@ -68,8 +68,8 @@ type
TReplTypeVars* = object TReplTypeVars* = object
c*: PContext c*: PContext
typeMap*: LayeredIdTable # map PType to PType typeMap*: LayeredIdTable # map PType to PType
symMap*: SymMapping # map PSym to PSym symMap*: SymMapping # map PSym to PSym
localCache*: TypeMapping # local cache for remembering already replaced localCache*: TypeMapping # local cache for remembering already replaced
# types during instantiation of meta types # types during instantiation of meta types
# (they are not stored in the global cache) # (they are not stored in the global cache)
info*: TLineInfo info*: TLineInfo

View File

@@ -40,7 +40,7 @@ import closureiters, lambdalifting
type type
PTransCon = ref object # part of TContext; stackable PTransCon = ref object # part of TContext; stackable
mapping: Table[ItemId, PNode] # mapping from symbols to nodes mapping: TIdTable[PNode] # mapping from symbols to nodes
owner: PSym # current owner owner: PSym # current owner
forStmt: PNode # current for stmt forStmt: PNode # current for stmt
forLoopBody: PNode # transformed for loop body forLoopBody: PNode # transformed for loop body
@@ -78,7 +78,7 @@ proc newTransNode(kind: TNodeKind, n: PNode,
proc newTransCon(owner: PSym): PTransCon = proc newTransCon(owner: PSym): PTransCon =
assert owner != nil assert owner != nil
result = PTransCon(mapping: initTable[ItemId, PNode](), owner: owner) result = PTransCon(mapping: initIdTable[PNode](), owner: owner)
proc pushTransCon(c: PTransf, t: PTransCon) = proc pushTransCon(c: PTransf, t: PTransCon) =
t.next = c.transCon t.next = c.transCon