#pragma region CPL License
/*
Nuclex Native Framework
Copyright (C) 2002-2023 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#pragma endregion // CPL License
// If the library is compiled as a DLL, this ensures symbols are exported
#define NUCLEX_SUPPORT_SOURCE 1
#include "ConcurrentBufferTest.h"
#include "Nuclex/Support/Threading/Thread.h"
namespace {
// ------------------------------------------------------------------------------------------- //
/// Forms a bit mask where the specific number of bits are set
/// Zero-based index of the lowest bit that will be set
/// Number of bits that will be set
/// A bit mask with the specified range of bits set
std::size_t BitMask(std::size_t lowestBitIndex, std::size_t bitCount) {
return (
(static_cast(-1) << static_cast(lowestBitIndex + bitCount)) ^
(static_cast(-1) << static_cast(lowestBitIndex))
);
}
// ------------------------------------------------------------------------------------------- //
} // anonymous namespace
namespace Nuclex { namespace Support { namespace Collections {
// ------------------------------------------------------------------------------------------- //
void HighContentionBufferTest::StartThreads() {
for(std::size_t index = 0; index < this->threadCount; ++index) {
this->threads.push_back(
std::make_unique(&HighContentionBufferTest::threadStarter, this, index)
);
}
}
// ------------------------------------------------------------------------------------------- //
void HighContentionBufferTest::JoinThreads() {
for(std::size_t index = 0; index < this->threads.size(); ++index) {
this->threads[index]->join();
}
this->threads.clear();
}
// ------------------------------------------------------------------------------------------- //
void HighContentionBufferTest::threadStarter(std::size_t threadIndex) {
#if 0 // This would require all tests cap themselves to the number of hardware threads...
// Change the CPU affinity so this thread runs on the specified CPU
{
std::uint64_t cpuAffinityMask = (std::uint64_t(1) << threadIndex);
Nuclex::Support::Threading::Thread::SetCpuAffinityMask(cpuAffinityMask);
}
#endif
std::size_t runningThreadsMask = this->startSignals.fetch_or(
(std::size_t(1) << threadIndex), std::memory_order_acq_rel
);
// Do a busy spin until all threads are ready to launch (yep, this whacks CPU
// load to 100% on the core running this thread!)
while((runningThreadsMask & this->allThreadsMask) != this->allThreadsMask) {
runningThreadsMask = this->startSignals.load(std::memory_order_consume);
}
// All threads are confirmed to be in their busy spins and should very nearly
// simultaneously have detected this, so begin the actual work
markStartTime();
Thread(threadIndex);
markEndTime();
}
// ------------------------------------------------------------------------------------------- //
/// Marks the benchmark starting time if this is the first call
void HighContentionBufferTest::markStartTime() {
std::chrono::microseconds::rep zero = 0;
this->startMicroseconds.compare_exchange_strong(
zero,
std::chrono::duration_cast(
std::chrono::high_resolution_clock::now() - this->constructionTime
).count()
);
}
// ------------------------------------------------------------------------------------------- //
void HighContentionBufferTest::markEndTime() {
std::chrono::microseconds::rep zero = 0;
this->endMicroseconds.compare_exchange_strong(
zero,
std::chrono::duration_cast(
std::chrono::high_resolution_clock::now() - this->constructionTime
).count()
);
}
// ------------------------------------------------------------------------------------------- //
std::size_t HighContentionBufferTest::getBitMaskForThreadCount(std::size_t threadCount) {
return BitMask(0, threadCount);
}
// ------------------------------------------------------------------------------------------- //
TEST(HighContentionBufferTestTest, BitMaskIsCalculatedCorrectly) {
EXPECT_EQ(BitMask(0, 1), 1U);
EXPECT_EQ(BitMask(0, 2), 3U);
EXPECT_EQ(BitMask(0, 3), 7U);
EXPECT_EQ(BitMask(0, 4), 15U);
EXPECT_EQ(BitMask(0, 5), 31U);
EXPECT_EQ(BitMask(0, 6), 63U);
EXPECT_EQ(BitMask(0, 7), 127U);
EXPECT_EQ(BitMask(0, 8), 255U);
EXPECT_EQ(BitMask(0, 9), 511U);
EXPECT_EQ(BitMask(0, 10), 1023U);
EXPECT_EQ(BitMask(0, 11), 2047U);
EXPECT_EQ(BitMask(0, 12), 4095U);
EXPECT_EQ(BitMask(0, 13), 8191U);
EXPECT_EQ(BitMask(0, 14), 16383U);
EXPECT_EQ(BitMask(0, 15), 32767U);
EXPECT_EQ(BitMask(0, 16), 65535U);
}
// ------------------------------------------------------------------------------------------- //
TEST(HighContentionBufferTestTest, CanSpinUpOneThread) {
HighContentionBufferTest oneThread(1);
oneThread.StartThreads();
}
// ------------------------------------------------------------------------------------------- //
TEST(HighContentionBufferTestTest, CanSpinUpTwoThreads) {
HighContentionBufferTest twoThreads(2);
twoThreads.StartThreads();
}
// ------------------------------------------------------------------------------------------- //
TEST(HighContentionBufferTestTest, CanSpinUpFourThreads) {
HighContentionBufferTest fourThreads(4);
fourThreads.StartThreads();
}
// ------------------------------------------------------------------------------------------- //
TEST(HighContentionBufferTestTest, AllThreadsActuallyRun) {
class ThreadCountingTest : public HighContentionBufferTest {
public: ThreadCountingTest() :
HighContentionBufferTest(4),
executedThreadCount(0) {}
public: std::size_t CountExecutedThreads() const {
return this->executedThreadCount.load(std::memory_order_acquire);
}
protected: void Thread(std::size_t) override {
this->executedThreadCount.fetch_add(1, std::memory_order_relaxed);
}
private: std::atomic executedThreadCount;
};
ThreadCountingTest fourThreads;
fourThreads.StartThreads();
fourThreads.JoinThreads();
EXPECT_EQ(fourThreads.CountExecutedThreads(), 4U);
}
// ------------------------------------------------------------------------------------------- //
}}} // namespace Nuclex::Support::Collections