Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/include/OSL/oslexec.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,33 @@ class OSLEXECPUBLIC ShadingSystem {
/// library build dependencies and their versions (for example,
/// "OIIO-2.3.0,LLVM-10.0.0,OpenEXR-2.5.0").
///
/// - `stat:compiled_<metric>:<subkey>` : Post-optimization compile
/// statistics aggregated over *all* shader groups, where `<metric>` is
/// one of `active_layers`, `network_depth`, `texture_ops`, or
/// `noise_ops` (the same metrics available for a single group via
/// `getattribute(group, "stat:compiled_<metric>", ...)`), and
/// `<subkey>` is one of:
///
/// - `int top_count` : How many groups have a nonzero value for this
/// metric. This is the full count, not clipped by the
/// `stat:rank_groups` option (which affects only the printed report).
/// - `string top_names[n]` : Names of the highest-valued groups, best
/// first. Fills `min(n, top_count)` entries; any remainder is filled
/// with empty strings. A group that was never named yields an empty
/// string.
/// - `int top_values[n]` : The corresponding metric values, in the same
/// order as `top_names`; any remainder is filled with 0.
/// - `int min`, `int max`, `int median` : Distribution of the metric.
/// Unlike the ranked list, these include groups whose value is 0.
///
/// Only groups that have already been optimized are considered;
/// querying these does not force optimization of any group. Ranking is
/// by value descending, ties broken by group name ascending. If no
/// group has been optimized yet, all of these succeed and report 0.
/// Each query re-gathers from the live groups, so `top_names` and
/// `top_values` may disagree if groups are created or destroyed
/// between the two calls.
///
bool getattribute(string_view name, TypeDesc type, void* val);

/// Shortcut getattribute() for retrieving a single integer.
Expand Down
16 changes: 16 additions & 0 deletions src/liboslexec/oslexec_pvt.h
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,22 @@ class ShadingSystemImpl {

std::string getstats(int level = 1) const;

/// One metric's worth of post-optimization compile statistics, gathered
/// fresh from all currently live, optimized shader groups.
struct GroupStatSnapshot {
// (value, groupname) for the groups whose value is > 0, sorted by
// value descending, then by group name ascending.
std::vector<std::pair<int, ustring>> ranked;
int vmin = 0, vmax = 0, vmedian = 0; ///< Over ALL optimized groups
int ngroups = 0; ///< How many optimized groups were examined
};

/// Gather the named per-group compile stat -- one of "active_layers",
/// "network_depth", "texture_ops", "noise_ops" -- across every live,
/// optimized shader group. Returns false (leaving snap untouched) if the
/// metric name is not recognized.
bool gather_group_stats(string_view metric, GroupStatSnapshot& snap) const;

ErrorHandler& errhandler() const { return *m_err; }

ShaderMaster::ref loadshader(string_view name);
Expand Down
194 changes: 135 additions & 59 deletions src/liboslexec/shadingsys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1814,6 +1814,58 @@ ShadingSystemImpl::getattribute(string_view name, TypeDesc type, void* val)
return true; \
}

// Ranked and aggregate post-optimization compile stats, across all live
// optimized groups:
// stat:compiled_<metric>:top_count int -- # groups with value>0
// stat:compiled_<metric>:top_names string[] -- ranked group names
// stat:compiled_<metric>:top_values int[] -- ranked values
// stat:compiled_<metric>:min/max/median int -- incl. zero-valued
// These are handled before taking m_mutex: gather_group_stats() needs
// only m_all_shader_groups_mutex, and acquiring that while holding
// m_mutex would establish a new lock ordering.
if (Strutil::starts_with(name, "stat:compiled_")) {
string_view rest = name.substr(sizeof("stat:compiled_") - 1);
size_t colon = rest.find(':');
GroupStatSnapshot snap;
if (colon != string_view::npos
&& gather_group_stats(rest.substr(0, colon), snap)) {
string_view sub = rest.substr(colon + 1);
if (sub == "top_count" && type == TypeInt) {
*(int*)val = (int)snap.ranked.size();
return true;
}
if (sub == "top_names" && type.basetype == TypeDesc::STRING) {
size_t n = std::min(type.numelements(), snap.ranked.size());
for (size_t i = 0; i < n; ++i)
((ustring*)val)[i] = snap.ranked[i].second;
for (size_t i = n; i < type.numelements(); ++i)
((ustring*)val)[i] = ustring();
return true;
}
if (sub == "top_values" && type.basetype == TypeDesc::INT) {
size_t n = std::min(type.numelements(), snap.ranked.size());
for (size_t i = 0; i < n; ++i)
((int*)val)[i] = snap.ranked[i].first;
for (size_t i = n; i < type.numelements(); ++i)
((int*)val)[i] = 0;
return true;
}
if (sub == "min" && type == TypeInt) {
*(int*)val = snap.vmin;
return true;
}
if (sub == "max" && type == TypeInt) {
*(int*)val = snap.vmax;
return true;
}
if (sub == "median" && type == TypeInt) {
*(int*)val = snap.vmedian;
return true;
}
}
// Unrecognized metric or subkey: fall through to the ordinary lookup.
}

lock_guard guard(m_mutex); // Thread safety

ATTR_DECODE_STRING("searchpath:shader", m_searchpath);
Expand Down Expand Up @@ -2759,73 +2811,97 @@ ShadingSystemImpl::getstats(int level) const

// Ranked shader groups by compile-time complexity metrics
if (m_stat_groups_compiled > 0) {
// Collect a snapshot of all still-live compiled (optimized) groups.
std::vector<ShaderGroupRef> groups;
{
spin_lock lock(m_all_shader_groups_mutex);
for (auto&& w : m_all_shader_groups)
if (ShaderGroupRef g = w.lock())
if (g->optimized())
groups.push_back(g);
}
using StatVal = std::pair<int, ustring>;
print(out, " Shader compilation stats, post-optimized:\n");
auto emit_ranked_groups =
[&](string_view label, string_view unit,
std::function<int(const ShaderGroup&)> getter) {
if (groups.empty())
return;
// Gather values from all compiled groups for aggregate stats.
std::vector<int> vals;
vals.reserve(groups.size());
for (auto&& g : groups)
vals.push_back(getter(*g));
std::sort(vals.begin(), vals.end());
int vmin = vals.front();
int vmax = vals.back();
int vmedian = vals[vals.size() / 2];
print(out, " {}: min={} max={} median={}\n", label, vmin,
vmax, vmedian);
// Ranked list: exclude groups with value 0.
std::vector<StatVal> ranked;
for (auto&& g : groups) {
int v = getter(*g);
if (v > 0)
ranked.emplace_back(v, g->name());
}
if (ranked.empty())
return;
std::sort(ranked.begin(), ranked.end(),
[](const StatVal& a, const StatVal& b) {
return a.first != b.first ? a.first > b.first
: a.second < b.second;
});
if ((int)ranked.size() > m_stat_rank_groups)
ranked.resize(m_stat_rank_groups);
print(out, " Top shader groups:\n");
for (auto&& [v, name] : ranked)
print(out, " {:>6} {} \"{}\"\n", v, unit,
name.size() ? name.c_str() : "<unnamed>");
};
emit_ranked_groups("Active layers", "layers", [](const ShaderGroup& g) {
return g.stat_active_layers();
});
emit_ranked_groups("Network depth", "depth", [](const ShaderGroup& g) {
return g.stat_network_depth();
});
emit_ranked_groups("Texture ops", "ops", [](const ShaderGroup& g) {
return g.stat_texture_ops();
});
emit_ranked_groups("Noise ops", "ops", [](const ShaderGroup& g) {
return g.stat_noise_ops();
});
// Purely formatting -- gather_group_stats() does the gathering,
// sorting and aggregating, so this report and the "stat:compiled_*"
// getattribute queries can never disagree.
auto emit_ranked_groups = [&](string_view label, string_view unit,
string_view metric) {
GroupStatSnapshot snap;
if (!gather_group_stats(metric, snap) || snap.ngroups == 0)
return;
print(out, " {}: min={} max={} median={}\n", label, snap.vmin,
snap.vmax, snap.vmedian);
if (snap.ranked.empty())
return;
int n = std::min((int)snap.ranked.size(), m_stat_rank_groups);
print(out, " Top shader groups:\n");
for (int i = 0; i < n; ++i) {
auto&& [v, name] = snap.ranked[i];
print(out, " {:>6} {} \"{}\"\n", v, unit,
name.size() ? name.c_str() : "<unnamed>");
}
};
emit_ranked_groups("Active layers", "layers", "active_layers");
emit_ranked_groups("Network depth", "depth", "network_depth");
emit_ranked_groups("Texture ops", "ops", "texture_ops");
emit_ranked_groups("Noise ops", "ops", "noise_ops");
}

return out.str();
}



bool
ShadingSystemImpl::gather_group_stats(string_view metric,
GroupStatSnapshot& snap) const
{
// Captureless lambdas convert to plain function pointers.
int (*getter)(const ShaderGroup&) = nullptr;
if (metric == "active_layers")
getter = [](const ShaderGroup& g) { return g.stat_active_layers(); };
else if (metric == "network_depth")
getter = [](const ShaderGroup& g) { return g.stat_network_depth(); };
else if (metric == "texture_ops")
getter = [](const ShaderGroup& g) { return g.stat_texture_ops(); };
else if (metric == "noise_ops")
getter = [](const ShaderGroup& g) { return g.stat_noise_ops(); };
else
return false;

// Collect a snapshot of all still-live compiled (optimized) groups.
// Note that unlike the per-group getattribute() queries, this never
// forces optimization of a group that isn't optimized yet.
std::vector<ShaderGroupRef> groups;
{
spin_lock lock(m_all_shader_groups_mutex);
for (auto&& w : m_all_shader_groups)
if (ShaderGroupRef g = w.lock())
if (g->optimized())
groups.push_back(g);
}
snap.ngroups = (int)groups.size();
if (groups.empty())
return true;

// Aggregates are over all compiled groups, including zero-valued ones.
std::vector<int> vals;
vals.reserve(groups.size());
for (auto&& g : groups)
vals.push_back(getter(*g));
std::sort(vals.begin(), vals.end());
snap.vmin = vals.front();
snap.vmax = vals.back();
snap.vmedian = vals[vals.size() / 2];

// Ranked list: exclude groups with value 0.
using StatVal = std::pair<int, ustring>;
for (auto&& g : groups) {
int v = getter(*g);
if (v > 0)
snap.ranked.emplace_back(v, g->name());
}
std::sort(snap.ranked.begin(), snap.ranked.end(),
[](const StatVal& a, const StatVal& b) {
return a.first != b.first ? a.first > b.first
: a.second < b.second;
});
return true;
}



void
ShadingSystemImpl::printstats() const
{
Expand Down
33 changes: 33 additions & 0 deletions src/testrender/testrender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ static bool runstats = false;
static bool saveptx = false;
static bool warmup = false;
static bool profile = false;
static bool print_group_stats = false;
static bool O0 = false, O1 = false, O2 = false;
static int llvm_opt = 1; // LLVM optimization level
static bool debugnan = false;
Expand Down Expand Up @@ -164,6 +165,8 @@ getargs(int argc, const char* argv[])
.hidden(); // DEPRECATED 1.7
ap.arg("--profile", &profile)
.help("Print profile information");
ap.arg("--print-group-stats", &print_group_stats)
.help("Print ranked/aggregate compile stats across all shader groups");
ap.arg("--saveptx", &saveptx)
.help("Save the generated PTX (OptiX mode only)");
ap.arg("--warmup", &warmup)
Expand Down Expand Up @@ -378,6 +381,36 @@ main(int argc, const char* argv[])
std::cout << ustring::getstats() << "\n";
}

if (print_group_stats) {
// Ranked and aggregate compile stats across every shader group the
// shading system knows about, via the system-level getattribute.
static const char* metrics[] = { "active_layers", "network_depth",
"texture_ops", "noise_ops" };
for (const char* metric : metrics) {
std::string key = OSL::fmtformat("stat:compiled_{}", metric);
int count = 0, vmin = 0, vmax = 0, vmedian = 0;
shadingsys->getattribute(key + ":top_count", count);
shadingsys->getattribute(key + ":min", vmin);
shadingsys->getattribute(key + ":max", vmax);
shadingsys->getattribute(key + ":median", vmedian);
OSL::print("{}: min={} max={} median={} top_count={}\n", key, vmin,
vmax, vmedian, count);
if (count <= 0)
continue;
std::vector<ustring> names(count);
std::vector<int> values(count);
shadingsys->getattribute(key + ":top_names",
TypeDesc(TypeDesc::STRING, count),
names.data());
shadingsys->getattribute(key + ":top_values",
TypeDesc(TypeDesc::INT, count),
values.data());
for (int i = 0; i < count; ++i)
OSL::print("{}: top[{}]={} \"{}\"\n", key, i, values[i],
names[i]);
}
}

