VM profiler (#14833)
* Crude VM profiler * Added --profileVM flag, refactoring * fixed FileLine hash * Use TLineInfo instead of own FileLine, updated formatting * Refactoring, moved PStackFrame to vmdefs for cleaner data structure * Moved vmprofiler to separate file * Simplified TLineInfo hash, use toFileLineCol for output * Hash * Moved profile data into Config, dump only once at end of run * Changed profile output header to show '#instr' instead of 'count' * Do not accumulate instrcount for non-top frames Co-authored-by: Ico Doornekamp <git@zevv.nl>
This commit is contained in:
parent
c38a459582
commit
d7ccd82eac
8 changed files with 86 additions and 15 deletions
|
|
@ -884,6 +884,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||||
localError(conf, info, "unknown Nim version; currently supported values are: {1.0}")
|
localError(conf, info, "unknown Nim version; currently supported values are: {1.0}")
|
||||||
of "benchmarkvm":
|
of "benchmarkvm":
|
||||||
processOnOffSwitchG(conf, {optBenchmarkVM}, arg, pass, info)
|
processOnOffSwitchG(conf, {optBenchmarkVM}, arg, pass, info)
|
||||||
|
of "profilevm":
|
||||||
|
processOnOffSwitchG(conf, {optProfileVM}, arg, pass, info)
|
||||||
of "sinkinference":
|
of "sinkinference":
|
||||||
processOnOffSwitch(conf, {optSinkInference}, arg, pass, info)
|
processOnOffSwitch(conf, {optSinkInference}, arg, pass, info)
|
||||||
of "panics":
|
of "panics":
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
## This module contains the ``TMsgKind`` enum as well as the
|
## This module contains the ``TMsgKind`` enum as well as the
|
||||||
## ``TLineInfo`` object.
|
## ``TLineInfo`` object.
|
||||||
|
|
||||||
import ropes, tables, pathutils
|
import ropes, tables, pathutils, hashes
|
||||||
|
|
||||||
const
|
const
|
||||||
explanationsBaseUrl* = "https://nim-lang.github.io/Nim"
|
explanationsBaseUrl* = "https://nim-lang.github.io/Nim"
|
||||||
|
|
@ -271,6 +271,9 @@ type
|
||||||
|
|
||||||
proc `==`*(a, b: FileIndex): bool {.borrow.}
|
proc `==`*(a, b: FileIndex): bool {.borrow.}
|
||||||
|
|
||||||
|
proc hash*(i: TLineInfo): Hash =
|
||||||
|
hash (i.line.int, i.col.int, i.fileIndex.int)
|
||||||
|
|
||||||
proc raiseRecoverableError*(msg: string) {.noinline.} =
|
proc raiseRecoverableError*(msg: string) {.noinline.} =
|
||||||
raise newException(ERecoverableError, msg)
|
raise newException(ERecoverableError, msg)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import
|
||||||
cgen, json, nversion,
|
cgen, json, nversion,
|
||||||
platform, nimconf, passaux, depends, vm, idgen,
|
platform, nimconf, passaux, depends, vm, idgen,
|
||||||
modules,
|
modules,
|
||||||
modulegraphs, tables, rod, lineinfos, pathutils
|
modulegraphs, tables, rod, lineinfos, pathutils, vmprofiler
|
||||||
|
|
||||||
when not defined(leanCompiler):
|
when not defined(leanCompiler):
|
||||||
import jsgen, docgen, docgen2
|
import jsgen, docgen, docgen2
|
||||||
|
|
@ -404,6 +404,8 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||||
else:
|
else:
|
||||||
output = $conf.absOutFile
|
output = $conf.absOutFile
|
||||||
if optListFullPaths notin conf.globalOptions: output = output.AbsoluteFile.extractFilename
|
if optListFullPaths notin conf.globalOptions: output = output.AbsoluteFile.extractFilename
|
||||||
|
if optProfileVM in conf.globalOptions:
|
||||||
|
echo conf.dump(conf.vmProfileData)
|
||||||
rawMessage(conf, hintSuccessX, [
|
rawMessage(conf, hintSuccessX, [
|
||||||
"loc", loc,
|
"loc", loc,
|
||||||
"sec", sec,
|
"sec", sec,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
os, strutils, strtabs, sets, lineinfos, platform,
|
os, strutils, strtabs, sets, lineinfos, platform,
|
||||||
prefixmatches, pathutils, nimpaths
|
prefixmatches, pathutils, nimpaths, tables
|
||||||
|
|
||||||
from terminal import isatty
|
from terminal import isatty
|
||||||
from times import utc, fromUnix, local, getTime, format, DateTime
|
from times import utc, fromUnix, local, getTime, format, DateTime
|
||||||
|
|
@ -95,6 +95,7 @@ type # please make sure we have under 32 options
|
||||||
optPanics # turn panics (sysFatal) into a process termination
|
optPanics # turn panics (sysFatal) into a process termination
|
||||||
optNimV1Emulation # emulate Nim v1.0
|
optNimV1Emulation # emulate Nim v1.0
|
||||||
optSourcemap
|
optSourcemap
|
||||||
|
optProfileVM # enable VM profiler
|
||||||
|
|
||||||
TGlobalOptions* = set[TGlobalOption]
|
TGlobalOptions* = set[TGlobalOption]
|
||||||
|
|
||||||
|
|
@ -217,6 +218,13 @@ type
|
||||||
version*: int
|
version*: int
|
||||||
Suggestions* = seq[Suggest]
|
Suggestions* = seq[Suggest]
|
||||||
|
|
||||||
|
ProfileInfo* = object
|
||||||
|
time*: float
|
||||||
|
count*: int
|
||||||
|
|
||||||
|
ProfileData* = ref object
|
||||||
|
data*: TableRef[TLineInfo, ProfileInfo]
|
||||||
|
|
||||||
ConfigRef* = ref object ## every global configuration
|
ConfigRef* = ref object ## every global configuration
|
||||||
## fields marked with '*' are subject to
|
## fields marked with '*' are subject to
|
||||||
## the incremental compilation mechanisms
|
## the incremental compilation mechanisms
|
||||||
|
|
@ -314,6 +322,7 @@ type
|
||||||
structuredErrorHook*: proc (config: ConfigRef; info: TLineInfo; msg: string;
|
structuredErrorHook*: proc (config: ConfigRef; info: TLineInfo; msg: string;
|
||||||
severity: Severity) {.closure, gcsafe.}
|
severity: Severity) {.closure, gcsafe.}
|
||||||
cppCustomNamespace*: string
|
cppCustomNamespace*: string
|
||||||
|
vmProfileData*: ProfileData
|
||||||
|
|
||||||
proc assignIfDefault*[T](result: var T, val: T, def = default(T)) =
|
proc assignIfDefault*[T](result: var T, val: T, def = default(T)) =
|
||||||
## if `result` was already assigned to a value (that wasn't `def`), this is a noop.
|
## if `result` was already assigned to a value (that wasn't `def`), this is a noop.
|
||||||
|
|
@ -390,6 +399,9 @@ template newPackageCache*(): untyped =
|
||||||
else:
|
else:
|
||||||
modeCaseSensitive)
|
modeCaseSensitive)
|
||||||
|
|
||||||
|
proc newProfileData(): ProfileData =
|
||||||
|
ProfileData(data: newTable[TLineInfo, ProfileInfo]())
|
||||||
|
|
||||||
proc newConfigRef*(): ConfigRef =
|
proc newConfigRef*(): ConfigRef =
|
||||||
result = ConfigRef(
|
result = ConfigRef(
|
||||||
selectedGC: gcRefc,
|
selectedGC: gcRefc,
|
||||||
|
|
@ -442,6 +454,7 @@ proc newConfigRef*(): ConfigRef =
|
||||||
arguments: "",
|
arguments: "",
|
||||||
suggestMaxResults: 10_000,
|
suggestMaxResults: 10_000,
|
||||||
maxLoopIterationsVM: 10_000_000,
|
maxLoopIterationsVM: 10_000_000,
|
||||||
|
vmProfileData: newProfileData(),
|
||||||
)
|
)
|
||||||
setTargetFromSystem(result.target)
|
setTargetFromSystem(result.target)
|
||||||
# enable colors by default on terminals
|
# enable colors by default on terminals
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import
|
||||||
strutils, msgs, vmdef, vmgen, nimsets, types, passes,
|
strutils, msgs, vmdef, vmgen, nimsets, types, passes,
|
||||||
parser, vmdeps, idents, trees, renderer, options, transf, parseutils,
|
parser, vmdeps, idents, trees, renderer, options, transf, parseutils,
|
||||||
vmmarshal, gorgeimpl, lineinfos, tables, btrees, macrocacheimpl,
|
vmmarshal, gorgeimpl, lineinfos, tables, btrees, macrocacheimpl,
|
||||||
modulegraphs, sighashes, int128
|
modulegraphs, sighashes, int128, vmprofiler
|
||||||
|
|
||||||
from semfold import leValueConv, ordinalValToString
|
from semfold import leValueConv, ordinalValToString
|
||||||
from evaltempl import evalTemplate
|
from evaltempl import evalTemplate
|
||||||
|
|
@ -27,17 +27,6 @@ const
|
||||||
when hasFFI:
|
when hasFFI:
|
||||||
import evalffi
|
import evalffi
|
||||||
|
|
||||||
type
|
|
||||||
PStackFrame* = ref TStackFrame
|
|
||||||
TStackFrame* = object
|
|
||||||
prc: PSym # current prc; proc that is evaluated
|
|
||||||
slots: seq[TFullReg] # parameters passed to the proc + locals;
|
|
||||||
# parameters come first
|
|
||||||
next: PStackFrame # for stacking
|
|
||||||
comesFrom: int
|
|
||||||
safePoints: seq[int] # used for exception handling
|
|
||||||
# XXX 'break' should perform cleanup actions
|
|
||||||
# What does the C backend do for it?
|
|
||||||
|
|
||||||
proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) =
|
proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) =
|
||||||
if x != nil:
|
if x != nil:
|
||||||
|
|
@ -552,6 +541,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||||
"ra", regDescr("ra", ra), "rb", regDescr("rb", instr.regB),
|
"ra", regDescr("ra", ra), "rb", regDescr("rb", instr.regB),
|
||||||
"rc", regDescr("rc", instr.regC)]
|
"rc", regDescr("rc", instr.regC)]
|
||||||
|
|
||||||
|
c.profiler.enter(c, tos)
|
||||||
|
|
||||||
case instr.opcode
|
case instr.opcode
|
||||||
of opcEof: return regs[ra]
|
of opcEof: return regs[ra]
|
||||||
of opcRet:
|
of opcRet:
|
||||||
|
|
@ -2077,6 +2068,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
|
||||||
if regs[ra].node.strVal.isNil: regs[ra].node.strVal = newStringOfCap(1000)
|
if regs[ra].node.strVal.isNil: regs[ra].node.strVal = newStringOfCap(1000)
|
||||||
storeAny(regs[ra].node.strVal, typ, regs[rb].regToNode, c.config)
|
storeAny(regs[ra].node.strVal, typ, regs[rb].regToNode, c.config)
|
||||||
|
|
||||||
|
c.profiler.leave(c)
|
||||||
|
|
||||||
inc pc
|
inc pc
|
||||||
|
|
||||||
proc execute(c: PCtx, start: int): PNode =
|
proc execute(c: PCtx, start: int): PNode =
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,21 @@ type
|
||||||
config*: ConfigRef
|
config*: ConfigRef
|
||||||
graph*: ModuleGraph
|
graph*: ModuleGraph
|
||||||
oldErrorCount*: int
|
oldErrorCount*: int
|
||||||
|
profiler*: Profiler
|
||||||
|
|
||||||
|
PStackFrame* = ref TStackFrame
|
||||||
|
TStackFrame* = object
|
||||||
|
prc*: PSym # current prc; proc that is evaluated
|
||||||
|
slots*: seq[TFullReg] # parameters passed to the proc + locals;
|
||||||
|
# parameters come first
|
||||||
|
next*: PStackFrame # for stacking
|
||||||
|
comesFrom*: int
|
||||||
|
safePoints*: seq[int] # used for exception handling
|
||||||
|
# XXX 'break' should perform cleanup actions
|
||||||
|
# What does the C backend do for it?
|
||||||
|
Profiler* = object
|
||||||
|
tEnter*: float
|
||||||
|
tos*: PStackFrame
|
||||||
|
|
||||||
TPosition* = distinct int
|
TPosition* = distinct int
|
||||||
|
|
||||||
|
|
|
||||||
42
compiler/vmprofiler.nim
Normal file
42
compiler/vmprofiler.nim
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
import
|
||||||
|
options, vmdef, times, lineinfos, strutils, tables,
|
||||||
|
msgs
|
||||||
|
|
||||||
|
proc enter*(prof: var Profiler, c: PCtx, tos: PStackFrame) {.inline.} =
|
||||||
|
if optProfileVM in c.config.globalOptions:
|
||||||
|
prof.tEnter = cpuTime()
|
||||||
|
prof.tos = tos
|
||||||
|
|
||||||
|
proc leave*(prof: var Profiler, c: PCtx) {.inline.} =
|
||||||
|
if optProfileVM in c.config.globalOptions:
|
||||||
|
let tLeave = cpuTime()
|
||||||
|
var tos = prof.tos
|
||||||
|
var data = c.config.vmProfileData.data
|
||||||
|
while tos != nil:
|
||||||
|
if tos.prc != nil:
|
||||||
|
let li = tos.prc.info
|
||||||
|
if li notin data:
|
||||||
|
data[li] = ProfileInfo()
|
||||||
|
data[li].time += tLeave - prof.tEnter
|
||||||
|
if tos == prof.tos:
|
||||||
|
inc data[li].count
|
||||||
|
tos = tos.next
|
||||||
|
|
||||||
|
proc dump*(conf: ConfigRef, pd: ProfileData): string =
|
||||||
|
var data = pd.data
|
||||||
|
echo "\nprof: µs #instr location"
|
||||||
|
for i in 0..<32:
|
||||||
|
var tMax: float
|
||||||
|
var infoMax: ProfileInfo
|
||||||
|
var flMax: TLineInfo
|
||||||
|
for fl, info in data:
|
||||||
|
if info.time > infoMax.time:
|
||||||
|
infoMax = info
|
||||||
|
flMax = fl
|
||||||
|
if infoMax.count == 0:
|
||||||
|
break
|
||||||
|
result.add " " & align($int(infoMax.time * 1e6), 10) &
|
||||||
|
align($int(infoMax.count), 10) & " " &
|
||||||
|
conf.toFileLineCol(flMax) & "\n"
|
||||||
|
data.del flMax
|
||||||
|
|
@ -150,5 +150,6 @@ Advanced options:
|
||||||
works better with `--stackTrace:on`
|
works better with `--stackTrace:on`
|
||||||
see also https://nim-lang.github.io/Nim/estp.html
|
see also https://nim-lang.github.io/Nim/estp.html
|
||||||
--benchmarkVM:on|off enable benchmarking of VM code with cpuTime()
|
--benchmarkVM:on|off enable benchmarking of VM code with cpuTime()
|
||||||
|
--profileVM:on|off enable compile time VM profiler
|
||||||
--sinkInference:on|off en-/disable sink parameter inference (default: on)
|
--sinkInference:on|off en-/disable sink parameter inference (default: on)
|
||||||
--panics:on|off turn panics into process terminations (default: off)
|
--panics:on|off turn panics into process terminations (default: off)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue