mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-02 03:43:41 +00:00
The profiling added with the cursor path was local to `bnode`, so it could only
answer questions about the cursor path. Moved to `compiler/icprof.nim` — no
compiler imports, so any stage can use it without a cycle — and extended to the
stage boundaries: the closure load and its three phases, `transformBody`,
`handOffBody`, `genProcBody`.
The budget that produces, on a cold `--ic:on` build of a 68-module target
(10.1s wall, summed over 177 backend processes):
loadDepClosure 3306ms of which moduleId 1285ms
processTopLevel 1516ms
interface tbls 1323ms
genProcBody 333ms
handOffBody 60ms
transformBody 28ms
This is worth having written down because it reprices the migration this branch
is doing. Reading a routine body off a cursor rather than a tree is finished and
costs nothing — `genProcBody` is the same either way. But FINISHING the job,
reading a `.t.bif` body directly and never materialising the `PNode`, can only
win back `handOffBody` + `transformBody`: under 1% of the build. The 41% is in
getting the closure's INTERFACE into memory, which no amount of body-reading
touches.
The remaining blockers in `bnode`'s header — `TLoc.lode` above all, 72 call
sites and hard, because a symbol's `loc.lode` outlives the body it was built in
and `lode == nil` is a sentinel — are worth exactly that under 1% until
something else changes. Said so in the header, replacing the older 0.20s/0.16s
figures, since that paragraph is the map read first.
The obvious lever on the real cost was tried and is not taken:
`{SkipInterfaceTables}` for dep-of-a-dep loads in `loadDepClosure` builds and
runs correctly but returns ~200ms, because most of that phase is the target and
system modules rather than the transitive ones. Not worth a name that silently
fails to resolve, so the flag stays restricted to `loadTransitiveHooks`.
Verified: both configurations build; `tests/ic` 40/40; the instrumentation
changes no codegen — 67/67 `.c` identical, cursor still identical to `PNode`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMyRHByv7hhaQJ4Pa1bHbE
83 lines
2.9 KiB
Nim
83 lines
2.9 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2026 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
## Opt-in instrumentation for the IC backend, enabled with `-d:icBNodeProf`.
|
|
## Off, every template below is `discard` and nothing is linked in.
|
|
##
|
|
## It lives in its own module with NO compiler imports so that any stage can
|
|
## use it without creating a cycle — `bnode` needs it for the accessors,
|
|
## `nifbackend` for the stage phases, `cgen` for what happens per routine.
|
|
##
|
|
## Each backend process appends ONE line to `$NIM_IC_BNODE_PROF` at exit (or to
|
|
## stderr when that is unset), because a `--ic:on` build fans out a process per
|
|
## module per stage and interleaved writes would tear. Use `-d:icNoParallel`
|
|
## when the numbers need to be attributable to a particular module.
|
|
##
|
|
## Counts are for volume, timings for cost, and the two answer different
|
|
## questions: the accessors turned out to be 700k calls worth 8ms, while `info`
|
|
## was 259k calls worth 1.36s. Neither number alone would have found that.
|
|
|
|
when defined(icBNodeProf):
|
|
import std / [envvars, exitprocs, syncio, monotimes]
|
|
from std / times import inNanoseconds
|
|
|
|
type
|
|
ProfSlot* = enum
|
|
pKind, pTagKindHit, pTagKindMiss, pAstChildren, pSkip, pSon, pLen,
|
|
pLastSon, pIterYield, pSym, pTyp, pTypTagLit, pOrigin, pNilType,
|
|
pGenBodyCalls, pInfo
|
|
TimeSlot* = enum
|
|
tLoadClosure, tModuleId, tTopLevel, tInterfTables,
|
|
tTransform, tHandOff, tGenBody, tAnalyses,
|
|
tSym, tTyp, tInfo, tOrigin
|
|
|
|
var profCounts: array[ProfSlot, int]
|
|
var profNanos: array[TimeSlot, int64]
|
|
var profStart: array[TimeSlot, MonoTime]
|
|
var profArmed = false
|
|
|
|
proc profDump() =
|
|
var line = "BNODEPROF"
|
|
for s in ProfSlot: line.add " " & ($s)[1..^1] & "=" & $profCounts[s]
|
|
for s in TimeSlot: line.add " " & ($s)[1..^1] & "ms=" & $(profNanos[s] div 1_000_000)
|
|
let f = getEnv("NIM_IC_BNODE_PROF")
|
|
if f.len > 0:
|
|
let h = open(f, fmAppend)
|
|
h.writeLine line
|
|
h.close()
|
|
else:
|
|
stderr.writeLine line
|
|
|
|
template armProf() =
|
|
if not profArmed:
|
|
profArmed = true
|
|
addExitProc profDump
|
|
|
|
template prof*(s: ProfSlot; n = 1) =
|
|
armProf()
|
|
inc profCounts[s], n
|
|
template icProfStart*(s: TimeSlot) =
|
|
armProf()
|
|
profStart[s] = getMonoTime()
|
|
template icProfStop*(s: TimeSlot) =
|
|
profNanos[s] += (getMonoTime() - profStart[s]).inNanoseconds
|
|
|
|
template timed*(s: TimeSlot; body: untyped) =
|
|
## Leaf timing. NOT re-entrant, and the phase slots are not disjoint —
|
|
## `tTransform` contains body materialization, `tTyp` reaches `tSym`. Read
|
|
## them as nested, not additive.
|
|
let t0 = getMonoTime()
|
|
body
|
|
profNanos[s] += (getMonoTime() - t0).inNanoseconds
|
|
else:
|
|
template prof*(s: untyped; n = 1) = discard
|
|
template icProfStart*(s: untyped) = discard
|
|
template icProfStop*(s: untyped) = discard
|
|
template timed*(s: untyped; body: untyped) = body
|