Skip to content
Closed
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
17 changes: 15 additions & 2 deletions bindings/pyroot/cppjit/src/interop/interop_wrapper.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,17 @@ interop::TCppScope_t interop::GetUnderlyingScope(TCppScope_t scope) {
interop::TCppScope_t interop::GetScope(const std::string& name,
TCppScope_t parent_scope) {
std::unique_lock<RInterOpMutex> Lock(InterOpMutex);
// Cpp::GetScope fails on a templated name, which makes ROOT try to autoload
// it, parsing the name again, and AppendTypesSlow below declares a new
// variable to resolve it. Both keep memory that is never freed, so remember
// the result instead of repeating this on every lookup of the same name.
static std::map<std::pair<void*, std::string>, TCppScope_t> template_scopes;
const bool is_template = name.find('<') != std::string::npos;
const auto key = std::make_pair(parent_scope.data, name);
if (is_template)
if (auto it = template_scopes.find(key); it != template_scopes.end())
return it->second;

// CppInterOp directly looks at the AST which is not enough.
// We require lazy module loading that ROOT relies on, so we do it here
// first. Use TClass::GetClass to trigger auto-loading of dictionaries and
Expand Down Expand Up @@ -792,7 +803,7 @@ interop::TCppScope_t interop::GetScope(const std::string& name,
}

// FIXME: avoid string parsing here
if (name.find('<') != std::string::npos) {
if (is_template) {
// Templated type; may need instantiation. Resolve the whole type
// expression (e.g. "std::array<float, 3>") and read back its scope.
// Splitting off the argument list and resolving it directly cannot
Expand All @@ -815,8 +826,10 @@ interop::TCppScope_t interop::GetScope(const std::string& name,
// it, so the specialization may still be declared-but-undefined.
// Force its definition: callers expect a complete scope, e.g. to
// walk its base classes.
if (scope)
if (scope) {
Cpp::IsComplete(scope);
template_scopes[key] = scope;
}
return scope;
}
}
Expand Down
21 changes: 21 additions & 0 deletions bindings/pyroot/cppjit/test/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -1733,3 +1733,24 @@ def test56_enum_arg_typedefed_underlying_type(self):

assert ns.pick(ns.S.A) == 1
assert ns.pick(ns.S.B) == 5

def test57_repeated_template_scope_lookup(self):
"""Binding to a templated class by name does not grow the memory"""

from pytest import importorskip

psutil = importorskip("psutil")

import cppjit

v = cppjit.gbl.std.vector[int]()
addr = cppjit.addressof(v)
process = psutil.Process()
for name in ["std::vector<int>", "vector<int>"]:
cppjit.bind_object(addr, name)
before = process.memory_info().rss
for _ in range(2000):
cppjit.bind_object(addr, name)
# resolving the name used to declare code in the interpreter on
# every call, growing the memory by about 10 kB each time
assert process.memory_info().rss - before < 8 * 1024 * 1024, name