Fixing TLS for Multithreaded Engine Calls

Начинающий
Статус
Оффлайн
Регистрация
6 Мар 2019
Сообщения
5
Реакции[?]
2
Поинты[?]
0
Hey, everyone, so I haven't been doing any CS coding lately, but there is one thing I figured out a long while back when working on multithreaded tracer.

As many of you know, calling engine functions from non-game threads crashes the game, but if you were to solve that, then you could do all sorts of magic, such as threading the engine trace, or even increase the performance of the game itself by
Пожалуйста, авторизуйтесь для просмотра ссылки.
(faithful recreation of my boy @
Пожалуйста, авторизуйтесь для просмотра ссылки.
's fps optimizeR video on a slower CPU power VM). So, how do we solve it? If you ask the right set of questions, you should be able to figure it out rather quickly:

Where does the game crash?
As far as I remember, the first source of crashes when threading game trace is in datacache lock/unlock functions. Sometimes it does not crash there, then the next crash is inside engine's
Пожалуйста, авторизуйтесь для просмотра ссылки.
functions that involve thread-local variables (TLS vars).

Okay, so it is most likely due to TLS which means the functions were meant to be multithreadable. We also know that the main game's thread is fine calling those functions. So, what is the difference between the game's threads and your threads?
Obviously, game threads have the thread local storage initialized properly.

But how exactly do the game's threads have their TLS initialized properly?
This is the key question. Obviously, the game does not do any of the messy "let's manually copy out TLS pages" since it is highly platform dependant and would not be stable to say the least. Surprise surprise, Source engine has this really cool Tier0 lib that includes functions regarding
Пожалуйста, авторизуйтесь для просмотра ссылки.
and if you were to look into CSGO's libraries, you would find these beautiful exported functions:
AllocateThreadID
FreeThreadID

They get called down the thread initialization/deinitialization chain managing all the necessary TLS entries correctly.

And if you were to call AllocateThreadID on all threads of your thread-pool, then you would notice that the game does not crash anymore.

Since I have not yet built in a function to dispatch a function to all pool threads to
Пожалуйста, авторизуйтесь для просмотра ссылки.
, or a custom initialization function, I called the functions this way:

static Semaphore dispatchSem;
static SharedMutex smtx;

template<typename T, T& Fn>
static void AllThreadsStub(void*)
{
dispatchSem.Post();
smtx.rlock();
smtx.runlock();
Fn();
}

TODO: Build this into the threading library
template<typename T, T& Fn>
static void DispatchToAllThreads(void* data)
{
smtx.wlock();

for (size_t i = 0; i < Threading::numThreads; i++)
Threading::QueueJobRef(AllThreadsStub<T, Fn>, data);

for (size_t i = 0; i < Threading::numThreads; i++)
dispatchSem.Wait();

smtx.wunlock();

Threading::FinishQueue(false);
}

...
DispatchToAllThreads<ThreadIDFn, AllocateThreadID>(nullptr);
...
DispatchToAllThreads<ThreadIDFn, FreeThreadID>(nullptr);
Personally, I still rebuilt most of the game's tracing loops to incorporate some more advanced optimizations, but, at least according to Flaw, rebuilding is not necessary as the game moved previously global
Пожалуйста, авторизуйтесь для просмотра ссылки.
to Ray_t structure.

Anyways, that's it for now. Happy optimizing!

 
Сверху Снизу