This is an instrumenting profiler for Lua ReaScripts. It measures function execution time and call frequency. Results are displayed either as a call tree or a flat list. Eight separate profile slots are available for quick A-B comparison.
This script is provided in the form of a library. Search for "Lua profiler" in the ReaTeam Scripts default
ReaPack repository. ReaImGui is required as a dependency for the profiler's user interface.
The following data is collected for each function (many columns are hidden in the default view: right-click on the table header to enable them):
- Source file and line
- Execution time (with % of total and % of parent)
- Call count
- Frame count
- Time per call (min/avg/max)
- Time per frame (min/avg/max)
- Calls per frame (min/avg/max)
Basic usage
The highest-level API for measuring long-running scripts is:
Code:
local profiler = dofile(reaper.GetResourcePath() ..
'/Scripts/ReaTeam Scripts/Development/cfillion_Lua profiler.lua')
reaper.defer = profiler.defer
profiler.attachToWorld() -- after all functions have been defined
profiler.run()
Select one of the options in the "Acquisition" menu to begin measurement.
run launches the profiler's user interface.
defer overrides reaper.defer to capture frame information (should be called exactly once per frame).
Instrumentation
The
attachToWorld function above injects instrumentation into all functions found in the global scope, local scope and within tables recursively.
This adds some overhead and a stack level to all of those functions for the profiler to collect call information. It may be desirable to add instrumentation only to select functions instead of
everything.
There are several functions provided for this purpose:
- profiler.attachTo(string var, nil|table opts)
- profiler.detachFrom(string var, nil|table opts)
- profiler.attachToLocals(nil|table opts)
- profiler.detachFromLocals(nil|table opts)
- profiler.attachToWorld()
- profiler.detachFromWorld()
attachTo and
detachFrom take a
variable path as a string. Here are some examples:
Code:
profiler.attachTo('reaper') -- all functions in the reaper table
profiler.detachFrom('reaper') -- undoes the previous line
profiler.attachTo('reaper.GetTrack') -- only this function
profiler.attachTo('gfx`meta') -- all functions in getmetatable(gfx)
profiler.attachTo('gfx`meta.__newindex') -- getmetatable(gfx).__newindex
profiler.attachTo('foo.bar.baz', { recursive = false })
'opts' is an optional table to customize the variable lookup behavior. Supported keys and default values are:
- recursive = true
Whether to dig into subtables (maximum depth is 8).
- search_above = true
For {attachTo,detachFrom}Locals only: whether to look into scopes above the caller's.
- metatable = true
Whether to also attach to the value's metatable if present.
Intermediate usage
The following API is provided to control when measurements are taken for single-shot scripts or more advanced needs.
start and
stop activates and deactivates data acquisition.
Code:
local profiler = ...
profiler.attachTo('foo')
profiler.attachTo('bar')
profiler.run()
profiler.start()
foo()
bar()
profiler.stop()
Use
enter and
leave to measure a specific block of code (eg. within a larger function). They can be nested. The given string identifier should be unique.
Code:
local foo = 4
bar = 4
profiler.start()
profiler.enter('local')
for i = 1, 1<<16 do foo = foo * 4 end
profiler.leave()
profiler.enter('global')
for i = 1, 1<<16 do bar = bar * 4 end
profiler.leave()
profiler.stop()
Advanced usage
There are a few more features exposed to the target script. The
defer function shown in the "Basic usage" section above is actually a shortcut for
start +
stop +
frame.
frame increments the frame counter and updates per-frame timings.
Code:
profiler.start()
-- user code here
profiler.stop()
profiler.frame()
To start data acquisition over multiple frames when using the
defer override:
Code:
profiler.auto_start = true -- start until manually stopped
profiler.auto_start = 60 -- start for 60 frames then stop automatically
profiler.auto_start = false -- stop
The profiler's user interface can be embedded into any ReaImGui script using
showProfile.
Code:
profiler.showProfile(ctx, 'my_profile', width, height)
The
run function is a shortcut for creating a ReaImGui context and calling
showWindow in a loop.
Code:
local open = profiler.showWindow(ctx, true, flags)
Call
reset to clear all data in the selected profile.