CoreTiming: Data structure cleanup
Replace adhoc linked list with a priority heap. Performance characteristics are mostly the same, but is more cache friendly. [Priority Queues have O(log n) push/pop compared to the linked list's O(n) push/O(1) pop but the queue is not big enough for that to matter, so linear is faster over linked. Very slight gains when framelimit is unlimited (Wind Waker), 1900% -> 1950%]
This commit is contained in:
parent
b88b188819
commit
17c34ae0b1
|
@ -51,12 +51,6 @@
|
|||
#error No version of is_trivially_copyable
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
struct LinkedListItem : public T
|
||||
{
|
||||
LinkedListItem<T>* next;
|
||||
};
|
||||
|
||||
// Wrapper class
|
||||
class PointerWrap
|
||||
{
|
||||
|
@ -244,67 +238,6 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
// Let's pretend std::list doesn't exist!
|
||||
template <class T, LinkedListItem<T>* (*TNew)(), void (*TFree)(LinkedListItem<T>*),
|
||||
void (*TDo)(PointerWrap&, T*)>
|
||||
void DoLinkedList(LinkedListItem<T>*& list_start, LinkedListItem<T>** list_end = 0)
|
||||
{
|
||||
LinkedListItem<T>* list_cur = list_start;
|
||||
LinkedListItem<T>* prev = nullptr;
|
||||
|
||||
while (true)
|
||||
{
|
||||
u8 shouldExist = !!list_cur;
|
||||
Do(shouldExist);
|
||||
if (shouldExist == 1)
|
||||
{
|
||||
LinkedListItem<T>* cur = list_cur ? list_cur : TNew();
|
||||
TDo(*this, (T*)cur);
|
||||
if (!list_cur)
|
||||
{
|
||||
if (mode == MODE_READ)
|
||||
{
|
||||
cur->next = nullptr;
|
||||
list_cur = cur;
|
||||
if (prev)
|
||||
prev->next = cur;
|
||||
else
|
||||
list_start = cur;
|
||||
}
|
||||
else
|
||||
{
|
||||
TFree(cur);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mode == MODE_READ)
|
||||
{
|
||||
if (prev)
|
||||
prev->next = nullptr;
|
||||
if (list_end)
|
||||
*list_end = prev;
|
||||
if (list_cur)
|
||||
{
|
||||
if (list_start == list_cur)
|
||||
list_start = nullptr;
|
||||
do
|
||||
{
|
||||
LinkedListItem<T>* next = list_cur->next;
|
||||
TFree(list_cur);
|
||||
list_cur = next;
|
||||
} while (list_cur);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
prev = list_cur;
|
||||
list_cur = list_cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
void DoMarker(const std::string& prevName, u32 arbitraryNumber = 0x42)
|
||||
{
|
||||
u32 cookie = arbitraryNumber;
|
||||
|
@ -319,16 +252,22 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Functor>
|
||||
void DoEachElement(T& container, Functor member)
|
||||
{
|
||||
u32 size = static_cast<u32>(container.size());
|
||||
Do(size);
|
||||
container.resize(size);
|
||||
|
||||
for (auto& elem : container)
|
||||
member(*this, elem);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void DoContainer(T& x)
|
||||
{
|
||||
u32 size = (u32)x.size();
|
||||
Do(size);
|
||||
x.resize(size);
|
||||
|
||||
for (auto& elem : x)
|
||||
Do(elem);
|
||||
DoEachElement(x, [](PointerWrap& p, typename T::value_type& elem) { p.Do(elem); });
|
||||
}
|
||||
|
||||
__forceinline void DoVoid(void* data, u32 size)
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/FifoQueue.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Thread.h"
|
||||
|
||||
|
@ -31,22 +33,30 @@ struct EventType
|
|||
|
||||
static std::vector<EventType> s_event_types;
|
||||
|
||||
struct BaseEvent
|
||||
struct Event
|
||||
{
|
||||
s64 time;
|
||||
u64 userdata;
|
||||
int type;
|
||||
};
|
||||
|
||||
typedef LinkedListItem<BaseEvent> Event;
|
||||
constexpr bool operator>(const Event& left, const Event& right)
|
||||
{
|
||||
return left.time > right.time;
|
||||
}
|
||||
constexpr bool operator<(const Event& left, const Event& right)
|
||||
{
|
||||
return left.time < right.time;
|
||||
}
|
||||
|
||||
// STATE_TO_SAVE
|
||||
static Event* s_first_event;
|
||||
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
|
||||
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
|
||||
// erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't accomodated
|
||||
// by the standard adaptor class.
|
||||
static std::vector<Event> s_event_queue;
|
||||
static std::mutex s_ts_write_lock;
|
||||
static Common::FifoQueue<BaseEvent, false> s_ts_queue;
|
||||
|
||||
// event pools
|
||||
static Event* s_event_pool = nullptr;
|
||||
static Common::FifoQueue<Event, false> s_ts_queue;
|
||||
|
||||
static float s_last_OC_factor;
|
||||
float g_last_OC_factor_inverted;
|
||||
|
@ -66,22 +76,6 @@ u64 g_fake_TB_start_ticks;
|
|||
|
||||
static int s_ev_lost;
|
||||
|
||||
static Event* GetNewEvent()
|
||||
{
|
||||
if (!s_event_pool)
|
||||
return new Event;
|
||||
|
||||
Event* ev = s_event_pool;
|
||||
s_event_pool = ev->next;
|
||||
return ev;
|
||||
}
|
||||
|
||||
static void FreeEvent(Event* ev)
|
||||
{
|
||||
ev->next = s_event_pool;
|
||||
s_event_pool = ev;
|
||||
}
|
||||
|
||||
static void EmptyTimedCallback(u64 userdata, s64 cyclesLate)
|
||||
{
|
||||
}
|
||||
|
@ -132,9 +126,9 @@ int RegisterEvent(const std::string& name, TimedCallback callback)
|
|||
|
||||
void UnregisterAllEvents()
|
||||
{
|
||||
if (s_first_event)
|
||||
PanicAlert("Cannot unregister events with events pending");
|
||||
_assert_msg_(POWERPC, s_event_queue.empty(), "Cannot unregister events with events pending");
|
||||
s_event_types.clear();
|
||||
s_event_types.shrink_to_fit();
|
||||
}
|
||||
|
||||
void Init()
|
||||
|
@ -156,50 +150,6 @@ void Shutdown()
|
|||
MoveEvents();
|
||||
ClearPendingEvents();
|
||||
UnregisterAllEvents();
|
||||
|
||||
while (s_event_pool)
|
||||
{
|
||||
Event* ev = s_event_pool;
|
||||
s_event_pool = ev->next;
|
||||
delete ev;
|
||||
}
|
||||
}
|
||||
|
||||
static void EventDoState(PointerWrap& p, BaseEvent* ev)
|
||||
{
|
||||
p.Do(ev->time);
|
||||
|
||||
// this is why we can't have (nice things) pointers as userdata
|
||||
p.Do(ev->userdata);
|
||||
|
||||
// we can't savestate ev->type directly because events might not get registered in the same order
|
||||
// (or at all) every time.
|
||||
// so, we savestate the event's type's name, and derive ev->type from that when loading.
|
||||
std::string name;
|
||||
if (p.GetMode() != PointerWrap::MODE_READ)
|
||||
name = s_event_types[ev->type].name;
|
||||
|
||||
p.Do(name);
|
||||
if (p.GetMode() == PointerWrap::MODE_READ)
|
||||
{
|
||||
bool foundMatch = false;
|
||||
for (unsigned int i = 0; i < s_event_types.size(); ++i)
|
||||
{
|
||||
if (name == s_event_types[i].name)
|
||||
{
|
||||
ev->type = i;
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundMatch)
|
||||
{
|
||||
WARN_LOG(POWERPC,
|
||||
"Lost event from savestate because its type, \"%s\", has not been registered.",
|
||||
name.c_str());
|
||||
ev->type = s_ev_lost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DoState(PointerWrap& p)
|
||||
|
@ -218,9 +168,44 @@ void DoState(PointerWrap& p)
|
|||
p.DoMarker("CoreTimingData");
|
||||
|
||||
MoveEvents();
|
||||
p.DoEachElement(s_event_queue, [](PointerWrap& pw, Event& ev) {
|
||||
pw.Do(ev.time);
|
||||
|
||||
p.DoLinkedList<BaseEvent, GetNewEvent, FreeEvent, EventDoState>(s_first_event);
|
||||
// this is why we can't have (nice things) pointers as userdata
|
||||
pw.Do(ev.userdata);
|
||||
|
||||
// we can't savestate ev.type directly because events might not get registered in the same
|
||||
// order (or at all) every time.
|
||||
// so, we savestate the event's type's name, and derive ev.type from that when loading.
|
||||
std::string name;
|
||||
if (pw.GetMode() != PointerWrap::MODE_READ)
|
||||
name = s_event_types[ev.type].name;
|
||||
|
||||
pw.Do(name);
|
||||
if (pw.GetMode() == PointerWrap::MODE_READ)
|
||||
{
|
||||
auto itr = std::find_if(s_event_types.begin(), s_event_types.end(),
|
||||
[&](const EventType& evt) { return evt.name == name; });
|
||||
if (itr != s_event_types.end())
|
||||
{
|
||||
ev.type = static_cast<int>(itr - s_event_types.begin());
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(POWERPC,
|
||||
"Lost event from savestate because its type, \"%s\", has not been registered.",
|
||||
name.c_str());
|
||||
ev.type = s_ev_lost;
|
||||
}
|
||||
}
|
||||
});
|
||||
p.DoMarker("CoreTimingEvents");
|
||||
|
||||
// When loading from a save state, we must assume the Event order is random and meaningless.
|
||||
// The exact layout of the heap in memory is implementation defined, therefore it is platform
|
||||
// and library version specific.
|
||||
if (p.GetMode() == PointerWrap::MODE_READ)
|
||||
std::make_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
|
||||
}
|
||||
|
||||
// This should only be called from the CPU thread. If you are calling
|
||||
|
@ -243,30 +228,7 @@ u64 GetIdleTicks()
|
|||
|
||||
void ClearPendingEvents()
|
||||
{
|
||||
while (s_first_event)
|
||||
{
|
||||
Event* e = s_first_event->next;
|
||||
FreeEvent(s_first_event);
|
||||
s_first_event = e;
|
||||
}
|
||||
}
|
||||
|
||||
static void AddEventToQueue(Event* ne)
|
||||
{
|
||||
Event* prev = nullptr;
|
||||
Event** pNext = &s_first_event;
|
||||
for (;;)
|
||||
{
|
||||
Event*& next = *pNext;
|
||||
if (!next || ne->time < next->time)
|
||||
{
|
||||
ne->next = next;
|
||||
next = ne;
|
||||
break;
|
||||
}
|
||||
prev = next;
|
||||
pNext = &prev->next;
|
||||
}
|
||||
s_event_queue.clear();
|
||||
}
|
||||
|
||||
void ScheduleEvent(s64 cycles_into_future, int event_type, u64 userdata, FromThread from)
|
||||
|
@ -285,16 +247,14 @@ void ScheduleEvent(s64 cycles_into_future, int event_type, u64 userdata, FromThr
|
|||
|
||||
if (from_cpu_thread)
|
||||
{
|
||||
Event* ne = GetNewEvent();
|
||||
ne->time = GetTicks() + cycles_into_future;
|
||||
ne->userdata = userdata;
|
||||
ne->type = event_type;
|
||||
s64 timeout = GetTicks() + cycles_into_future;
|
||||
|
||||
// If this event needs to be scheduled before the next advance(), force one early
|
||||
if (!s_is_global_timer_sane)
|
||||
ForceExceptionCheck(cycles_into_future);
|
||||
|
||||
AddEventToQueue(ne);
|
||||
s_event_queue.emplace_back(Event{timeout, userdata, event_type});
|
||||
std::push_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -306,41 +266,20 @@ void ScheduleEvent(s64 cycles_into_future, int event_type, u64 userdata, FromThr
|
|||
}
|
||||
|
||||
std::lock_guard<std::mutex> lk(s_ts_write_lock);
|
||||
Event ne;
|
||||
ne.time = g_global_timer + cycles_into_future;
|
||||
ne.type = event_type;
|
||||
ne.userdata = userdata;
|
||||
s_ts_queue.Push(ne);
|
||||
s_ts_queue.Push(Event{g_global_timer + cycles_into_future, userdata, event_type});
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveEvent(int event_type)
|
||||
{
|
||||
while (s_first_event && s_first_event->type == event_type)
|
||||
{
|
||||
Event* next = s_first_event->next;
|
||||
FreeEvent(s_first_event);
|
||||
s_first_event = next;
|
||||
}
|
||||
auto itr = std::remove_if(s_event_queue.begin(), s_event_queue.end(),
|
||||
[&](const Event& e) { return e.type == event_type; });
|
||||
|
||||
if (!s_first_event)
|
||||
return;
|
||||
|
||||
Event* prev = s_first_event;
|
||||
Event* ptr = prev->next;
|
||||
while (ptr)
|
||||
// Removing random items breaks the invariant so we have to re-establish it.
|
||||
if (itr != s_event_queue.end())
|
||||
{
|
||||
if (ptr->type == event_type)
|
||||
{
|
||||
prev->next = ptr->next;
|
||||
FreeEvent(ptr);
|
||||
ptr = prev->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev = ptr;
|
||||
ptr = ptr->next;
|
||||
}
|
||||
s_event_queue.erase(itr, s_event_queue.end());
|
||||
std::make_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,14 +302,10 @@ void ForceExceptionCheck(s64 cycles)
|
|||
|
||||
void MoveEvents()
|
||||
{
|
||||
BaseEvent sevt;
|
||||
while (s_ts_queue.Pop(sevt))
|
||||
for (Event ev; s_ts_queue.Pop(ev);)
|
||||
{
|
||||
Event* evt = GetNewEvent();
|
||||
evt->time = sevt.time;
|
||||
evt->userdata = sevt.userdata;
|
||||
evt->type = sevt.type;
|
||||
AddEventToQueue(evt);
|
||||
s_event_queue.emplace_back(std::move(ev));
|
||||
std::push_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -386,24 +321,23 @@ void Advance()
|
|||
|
||||
s_is_global_timer_sane = true;
|
||||
|
||||
while (s_first_event && s_first_event->time <= g_global_timer)
|
||||
while (!s_event_queue.empty() && s_event_queue.front().time <= g_global_timer)
|
||||
{
|
||||
// LOG(POWERPC, "[Scheduler] %s (%lld, %lld) ",
|
||||
// s_event_types[s_first_event->type].name ? s_event_types[s_first_event->type].name
|
||||
// : "?",
|
||||
// (u64)g_global_timer, (u64)s_first_event->time);
|
||||
Event* evt = s_first_event;
|
||||
s_first_event = s_first_event->next;
|
||||
s_event_types[evt->type].callback(evt->userdata, g_global_timer - evt->time);
|
||||
FreeEvent(evt);
|
||||
Event evt = std::move(s_event_queue.front());
|
||||
std::pop_heap(s_event_queue.begin(), s_event_queue.end(), std::greater<Event>());
|
||||
s_event_queue.pop_back();
|
||||
// NOTICE_LOG(POWERPC, "[Scheduler] %-20s (%lld, %lld)", evt.type->name->c_str(),
|
||||
// g_global_timer, evt.time);
|
||||
s_event_types[evt.type].callback(evt.userdata, g_global_timer - evt.time);
|
||||
}
|
||||
|
||||
s_is_global_timer_sane = false;
|
||||
|
||||
if (s_first_event)
|
||||
// Still events left (scheduled in the future)
|
||||
if (!s_event_queue.empty())
|
||||
{
|
||||
g_slice_length =
|
||||
static_cast<int>(std::min<s64>(s_first_event->time - g_global_timer, MAX_SLICE_LENGTH));
|
||||
g_slice_length = static_cast<int>(
|
||||
std::min<s64>(s_event_queue.front().time - g_global_timer, MAX_SLICE_LENGTH));
|
||||
}
|
||||
|
||||
PowerPC::ppcState.downcount = CyclesToDowncount(g_slice_length);
|
||||
|
@ -417,12 +351,14 @@ void Advance()
|
|||
|
||||
void LogPendingEvents()
|
||||
{
|
||||
Event* ptr = s_first_event;
|
||||
while (ptr)
|
||||
auto clone = s_event_queue;
|
||||
std::sort(clone.begin(), clone.end());
|
||||
for (const Event& ev : clone)
|
||||
{
|
||||
INFO_LOG(POWERPC, "PENDING: Now: %" PRId64 " Pending: %" PRId64 " Type: %d", g_global_timer,
|
||||
ptr->time, ptr->type);
|
||||
ptr = ptr->next;
|
||||
INFO_LOG(POWERPC, "PENDING: Now: %" PRId64 " Pending: %" PRId64 " Type: %s (%d)",
|
||||
g_global_timer, ev.time,
|
||||
ev.type < s_event_types.size() ? s_event_types[ev.type].name.c_str() : "<INVALID>",
|
||||
ev.type);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -442,20 +378,24 @@ void Idle()
|
|||
|
||||
std::string GetScheduledEventsSummary()
|
||||
{
|
||||
Event* ptr = s_first_event;
|
||||
std::string text = "Scheduled events\n";
|
||||
text.reserve(1000);
|
||||
while (ptr)
|
||||
|
||||
auto clone = s_event_queue;
|
||||
std::sort(clone.begin(), clone.end());
|
||||
for (const Event& ev : clone)
|
||||
{
|
||||
unsigned int t = ptr->type;
|
||||
unsigned int t = ev.type;
|
||||
if (t >= s_event_types.size())
|
||||
{
|
||||
PanicAlertT("Invalid event type %i", t);
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& name = s_event_types[ptr->type].name;
|
||||
const std::string& name = s_event_types[ev.type].name;
|
||||
|
||||
text += StringFromFormat("%s : %" PRIi64 " %016" PRIx64 "\n", name.c_str(), ptr->time,
|
||||
ptr->userdata);
|
||||
ptr = ptr->next;
|
||||
text +=
|
||||
StringFromFormat("%s : %" PRIi64 " %016" PRIx64 "\n", name.c_str(), ev.time, ev.userdata);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
|
|||
static std::thread g_save_thread;
|
||||
|
||||
// Don't forget to increase this after doing changes on the savestate system
|
||||
static const u32 STATE_VERSION = 55;
|
||||
static const u32 STATE_VERSION = 56;
|
||||
|
||||
// Maps savestate versions to Dolphin versions.
|
||||
// Versions after 42 don't need to be added to this list,
|
||||
|
|
Loading…
Reference in New Issue