228 lines
7.2 KiB
C++
228 lines
7.2 KiB
C++
/***
|
|
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: Main.cpp
|
|
Date: 2022-2-18
|
|
Author: Reece
|
|
***/
|
|
#include <AuroraRuntime.hpp>
|
|
#include <gtest/gtest.h>
|
|
|
|
TEST(Loop, Semaphore)
|
|
{
|
|
auto semaA = Aurora::Loop::NewLSSemaphore(1);
|
|
auto semaB = Aurora::Loop::NewLSSemaphore(0);
|
|
auto semac = Aurora::Loop::NewLSSemaphore(0);
|
|
auto loop = Aurora::Loop::NewLoopQueue();
|
|
|
|
// Add initial loop sources
|
|
ASSERT_TRUE(loop->SourceAdd(semaA));
|
|
ASSERT_TRUE(loop->SourceAdd(semaB));
|
|
ASSERT_TRUE(loop->SourceAddWithTimeout(semac, 2000));
|
|
|
|
|
|
auto semACallback = AuMakeShared<Aurora::Loop::ILoopSourceSubscriberFunctional>([](const AuSPtr<Aurora::Loop::ILoopSource> &source) -> bool
|
|
{
|
|
AuLogInfo("Hello from semaphore a's work queue");
|
|
return false; // dont evict, we'll reuse this *1 (search for me)
|
|
});
|
|
|
|
auto semBCallback = AuMakeShared<Aurora::Loop::ILoopSourceSubscriberFunctional>([](const AuSPtr<Aurora::Loop::ILoopSource> &source) -> bool
|
|
{
|
|
AuLogInfo("Hello from semaphore b's work queue");
|
|
return true; // evict
|
|
});
|
|
|
|
|
|
auto semCCallback = AuMakeShared<Aurora::Loop::ILoopSourceSubscriberExFunctional>([](const AuSPtr<Aurora::Loop::ILoopSource> &source) -> bool
|
|
{
|
|
AuLogInfo("This should not have been triggered from C");
|
|
return false;
|
|
},
|
|
|
|
[](const AuSPtr<Aurora::Loop::ILoopSource> &source) -> void
|
|
{
|
|
AuLogInfo("C timedout succesfully!");
|
|
});
|
|
|
|
// Add optional subscriptions
|
|
ASSERT_TRUE(loop->AddCallback(semaA, semACallback));
|
|
ASSERT_TRUE(loop->AddCallback(semaB, semBCallback));
|
|
ASSERT_TRUE(loop->AddCallbackEx(semac, semCCallback));
|
|
|
|
|
|
// Commit source changes
|
|
ASSERT_TRUE(loop->Commit());
|
|
|
|
// Dispatch any (IE: semaphore A) non-blocking
|
|
ASSERT_TRUE(loop->IsSignaled());
|
|
|
|
// Reuse semaphore A
|
|
semaA->AddOne(); // *1
|
|
|
|
// And demonstrate blocking function
|
|
ASSERT_TRUE(loop->WaitAny(0)); // *1
|
|
|
|
// ...or what about now? now will you block?
|
|
ASSERT_FALSE(loop->WaitAny(1000));
|
|
|
|
// Trigger A and B
|
|
semaB->AddOne();
|
|
semaA->AddOne();
|
|
|
|
// Dispatch all
|
|
ASSERT_TRUE(loop->WaitAny(1000));
|
|
|
|
// Stall for 10s unless A comes alives (it wont)
|
|
loop->WaitAll(10000);
|
|
ASSERT_FALSE(loop->WaitAll(100));
|
|
|
|
// Manually evict semaphore A
|
|
loop->SourceRemove(semaA);
|
|
loop->Commit();
|
|
|
|
ASSERT_TRUE(loop->WaitAll(100));
|
|
}
|
|
|
|
|
|
TEST(Loop, Performance)
|
|
{
|
|
SysBenchmark("Loop Performance A");
|
|
|
|
auto semaA = Aurora::Loop::NewLSSemaphore(1);
|
|
auto semaB = Aurora::Loop::NewLSSemaphore(0);
|
|
auto semac = Aurora::Loop::NewLSSemaphore(0);
|
|
auto loop = Aurora::Loop::NewLoopQueue();
|
|
|
|
|
|
auto semACallback = AuMakeShared<Aurora::Loop::ILoopSourceSubscriberFunctional>([](const AuSPtr<Aurora::Loop::ILoopSource> &source) -> bool
|
|
{
|
|
//AuLogInfo("Hello from semaphore a's work queue");
|
|
return false; // dont evict, we'll reuse this *1
|
|
});
|
|
|
|
auto semBCallback = AuMakeShared<Aurora::Loop::ILoopSourceSubscriberFunctional>([](const AuSPtr<Aurora::Loop::ILoopSource> &source) -> bool
|
|
{
|
|
//AuLogInfo("Hello from semaphore b's work queue");
|
|
return true; // evict
|
|
});
|
|
|
|
|
|
auto semCCallback = AuMakeShared<Aurora::Loop::ILoopSourceSubscriberExFunctional>([](const AuSPtr<Aurora::Loop::ILoopSource> &source) -> bool
|
|
{
|
|
//AuLogInfo("This should not have been triggered from C");
|
|
return false;
|
|
},
|
|
|
|
[](const AuSPtr<Aurora::Loop::ILoopSource> &source) -> void
|
|
{
|
|
//AuLogInfo("C timedout succesfully!");
|
|
});
|
|
|
|
|
|
/*
|
|
------------------------------------------------------------------------------------
|
|
NT:
|
|
|
|
At 10k iterations:
|
|
[2022-03-31 13:10:29] [Debug] | [Benchmark] Loop Performance A took 51.01004ms
|
|
[2022-03-31 13:10:54] [Debug] | [Benchmark] Loop Performance A took 50.02065ms
|
|
|
|
...not bad, here's why:
|
|
Reece, [30/03/2022 19:39]
|
|
1 whole frame (~20Hz) of work to blast WaitForMultipleObjectsEx/io_submit/epoll abstraction with a test covering multiple apis 10k times/iterations
|
|
|
|
Reece, [30/03/2022 19:39]
|
|
imagine a scheduling thread working on 10k unique kernel work objects per frame
|
|
|
|
Reece, [30/03/2022 19:39]
|
|
that alone isn't comparable to sockets because sockets are grouped into 1 worker event per thread
|
|
|
|
Reece, [30/03/2022 19:40]
|
|
general purpose work should go through semaphores and condition variables, not a unique object per work item
|
|
|
|
Reece, [30/03/2022 19:40]
|
|
it would be like sleeping on a kernel object 40,000 times per frame
|
|
|
|
Reece, [30/03/2022 19:40]
|
|
by "like" i mean 10k iterations * 4 apis that can dispatch a callback or yield to kernel
|
|
|
|
Reece, [30/03/2022 19:42]
|
|
best practices say you dont fucking spam resources. 40k is a hell of a lot more than, idk, checking a file read transaction a few tens-to-hundreds of times a second on an serializer thread that's not io-bound
|
|
|
|
|
|
Further data:
|
|
VS Profiler reports: 1.6% other, 77.5% IO, kernel: 20%
|
|
[TODO: link]
|
|
|
|
...almost all of the overhead is the semaphore callbacks themselves
|
|
[TODO: link]
|
|
|
|
Needless to say, I think this is alright.
|
|
|
|
------------------------------------------------------------------------------------
|
|
Linux: TODO: io_submit
|
|
|
|
|
|
------------------------------------------------------------------------------------
|
|
BSD: kevent is a long way away
|
|
|
|
------------------------------------------------------------------------------------
|
|
*/
|
|
|
|
for (int i = 0; i < 10'000; i++)
|
|
{
|
|
// Add initial loop sources
|
|
ASSERT_TRUE(loop->SourceAdd(semaA));
|
|
ASSERT_TRUE(loop->SourceAdd(semaB));
|
|
ASSERT_TRUE(loop->SourceAddWithTimeout(semac, 2000));
|
|
|
|
// Add optional subscriptions
|
|
ASSERT_TRUE(loop->AddCallback(semaA, semACallback));
|
|
ASSERT_TRUE(loop->AddCallback(semaB, semBCallback));
|
|
ASSERT_TRUE(loop->AddCallbackEx(semac, semCCallback));
|
|
|
|
|
|
// Commit source changes
|
|
ASSERT_TRUE(loop->Commit());
|
|
|
|
// Dispatch any (IE: semaphore A) non-blocking
|
|
ASSERT_TRUE(loop->IsSignaled());
|
|
|
|
// Reuse semaphore A
|
|
semaA->AddOne(); // *1
|
|
|
|
// And demonstrate blocking function
|
|
ASSERT_TRUE(loop->WaitAny(0)); // *1
|
|
|
|
//ASSERT_FALSE(loop->WaitAny(1000));
|
|
|
|
// Trigger A and B
|
|
semaB->AddOne();
|
|
semaA->AddOne();
|
|
|
|
// Dispatch all
|
|
ASSERT_TRUE(loop->WaitAny(1000));
|
|
|
|
// Stall for 10s unless A comes alives (it wont)
|
|
//loop->WaitAll(10000);
|
|
//ASSERT_FALSE(loop->WaitAll(100));
|
|
|
|
// Manually evict semaphore A
|
|
loop->SourceRemove(semaA);
|
|
loop->SourceRemove(semac);
|
|
loop->Commit();
|
|
|
|
ASSERT_TRUE(loop->WaitAll(100));
|
|
|
|
semaA->AddOne(); // account for initial starting count of 1
|
|
}
|
|
}
|
|
|
|
void RunTests()
|
|
{
|
|
Aurora::RuntimeStartInfo info;
|
|
info.console.fio.enableLogging = false;
|
|
info.console.asyncVSLog = true;
|
|
Aurora::RuntimeStart(info);
|
|
} |