Issie logo Issie

SimulationBudget Module

How much memory a simulation may take, and of which kind. Here rather than beside the code that spends it because two different parts of the simulator spend it: GraphMerger, which expands the design into a graph, and FastCreate, which allocates the step arrays. Both come out of the same memory and GraphMerger is compiled first. Sizes are float and not int64 on purpose. Fable compiles int64 to BigInt, so every comparison here would allocate one - a poor thing to spend on deciding whether a design is too big. A float carries integers exactly to 2^53 and the largest number reached here is a few hundred GB.

Functions and values

Function or value Description

formatBytes bytes

Full Usage: formatBytes bytes

Parameters:
    bytes : float

Returns: string

A size in bytes, written the way a message should read it.

bytes : float
Returns: string

heapBytesPerComponent ports

Full Usage: heapBytesPerComponent ports

Parameters:
    ports : float

Returns: float
 What one component of an expanded design costs in heap, by how many ports it has (ports
 here is inputs plus outputs, as GraphMerger.expandedSize counts them).

 Measured rather than derived, on a hierarchy of 120,000 expanded components with one output
 port each and 2.9 ports each in all, by GC-forced usedJSHeapSize deltas at two step-array
 lengths (which separates the fixed cost from the per-step cost - the fixed part was the
 same at both lengths to within noise). What one waveform simulation retains, per component:

   the SimulationGraph            ~15 bytes - merger shares non-custom nodes between
                                  instances of a sheet, so this is two orders of magnitude
                                  down on the rest
   the FastComponents            ~990 bytes fixed - the 25-field record, its map node, the
                                  output IOArrays with their typed-array wrappers (~120 B a
                                  wrapper, measured), path strings, reducer closures
   the Wave records               ~430 bytes a wave, at ~0.6 waves per port when this was
                                  measured - the Wave record's strings, built by the
                                  waveform simulator only, but charged here because this
                                  guard cannot know which simulator is coming and the
                                  waveform one is both the heavier and the one large designs
                                  are opened in

 That last term is no longer spent. The waveform simulator described every wave the
 simulation offered and kept the lot in its model; it now describes the waves of the sheet
 instances the selector is drawing, as it draws them, and keeps records only for the ones
 SELECTED - at most maxAllowedViewerWaves, which is a hundred. So about 260 of the 350 bytes
 per port below is a cost that has gone, and this guard now refuses designs it has the memory
 for. The number is left where it is because it was MEASURED and the replacement has not
 been: subtracting a term from someone else's measurement is how a guard ends up on the wrong
 side. Remeasure and lower it.

 The step arrays themselves are NOT here - they scale with cycles, not components, and are
 StepCost's business. The formula sits ~15% above the measured total at 2.9 ports, which is
 the right side to miss on for a guard. To remeasure after changing these structures: build
 phase deltas are logged under --log=perf (FastBuild.buildFastSimulation and the defaultWaves
 line), and `node scripts/drive.js` + window.issieDev.simStats() gives the exact component,
 port and wave counts to divide by.
ports : float
Returns: float

heapShareOfLimit

Full Usage: heapShareOfLimit

Returns: float

Share of the V8 heap limit a simulation may take, for the expanded design and for the step arrays of buses wider than 32 bits. Not half, although half of USABLE heap is the intention. The heap limit is not usable in full: the scavenger needs its to-space free, mark-compact needs somewhere to evacuate pages to, and what a simulation puts there is millions of small objects promoted out of new space, which is the shape old space handles least well. Filling the cage makes the renderer stop responding well before the limit is reached. A third of the limit is about half of what can really be used. The two heap checks - the expanded design, and the step arrays - are made separately and each against this whole figure, so a design that passed both could in the worst case use twice it. That needs a design that is both deeply instantiated AND full of wide buses, and even then 70% of the limit is the boundary rather than past it.

Returns: float

maxHeapBytes

Full Usage: maxHeapBytes

Returns: float

The most V8 heap one simulation may take, for the expanded design and for the BigInt step arrays alike. Far smaller than the budget above, because V8's pointer compression caps the whole heap at 4GB - a limit no flag can lift, since raising it needs V8 built without pointer compression - and the model, the design, the waveforms and everything else the renderer holds come out of that same 4GB.

Returns: float

maxTypedArrayBytes

Full Usage: maxTypedArrayBytes

Returns: float

The most Uint32Array step-array memory one simulation may take: memory outside the V8 heap, so bounded by the machine rather than by anything Issie is built with. Most designs are 32 bits and under, so this is the budget that decides how long they may run. Mutable because it is a fact about the machine, discovered once at startup - see setBudgetsFromMachine. The value here is the fallback for when there is no machine to ask, which is every run of the test suite: those run under plain .NET with no Electron.

Returns: float

runtimeHeadroom

Full Usage: runtimeHeadroom

Returns: float

How far past a budget the runtime memory check (FastCreate.checkSimulationFits) lets a simulation go before refusing to build it. The budgets are deliberately conservative - a third of physical where allocation was measured to fail just under half, and 0.35 of a heap limit of which roughly 0.7 is usable - so there is real margin between "past the budget" and "will crash". The configuration dialog holds users to the budgets exactly (FastCreate.maxLastClockFor); this headroom exists for the simulation that arrives WITHOUT passing that dialog - a LastClock saved into a sheet on a machine with more memory than this one, most commonly - which should run if it safely can, not be refused over a bound it only just misses. At 1.5 the worst case stays at half of physical for the typed arrays and just over half of the heap limit for the BigInt arrays: inside the measured margin on both.

Returns: float

setBudgetsFromMachine physicalBytes heapLimitBytes

Full Usage: setBudgetsFromMachine physicalBytes heapLimitBytes

Parameters:
    physicalBytes : float
    heapLimitBytes : float

Size both budgets to the machine this is running on. Called once from renderer startup. physicalBytes comes from process.getSystemMemoryInfo, heapLimitBytes from performance.memory.jsHeapSizeLimit - the limit actually in force, whatever Main.fs asked for and whatever V8 decided to grant. Either being zero or absent leaves that budget at its fallback, so a machine that cannot answer is never told it has no memory.

physicalBytes : float
heapLimitBytes : float

typedArrayShareOfMachine

Full Usage: typedArrayShareOfMachine

Returns: float

Share of the machine's physical memory a simulation's Uint32Arrays may take. A third, because that is comfortably clear of where the allocator actually gives up: measured on a 32GB machine, Uint32Array allocation failed at 15.5GB, a little under half of physical. A third leaves the operating system, the rest of Chromium and whatever else the user has open their room, and still allows several million cycles of a real design.

Returns: float

Type something to start searching.