Skip to content

Commit 902cefa

Browse files
committed
Introduce a thread pool for batch execution of parallel CPU-bound tasks.
This introduced a new `BatchExecutor` class that uses a pool of physical CPU-pinned (if possible) workers to execute CPU-heavy workloads in parallel. Currently this is only used for the Soroban apply stage (which is already parallel), but in the followups we'll be able to use this for more apply flow stages (pre/post apply), as well as tasks outside of apply (like parallel tx set building). Pinning the threads helps a lot with making per-thread runtime more even on machines with hyper-threading (which is probably pretty much every CPU out there). Pooling the threads is less impactful, but it helps a bit with the allocator churn.
1 parent c29192c commit 902cefa

12 files changed

Lines changed: 561 additions & 39 deletions

Builds/VisualStudio/stellar-core.vcxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,7 @@ exit /b 0
626626
<ClCompile Include="..\..\src\main\AppConnector.cpp" />
627627
<ClCompile Include="..\..\src\main\BannedAccountsPersistor.cpp" />
628628
<ClCompile Include="..\..\src\main\Diagnostics.cpp" />
629+
<ClCompile Include="..\..\src\main\NtpProbe.cpp" />
629630
<ClCompile Include="..\..\src\main\QueryServer.cpp" />
630631
<ClCompile Include="..\..\src\main\SettingsUpgradeUtils.cpp" />
631632
<ClCompile Include="..\..\src\main\test\ApplicationUtilsTests.cpp" />
@@ -759,6 +760,7 @@ exit /b 0
759760
<ClCompile Include="..\..\src\transactions\TransactionUtils.cpp" />
760761
<ClCompile Include="..\..\src\transactions\TrustFlagsOpFrameBase.cpp" />
761762
<ClCompile Include="..\..\src\util\Backtrace.cpp" />
763+
<ClCompile Include="..\..\src\util\BatchExecutor.cpp" />
762764
<ClCompile Include="..\..\src\util\BinaryFuseFilter.cpp" />
763765
<ClCompile Include="..\..\src\util\DebugMetaUtils.cpp" />
764766
<ClCompile Include="..\..\src\util\FileSystemException.cpp" />
@@ -1092,6 +1094,7 @@ exit /b 0
10921094
<ClInclude Include="..\..\src\main\AppConnector.h" />
10931095
<ClInclude Include="..\..\src\main\BannedAccountsPersistor.h" />
10941096
<ClInclude Include="..\..\src\main\Diagnostics.h" />
1097+
<ClInclude Include="..\..\src\main\NtpProbe.h" />
10951098
<ClInclude Include="..\..\src\main\QueryServer.h" />
10961099
<ClInclude Include="..\..\src\main\SettingsUpgradeUtils.h" />
10971100
<ClInclude Include="..\..\src\overlay\BanManager.h" />
@@ -1182,6 +1185,7 @@ exit /b 0
11821185
<ClInclude Include="..\..\src\transactions\TransactionUtils.h" />
11831186
<ClInclude Include="..\..\src\transactions\TrustFlagsOpFrameBase.h" />
11841187
<ClInclude Include="..\..\src\util\Backtrace.h" />
1188+
<ClInclude Include="..\..\src\util\BatchExecutor.h" />
11851189
<ClInclude Include="..\..\src\util\BinaryFuseFilter.h" />
11861190
<ClInclude Include="..\..\src\util\BufferedAsioCerealOutputArchive.h" />
11871191
<ClInclude Include="..\..\src\util\DebugMetaUtils.h" />

Builds/VisualStudio/stellar-core.vcxproj.filters

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -462,9 +462,6 @@
462462
<ClCompile Include="..\..\src\work\test\WorkTests.cpp">
463463
<Filter>work\tests</Filter>
464464
</ClCompile>
465-
<ClCompile Include="..\..\src\test\fuzz\FuzzMain.cpp">
466-
<Filter>test\fuzz</Filter>
467-
</ClCompile>
468465
<ClCompile Include="..\..\src\test\fuzz\FuzzRegressionTests.cpp">
469466
<Filter>test\fuzz</Filter>
470467
</ClCompile>
@@ -1459,6 +1456,12 @@
14591456
<ClCompile Include="..\..\src\test\CovMark.cpp">
14601457
<Filter>test</Filter>
14611458
</ClCompile>
1459+
<ClCompile Include="..\..\src\util\BatchExecutor.cpp">
1460+
<Filter>util</Filter>
1461+
</ClCompile>
1462+
<ClCompile Include="..\..\src\main\NtpProbe.cpp">
1463+
<Filter>main</Filter>
1464+
</ClCompile>
14621465
</ItemGroup>
14631466
<ItemGroup>
14641467
<ClInclude Include="..\..\lib\util\cpptoml.h">
@@ -2590,6 +2593,12 @@
25902593
<ClInclude Include="..\..\src\test\CovMark.h">
25912594
<Filter>test</Filter>
25922595
</ClInclude>
2596+
<ClInclude Include="..\..\src\util\BatchExecutor.h">
2597+
<Filter>util</Filter>
2598+
</ClInclude>
2599+
<ClInclude Include="..\..\src\main\NtpProbe.h">
2600+
<Filter>main</Filter>
2601+
</ClInclude>
25932602
</ItemGroup>
25942603
<ItemGroup>
25952604
<None Include="..\..\COPYING" />

