Skip to content

[interop] Remember templated names resolved by GetScope - #166

Open
jmcarcell wants to merge 1 commit into
compiler-research:mainfrom
jmcarcell:getscope-template-cache
Open

jmcarcell wants to merge 1 commit into
compiler-research:mainfrom
jmcarcell:getscope-template-cache

Conversation

@jmcarcell

Copy link
Copy Markdown
Contributor

Binding an object to a templated class by name, for example cppjit.bind_object(addr, "std::vector<double>"), grows the memory on every call: by about 7 kB per call here, and about 11 kB per call in ROOT, where TDirectoryFile.Get does this for every object it returns. ROOT 6.40, before cppjit, doesn't have this growth. In a ROOT master build:

vector<double>         RSS growth after 2000 bind_object calls: 23772 kB
std::vector<double>    RSS growth after 2000 bind_object calls: 25268 kB
TGraph                 RSS growth after 2000 bind_object calls: 0 kB
TH1F                   RSS growth after 2000 bind_object calls: 0 kB

interop::GetScope doesn't remember what a templated name resolved to, so every lookup of the same name repeats this:

  • Cpp::GetScope and Cpp::GetScopeFromCompleteName fail on the name. In ROOT, the failed lookup makes TClingCallbacks::LookupObject try to autoload it, and TCling::GetClassSharedLibs parses the name again with cling::LookupHelper::findScope. For std::vector<double> this adds new AST nodes, about 350 B per call (found with heaptrack).
  • Then AppendTypesSlow declares a new __cppjit_interop_sN variable in the interpreter to resolve the name. This is most of the growth.

This PR caches successful lookups of templated names at the start of interop::GetScope, so both are skipped. Failed lookups aren't cached because they may succeed once more declarations are available. With the change, the growth after 20000 calls in ROOT is 0 kB for both vector<double> and std::vector<double>.

#155 is related, but it doesn't fully fix this issue. It memoizes the result inside AppendTypesSlow, which removes the new variable on every call, including for the other callers of AppendTypesSlow such as overload ranking. However, the failing lookups, and in ROOT the autoload, happen before AppendTypesSlow is called. Caching at that later point, which I tried first in ROOT, still leaves std::vector<double> growing by a few hundred bytes per call. The two changes are complementary.

The new test test58_repeated_template_scope_lookup fails without the change (the memory grows by 13 MB over 2000 calls, with a threshold of 4 MB) and passes with it.

Tested on AlmaLinux 9 with LLVM 22.1.5 and gcc 16, and in a ROOT master build, where the cppjit tests also pass with this change applied to the ROOT branch's copy of interop::GetScope.

// Resolving a templated name below declares a new variable in the
// interpreter (see AppendTypesSlow), so remember the result to not declare
// one, and grow the memory, on every lookup of the same name.
static std::map<std::pair<void*, std::string>, TCppScope_t> template_scopes;

@jmcarcell jmcarcell Oct 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::unordered_map probably not worth it if there aren't ton of templates

@aaronj0 aaronj0 Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't make an assumption on the size of loaded libraries/TU's which can grow exponentially e.g boost, eigen. So that would probably need to be an unordered_map

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the cache doesn't grow with the size of the loaded libraries. It only holds templated names that someone actually looked up by string, failed both direct lookups and then resolved through AppendTypesSlow. The thing is that there is no hash function for std::pair, so I either have to implement one or use a nested std::unordered_map.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I meant the number of templates that can come from loaded libraries. Some TU's have lots of templates that can come back for a name. But I missed the fact that this is only the ones that are resolved with AppendTypesSlow. For that the current map is fine.

@aaronj0

aaronj0 commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

The deecription makes sense. I believe the best way forward would be to minimally modify #155 to cache ahead of the autoload point that you've mentioned here. Can you give that a shot and update this PR? I can then just swap #155 's patch for this one on the ROOT branch used in the sync-up.

@jmcarcell

Copy link
Copy Markdown
Contributor Author

#155's cache is inside AppendTypesSlow, but the autoload happens earlier, when Cpp::GetScope and Cpp::GetScopeFromCompleteName fail in interop::GetScope. So the cache has to be at the start of interop::GetScope, which is what this PR does. #155 is still useful for the other callers of AppendTypesSlow.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants