Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
[cxxmodules] Enable the semantic global module index to boost perform…
…ance.

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.
  • Loading branch information
vgvassilev committed Aug 7, 2020
commit 2149fcf28b7d3dd66ec33a11b1824cdc00eab16a
62 changes: 46 additions & 16 deletions README/README.CXXMODULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,6 @@ different. There are several differences which can be noticed:
* rootcling -cxxmodule creates a single artifact *Name.pcm* after the library
name. At a final stage, ROOT might be able to integrate the Name.pcm with the
shared library itself.
* Preloads all \*pcm files at start up time -- this currently is the only
remaining bottleneck which introduces a relatively small performance overhead
at startup time and is described bellow. It will be negligible for third-
party code (dominated by header parsing).
* Improved correctness in number of cases -- in a few cases ROOT is more
correct. In particular, when resolving global variables and function
declarations which are not part of the ROOT PCH.
Expand All @@ -323,6 +319,21 @@ different. There are several differences which can be noticed:
the LD_LIBRARY_PATH descending to the system libraries. The algorithm is very
efficient because it uses bloom filters[[5]]. This in turn allows ROOT symbol
to be extended to system libraries.

### Module Registration Approaches

The C++ modules system supports /*preloading*/ of all modules at startup time.
The current implementation of loading of C++ modules in clang has an overhead
and is between 40-60 MB depending on the ROOT configuration while there might
be 2x slowdown depending on the workflow. These issues are very likely to be
addressed by the LLVM community in midterm.

Preloading of all C++ modules is semantically the closest to C++ behavior.
However, in order to achieve performance ROOT loads them on demand using
a global module index file. It has sufficient information to map a looked up
identifier to the module which contains the corresponding definition. Switching
back to preloading of all C++ modules is done by setting the `ROOT_USE_GMI`
environment variable to false.

### Supported Platforms

Expand All @@ -349,14 +360,15 @@ different. There are several differences which can be noticed:

## State of the union

C++ Modules-aware ROOT preloads all modules at start up time. Our motivating
example:
Preloading all modules at start up time turn our motivating example into:

```cpp
// ROOT prompt
root [] S *s; // #1: does not require a definition.
root [] foo::bar *baz1; // #2: does not require a definition.
root [] foo::bar baz2; // #3: requires a definition.
root [] TCanvas* c = new TCanvas(); // #4 requires a definition

```

