Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serving cached AOT methods at the JITServer and loading them at the client #14017

Merged
merged 10 commits into from
Nov 27, 2021
Merged
2 changes: 1 addition & 1 deletion doc/compiler/jitserver/Caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ There are 2 types of caching that JITServer does: global and per-compilation.
- Global caching (in persistent memory) is done for entities that will not change (or are very unlikely to change) over the lifetime of a client JVM, e.g. GC mode, IProfiler data for compiled methods, parent class of a J9 class, etc. Data stored in global caches will persist across multiple compilations or until the Java class it's describing is unloaded/redefined.
- Local caching (on the compilation heap) is done for entities that are not going to change during the current compilation, but might change in-between compilations or are just unique for each compilation, e.g. resolved methods are created anew for each compilation. We also use local caching for entities that can change, but are unlikely to do so during the limited life span of the current compilation, e.g. IProfiler data for interpreted methods. Since method is still interpreted, new profiling data might be added, but it's unlikely to change significantly enough to affect performance over the duration of the current compilation.

Both types of caching are done on per-client basis, that is, if multiple clients are connected to the same server, they will not share caches, as that would make entities very complicated. There is one exception: when an option `-Xjit:shareROMClasses` is specified on the server, cached ROM classes can be shared between different clients.
Both types of caching are done on per-client basis, that is, if multiple clients are connected to the same server, they will not share caches, as that would make entities very complicated. There is one exception: when an option `-XX:+JITServerShareROMClasses` is specified on the server, cached ROM classes can be shared between different clients.

Whenever possible, caching should be done globally, because hit rates will be higher, but one should be careful and make sure that the client data will not actually change.

