Skip to content

Commit 9368f05

Browse files
committed
[cxxmodules] Enable the semantic global module index to boost performance.
The global module index (GMI) is an optimization which hides the introduced by clang overhead when pre-loading the C++ modules at startup. The GMI represents a mapping between an identifier and a set of modules which contain this indentifier. This mean that if we TH1 is undeclared the GMI will load all modules which contain this identifier which is usually suboptimal, too. The semantic GMI maps identifiers only to modules which contain a definition of the entity behind the identifier. For cases such as typedefs where the entity introduces a synonym (rather than declaration) we map the first module we encounter. For namespaces we add all modules which has a namespace partition. The namespace case is still suboptimal and further improved by inspecting what exactly is being looked up in the namespace by the qualified lookup facilities.
1 parent 6a9b5cc commit 9368f05

File tree

10 files changed

+114
-49
lines changed

10 files changed

+114
-49
lines changed

README/README.CXXMODULES.md

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,6 @@ different. There are several differences which can be noticed:
309309
* rootcling -cxxmodule creates a single artifact *Name.pcm* after the library
310310
name. At a final stage, ROOT might be able to integrate the Name.pcm with the
311311
shared library itself.
312-
* Preloads all \*pcm files at start up time -- this currently is the only
313-
remaining bottleneck which introduces a relatively small performance overhead
314-
at startup time and is described bellow. It will be negligible for third-
315-
party code (dominated by header parsing).
316312
* Improved correctness in number of cases -- in a few cases ROOT is more
317313
correct. In particular, when resolving global variables and function
318314
declarations which are not part of the ROOT PCH.
@@ -323,6 +319,21 @@ different. There are several differences which can be noticed:
323319
the LD_LIBRARY_PATH descending to the system libraries. The algorithm is very
324320
efficient because it uses bloom filters[[5]]. This in turn allows ROOT symbol
325321
to be extended to system libraries.
322+
323+
### Module Registration Approaches
324+
325+
The C++ modules system supports /*preloading*/ of all modules at startup time.
326+
The current implementation of loading of C++ modules in clang has an overhead
327+
and is between 40-60 MB depending on the ROOT configuration while there might
328+
be 2x slowdown depending on the workflow. These issues are very likely to be
329+
addressed by the LLVM community in midterm.
330+
331+
Preloading of all C++ modules is semantically the closest to C++ behavior.
332+
However, in order to achieve performance ROOT loads them on demand using
333+
a global module index file. It has sufficient information to map a looked up
334+
identifier to the module which contains the corresponding definition. Switching
335+
back to preloading of all C++ modules is done by setting the `ROOT_USE_GMI`
336+
environment variable to false.
326337
327338
### Supported Platforms
328339
@@ -349,14 +360,15 @@ different. There are several differences which can be noticed:
349360
350361
## State of the union
351362
352-
C++ Modules-aware ROOT preloads all modules at start up time. Our motivating
353-
example:
363+
Preloading all modules at start up time turn our motivating example into:
354364
355365
```cpp
356366
// ROOT prompt
357367
root [] S *s; // #1: does not require a definition.
358368
root [] foo::bar *baz1; // #2: does not require a definition.
359369
root [] foo::bar baz2; // #3: requires a definition.
370+
root [] TCanvas* c = new TCanvas(); // #4 requires a definition
371+
360372
```
361373
362374
becomes equivalent to
@@ -368,12 +380,29 @@ root [] import Foo.*;
368380
root [] S *s; // #1: does not require a definition.
369381
root [] foo::bar *baz1; // #2: does not require a definition.
370382
root [] foo::bar baz2; // #3: requires a definition.
383+
root [] TCanvas* c = new TCanvas(); // #4 requires a definition
371384
```
372385
373386
The implementation avoids recursive actions and relies on a well-defined (by
374387
the C++ standard) behavior. Currently, this comes with a constant performance
375388
overhead which we go in details bellow.
376389
390+
ROOT uses the global module index (GMI) to avoid the performance overhead. ROOT
391+
only preloads the set of C++ modules which are not present in the GMI. The
392+
example becomes equivalent to:
393+
394+
```cpp
395+
// ROOT prompt
396+
root [] import Foo.*; // Preload Foo if it is not in the GMI.
397+
root [] S *s; // #1: does not require a definition.
398+
root [] foo::bar *baz1; // #2: does not require a definition.
399+
root [] foo::bar baz2; // #3: requires a definition.
400+
root [] TCanvas* c = new TCanvas(); // #4 requires a definition
401+
```
402+
403+
Line #4 forces cling to send ROOT a callback that TCanvas in unknown but
404+
the GMI resolves it to module Gpad, loads it and returns the control to cling.
405+
377406
378407
### Performance
379408
This section compares ROOT PCH technology with C++ Modules which is important but
@@ -385,16 +414,9 @@ is not available.
385414
The comparisons are to give a good metric when we are ready to switch ROOT to use
386415
C++ Modules by default. However, since it is essentially the same technology,
387416
optimizations of C++ Modules also affect the PCH. We have a few tricks up in
388-
the slaves to but they come with given trade-offs. For example, we can avoid
389-
preloading of all modules at the cost of introducing recursive behavior in
390-
loading. This requires to build a global module index which is an on-disk
391-
hash table. It will contain information about the mapping between an
392-
identifier and a module name. Upon failed identifier lookup we will use the
393-
map to decide which set of modules should be loaded. Another optimization
394-
includes building some of the modules without `-fmodules-local-submodule-visibility`.
395-
In turn, this would flatten the C++ modules structure and give us performance
396-
comparable to the ROOT PCH. The trade-off is that we will decrease the
397-
encapsulation and leak information about implementation-specific header files.
417+
the sleeves to but they come with given trade-offs.
418+
419+
#### Preloading of C++ Modules
398420
399421
The main focus for the technology preview was not in performance until recently.
400422
We have invested some resources in optimizations and we would like to show you
@@ -413,6 +435,14 @@ The performance is dependent on many factors such as configuration of ROOT and
413435
workflow. You can read more at our Intel IPCC-ROOT Showcase presentation
414436
here (pp 25-33)[[8]].
415437
438+
#### Loading C++ Modules on Demand
439+
440+
In long term, we should optimize the preloading of modules to be a no-op and
441+
avoid recursive behavior based on identifier lookup callbacks. Unfortunately,
442+
at the moment the loading of C++ modules on demand shows significantly better
443+
performance results.
444+
445+
416446
You can visit our continuous performance monitoring tool where we compare
417447
the performance of ROOT against ROOT with a PCH [[9]].
418448
*Note: if you get error 400, clean your cache or open a private browser session.*

core/dictgen/src/rootcling_impl.cxx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4332,6 +4332,8 @@ int RootClingMain(int argc,
43324332
GetModuleNameFromRdictName(DepMod).str().data());
43334333
}
43344334
DepMod = GetModuleNameFromRdictName(DepMod);
4335+
// We might deserialize.
4336+
cling::Interpreter::PushTransactionRAII RAII(&interp);
43354337
if (!interp.loadModule(DepMod, /*complain*/false)) {
43364338
ROOT::TMetaUtils::Error(0, "Module '%s' failed to load.\n",
43374339
DepMod.data());

core/foundation/test/FoundationUtilsTests.cxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ using namespace ROOT::FoundationUtils;
2222

2323
TEST(FoundationUtilsTests, CanConvertEnvValueToBool)
2424
{
25+
ASSERT_FALSE(CanConvertEnvValueToBool(""));
2526
ASSERT_TRUE(CanConvertEnvValueToBool("0"));
2627
ASSERT_TRUE(CanConvertEnvValueToBool("false"));
2728
ASSERT_TRUE(CanConvertEnvValueToBool("False"));

core/metacling/src/TCling.cxx

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,7 @@ static bool LoadModule(const std::string &ModuleName, cling::Interpreter &interp
10411041
::Info("TCling::__LoadModule", "Preloading module %s. \n",
10421042
ModuleName.c_str());
10431043

1044+
cling::Interpreter::PushTransactionRAII deserRAII(&interp);
10441045
return interp.loadModule(ModuleName, /*Complain=*/true);
10451046
}
10461047

@@ -1115,23 +1116,23 @@ static GlobalModuleIndex *loadGlobalModuleIndex(SourceLocation TriggerLoc, cling
11151116
TraverseDecl(TU);
11161117
}
11171118
bool VisitNamedDecl(NamedDecl *ND) {
1118-
for (auto R : ND->redecls()) {
1119-
if (!R->isFromASTFile())
1120-
continue;
1121-
if (TagDecl *TD = llvm::dyn_cast<TagDecl>(R)) {
1122-
if (TD->isCompleteDefinition())
1123-
Register(TD);
1124-
} else if (NamespaceDecl *NSD = llvm::dyn_cast<NamespaceDecl>(R))
1125-
Register(NSD, /*AddSingleEntry=*/ false);
1126-
else if (TypedefNameDecl *TND = dyn_cast<TypedefNameDecl>(R))
1127-
Register(TND);
1128-
// FIXME: Add the rest...
1119+
if (!ND->isFromASTFile())
1120+
return true;
1121+
1122+
if (TagDecl *TD = llvm::dyn_cast<TagDecl>(ND)) {
1123+
if (TD->isCompleteDefinition())
1124+
Register(TD);
1125+
} else if (NamespaceDecl *NSD = llvm::dyn_cast<NamespaceDecl>(ND)) {
1126+
Register(NSD, /*AddSingleEntry=*/ false);
11291127
}
1128+
else if (TypedefNameDecl *TND = dyn_cast<TypedefNameDecl>(ND))
1129+
Register(TND);
1130+
// FIXME: Add the rest...
11301131
return true; // continue decending
11311132
}
11321133
private:
11331134
clang::GlobalModuleIndex::UserDefinedInterestingIDs &DefinitionIDs;
1134-
void Register(NamedDecl* ND, bool AddSingleEntry = true) {
1135+
void Register(const NamedDecl* ND, bool AddSingleEntry = true) {
11351136
assert(ND->isFromASTFile());
11361137
// FIXME: All decls should have an owning module once rootcling
11371138
// updates its generated decls from within the LookupHelper & co.
@@ -1220,18 +1221,32 @@ static void RegisterCxxModules(cling::Interpreter &clingInterp)
12201221
clang::CompilerInstance &CI = *clingInterp.getCI();
12211222
GlobalModuleIndex *GlobalIndex = nullptr;
12221223
// Conservatively enable platform by platform.
1223-
bool supportedPlatform = false;
1224-
// Allow forcefully enabling the GMI.
1225-
llvm::Optional<std::string> EnvEnGMI = llvm::sys::Process::GetEnv("ROOT_EXPERIMENTAL_GMI");
1226-
if (EnvEnGMI.hasValue() && ROOT::FoundationUtils::ConvertEnvValueToBool(*EnvEnGMI))
1227-
supportedPlatform = true;
1228-
1229-
llvm::Optional<std::string> EnvDisGMI = llvm::sys::Process::GetEnv("ROOT_DISABLE_GMI");
1230-
if (EnvDisGMI.hasValue() && EnvEnGMI.hasValue())
1231-
::Error("TCling__RegisterCxxModules",
1232-
"Both ROOT_EXPERIMENTAL_GMI and ROOT_DISABLE_GMI env vars are set!");
1233-
1234-
if (supportedPlatform && EnvDisGMI.hasValue() && ROOT::FoundationUtils::ConvertEnvValueToBool(*EnvDisGMI)) {
1224+
bool supportedPlatform =
1225+
#ifdef R__LINUX
1226+
true
1227+
#elif defined(R__MACOSX)
1228+
true
1229+
#else // Windows
1230+
false
1231+
#endif
1232+
;
1233+
// Allow forcefully enabling/disabling the GMI.
1234+
llvm::Optional<std::string> envUseGMI = llvm::sys::Process::GetEnv("ROOT_USE_GMI");
1235+
if (envUseGMI.hasValue()) {
1236+
if (!envUseGMI->empty() && !ROOT::FoundationUtils::CanConvertEnvValueToBool(*envUseGMI))
1237+
::Warning("TCling__RegisterCxxModules",
1238+
"Cannot convert '%s' to bool, setting to false!",
1239+
envUseGMI->c_str());
1240+
1241+
bool value = envUseGMI->empty() || ROOT::FoundationUtils::ConvertEnvValueToBool(*envUseGMI);
1242+
1243+
if (supportedPlatform == value)
1244+
::Warning("TCling__RegisterCxxModules", "Global module index is%sused already!",
1245+
(value) ? " " :" not ");
1246+
supportedPlatform = value;
1247+
}
1248+
1249+
if (supportedPlatform) {
12351250
loadGlobalModuleIndex(SourceLocation(), clingInterp);
12361251
// FIXME: The ASTReader still calls loadGlobalIndex and loads the file
12371252
// We should investigate how to suppress it completely.

core/metacling/src/TClingCallbacks.cxx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
#include "TClingCallbacks.h"
1313

14+
#include <ROOT/FoundationUtils.hxx>
15+
1416
#include "cling/Interpreter/DynamicLibraryManager.h"
1517
#include "cling/Interpreter/Interpreter.h"
1618
#include "cling/Interpreter/InterpreterCallbacks.h"
@@ -38,6 +40,7 @@
3840
#include "llvm/Support/Error.h"
3941
#include "llvm/Support/FileSystem.h"
4042
#include "llvm/Support/Path.h"
43+
#include "llvm/Support/Process.h"
4144

4245
#include "TClingUtils.h"
4346
#include "ClingRAII.h"
@@ -288,8 +291,10 @@ bool TClingCallbacks::LookupObject(LookupResult &R, Scope *S) {
288291

289292
bool TClingCallbacks::findInGlobalModuleIndex(DeclarationName Name, bool loadFirstMatchOnly /*=true*/)
290293
{
291-
if (::getenv("ROOT_DISABLE_GMI"))
292-
return false;
294+
llvm::Optional<std::string> envUseGMI = llvm::sys::Process::GetEnv("ROOT_USE_GMI");
295+
if (envUseGMI.hasValue())
296+
if (!envUseGMI->empty() && !ROOT::FoundationUtils::ConvertEnvValueToBool(*envUseGMI))
297+
return false;
293298

294299
const CompilerInstance *CI = m_Interpreter->getCI();
295300
const LangOptions &LangOpts = CI->getPreprocessor().getLangOpts();
@@ -315,15 +320,25 @@ bool TClingCallbacks::findInGlobalModuleIndex(DeclarationName Name, bool loadFir
315320
// Find the modules that reference the identifier.
316321
// Note that this only finds top-level modules.
317322
if (Index->lookupIdentifier(Name.getAsString(), FoundModules)) {
318-
for (auto FileName : FoundModules) {
319-
StringRef ModuleName = llvm::sys::path::stem(*FileName);
323+
for (llvm::StringRef FileName : FoundModules) {
324+
StringRef ModuleName = llvm::sys::path::stem(FileName);
325+
326+
// Skip to the first not-yet-loaded module.
327+
if (m_LoadedModuleFiles.count(FileName)) {
328+
if (gDebug > 2)
329+
llvm::errs() << "Module '" << ModuleName << "' already loaded"
330+
<< " for '" << Name.getAsString() << "'\n";
331+
continue;
332+
}
333+
320334
fIsLoadingModule = true;
321335
if (gDebug > 2)
322-
llvm::errs() << "Loading " << ModuleName << " on demand"
323-
<< " for " << Name.getAsString() << "\n";
336+
llvm::errs() << "Loading '" << ModuleName << "' on demand"
337+
<< " for '" << Name.getAsString() << "'\n";
324338

325339
m_Interpreter->loadModule(ModuleName);
326340
fIsLoadingModule = false;
341+
m_LoadedModuleFiles[FileName] = Name;
327342
if (loadFirstMatchOnly)
328343
break;
329344
}

core/metacling/src/TClingCallbacks.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class TClingCallbacks : public cling::InterpreterCallbacks {
4949
bool fPPChanged = false;
5050
bool fIsCodeGening = false;
5151
bool fIsLoadingModule = false;
52+
llvm::DenseMap<llvm::StringRef, clang::DeclarationName> m_LoadedModuleFiles;
5253

5354
public:
5455
TClingCallbacks(cling::Interpreter* interp, bool hasCodeGen);

core/metacling/src/TClingDataMemberInfo.cxx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,8 @@ long TClingDataMemberInfo::Property() const
442442
property = TClingDeclInfo::Property(property, qt);
443443
const clang::TagType *tt = qt->getAs<clang::TagType>();
444444
if (tt) {
445+
// tt->getDecl() might deserialize.
446+
cling::Interpreter::PushTransactionRAII RAII(fInterp);
445447
const clang::TagDecl *td = tt->getDecl();
446448
if (td->isClass()) {
447449
property |= kIsClass;

interpreter/cling/lib/Interpreter/Interpreter.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,6 @@ namespace cling {
883883
Preprocessor& PP = getCI()->getPreprocessor();
884884
IdentifierInfo* II = PP.getIdentifierInfo(M->Name);
885885
SourceLocation ValidLoc = getNextAvailableLoc();
886-
Interpreter::PushTransactionRAII RAII(this);
887886
bool success =
888887
!getSema().ActOnModuleImport(ValidLoc, ValidLoc,
889888
std::make_pair(II, ValidLoc)).isInvalid();

interpreter/llvm/src/tools/clang/include/clang/Serialization/GlobalModuleIndex.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ class GlobalModuleIndex {
186186
/// \returns true if the identifier is known to the index, false otherwise.
187187
bool lookupIdentifier(StringRef Name, HitSet &Hits);
188188

189-
typedef llvm::SmallPtrSet<std::string *, 4> FileNameHitSet;
189+
typedef llvm::SmallDenseSet<llvm::StringRef, 4> FileNameHitSet;
190190
bool lookupIdentifier(StringRef Name, FileNameHitSet &Hits);
191191

192192
/// \brief Note that the given module file has been loaded.

interpreter/llvm/src/tools/clang/lib/Serialization/GlobalModuleIndex.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ bool GlobalModuleIndex::lookupIdentifier(StringRef Name, FileNameHitSet &Hits) {
339339
SmallVector<unsigned, 2> ModuleIDs = *Known;
340340
for (unsigned I = 0, N = ModuleIDs.size(); I != N; ++I) {
341341
assert(!Modules[ModuleIDs[I]].FileName.empty());
342-
Hits.insert(&Modules[ModuleIDs[I]].FileName);
342+
Hits.insert(Modules[ModuleIDs[I]].FileName);
343343
}
344344

345345
++NumIdentifierLookupHits;

0 commit comments

Comments
 (0)