Add a scheduling stress test to TestThreadManager.cpp.

Was hoping to find the cause of the issue I looked at in #15431
This commit is contained in:
Henrik Rydgård 2022-04-08 11:41:50 +02:00
parent 477b1773c2
commit b04e5925d2
5 changed files with 98 additions and 2 deletions

View file

@ -529,6 +529,7 @@
<ClInclude Include="System\Display.h" /> <ClInclude Include="System\Display.h" />
<ClInclude Include="System\NativeApp.h" /> <ClInclude Include="System\NativeApp.h" />
<ClInclude Include="System\System.h" /> <ClInclude Include="System\System.h" />
<ClInclude Include="Thread\Barrier.h" />
<ClInclude Include="Thread\Channel.h" /> <ClInclude Include="Thread\Channel.h" />
<ClInclude Include="Thread\Event.h" /> <ClInclude Include="Thread\Event.h" />
<ClInclude Include="Thread\ParallelLoop.h" /> <ClInclude Include="Thread\ParallelLoop.h" />

View file

@ -415,6 +415,9 @@
<ClInclude Include="GPU\Vulkan\VulkanProfiler.h"> <ClInclude Include="GPU\Vulkan\VulkanProfiler.h">
<Filter>GPU\Vulkan</Filter> <Filter>GPU\Vulkan</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Thread\Barrier.h">
<Filter>Thread</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="ABI.cpp" /> <ClCompile Include="ABI.cpp" />

31
Common/Thread/Barrier.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include <condition_variable>
#include <mutex>
// Similar to C++20's std::barrier
class CountingBarrier {
public:
CountingBarrier(size_t count) : threadCount_(count) {}
void Arrive() {
std::unique_lock<std::mutex> lk(m);
counter++;
waiting++;
cv.wait(lk, [&] {return counter >= threadCount_; });
cv.notify_one();
waiting--;
if (waiting == 0) {
// Reset so it can be re-used.
counter = 0;
}
lk.unlock();
}
private:
std::mutex m;
std::condition_variable cv;
size_t counter = 0;
size_t waiting = 0;
size_t threadCount_;
};

View file

