Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/chrono/Graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,8 @@ export class ChronoGraph extends Base {

this.$followingRevision = undefined

this.markAndSweep()
// Perf: skip markAndSweep on initial commit — no meaningful revision history to compact
if (!this._isInitialCommit) this.markAndSweep()
} else {
// `baseRevisionStable` might be already cleared in the `reject` method of the graph
if (this.baseRevisionStable) this.baseRevision = this.baseRevisionStable
Expand Down
19 changes: 17 additions & 2 deletions src/chrono/Identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,21 @@ export class Identifier<ValueT = any, ContextT extends Context = Context> extend
isWritingUndefined : boolean = false


// Perf: flag for sync identifiers — set on prototype by CalculatedValueSync and Variable
@prototypeValue(true)
genCalc : boolean

newQuark (createdAt : Revision) : InstanceType<this[ 'quarkClass' ]> {
// micro-optimization - we don't pass a config object to the `new` constructor
// but instead assign directly to instance
const newQuark = this.quarkClass.new() as InstanceType<this[ 'quarkClass' ]>

newQuark.identifier = this
newQuark.needToBuildProposedValue = this.proposedValueIsBuilt
// Perf: cache calculation and context to avoid getter delegation per startCalculation
newQuark.calculation = this.calculation
newQuark.context = this.context || this
;(newQuark as any)._isGenCalc = this.genCalc

return newQuark
}
Expand Down Expand Up @@ -263,10 +271,11 @@ export const IdentifierC = <ValueT, ContextT extends Context>(config : Partial<I
Identifier.new(config) as Identifier<ValueT, ContextT>


//@ts-ignore
export const QuarkSync = Quark.mix(CalculationSync.mix(Map))
// Perf: unified quark class — both sync and gen identifiers use the same class
// so V8 sees a single hidden class, eliminating LoadIC_Megamorphic overhead (~14% of CPU)
//@ts-ignore
export const QuarkGen = Quark.mix(CalculationGen.mix(Map))
export const QuarkSync = QuarkGen

//---------------------------------------------------------------------------------------------------------------------
/**
Expand All @@ -279,6 +288,9 @@ export class Variable<ValueT = any> extends Identifier<ValueT, typeof ContextSyn
@prototypeValue(Levels.UserInput)
level : Levels

@prototypeValue(false)
genCalc : boolean

@prototypeValue(QuarkSync)
quarkClass : QuarkConstructor

Expand Down Expand Up @@ -310,6 +322,9 @@ export function VariableC<ValueT> (...args) : Variable<ValueT> {
*/
export class CalculatedValueSync<ValueT = any> extends Identifier<ValueT, typeof ContextSync> {

@prototypeValue(false)
genCalc : boolean

@prototypeValue(QuarkSync)
quarkClass : QuarkConstructor

Expand Down
15 changes: 5 additions & 10 deletions src/chrono/Quark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,9 @@ class Quark extends base {
}


get calculation () : this[ 'identifier' ][ 'calculation' ] {
return this.identifier.calculation
}


get context () : any {
return this.identifier.context || this.identifier
}
// Perf: cached from identifier at creation time to avoid getter delegation per calculation
calculation : this[ 'identifier' ][ 'calculation' ]
context : any


forceCalculation () {
Expand Down Expand Up @@ -229,10 +224,10 @@ class Quark extends base {
}


addOutgoingTo (toQuark : Quark, type : EdgeType) {
addOutgoingTo (toQuark : Quark, type : EdgeType, toIdentifier : Identifier) {
const outgoing = type === EdgeType.Normal ? this as Map<Identifier, Quark> : this.getOutgoingPast()

outgoing.set(toQuark.identifier, toQuark)
outgoing.set(toIdentifier, toQuark)
}


Expand Down
53 changes: 37 additions & 16 deletions src/chrono/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,19 @@ export class Transaction extends Base {
// during a transaction, so these results are stable and can be safely cached.
// Entries are removed from cache when a shadow quark is created for that identifier
// (via entries.set), since after that point the transaction's own entry takes precedence.

// Perf: flag to skip base revision lookups entirely when base is empty (initial commit)
baseRevisionEmpty : boolean = false
baseRevisionCache : Map<Identifier, Quark> = new Map()

readingIdentifier : Identifier

// A Set of faced computation cycles
cycles : Set<ComputationCycle> = new Set()

// Perf: fast boolean flag for cycle presence — avoids Map.has() hash computation in hot loops
hasCycles : boolean = false

// A Map of identifiers that caused a cycle
cycledIdentifiers : Map<Identifier, ComputationCycle> = new Map()

Expand All @@ -131,6 +137,9 @@ export class Transaction extends Base {
// instead inside of `read` delegate to `yieldSync` for non-identifiers
this.onEffectSync = /*this.onEffectAsync =*/ this.read.bind(this)
this.onEffectAsync = this.readAsync.bind(this)

// Perf: detect empty base revision so cachedBaseRevisionLookup can short-circuit
this.baseRevisionEmpty = this.baseRevision.scope.size === 0 && !this.baseRevision.previous
}


Expand Down Expand Up @@ -772,21 +781,29 @@ export class Transaction extends Base {
async commitAsync (args? : CommitArguments) : Promise<TransactionCommitResult> {
this.preCommit(args)

// Perf: use the synchronous commit path when no async effects are possible.
// This avoids all generator overhead (~41% faster for large initial loads).
const canUseSyncPath = this.graph && this.graph.onComputationCycle !== 'effect'

return this.ongoing = this.ongoing.then(() => {
return runGeneratorAsyncWithEffect(this.onEffectAsync, this.calculateTransitions, [ this.onEffectAsync ], this)
if (canUseSyncPath) {
this.calculateTransitionsSync(this.onEffectSync)
}
else {
return runGeneratorAsyncWithEffect(this.onEffectAsync, this.calculateTransitions, [ this.onEffectAsync ], this)
}
}).then(() => {
return this.postCommit()
})

// await runGeneratorAsyncWithEffect(this.onEffectAsync, this.calculateTransitions, [ this.onEffectAsync ], this)
//
// return this.postCommit()
}


// Perf: cached wrapper for baseRevision.getLatestEntryFor().
// Avoids repeated O(revisions) walks for the same identifier within a commit cycle.
cachedBaseRevisionLookup (identifier : Identifier) : Quark {
// Perf: short-circuit for initial commit when base revision is empty
if (this.baseRevisionEmpty) return null

const cache = this.baseRevisionCache

let result = cache.get(identifier)
Expand Down Expand Up @@ -867,7 +884,7 @@ export class Transaction extends Base {
this.entries.set(identifierRead, entry)
}

entry.addOutgoingTo(activeEntry, type)
entry.addOutgoingTo(activeEntry, type, identifier)

return entry
}
Expand Down Expand Up @@ -1179,6 +1196,7 @@ export class Transaction extends Base {
}

addCycle (cycle : ComputationCycle) {
this.hasCycles = true
this.cycles.add(cycle)

const { cycledIdentifiers } = this
Expand Down Expand Up @@ -1213,6 +1231,7 @@ export class Transaction extends Base {
}

clearCycles () {
this.hasCycles = false
this.cycles.clear()

this.cycledIdentifiers.clear()
Expand All @@ -1238,22 +1257,24 @@ export class Transaction extends Base {

// If the identifier is involved into a cycle the transaction faced
// we ignore it and proceed ot the next step
if (cycledIdentifiers.has(identifier)) {
if (this.hasCycles && cycledIdentifiers.has(identifier)) {
// add dependent identifiers to the list of involved in a cycle
this.markDependentCycledIdentifiers (entry, cycledIdentifiers.get(identifier))
this.markDependentCycledIdentifiers(entry, cycledIdentifiers.get(identifier))

entry.cleanup()
stack.pop()
continue
}

// TODO can avoid `.get()` call by comparing some another "epoch" counter on the entry
const ownEntry = entries.get(identifier)
if (ownEntry !== entry) {
entry.cleanup()
// Perf: skip ownership check during initial commit — entries are never replaced
if (!this.baseRevisionEmpty) {
const ownEntry = entries.get(identifier)
if (ownEntry !== entry) {
entry.cleanup()

stack.pop()
continue
stack.pop()
continue
}
}

if (entry.edgesFlow == 0) {
Expand Down Expand Up @@ -1316,9 +1337,9 @@ export class Transaction extends Base {
break
}
else if (value instanceof Identifier) {
if (cycledIdentifiers.has(value)) {
if (this.hasCycles && cycledIdentifiers.has(value)) {
// add dependent identifiers to the list of involved in a cycle
this.markDependentCycledIdentifiers (entry, cycledIdentifiers.get(identifier))
this.markDependentCycledIdentifiers(entry, cycledIdentifiers.get(identifier))

iterationResult = undefined
entry.cleanup()
Expand Down
18 changes: 16 additions & 2 deletions src/primitives/Calculation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,24 @@ class CalculationGen extends base implements GenericCalculation<typeof ContextGe
}


// Perf: `_isGenCalc` flag set at quark creation time to branch sync vs gen
// without runtime type checking. Unified QuarkGen/QuarkSync eliminates V8 megamorphic deopt.
_isGenCalc : boolean = true

startCalculation (onEffect : CalculationContext<YieldT>, ...args : any[]) : IteratorResult<any> {
const iterator : this[ 'iterator' ] = this.iterator = this.calculation.call(this.context || this, onEffect, ...args)
if (this._isGenCalc) {
const iterator : this[ 'iterator' ] = this.iterator = this.calculation.call(this.context || this, onEffect, ...args)

return this.iterationResult = iterator.next()
}

// Sync calculation — mark as started for cycle detection, then return completed result
this.iterationResult = calculationStartedConstant

return this.iterationResult = iterator.next()
return this.iterationResult = {
done : true,
value : this.calculation.call(this.context || this, onEffect, ...args)
}
}


Expand Down
3 changes: 2 additions & 1 deletion src/replica/Entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ export class Entity extends Mixin(

identifier.context = this
identifier.self = this
identifier.name = `${this.$$.name}.$.${field.name}`
// Perf: use field name directly instead of template string (avoids string allocation per field)
identifier.name = name

return identifier
}
Expand Down