-
Notifications
You must be signed in to change notification settings - Fork 978
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support reusable module cache with multithreaded pre-compilation
- Loading branch information
Showing
13 changed files
with
663 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#include "ledger/SharedModuleCacheCompiler.h" | ||
#include "bucket/SearchableBucketList.h" | ||
#include "crypto/Hex.h" | ||
#include "crypto/SHA.h" | ||
#include "main/Application.h" | ||
#include "rust/RustBridge.h" | ||
#include "util/Logging.h" | ||
#include <chrono> | ||
|
||
using namespace stellar; | ||
|
||
SharedModuleCacheCompiler::SharedModuleCacheCompiler( | ||
Application& app, rust_bridge::SorobanModuleCache& moduleCache, | ||
std::vector<uint32_t> const& ledgerVersions) | ||
: mApp(app) | ||
, mModuleCache(moduleCache) | ||
, mSnap(app.getBucketManager() | ||
.getBucketSnapshotManager() | ||
.copySearchableLiveBucketListSnapshot()) | ||
, mNumThreads( | ||
static_cast<size_t>(std::max(2, app.getConfig().WORKER_THREADS) - 1)) | ||
, mLedgerVersions(ledgerVersions) | ||
{ | ||
} | ||
|
||
void | ||
SharedModuleCacheCompiler::pushWasm(xdr::xvector<uint8_t> const& vec) | ||
{ | ||
std::unique_lock<std::mutex> lock(mMutex); | ||
mHaveSpace.wait( | ||
lock, [&] { return mBytesLoaded - mBytesCompiled < MAX_MEM_BYTES; }); | ||
xdr::xvector<uint8_t> buf(vec); | ||
auto size = buf.size(); | ||
mWasms.emplace_back(std::move(buf)); | ||
mBytesLoaded += size; | ||
lock.unlock(); | ||
mHaveContracts.notify_all(); | ||
LOG_INFO(DEFAULT_LOG, "Loaded contract with {} bytes of wasm code", size); | ||
} | ||
|
||
bool | ||
SharedModuleCacheCompiler::isFinishedCompiling( | ||
std::unique_lock<std::mutex>& lock) | ||
{ | ||
releaseAssert(lock.owns_lock()); | ||
return mLoadedAll && mBytesCompiled == mBytesLoaded; | ||
} | ||
|
||
void | ||
SharedModuleCacheCompiler::setFinishedLoading() | ||
{ | ||
std::unique_lock lock(mMutex); | ||
mLoadedAll = true; | ||
lock.unlock(); | ||
mHaveContracts.notify_all(); | ||
} | ||
|
||
bool | ||
SharedModuleCacheCompiler::popAndCompileWasm(size_t thread, | ||
std::unique_lock<std::mutex>& lock) | ||
{ | ||
|
||
releaseAssert(lock.owns_lock()); | ||
|
||
// Wait for a new contract to compile (or being done). | ||
mHaveContracts.wait( | ||
lock, [&] { return !mWasms.empty() || isFinishedCompiling(lock); }); | ||
|
||
// Check to see if we were woken up due to end-of-compilation. | ||
if (isFinishedCompiling(lock)) | ||
{ | ||
return false; | ||
} | ||
|
||
xdr::xvector<uint8_t> wasm = std::move(mWasms.front()); | ||
mWasms.pop_front(); | ||
|
||
// Make a local shallow copy of the cache, so we don't race on the | ||
// shared host. | ||
auto cache = mModuleCache.shallow_clone(); | ||
|
||
lock.unlock(); | ||
|
||
auto start = std::chrono::steady_clock::now(); | ||
auto slice = rust::Slice<const uint8_t>(wasm.data(), wasm.size()); | ||
try | ||
{ | ||
for (auto ledgerVersion : mLedgerVersions) | ||
{ | ||
cache->compile(ledgerVersion, slice); | ||
} | ||
} | ||
catch (std::exception const& e) | ||
{ | ||
LOG_ERROR(DEFAULT_LOG, "Thread {} failed to compile wasm code: {}", | ||
thread, e.what()); | ||
} | ||
auto end = std::chrono::steady_clock::now(); | ||
auto dur_us = | ||
std::chrono::duration_cast<std::chrono::microseconds>(end - start); | ||
LOG_INFO(DEFAULT_LOG, "Thread {} compiled {} byte wasm contract {} in {}us", | ||
thread, wasm.size(), binToHex(sha256(wasm)), dur_us.count()); | ||
lock.lock(); | ||
mTotalCompileTime += dur_us; | ||
mBytesCompiled += wasm.size(); | ||
wasm.clear(); | ||
mHaveSpace.notify_all(); | ||
mHaveContracts.notify_all(); | ||
return true; | ||
} | ||
|
||
void | ||
SharedModuleCacheCompiler::run() | ||
{ | ||
auto self = shared_from_this(); | ||
auto start = std::chrono::steady_clock::now(); | ||
LOG_INFO(DEFAULT_LOG, | ||
"Launching 1 loading and {} compiling background threads", | ||
mNumThreads); | ||
mApp.postOnBackgroundThread( | ||
[self]() { | ||
self->mSnap->scanForContractCode([&](LedgerEntry const& entry) { | ||
self->pushWasm(entry.data.contractCode().code); | ||
return Loop::INCOMPLETE; | ||
}); | ||
self->setFinishedLoading(); | ||
}, | ||
"contract loading thread"); | ||
|
||
for (auto thread = 0; thread < self->mNumThreads; ++thread) | ||
{ | ||
mApp.postOnBackgroundThread( | ||
[self, thread]() { | ||
size_t nContractsCompiled = 0; | ||
std::unique_lock<std::mutex> lock(self->mMutex); | ||
while (!self->isFinishedCompiling(lock)) | ||
{ | ||
if (self->popAndCompileWasm(thread, lock)) | ||
{ | ||
++nContractsCompiled; | ||
} | ||
} | ||
LOG_INFO(DEFAULT_LOG, "Thread {} compiled {} contracts", thread, | ||
nContractsCompiled); | ||
}, | ||
fmt::format("compilation thread {}", thread)); | ||
} | ||
|
||
std::unique_lock lock(self->mMutex); | ||
self->mHaveContracts.wait( | ||
lock, [self, &lock] { return self->isFinishedCompiling(lock); }); | ||
|
||
auto end = std::chrono::steady_clock::now(); | ||
LOG_INFO(DEFAULT_LOG, | ||
"All contracts compiled in {}ms real time, {}ms CPU time", | ||
std::chrono::duration_cast<std::chrono::milliseconds>(end - start) | ||
.count(), | ||
std::chrono::duration_cast<std::chrono::milliseconds>( | ||
self->mTotalCompileTime) | ||
.count()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#pragma once | ||
// Copyright 2024 Stellar Development Foundation and contributors. Licensed | ||
// under the Apache License, Version 2.0. See the COPYING file at the root | ||
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
#include "bucket/BucketSnapshotManager.h" | ||
#include "rust/RustBridge.h" | ||
#include "xdrpp/types.h" | ||
|
||
#include <cstdint> | ||
#include <deque> | ||
|
||
#include <condition_variable> | ||
#include <memory> | ||
#include <mutex> | ||
|
||
namespace stellar | ||
{ | ||
class Application; | ||
} | ||
|
||
// This class encapsulates a multithreaded strategy for loading contracts | ||
// out of the database (on one thread) and compiling them (on N-1 others). | ||
class SharedModuleCacheCompiler | ||
: public std::enable_shared_from_this<SharedModuleCacheCompiler> | ||
{ | ||
stellar::Application& mApp; | ||
stellar::rust_bridge::SorobanModuleCache& mModuleCache; | ||
stellar::SearchableSnapshotConstPtr mSnap; | ||
std::deque<xdr::xvector<uint8_t>> mWasms; | ||
|
||
const size_t mNumThreads; | ||
const size_t MAX_MEM_BYTES = 10 * 1024 * 1024; | ||
bool mLoadedAll{false}; | ||
size_t mBytesLoaded{0}; | ||
size_t mBytesCompiled{0}; | ||
std::vector<uint32_t> mLedgerVersions; | ||
|
||
std::mutex mMutex; | ||
std::condition_variable mHaveSpace; | ||
std::condition_variable mHaveContracts; | ||
|
||
std::chrono::microseconds mTotalCompileTime{0}; | ||
|
||
void setFinishedLoading(); | ||
bool isFinishedCompiling(std::unique_lock<std::mutex>& lock); | ||
// This gets called in a loop on the loader/producer thread. | ||
void pushWasm(xdr::xvector<uint8_t> const& vec); | ||
// This gets called in a loop on the compiler/consumer threads. It returns | ||
// true if anything was actually compiled. | ||
bool popAndCompileWasm(size_t thread, std::unique_lock<std::mutex>& lock); | ||
|
||
public: | ||
SharedModuleCacheCompiler( | ||
stellar::Application& app, | ||
stellar::rust_bridge::SorobanModuleCache& moduleCache, | ||
std::vector<uint32_t> const& ledgerVersions); | ||
void run(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.