@ -1,11 +1,14 @@
#include "Common/Log.h" #include "Common/Log.h"
#include "Common/TimeUtil.h" #include "Common/TimeUtil.h"
#include "Common/Thread/Barrier.h"
#include "Common/Thread/ThreadManager.h" #include "Common/Thread/ThreadManager.h"
#include "Common/Thread/Channel.h" #include "Common/Thread/Channel.h"
#include "Common/Thread/Promise.h" #include "Common/Thread/Promise.h"
#include "Common/Thread/ParallelLoop.h" #include "Common/Thread/ParallelLoop.h"
#include "Common/Thread/ThreadUtil.h" #include "Common/Thread/ThreadUtil.h"
#include "UnitTest.h"
struct ResultObject { struct ResultObject {
bool ok; bool ok;
}; };
@ -56,16 +59,70 @@ bool TestParallelLoop(ThreadManager *threadMan) {
return true; return true;
} }
// This is some ugly stuff but realistic.
const size_t THREAD_COUNT = 6; // Must match the number of threads in TestMultithreadedScheduling
const size_t ITERATIONS = 100000;
static std::atomic<int> g_atomicCounter;
static ThreadManager *g_threadMan;
static CountingBarrier g_barrier(THREAD_COUNT + 1);
class IncrementTask : public Task {
public:
IncrementTask(TaskType type) : type_(type) {}
~IncrementTask() {}
virtual TaskType Type() const { return type_; }
virtual void Run() {
g_atomicCounter++;
}
private:
TaskType type_;
};
void ThreadFunc() {
for (int i = 0; i < ITERATIONS; i++) {
g_threadMan->EnqueueTask(new IncrementTask((i & 1) ? TaskType::CPU_COMPUTE : TaskType::IO_BLOCKING));
}
g_barrier.Arrive();
}
bool TestMultithreadedScheduling() {
g_atomicCounter = 0;
std::thread thread1(ThreadFunc);
std::thread thread2(ThreadFunc);
std::thread thread3(ThreadFunc);
std::thread thread4(ThreadFunc);
std::thread thread5(ThreadFunc);
std::thread thread6(ThreadFunc);
// Just testing the barrier
g_barrier.Arrive();
// OK, all are done.
EXPECT_EQ_INT(g_atomicCounter, THREAD_COUNT * ITERATIONS);
thread1.join();
thread2.join();
thread3.join();
thread4.join();
thread5.join();
thread6.join();
return true;
}
bool TestThreadManager() { bool TestThreadManager() {
ThreadManager manager; ThreadManager manager;
manager.Init(8, 1); manager.Init(8, 1);
g_threadMan = &manager;
Promise<ResultObject *> *object(Promise<ResultObject *>::Spawn(&manager, &ResultProducer, TaskType::IO_BLOCKING)); Promise<ResultObject *> *object(Promise<ResultObject *>::Spawn(&manager, &ResultProducer, TaskType::IO_BLOCKING));
if (!TestParallelLoop(&manager)) { if (!TestParallelLoop(&manager)) {
return false; return false;
} }
sleep_ms(1000); sleep_ms(100);
ResultObject *result = object->BlockUntilReady(); ResultObject *result = object->BlockUntilReady();
if (result) { if (result) {
@ -78,5 +135,9 @@ bool TestThreadManager() {
return false; return false;
} }
if (!TestMultithreadedScheduling()) {
return false;
}
return true; return true;
} }

View file

@ -2,7 +2,7 @@
#define EXPECT_TRUE(a) if (!(a)) { printf("%s:%i: Test Fail\n", __FUNCTION__, __LINE__); return false; } #define EXPECT_TRUE(a) if (!(a)) { printf("%s:%i: Test Fail\n", __FUNCTION__, __LINE__); return false; }
#define EXPECT_FALSE(a) if ((a)) { printf("%s:%i: Test Fail\n", __FUNCTION__, __LINE__); return false; } #define EXPECT_FALSE(a) if ((a)) { printf("%s:%i: Test Fail\n", __FUNCTION__, __LINE__); return false; }
#define EXPECT_EQ_INT(a, b) if ((a) != (b)) { printf("%s:%i: Test Fail\n%d\nvs\n%d\n", __FUNCTION__, __LINE__, a, b); return false; } #define EXPECT_EQ_INT(a, b) if ((a) != (b)) { printf("%s:%i: Test Fail\n%d\nvs\n%d\n", __FUNCTION__, __LINE__, (int)(a), (int)(b)); return false; }
#define EXPECT_EQ_HEX(a, b) if ((a) != (b)) { printf("%s:%i: Test Fail\n%x\nvs\n%x\n", __FUNCTION__, __LINE__, a, b); return false; } #define EXPECT_EQ_HEX(a, b) if ((a) != (b)) { printf("%s:%i: Test Fail\n%x\nvs\n%x\n", __FUNCTION__, __LINE__, a, b); return false; }
#define EXPECT_EQ_FLOAT(a, b) if ((a) != (b)) { printf("%s:%i: Test Fail\n%f\nvs\n%f\n", __FUNCTION__, __LINE__, a, b); return false; } #define EXPECT_EQ_FLOAT(a, b) if ((a) != (b)) { printf("%s:%i: Test Fail\n%f\nvs\n%f\n", __FUNCTION__, __LINE__, a, b); return false; }
#define EXPECT_APPROX_EQ_FLOAT(a, b) if (fabsf((a)-(b))>0.00001f) { printf("%s:%i: Test Fail\n%f\nvs\n%f\n", __FUNCTION__, __LINE__, a, b); /*return false;*/ } #define EXPECT_APPROX_EQ_FLOAT(a, b) if (fabsf((a)-(b))>0.00001f) { printf("%s:%i: Test Fail\n%f\nvs\n%f\n", __FUNCTION__, __LINE__, a, b); /*return false;*/ }