src/ledger/LedgerManagerImpl.cpp

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "transactions/TransactionFrameBase.h"
4141
#include "transactions/TransactionMeta.h"
4242
#include "transactions/TransactionUtils.h"
43+
#include "util/BatchExecutor.h"
4344
#include "util/DebugMetaUtils.h"
4445
#include "util/Decoder.h"
4546
#include "util/Fs.h"
@@ -2639,42 +2640,37 @@ LedgerManagerImpl::applySorobanStageClustersInParallel(
26392640
{
26402641
ZoneScoped;
26412642

2642-
std::vector<std::unique_ptr<ThreadParallelApplyLedgerState>> threadStates;
2643-
std::vector<std::future<std::unique_ptr<ThreadParallelApplyLedgerState>>>
2644-
threadFutures;
2645-
26462643
DeactivateScopeGuard globalStateDeactivateGuard(globalState);
26472644

2645+
std::vector<
2646+
std::function<std::unique_ptr<ThreadParallelApplyLedgerState>()>>
2647+
tasks;
2648+
tasks.reserve(stage.numClusters());
26482649
for (size_t i = 0; i < stage.numClusters(); ++i)
26492650
{
2650-
auto const& cluster = stage.getCluster(i);
2651-
auto threadStatePtr = std::make_unique<ThreadParallelApplyLedgerState>(
2652-
app, globalState, cluster, i);
2653-
threadFutures.emplace_back(std::async(
2654-
std::launch::async, &LedgerManagerImpl::applyThread, this,
2655-
std::ref(app), std::move(threadStatePtr), std::cref(cluster),
2656-
std::cref(config), ledgerInfo, sorobanBasePrngSeed));
2651+
tasks.emplace_back([this, &app, &globalState, &stage, i, &config,
2652+
&ledgerInfo, &sorobanBasePrngSeed]() {
2653+
auto const& cluster = stage.getCluster(i);
2654+
auto threadStatePtr =
2655+
std::make_unique<ThreadParallelApplyLedgerState>(
2656+
app, globalState, cluster, i);
2657+
return applyThread(app, std::move(threadStatePtr), cluster, config,
2658+
ledgerInfo, sorobanBasePrngSeed);
2659+
});
26572660
}
26582661

2659-
for (auto& threadFuture : threadFutures)
2662+
try
26602663
{
2661-
releaseAssert(threadFuture.valid());
2662-
try
2663-
{
2664-
auto futureResult = threadFuture.get();
2665-
threadStates.emplace_back(std::move(futureResult));
2666-
}
2667-
catch (std::exception const& e)
2668-
{
2669-
printErrorAndAbort("Exception on apply thread: ", e.what());
2670-
}
2671-
catch (...)
2672-
{
2673-
printErrorAndAbort("Unknown exception on apply thread");
2674-
}
2664+
return app.getBatchExecutor().executeBatch(std::move(tasks));
2665+
}
2666+
catch (std::exception const& e)
2667+
{
2668+
printErrorAndAbort("Exception on apply thread: ", e.what());
2669+
}
2670+
catch (...)
2671+
{
2672+
printErrorAndAbort("Unknown exception on apply thread");
26752673
}
2676-
threadFutures.clear();
2677-
return threadStates;
26782674
}
26792675

26802676
void

src/main/AppConnector.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ AppConnector::threadIsType(Application::ThreadType type) const
169169
return mApp.threadIsType(type);
170170
}
171171