Expand Down
2 changes: 2 additions & 0 deletions runtime/compiler/compile/J9Compilation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ J9::Compilation::Compilation(int32_t id,
_globalMemory(*::trPersistentMemory, heapMemoryRegion),
_perClientMemory(_trMemory),
_methodsRequiringTrampolines(getTypedAllocator<TR_OpaqueMethodBlock *>(self()->allocator())),
_deserializedAOTMethod(false),
_deserializedAOTMethodUsingSVM(false),
_aotCacheStore(false),
_serializationRecords(decltype(_serializationRecords)::allocator_type(heapMemoryRegion)),
#endif /* defined(J9VM_OPT_JITSERVER) */
Expand Down
12 changes: 12 additions & 0 deletions runtime/compiler/compile/J9Compilation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,12 @@ class OMR_EXTENSIBLE Compilation : public OMR::CompilationConnector

TR::list<TR_OpaqueMethodBlock *> &getMethodsRequiringTrampolines() { return _methodsRequiringTrampolines; }

bool isDeserializedAOTMethod() const { return _deserializedAOTMethod; }
void setDeserializedAOTMethod(bool deserialized) { _deserializedAOTMethod = deserialized; }

bool isDeserializedAOTMethodUsingSVM() const { return _deserializedAOTMethodUsingSVM; }
void setDeserializedAOTMethodUsingSVM(bool usingSVM) { _deserializedAOTMethodUsingSVM = usingSVM; }

bool isAOTCacheStore() const { return _aotCacheStore; }
void setAOTCacheStore(bool store) { _aotCacheStore = store; }

Expand Down Expand Up @@ -471,6 +477,12 @@ class OMR_EXTENSIBLE Compilation : public OMR::CompilationConnector
// so that trampolines can be reserved there.
TR::list<TR_OpaqueMethodBlock *> _methodsRequiringTrampolines;

// True if this remote compilation resulted in deserializing an AOT method
// received from the JITServer AOT cache; always false at the server
bool _deserializedAOTMethod;
// True if this deserialized AOT method received from the
// JITServer AOT cache uses SVM; always false at the server
bool _deserializedAOTMethodUsingSVM;
// True if the result of this out-of-process compilation will be
// stored in JITServer AOT cache; always false at the client
bool _aotCacheStore;
Expand Down
110 changes: 71 additions & 39 deletions runtime/compiler/control/CompilationThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
#include "control/JITServerCompilationThread.hpp"
#include "control/JITServerHelpers.hpp"
#include "runtime/JITClientSession.hpp"
#include "runtime/JITServerAOTDeserializer.hpp"
#include "net/ClientStream.hpp"
#include "net/ServerStream.hpp"
#include "omrformatconsts.h"
Expand Down Expand Up @@ -2131,6 +2132,7 @@ bool TR::CompilationInfo::shouldRetryCompilation(TR_MethodToBeCompiled *entry, T
case compilationStreamMessageTypeMismatch:
case compilationStreamVersionIncompatible:
case compilationStreamLostMessage:
case aotCacheDeserializationFailure:
#endif
case compilationInterrupted:
case compilationCodeReservationFailure:
Expand Down Expand Up @@ -3327,11 +3329,12 @@ void TR::CompilationInfo::stopCompilationThreads()
{
if (getPersistentInfo()->getRemoteCompilationMode() == JITServer::SERVER)
{
TR_J9VMBase * vmj9 = (TR_J9VMBase *)(TR_J9VMBase::get(_jitConfig, 0));
TR_J9VMBase *vmj9 = (TR_J9VMBase *)TR_J9VMBase::get(_jitConfig, NULL);
JITServerIProfiler *iProfiler = (JITServerIProfiler *)vmj9->getIProfiler();
iProfiler->printStats();
}
}

static char *printJITServerConnStats = feGetEnv("TR_PrintJITServerConnStats");
if (printJITServerConnStats)
{
Expand All @@ -3346,6 +3349,15 @@ void TR::CompilationInfo::stopCompilationThreads()
fprintf(stderr, "Number of connections closed = %u\n", JITServer::ClientStream::getNumConnectionsClosed());
}
}

static char *printJITServerAOTCacheStats = feGetEnv("TR_PrintJITServerAOTCacheStats");
if (printJITServerAOTCacheStats)
{
if (auto aotCacheMap = getJITServerAOTCacheMap())
aotCacheMap->printStats(stderr);
if (auto deserializer = getJITServerAOTDeserializer())
deserializer->printStats(stderr);
}
#endif /* defined(J9VM_OPT_JITSERVER) */

#ifdef STATS
Expand Down Expand Up @@ -9945,7 +9957,11 @@ TR::CompilationInfo::compilationEnd(J9VMThread * vmThread, TR::IlGeneratorMethod
}
else // Non-AOT compilation or remote JIT/AOT compilations (at the JITClient)
{
if (trvm->isAOT_DEPRECATED_DO_NOT_USE() && !TR::CompilationInfo::canRelocateMethod(comp))
if ((trvm->isAOT_DEPRECATED_DO_NOT_USE()
#if defined(J9VM_OPT_JITSERVER)
|| comp->isDeserializedAOTMethod()
#endif /* defined(J9VM_OPT_JITSERVER) */
) && !TR::CompilationInfo::canRelocateMethod(comp))
{
// Handle the case when relocations are delayed.
// Delete any assumptions that might still exist in persistent memory
Expand Down Expand Up @@ -10775,6 +10791,10 @@ TR::CompilationInfoPerThreadBase::processException(
// no need to set error code here because error code is set
// in remoteCompile at JITClient when the compilation failed.
}
catch (const J9::AOTCacheDeserializationFailure &e)
{
// error code was already set in remoteCompile()
}
#endif /* defined(J9VM_OPT_JITSERVER) */
catch (...)
{
Expand Down Expand Up @@ -11178,37 +11198,44 @@ TR::CompilationInfo::computeFreePhysicalLimitAndAbortCompilationIfLow(TR::Compil
}

void
TR::CompilationInfo::replenishInvocationCount(J9Method* method, TR::Compilation* comp)
TR::CompilationInfo::replenishInvocationCount(J9Method *method, TR::Compilation *comp)
{
// Replenish the counts of the method
// We are holding the compilation monitor at this point
//
J9ROMMethod *romMethod = J9_ROM_METHOD_FROM_RAM_METHOD(method);
if (!(romMethod->modifiers & J9AccNative))// Never change the extra field of a native method
{
int32_t methodVMExtra = TR::CompilationInfo::getJ9MethodVMExtra(method);
if (methodVMExtra == 1 || methodVMExtra == J9_JIT_QUEUED_FOR_COMPILATION)
{
// We want to use high counts unless the user specified counts on the command line
// or he used useLowerMethodCounts (or Xquickstart)
int32_t count;
if (TR::Options::getCountsAreProvidedByUser() || TR::Options::startupTimeMatters() == TR_yes)
count = getCount(romMethod, comp->getOptions(), comp->getOptions());
else
count = J9ROMMETHOD_HAS_BACKWARDS_BRANCHES(romMethod) ? TR_DEFAULT_INITIAL_BCOUNT : TR_DEFAULT_INITIAL_COUNT;
if (romMethod->modifiers & J9AccNative)// Never change the extra field of a native method
return;

TR::CompilationInfo::setInvocationCount(method, count);
if (TR::Options::getCmdLineOptions()->getVerboseOption(TR_VerbosePerformance))
{
// compiler must exist because startPC != NULL
TR_VerboseLog::writeLineLocked(TR_Vlog_PERF, "Reencoding count=%d for %s j9m=%p ", count, comp->signature(), method);
}
}
int32_t methodVMExtra = TR::CompilationInfo::getJ9MethodVMExtra(method);
if ((methodVMExtra == 1) || (methodVMExtra == J9_JIT_QUEUED_FOR_COMPILATION))
{
int32_t count;
#if defined(J9VM_OPT_JITSERVER)
// Even if the option to disable AOT relocation delay was specified, we need to delay relocation of deserialized
// AOT methods using SVM received from the JITServer AOT cache until the next interpreted invocation. Such methods
// cannot be immediately relocated in the current implementation; see CompilationInfo::canRelocateMethod().
if (comp->isDeserializedAOTMethodUsingSVM() && comp->getOption(TR_DisableDelayRelocationForAOTCompilations))
count = 0;
else
#endif /* defined(J9VM_OPT_JITSERVER) */
// We want to use high counts unless the user specified counts on the command line
// or used useLowerMethodCounts (or -Xquickstart)
if (TR::Options::getCountsAreProvidedByUser() || (TR::Options::startupTimeMatters() == TR_yes))
count = getCount(romMethod, comp->getOptions(), comp->getOptions());
else
count = J9ROMMETHOD_HAS_BACKWARDS_BRANCHES(romMethod) ? TR_DEFAULT_INITIAL_BCOUNT : TR_DEFAULT_INITIAL_COUNT;

TR::CompilationInfo::setInvocationCount(method, count);
if (TR::Options::getCmdLineOptions()->getVerboseOption(TR_VerbosePerformance))
{
TR_ASSERT(false, "Unexpected value for method->extra = %p (method=%p)\n", TR::CompilationInfo::getJ9MethodExtra(method), method);
// compiler must exist because startPC != NULL
TR_VerboseLog::writeLineLocked(TR_Vlog_PERF, "Reencoding count=%d for %s j9m=%p ", count, comp->signature(), method);
}
}
else
{
TR_ASSERT(false, "Unexpected value for method->extra = %p (method=%p)\n", TR::CompilationInfo::getJ9MethodExtra(method), method);
}
}

void
Expand Down Expand Up @@ -12395,22 +12422,27 @@ bool TR::CompilationInfo::canProcessJProfilingRequest()
bool
TR::CompilationInfo::canRelocateMethod(TR::Compilation *comp)
{
bool canRelocateMethod = true;
// Prevent the relocation if specific option is given
// Delay relocation by default, unless this option is enabled
if (!comp->getOption(TR_DisableDelayRelocationForAOTCompilations))
{
canRelocateMethod = false;
}
else
{
TR_Debug *debug = TR::Options::getDebug();
if (debug)
{
TR_FilterBST *filter = NULL;
canRelocateMethod = debug->methodSigCanBeRelocated(comp->signature(), filter);
}
}
return canRelocateMethod;
return false;

#if defined(J9VM_OPT_JITSERVER)
// Delay relocation if this is a deserialized AOT method using SVM received from the JITServer AOT cache.
// Such methods cannot be immediately relocated in the current implementation. An immediate AOT+SVM load
// uses the ID-symbol mapping created during compilation. This mapping is missing when the client receives
// a serialized AOT method from the server, and trying to load the deserialized method immediately
// triggers fatal assertions in SVM validation in certain cases. As a workaround, we delay the AOT load
// until the next interpreted invocation of the method; see CompilationInfo::replenishInvocationCounter().
//
//TODO: Avoid the overhead of rescheduling this compilation request by handling the deserialized AOT load as if
// the method came from the local SCC, rather than as if it was freshly AOT-compiled at the JITServer.
if (comp->isDeserializedAOTMethodUsingSVM())
return false;
#endif /* defined(J9VM_OPT_JITSERVER) */

TR_Debug *debug = TR::Options::getDebug();
TR_FilterBST *filter = NULL;
return debug ? debug->methodSigCanBeRelocated(comp->signature(), filter) : true;
}

#if defined(J9VM_OPT_JITSERVER)
Expand Down
11 changes: 4 additions & 7 deletions runtime/compiler/control/DLLMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
#include "runtime/IProfiler.hpp"
#include "runtime/J9Profiler.hpp"
#if defined(J9VM_OPT_JITSERVER)
#include "runtime/JITServerAOTDeserializer.hpp"
#include "runtime/Listener.hpp"
#endif /* J9VM_OPT_JITSERVER */
#include "runtime/codertinit.hpp"
Expand Down Expand Up @@ -371,19 +370,17 @@ IDATA J9VMDllMain(J9JavaVM* vm, IDATA stage, void * reserved)
TR_J9SharedCache *sharedCache = new (PERSISTENT_NEW) TR_J9SharedCache((TR_J9VMBase *)feWithoutThread);
if (sharedCache != NULL)
{
TR_PersistentMemory * persistentMemory = (TR_PersistentMemory *)(vm->jitConfig->scratchSegment);
TR_PersistentMemory *persistentMemory = (TR_PersistentMemory *)(vm->jitConfig->scratchSegment);
TR_PersistentClassLoaderTable *loaderTable = persistentMemory->getPersistentInfo()->getPersistentClassLoaderTable();
sharedCache->setPersistentClassLoaderTable(loaderTable);
loaderTable->setSharedCache(sharedCache);
#if defined(J9VM_OPT_JITSERVER)
if (auto deserializer = getCompilationInfo(vm->jitConfig)->getJITServerAOTDeserializer())
deserializer->setSharedCache(sharedCache);
#endif /* defined(J9VM_OPT_JITSERVER) */
}
}
else
#endif
#endif /* defined(J9VM_OPT_SHARED_CLASSES) */
{
TR::Options::setSharedClassCache(false);
}

if (!isAOT)
{
Expand Down
12 changes: 12 additions & 0 deletions runtime/compiler/control/J9Options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,7 @@ bool J9::Options::preProcessJitServer(J9JavaVM *vm, J9JITConfig *jitConfig)
if (!JITServerAlreadyParsed) // Avoid processing twice for AOT and JIT and produce duplicate messages
{
JITServerAlreadyParsed = true;
bool disabledShareROMClasses = false;
if (vm->internalVMFunctions->isJITServerEnabled(vm))
{
J9::PersistentInfo::_remoteCompilationMode = JITServer::SERVER;
Expand All @@ -2002,6 +2003,10 @@ bool J9::Options::preProcessJitServer(J9JavaVM *vm, J9JITConfig *jitConfig)
{
_shareROMClasses = true;
}
else if (xxDisableJITServerShareROMClassesArgIndex > xxJITServerShareROMClassesArgIndex)
{
disabledShareROMClasses = true;
}
}
else
{
Expand Down Expand Up @@ -2113,6 +2118,13 @@ bool J9::Options::preProcessJitServer(J9JavaVM *vm, J9JITConfig *jitConfig)
jitConfig->clientUID = 0;
jitConfig->serverUID = 0;
}

if ((compInfo->getPersistentInfo()->getRemoteCompilationMode() == JITServer::SERVER) &&
compInfo->getPersistentInfo()->getJITServerUseAOTCache() && !disabledShareROMClasses)
{
// Enable ROMClass sharing at the server by default (unless explicitly disabled) if using AOT cache
_shareROMClasses = true;
}
}
#endif /* defined(J9VM_OPT_JITSERVER) */
return true;
Expand Down
Loading