becomes equivalent to
Expand All @@ -368,12 +380,29 @@ root [] import Foo.*;
root [] S *s; // #1: does not require a definition.
root [] foo::bar *baz1; // #2: does not require a definition.
root [] foo::bar baz2; // #3: requires a definition.
root [] TCanvas* c = new TCanvas(); // #4 requires a definition
```

The implementation avoids recursive actions and relies on a well-defined (by
the C++ standard) behavior. Currently, this comes with a constant performance
overhead which we go in details bellow.

ROOT uses the global module index (GMI) to avoid the performance overhead. ROOT
only preloads the set of C++ modules which are not present in the GMI. The
example becomes equivalent to:

```cpp
// ROOT prompt
root [] import Foo.*; // Preload Foo if it is not in the GMI.
root [] S *s; // #1: does not require a definition.
root [] foo::bar *baz1; // #2: does not require a definition.
root [] foo::bar baz2; // #3: requires a definition.
root [] TCanvas* c = new TCanvas(); // #4 requires a definition
```

Line #4 forces cling to send ROOT a callback that TCanvas in unknown but
the GMI resolves it to module Gpad, loads it and returns the control to cling.


### Performance
This section compares ROOT PCH technology with C++ Modules which is important but
Expand All @@ -385,16 +414,9 @@ is not available.
The comparisons are to give a good metric when we are ready to switch ROOT to use
C++ Modules by default. However, since it is essentially the same technology,
optimizations of C++ Modules also affect the PCH. We have a few tricks up in
the slaves to but they come with given trade-offs. For example, we can avoid
preloading of all modules at the cost of introducing recursive behavior in
loading. This requires to build a global module index which is an on-disk
hash table. It will contain information about the mapping between an
identifier and a module name. Upon failed identifier lookup we will use the
map to decide which set of modules should be loaded. Another optimization
includes building some of the modules without `-fmodules-local-submodule-visibility`.
In turn, this would flatten the C++ modules structure and give us performance
comparable to the ROOT PCH. The trade-off is that we will decrease the
encapsulation and leak information about implementation-specific header files.
the sleeves to but they come with given trade-offs.

#### Preloading of C++ Modules

The main focus for the technology preview was not in performance until recently.
We have invested some resources in optimizations and we would like to show you
Expand All @@ -413,6 +435,14 @@ The performance is dependent on many factors such as configuration of ROOT and
workflow. You can read more at our Intel IPCC-ROOT Showcase presentation
here (pp 25-33)[[8]].

#### Loading C++ Modules on Demand

In long term, we should optimize the preloading of modules to be a no-op and
avoid recursive behavior based on identifier lookup callbacks. Unfortunately,
at the moment the loading of C++ modules on demand shows significantly better
performance results.


You can visit our continuous performance monitoring tool where we compare
the performance of ROOT against ROOT with a PCH [[9]].
*Note: if you get error 400, clean your cache or open a private browser session.*
Expand Down
2 changes: 2 additions & 0 deletions core/dictgen/src/rootcling_impl.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4325,6 +4325,8 @@ int RootClingMain(int argc,
GetModuleNameFromRdictName(DepMod).str().data());
}
DepMod = GetModuleNameFromRdictName(DepMod);
// We might deserialize.
cling::Interpreter::PushTransactionRAII RAII(&interp);
if (!interp.loadModule(DepMod, /*complain*/false)) {
ROOT::TMetaUtils::Error(0, "Module '%s' failed to load.\n",
DepMod.data());
Expand Down
1 change: 1 addition & 0 deletions core/foundation/test/FoundationUtilsTests.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ using namespace ROOT::FoundationUtils;

TEST(FoundationUtilsTests, CanConvertEnvValueToBool)
{
ASSERT_FALSE(CanConvertEnvValueToBool(""));
ASSERT_TRUE(CanConvertEnvValueToBool("0"));
ASSERT_TRUE(CanConvertEnvValueToBool("false"));
ASSERT_TRUE(CanConvertEnvValueToBool("False"));
Expand Down
34 changes: 22 additions & 12 deletions core/metacling/src/TCling.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,7 @@ static bool LoadModule(const std::string &ModuleName, cling::Interpreter &interp
::Info("TCling::__LoadModule", "Preloading module %s. \n",
ModuleName.c_str());

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

Expand Down Expand Up @@ -1220,18 +1221,27 @@ static void RegisterCxxModules(cling::Interpreter &clingInterp)
clang::CompilerInstance &CI = *clingInterp.getCI();
GlobalModuleIndex *GlobalIndex = nullptr;
// Conservatively enable platform by platform.
bool supportedPlatform = false;
// Allow forcefully enabling the GMI.
llvm::Optional<std::string> EnvEnGMI = llvm::sys::Process::GetEnv("ROOT_EXPERIMENTAL_GMI");
if (EnvEnGMI.hasValue() && ROOT::FoundationUtils::ConvertEnvValueToBool(*EnvEnGMI))
supportedPlatform = true;

llvm::Optional<std::string> EnvDisGMI = llvm::sys::Process::GetEnv("ROOT_DISABLE_GMI");
if (EnvDisGMI.hasValue() && EnvEnGMI.hasValue())
::Error("TCling__RegisterCxxModules",
"Both ROOT_EXPERIMENTAL_GMI and ROOT_DISABLE_GMI env vars are set!");

if (supportedPlatform && EnvDisGMI.hasValue() && ROOT::FoundationUtils::ConvertEnvValueToBool(*EnvDisGMI)) {
bool supportedPlatform =
#ifdef R__LINUX
true
#elif defined(R__MACOSX)
true
#else // Windows
false
#endif
;
// Allow forcefully enabling/disabling the GMI.
llvm::Optional<std::string> envUseGMI = llvm::sys::Process::GetEnv("ROOT_USE_GMI");
if (envUseGMI.hasValue()) {
bool value = envUseGMI->empty() || ROOT::FoundationUtils::ConvertEnvValueToBool(*envUseGMI);

if (supportedPlatform == value)
::Warning("TCling__RegisterCxxModules", "Global module index is%sused already!",
(value) ? " " :" not ");
supportedPlatform = value;
}

if (supportedPlatform) {
loadGlobalModuleIndex(SourceLocation(), clingInterp);
// FIXME: The ASTReader still calls loadGlobalIndex and loads the file
// We should investigate how to suppress it completely.
Expand Down
9 changes: 7 additions & 2 deletions core/metacling/src/TClingCallbacks.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

#include "TClingCallbacks.h"

#include <ROOT/FoundationUtils.hxx>

#include "cling/Interpreter/DynamicLibraryManager.h"
#include "cling/Interpreter/Interpreter.h"
#include "cling/Interpreter/InterpreterCallbacks.h"
Expand Down Expand Up @@ -38,6 +40,7 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"

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

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

const CompilerInstance *CI = m_Interpreter->getCI();
const LangOptions &LangOpts = CI->getPreprocessor().getLangOpts();
Expand Down
1 change: 0 additions & 1 deletion interpreter/cling/lib/Interpreter/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,6 @@ namespace cling {
Preprocessor& PP = getCI()->getPreprocessor();
IdentifierInfo* II = PP.getIdentifierInfo(M->Name);
SourceLocation ValidLoc = getNextAvailableLoc();
Interpreter::PushTransactionRAII RAII(this);
bool success =
!getSema().ActOnModuleImport(ValidLoc, ValidLoc,
std::make_pair(II, ValidLoc)).isInvalid();
Expand Down