172+
BatchExecutor&
173+
AppConnector::getBatchExecutor()
174+
{
175+
return mApp.getBatchExecutor();
176+
}
177+
172178
ImmutableLedgerView
173179
AppConnector::copyImmutableLedgerView()
174180
{

src/main/AppConnector.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class SorobanMetrics;
2222
class SearchableHotArchiveBucketListSnapshot;
2323
struct LedgerTxnDelta;
2424
class CapacityTrackedMessage;
25+
class BatchExecutor;
2526

2627
// Helper class to isolate access to Application; all function helpers must
2728
// either be called from main or be thread-safe
@@ -68,6 +69,7 @@ class AppConnector
6869
checkScheduledAndCache(std::shared_ptr<CapacityTrackedMessage> msgTracker);
6970
SorobanNetworkConfig const& getLastClosedSorobanNetworkConfig() const;
7071
bool threadIsType(Application::ThreadType type) const;
72+
BatchExecutor& getBatchExecutor();
7173

7274
MetricsRegistry& getMetrics() const;
7375

src/main/Application.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class WorkScheduler;
3737
class BanManager;
3838
class BannedAccountsPersistor;
3939
class StatusManager;
40+
class BatchExecutor;
4041
class AbstractLedgerTxnParent;
4142
class BasicWork;
4243
enum class LoadGenMode;
@@ -270,6 +271,13 @@ class Application
270271
virtual void postOnLedgerCloseThread(std::function<void()>&& f,
271272
std::string jobName) = 0;
272273

274+
// Get the shared executor for running batches of CPU-bound tasks in
275+
// parallel. This is mostly used in the apply path, though it may also be
276+
// used for other CPU-bound tasks, as long as they don't overlap with the
277+
// apply path (e.g. building the transaction set for the next ledger).
278+
// Batches are blocking and must be run one at a time.
279+
virtual BatchExecutor& getBatchExecutor() = 0;
280+
273281
// Perform actions necessary to transition from BOOTING_STATE to other
274282
// states. In particular: either reload or reinitialize the database, and
275283
// either restart or begin reacquiring SCP consensus (as instructed by

src/main/ApplicationImpl.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include "overlay/OverlayManagerImpl.h"
5454
#include "process/ProcessManager.h"
5555
#include "transactions/SignatureChecker.h"
56+
#include "util/BatchExecutor.h"
5657
#include "util/GlobalChecks.h"
5758
#include "util/JitterInjection.h"
5859
#include "util/LogSlowExecution.h"
@@ -109,6 +110,7 @@ ApplicationImpl::ApplicationImpl(VirtualClock& clock, Config const& cfg)
109110
: nullptr)
110111
, mWorkerThreads()
111112
, mEvictionThread()
113+
, mBatchExecutor(std::make_unique<BatchExecutor>())
112114
, mStopSignals(clock.getIOContext(), SIGINT)
113115
, mStarted(false)
114116
, mStopping(false)
@@ -1541,6 +1543,13 @@ ApplicationImpl::getLedgerCloseIOContext()
15411543
return *mLedgerCloseIOContext;
15421544
}
15431545

1546+
BatchExecutor&
1547+
ApplicationImpl::getBatchExecutor()
1548+
{
1549+
releaseAssert(mBatchExecutor);
1550+
return *mBatchExecutor;
1551+
}
1552+
15441553
void
15451554
ApplicationImpl::postOnMainThread(std::function<void()>&& f, std::string&& name,
15461555
Scheduler::ActionType type)

src/main/ApplicationImpl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ class ApplicationImpl : public Application
9090
virtual asio::io_context& getOverlayIOContext() override;
9191
virtual asio::io_context& getLedgerCloseIOContext() override;
9292

93+
virtual BatchExecutor& getBatchExecutor() override;
94+
9395
virtual void postOnMainThread(std::function<void()>&& f, std::string&& name,
9496
Scheduler::ActionType type) override;
9597
virtual void postOnBackgroundThread(std::function<void()>&& f,
@@ -239,6 +241,8 @@ class ApplicationImpl : public Application
239241
// thread for eviction scans.
240242
std::unique_ptr<std::thread> mEvictionThread;
241243

244+
std::unique_ptr<BatchExecutor> mBatchExecutor;
245+
242246
// NOTE: It is important that this map not be updated outside of the
243247
// constructor. `unordered_map` is safe for multiple threads to read from,
244248
// so long as there are no concurrent writers.

src/transactions/ParallelApplyUtils.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ GlobalParallelApplyLedgerState::GlobalParallelApplyLedgerState(
308308
mInMemorySorobanState.getLedgerSeq());
309309
releaseAssertOrThrow(ltx.getHeader().ledgerSeq ==
310310
mLCLApplyView.getLedgerSeq() + 1);
311-
311+
releaseAssert(threadIsMain() ||
312+
app.threadIsType(Application::ThreadType::APPLY));
312313
// From now on, we will be using globalState, liveSnapshots, and the
313314
// hotArchive to collect all entries. Before we continue though, we need to
314315
// load into the globalEntryMap any classic entries that have been modified
@@ -327,9 +328,6 @@ GlobalParallelApplyLedgerState::
327328
AppConnector& app, AbstractLedgerTxn& ltx,
328329
std::vector<ApplyStage> const& stages)
329330
{
330-
releaseAssert(threadIsMain() ||
331-
app.threadIsType(Application::ThreadType::APPLY));
332-
333331
auto fetchInMemoryClassicEntries =
334332
[&](xdr::xvector<LedgerKey> const& keys) {
335333
for (auto const& lk : keys)
@@ -564,9 +562,6 @@ ThreadParallelApplyLedgerState::collectClusterFootprintEntriesFromGlobal(
564562
AppConnector& app, GlobalParallelApplyLedgerState const& global,
565563
Cluster const& cluster)
566564
{
567-
releaseAssert(threadIsMain() ||
568-
app.threadIsType(Application::ThreadType::APPLY));
569-
570565
// As part of the initialization of this thread state, we need to
571566
// collect all the keys that are in the global state map. For any keys
572567
// we need not in the global state, we will fetch them from the live
@@ -751,7 +746,6 @@ ThreadParallelApplyLedgerState::upsertEntry(
751746
void
752747
ThreadParallelApplyLedgerState::eraseEntry(LedgerKey const& key)
753748
{
754-
755749
auto parAppEntry =
756750
ThreadParallelApplyEntry::dirty(scopeAdoptEntryOpt(std::nullopt));
757751
mThreadEntryMap.insert_or_assign(key, parAppEntry);

0 commit comments

Comments
 (0)