Skip to content

Commit f39e136

Browse files
committed
feat: Report per-ShaderGroup complexity statistics leaderboard (#2120)
Store the new compile-time complexity metrics that were added by PR #2085 (but only printed the the log as each group compiles) as fields on ShaderGroup so they survive after optimization. Expose them via getattribute(group, "stat:compiled_active_layers") etc., and extend getstats() to emit a "Shader compilation stats, post-optimized" section listing min/max/median and a leaderboard with a ranked top-N list for each metric (controlled by the existing "stat:rank_groups" option). Tests: testsuite/compstats/ test covering both the getstats output and the getattribute keys. Added a testshade --print-group-stats flag for querying per-group stat keys. Assisted-by: Claude Code / claude-sonnet-4-6 Signed-off-by: Larry Gritz <lg@larrygritz.com>
1 parent c2ce3cd commit f39e136

11 files changed

Lines changed: 201 additions & 2 deletions

File tree

src/cmake/testing.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ macro (osl_add_all_tests)
280280
cellnoise closure closure-array closure-layered closure-parameters closure-zero closure-conditional
281281
color color2 color4 color-reg colorspace comparison
282282
complement-reg compile-buffer compassign-bool compassign-reg
283-
component-range
283+
component-range compstats
284284
control-flow-reg connect-components
285285
const-array-params const-array-fill
286286
debugnan debug-uninit

src/liboslexec/oslexec_pvt.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,7 @@ class ShadingSystemImpl {
889889

890890
// Options
891891
int m_statslevel; ///< Statistics level
892+
int m_stat_rank_groups = 5; ///< How many groups to list in ranked stats
892893
bool m_lazylayers; ///< Evaluate layers on demand?
893894
bool m_lazyglobals; ///< Run lazily even if globals write?
894895
bool m_lazyunconnected; ///< Run lazily even if not connected?
@@ -1841,6 +1842,15 @@ class ShaderGroup {
18411842

18421843
long long int executions() const { return m_executions; }
18431844

1845+
int stat_active_layers() const { return m_stat_active_layers; }
1846+
void stat_active_layers(int n) { m_stat_active_layers = n; }
1847+
int stat_network_depth() const { return m_stat_network_depth; }
1848+
void stat_network_depth(int n) { m_stat_network_depth = n; }
1849+
int stat_texture_ops() const { return m_stat_texture_ops; }
1850+
void stat_texture_ops(int n) { m_stat_texture_ops = n; }
1851+
int stat_noise_ops() const { return m_stat_noise_ops; }
1852+
void stat_noise_ops(int n) { m_stat_noise_ops = n; }
1853+
18441854
void start_running()
18451855
{
18461856
#ifndef NDEBUG
@@ -2057,6 +2067,11 @@ class ShaderGroup {
20572067
bool m_unknown_attributes_needed;
20582068
atomic_ll m_executions { 0 }; ///< Number of times the group executed
20592069
atomic_ll m_stat_total_shading_time_ticks { 0 }; // Shading time (ticks)
2070+
// Post-optimization compile stats (written once in RuntimeOptimizer::run)
2071+
int m_stat_active_layers = 0; // Non-unused layers after dead-layer elim
2072+
int m_stat_network_depth = 0; // Max layer-to-layer connection chain length
2073+
int m_stat_texture_ops = 0; // Texture op count across active layers
2074+
int m_stat_noise_ops = 0; // Noise op count across active layers
20602075

20612076
std::string m_optix_cache_key;
20622077

src/liboslexec/runtimeoptimize.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3474,6 +3474,11 @@ RuntimeOptimizer::run()
34743474
max_network_depth = std::max(max_network_depth, layer_depth[layer]);
34753475
}
34763476

3477+
group().stat_active_layers(active_layers);
3478+
group().stat_network_depth(max_network_depth);
3479+
group().stat_texture_ops(n_texture_ops);
3480+
group().stat_noise_ops(n_noise_ops);
3481+
34773482
m_stat_specialization_time = rop_timer();
34783483
{
34793484
// adjust memory stats

src/liboslexec/shadingsys.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,7 @@ ShadingSystemImpl::attribute(string_view name, TypeDesc type, const void* val)
16051605

16061606
lock_guard guard(m_mutex); // Thread safety
16071607
ATTR_SET("statistics:level", int, m_statslevel);
1608+
ATTR_SET("stat:rank_groups", int, m_stat_rank_groups);
16081609
ATTR_SET("debug", int, m_debug);
16091610
ATTR_SET("lazylayers", int, m_lazylayers);
16101611
ATTR_SET("lazyglobals", int, m_lazyglobals);
@@ -1797,6 +1798,7 @@ ShadingSystemImpl::getattribute(string_view name, TypeDesc type, void* val)
17971798
ATTR_DECODE_STRING("searchpath:shader", m_searchpath);
17981799
ATTR_DECODE_STRING("searchpath:library", m_library_searchpath);
17991800
ATTR_DECODE("statistics:level", int, m_statslevel);
1801+
ATTR_DECODE("stat:rank_groups", int, m_stat_rank_groups);
18001802
ATTR_DECODE("lazylayers", int, m_lazylayers);
18011803
ATTR_DECODE("lazyglobals", int, m_lazyglobals);
18021804
ATTR_DECODE("lazyunconnected", int, m_lazyunconnected);
@@ -2168,6 +2170,22 @@ ShadingSystemImpl::getattribute(ShaderGroup* group, string_view name,
21682170
*(int*)val = group->m_exec_repeat;
21692171
return true;
21702172
}
2173+
if (name == "stat:compiled_active_layers" && type == TypeInt) {
2174+
*(int*)val = group->stat_active_layers();
2175+
return true;
2176+
}
2177+
if (name == "stat:compiled_network_depth" && type == TypeInt) {
2178+
*(int*)val = group->stat_network_depth();
2179+
return true;
2180+
}
2181+
if (name == "stat:compiled_texture_ops" && type == TypeInt) {
2182+
*(int*)val = group->stat_texture_ops();
2183+
return true;
2184+
}
2185+
if (name == "stat:compiled_noise_ops" && type == TypeInt) {
2186+
*(int*)val = group->stat_noise_ops();
2187+
return true;
2188+
}
21712189
if (name == "ptx_compiled_version" && type.basetype == TypeDesc::PTR) {
21722190
bool exists = !group->m_llvm_ptx_compiled_version.empty();
21732191
*(std::string*)val = exists ? group->m_llvm_ptx_compiled_version : "";
@@ -2714,6 +2732,70 @@ ShadingSystemImpl::getstats(int level) const
27142732
}
27152733
}
27162734

2735+
// Ranked shader groups by compile-time complexity metrics
2736+
if (m_stat_groups_compiled > 0) {
2737+
// Collect a snapshot of all still-live compiled (optimized) groups.
2738+
std::vector<ShaderGroupRef> groups;
2739+
{
2740+
spin_lock lock(m_all_shader_groups_mutex);
2741+
for (auto&& w : m_all_shader_groups)
2742+
if (ShaderGroupRef g = w.lock())
2743+
if (g->optimized())
2744+
groups.push_back(g);
2745+
}
2746+
using StatVal = std::pair<int, ustring>;
2747+
print(out, " Shader compilation stats, post-optimized:\n");
2748+
auto emit_ranked_groups =
2749+
[&](string_view label, string_view unit,
2750+
std::function<int(const ShaderGroup&)> getter) {
2751+
if (groups.empty())
2752+
return;
2753+
// Gather values from all compiled groups for aggregate stats.
2754+
std::vector<int> vals;
2755+
vals.reserve(groups.size());
2756+
for (auto&& g : groups)
2757+
vals.push_back(getter(*g));
2758+
std::sort(vals.begin(), vals.end());
2759+
int vmin = vals.front();
2760+
int vmax = vals.back();
2761+
int vmedian = vals[vals.size() / 2];
2762+
print(out, " {}: min={} max={} median={}\n", label, vmin,
2763+
vmax, vmedian);
2764+
// Ranked list: exclude groups with value 0.
2765+
std::vector<StatVal> ranked;
2766+
for (auto&& g : groups) {
2767+
int v = getter(*g);
2768+
if (v > 0)
2769+
ranked.emplace_back(v, g->name());
2770+
}
2771+
if (ranked.empty())
2772+
return;
2773+
std::sort(ranked.begin(), ranked.end(),
2774+
[](const StatVal& a, const StatVal& b) {
2775+
return a.first != b.first ? a.first > b.first
2776+
: a.second < b.second;
2777+
});
2778+
if ((int)ranked.size() > m_stat_rank_groups)
2779+
ranked.resize(m_stat_rank_groups);
2780+
print(out, " Top shader groups:\n");
2781+
for (auto&& [v, name] : ranked)
2782+
print(out, " {:>6} {} \"{}\"\n", v, unit,
2783+
name.size() ? name.c_str() : "<unnamed>");
2784+
};
2785+
emit_ranked_groups("Active layers", "layers", [](const ShaderGroup& g) {
2786+
return g.stat_active_layers();
2787+
});
2788+
emit_ranked_groups("Network depth", "depth", [](const ShaderGroup& g) {
2789+
return g.stat_network_depth();
2790+
});
2791+
emit_ranked_groups("Texture ops", "ops", [](const ShaderGroup& g) {
2792+
return g.stat_texture_ops();
2793+
});
2794+
emit_ranked_groups("Noise ops", "ops", [](const ShaderGroup& g) {
2795+
return g.stat_noise_ops();
2796+
});
2797+
}
2798+
27172799
return out.str();
27182800
}
27192801

src/testshade/testshade.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ static bool debug_uninit = false;
8181
static bool use_group_outputs = false;
8282
static bool do_oslquery = false;
8383
static bool print_groupdata = false;
84+
static bool print_group_stats = false;
8485
static bool inbuffer = false;
8586
static bool use_shade_image = false;
8687
static bool userdata_isconnected = false;
@@ -828,6 +829,8 @@ getargs(int argc, const char* argv[])
828829
.help("Test OSLQuery at runtime");
829830
ap.arg("--print-groupdata", &print_groupdata)
830831
.help("Print groupdata size to stdout");
832+
ap.arg("--print-group-stats", &print_group_stats)
833+
.help("Print per-group compile stats (active_layers, network_depth, texture_ops, noise_ops) to stdout");
831834
ap.arg("--inbuffer", &inbuffer)
832835
.help("Compile osl source from and to jbuffer");
833836
ap.arg("--no-output-placement")
@@ -2325,6 +2328,23 @@ test_shade(int argc, const char* argv[])
23252328
std::cout << "Groupdata size: " << groupdata_size << "\n";
23262329
}
23272330

2331+
if (print_group_stats && !batched) {
2332+
int active_layers = 0, network_depth = 0, texture_ops = 0,
2333+
noise_ops = 0;
2334+
shadingsys->getattribute(shadergroup.get(),
2335+
"stat:compiled_active_layers", active_layers);
2336+
shadingsys->getattribute(shadergroup.get(),
2337+
"stat:compiled_network_depth", network_depth);
2338+
shadingsys->getattribute(shadergroup.get(), "stat:compiled_texture_ops",
2339+
texture_ops);
2340+
shadingsys->getattribute(shadergroup.get(), "stat:compiled_noise_ops",
2341+
noise_ops);
2342+
OSL::print("stat:compiled_active_layers={}\n", active_layers);
2343+
OSL::print("stat:compiled_network_depth={}\n", network_depth);
2344+
OSL::print("stat:compiled_texture_ops={}\n", texture_ops);
2345+
OSL::print("stat:compiled_noise_ops={}\n", noise_ops);
2346+
}
2347+
23282348

23292349
// Give the renderer a chance to do initial cleanup while everything is still alive
23302350
rend->clear();

testsuite/compstats/layer_a.osl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright Contributors to the Open Shading Language project.
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
4+
5+
shader layer_a(output color Cout = 0)
6+
{
7+
Cout = texture("test.tx", u, v);
8+
}

testsuite/compstats/layer_b.osl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright Contributors to the Open Shading Language project.
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
4+
5+
shader layer_b(color Cin = 0, output color Cout = 0)
6+
{
7+
Cout = Cin + noise("perlin", P) + noise("perlin", P * 2);
8+
}

testsuite/compstats/ref/out.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Shader compilation stats, post-optimized:
2+
Active layers: min=2 max=2 median=2
3+
Top shader groups:
4+
2 layers "complex"
5+
Network depth: min=2 max=2 median=2
6+
Top shader groups:
7+
2 depth "complex"
8+
Texture ops: min=1 max=1 median=1
9+
Top shader groups:
10+
1 ops "complex"
11+
Noise ops: min=2 max=2 median=2
12+
Top shader groups:
13+
2 ops "complex"
14+
stat:compiled_active_layers=2
15+
stat:compiled_network_depth=2
16+
stat:compiled_texture_ops=1
17+
stat:compiled_noise_ops=2

testsuite/compstats/run.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright Contributors to the Open Shading Language project.
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
# https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
6+
7+
# Test that per-group compile stats are recorded and reported in getstats().
8+
#
9+
# "complex" group: layer_a (1 texture op) -> layer_b (2 noise ops)
10+
# Expected: active_layers=2, network_depth=2, texture_ops=1, noise_ops=2
11+
#
12+
# With statistics:level=1, getstats() should emit min/max/median and a
13+
# ranked list for each metric.
14+
15+
command = testshade(
16+
"--options statistics:level=1"
17+
" --groupname complex"
18+
" --shader layer_a la"
19+
" --shader layer_b lb"
20+
" --connect la Cout lb Cin"
21+
" -o Cout null"
22+
)
23+
24+
command += testshade(
25+
"--print-group-stats"
26+
" --groupname complex"
27+
" --shader layer_a la"
28+
" --shader layer_b lb"
29+
" --connect la Cout lb Cin"
30+
" -o Cout null"
31+
)
32+
33+
# Filter to only the new per-group ranked stats lines and getattribute
34+
# stat key output; everything else is machine- or build-specific.
35+
# Note: runtest uses re.match() (anchored at line start), so prefix with .*
36+
filter_re = r".*(Shader compilation stats|Active layers|Network depth|Texture ops|Noise ops|Top shader groups|stat:)"

testsuite/compstats/simple.osl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright Contributors to the Open Shading Language project.
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage
4+
5+
shader simple(output color Cout = 0)
6+
{
7+
Cout = color(u, v, 0);
8+
}

0 commit comments

Comments
 (0)