// We're done with the shading system now, destroy it
rend->clear();
delete shadingsys;
Expand Down
48 changes: 34 additions & 14 deletions src/testshade/testshade.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2339,20 +2339,40 @@ test_shade(int argc, const char* argv[])
}

if (print_group_stats && !batched) {
int active_layers = 0, network_depth = 0, texture_ops = 0,
noise_ops = 0;
shadingsys->getattribute(shadergroup.get(),
"stat:compiled_active_layers", active_layers);
shadingsys->getattribute(shadergroup.get(),
"stat:compiled_network_depth", network_depth);
shadingsys->getattribute(shadergroup.get(), "stat:compiled_texture_ops",
texture_ops);
shadingsys->getattribute(shadergroup.get(), "stat:compiled_noise_ops",
noise_ops);
OSL::print("stat:compiled_active_layers={}\n", active_layers);
OSL::print("stat:compiled_network_depth={}\n", network_depth);
OSL::print("stat:compiled_texture_ops={}\n", texture_ops);
OSL::print("stat:compiled_noise_ops={}\n", noise_ops);
static const char* metrics[] = { "active_layers", "network_depth",
"texture_ops", "noise_ops" };
// Per-group values.
for (const char* metric : metrics) {
std::string key = OSL::fmtformat("stat:compiled_{}", metric);
int v = 0;
shadingsys->getattribute(shadergroup.get(), key, v);
OSL::print("{}={}\n", key, v);
}
// Ranked and aggregate values across all of the shading system's
// groups, via the system-level (no ShaderGroup*) getattribute.
for (const char* metric : metrics) {
std::string key = OSL::fmtformat("stat:compiled_{}", metric);
int count = 0, vmin = 0, vmax = 0, vmedian = 0;
shadingsys->getattribute(key + ":top_count", count);
shadingsys->getattribute(key + ":min", vmin);
shadingsys->getattribute(key + ":max", vmax);
shadingsys->getattribute(key + ":median", vmedian);
OSL::print("{}: min={} max={} median={} top_count={}\n", key, vmin,
vmax, vmedian, count);
if (count <= 0)
continue;
std::vector<ustring> names(count);
std::vector<int> values(count);
shadingsys->getattribute(key + ":top_names",
TypeDesc(TypeDesc::STRING, count),
names.data());
shadingsys->getattribute(key + ":top_values",
TypeDesc(TypeDesc::INT, count),
values.data());
for (int i = 0; i < count; ++i)
OSL::print("{}: top[{}]={} \"{}\"\n", key, i, values[i],
names[i]);
}
}


Expand Down
10 changes: 10 additions & 0 deletions testsuite/compstats/mtl.osl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright Contributors to the Open Shading Language project.
// SPDX-License-Identifier: BSD-3-Clause
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage

// Terminal layer for the multi-group scene: consumes an upstream color so
// that the layers feeding it survive optimization.
surface mtl(color Cin = 0)
{
Ci = Cin * emission();
}
Loading
Loading