HelloAurora/Tests/Public/5. Hello Loop/Main.cpp
2022-04-01 05:14:58 +01:00

229 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 (~20Hhz) 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
}
printf(".");
}
void RunTests()
{
Aurora::RuntimeStartInfo info;
info.console.fio.enableLogging = false;
info.console.asyncVSLog = true;
Aurora::RuntimeStart(info);
}