diff options
Diffstat (limited to 'src/common/thread_queue_list.h')
| -rw-r--r-- | src/common/thread_queue_list.h | 218 |
1 files changed, 74 insertions, 144 deletions
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h index 4e1c0a215..444abf115 100644 --- a/src/common/thread_queue_list.h +++ b/src/common/thread_queue_list.h | |||
| @@ -4,213 +4,143 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | ||
| 8 | #include <deque> | ||
| 9 | |||
| 10 | #include <boost/range/algorithm_ext/erase.hpp> | ||
| 11 | |||
| 7 | #include "common/common.h" | 12 | #include "common/common.h" |
| 8 | 13 | ||
| 9 | namespace Common { | 14 | namespace Common { |
| 10 | 15 | ||
| 11 | template<class IdType> | 16 | template<class T, unsigned int N> |
| 12 | struct ThreadQueueList { | 17 | struct ThreadQueueList { |
| 13 | // Number of queues (number of priority levels starting at 0.) | 18 | // TODO(yuriks): If performance proves to be a problem, the std::deques can be replaced with |
| 14 | static const int NUM_QUEUES = 128; | 19 | // (dynamically resizable) circular buffers to remove their overhead when |
| 20 | // inserting and popping. | ||
| 15 | 21 | ||
| 16 | // Initial number of threads a single queue can handle. | 22 | typedef unsigned int Priority; |
| 17 | static const int INITIAL_CAPACITY = 32; | ||
| 18 | 23 | ||
| 19 | struct Queue { | 24 | // Number of priority levels. (Valid levels are [0..NUM_QUEUES).) |
| 20 | // Next ever-been-used queue (worse priority.) | 25 | static const Priority NUM_QUEUES = N; |
| 21 | Queue *next; | ||
| 22 | // First valid item in data. | ||
| 23 | int first; | ||
| 24 | // One after last valid item in data. | ||
| 25 | int end; | ||
| 26 | // A too-large array with room on the front and end. | ||
| 27 | IdType *data; | ||
| 28 | // Size of data array. | ||
| 29 | int capacity; | ||
| 30 | }; | ||
| 31 | 26 | ||
| 32 | ThreadQueueList() { | 27 | ThreadQueueList() { |
| 33 | memset(queues, 0, sizeof(queues)); | 28 | first = nullptr; |
| 34 | first = invalid(); | ||
| 35 | } | ||
| 36 | |||
| 37 | ~ThreadQueueList() { | ||
| 38 | for (int i = 0; i < NUM_QUEUES; ++i) | ||
| 39 | { | ||
| 40 | if (queues[i].data != nullptr) | ||
| 41 | free(queues[i].data); | ||
| 42 | } | ||
| 43 | } | 29 | } |
| 44 | 30 | ||
| 45 | // Only for debugging, returns priority level. | 31 | // Only for debugging, returns priority level. |
| 46 | int contains(const IdType uid) { | 32 | Priority contains(const T& uid) { |
| 47 | for (int i = 0; i < NUM_QUEUES; ++i) | 33 | for (Priority i = 0; i < NUM_QUEUES; ++i) { |
| 48 | { | 34 | Queue& cur = queues[i]; |
| 49 | if (queues[i].data == nullptr) | 35 | if (std::find(cur.data.cbegin(), cur.data.cend(), uid) != cur.data.cend()) { |
| 50 | continue; | 36 | return i; |
| 51 | |||
| 52 | Queue *cur = &queues[i]; | ||
| 53 | for (int j = cur->first; j < cur->end; ++j) | ||
| 54 | { | ||
| 55 | if (cur->data[j] == uid) | ||
| 56 | return i; | ||
| 57 | } | 37 | } |
| 58 | } | 38 | } |
| 59 | 39 | ||
| 60 | return -1; | 40 | return -1; |
| 61 | } | 41 | } |
| 62 | 42 | ||
| 63 | inline IdType pop_first() { | 43 | T pop_first() { |
| 64 | Queue *cur = first; | 44 | Queue *cur = first; |
| 65 | while (cur != invalid()) | 45 | while (cur != nullptr) { |
| 66 | { | 46 | if (!cur->data.empty()) { |
| 67 | if (cur->end - cur->first > 0) | 47 | auto tmp = std::move(cur->data.front()); |
| 68 | return cur->data[cur->first++]; | 48 | cur->data.pop_front(); |
| 69 | cur = cur->next; | 49 | return tmp; |
| 50 | } | ||
| 51 | cur = cur->next_nonempty; | ||
| 70 | } | 52 | } |
| 71 | 53 | ||
| 72 | //_dbg_assert_msg_(SCEKERNEL, false, "ThreadQueueList should not be empty."); | 54 | return T(); |
| 73 | return 0; | ||
| 74 | } | 55 | } |
| 75 | 56 | ||
| 76 | inline IdType pop_first_better(u32 priority) { | 57 | T pop_first_better(Priority priority) { |
| 77 | Queue *cur = first; | 58 | Queue *cur = first; |
| 78 | Queue *stop = &queues[priority]; | 59 | Queue *stop = &queues[priority]; |
| 79 | while (cur < stop) | 60 | while (cur < stop) { |
| 80 | { | 61 | if (!cur->data.empty()) { |
| 81 | if (cur->end - cur->first > 0) | 62 | auto tmp = std::move(cur->data.front()); |
| 82 | return cur->data[cur->first++]; | 63 | cur->data.pop_front(); |
| 83 | cur = cur->next; | 64 | return tmp; |
| 65 | } | ||
| 66 | cur = cur->next_nonempty; | ||
| 84 | } | 67 | } |
| 85 | 68 | ||
| 86 | return 0; | 69 | return T(); |
| 87 | } | 70 | } |
| 88 | 71 | ||
| 89 | inline void push_front(u32 priority, const IdType threadID) { | 72 | void push_front(Priority priority, const T& thread_id) { |
| 90 | Queue *cur = &queues[priority]; | 73 | Queue *cur = &queues[priority]; |
| 91 | cur->data[--cur->first] = threadID; | 74 | cur->data.push_front(thread_id); |
| 92 | if (cur->first == 0) | ||
| 93 | rebalance(priority); | ||
| 94 | } | 75 | } |
| 95 | 76 | ||
| 96 | inline void push_back(u32 priority, const IdType threadID) { | 77 | void push_back(Priority priority, const T& thread_id) { |
| 97 | Queue *cur = &queues[priority]; | 78 | Queue *cur = &queues[priority]; |
| 98 | cur->data[cur->end++] = threadID; | 79 | cur->data.push_back(thread_id); |
| 99 | if (cur->end == cur->capacity) | ||
| 100 | rebalance(priority); | ||
| 101 | } | 80 | } |
| 102 | 81 | ||
| 103 | inline void remove(u32 priority, const IdType threadID) { | 82 | void remove(Priority priority, const T& thread_id) { |
| 104 | Queue *cur = &queues[priority]; | 83 | Queue *cur = &queues[priority]; |
| 105 | //_dbg_assert_msg_(SCEKERNEL, cur->next != NULL, "ThreadQueueList::Queue should already be linked up."); | 84 | boost::remove_erase(cur->data, thread_id); |
| 106 | |||
| 107 | for (int i = cur->first; i < cur->end; ++i) | ||
| 108 | { | ||
| 109 | if (cur->data[i] == threadID) | ||
| 110 | { | ||
| 111 | int remaining = --cur->end - i; | ||
| 112 | if (remaining > 0) | ||
| 113 | memmove(&cur->data[i], &cur->data[i + 1], remaining * sizeof(IdType)); | ||
| 114 | return; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | // Wasn't there. | ||
| 119 | } | 85 | } |
| 120 | 86 | ||
| 121 | inline void rotate(u32 priority) { | 87 | void rotate(Priority priority) { |
| 122 | Queue *cur = &queues[priority]; | 88 | Queue *cur = &queues[priority]; |
| 123 | //_dbg_assert_msg_(SCEKERNEL, cur->next != NULL, "ThreadQueueList::Queue should already be linked up."); | ||
| 124 | 89 | ||
| 125 | if (cur->end - cur->first > 1) | 90 | if (cur->data.size() > 1) { |
| 126 | { | 91 | cur->data.push_back(std::move(cur->data.front())); |
| 127 | cur->data[cur->end++] = cur->data[cur->first++]; | 92 | cur->data.pop_front(); |
| 128 | if (cur->end == cur->capacity) | ||
| 129 | rebalance(priority); | ||
| 130 | } | 93 | } |
| 131 | } | 94 | } |
| 132 | 95 | ||
| 133 | inline void clear() { | 96 | void clear() { |
| 134 | for (int i = 0; i < NUM_QUEUES; ++i) | 97 | queues.fill(Queue()); |
| 135 | { | 98 | first = nullptr; |
| 136 | if (queues[i].data != nullptr) | ||
| 137 | free(queues[i].data); | ||
| 138 | } | ||
| 139 | memset(queues, 0, sizeof(queues)); | ||
| 140 | first = invalid(); | ||
| 141 | } | 99 | } |
| 142 | 100 | ||
| 143 | inline bool empty(u32 priority) const { | 101 | bool empty(Priority priority) const { |
| 144 | const Queue *cur = &queues[priority]; | 102 | const Queue *cur = &queues[priority]; |
| 145 | return cur->first == cur->end; | 103 | return cur->data.empty(); |
| 146 | } | 104 | } |
| 147 | 105 | ||
| 148 | inline void prepare(u32 priority) { | 106 | void prepare(Priority priority) { |
| 149 | Queue *cur = &queues[priority]; | 107 | Queue* cur = &queues[priority]; |
| 150 | if (cur->next == nullptr) | 108 | if (cur->next_nonempty == UnlinkedTag()) |
| 151 | link(priority, INITIAL_CAPACITY); | 109 | link(priority); |
| 152 | } | 110 | } |
| 153 | 111 | ||
| 154 | private: | 112 | private: |
| 155 | Queue *invalid() const { | 113 | struct Queue { |
| 156 | return (Queue *) -1; | 114 | // Points to the next active priority, skipping over ones that have never been used. |
| 115 | Queue* next_nonempty = UnlinkedTag(); | ||
| 116 | // Double-ended queue of threads in this priority level | ||
| 117 | std::deque<T> data; | ||
| 118 | }; | ||
| 119 | |||
| 120 | /// Special tag used to mark priority levels that have never been used. | ||
| 121 | static Queue* UnlinkedTag() { | ||
| 122 | return reinterpret_cast<Queue*>(1); | ||
| 157 | } | 123 | } |
| 158 | 124 | ||
| 159 | void link(u32 priority, int size) { | 125 | void link(Priority priority) { |
| 160 | //_dbg_assert_msg_(SCEKERNEL, queues[priority].data == NULL, "ThreadQueueList::Queue should only be initialized once."); | ||
| 161 | |||
| 162 | if (size <= INITIAL_CAPACITY) | ||
| 163 | size = INITIAL_CAPACITY; | ||
| 164 | else | ||
| 165 | { | ||
| 166 | int goal = size; | ||
| 167 | size = INITIAL_CAPACITY; | ||
| 168 | while (size < goal) | ||
| 169 | size *= 2; | ||
| 170 | } | ||
| 171 | Queue *cur = &queues[priority]; | 126 | Queue *cur = &queues[priority]; |
| 172 | cur->data = (IdType *) malloc(sizeof(IdType) * size); | 127 | |
| 173 | cur->capacity = size; | 128 | for (int i = priority - 1; i >= 0; --i) { |
| 174 | cur->first = size / 2; | 129 | if (queues[i].next_nonempty != UnlinkedTag()) { |
| 175 | cur->end = size / 2; | 130 | cur->next_nonempty = queues[i].next_nonempty; |
| 176 | 131 | queues[i].next_nonempty = cur; | |
| 177 | for (int i = (int) priority - 1; i >= 0; --i) | ||
| 178 | { | ||
| 179 | if (queues[i].next != nullptr) | ||
| 180 | { | ||
| 181 | cur->next = queues[i].next; | ||
| 182 | queues[i].next = cur; | ||
| 183 | return; | 132 | return; |
| 184 | } | 133 | } |
| 185 | } | 134 | } |
| 186 | 135 | ||
| 187 | cur->next = first; | 136 | cur->next_nonempty = first; |
| 188 | first = cur; | 137 | first = cur; |
| 189 | } | 138 | } |
| 190 | 139 | ||
| 191 | void rebalance(u32 priority) { | ||
| 192 | Queue *cur = &queues[priority]; | ||
| 193 | int size = cur->end - cur->first; | ||
| 194 | if (size >= cur->capacity - 2) { | ||
| 195 | IdType *new_data = (IdType *)realloc(cur->data, cur->capacity * 2 * sizeof(IdType)); | ||
| 196 | if (new_data != nullptr) { | ||
| 197 | cur->capacity *= 2; | ||
| 198 | cur->data = new_data; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | int newFirst = (cur->capacity - size) / 2; | ||
| 203 | if (newFirst != cur->first) { | ||
| 204 | memmove(&cur->data[newFirst], &cur->data[cur->first], size * sizeof(IdType)); | ||
| 205 | cur->first = newFirst; | ||
| 206 | cur->end = newFirst + size; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | // The first queue that's ever been used. | 140 | // The first queue that's ever been used. |
| 211 | Queue *first; | 141 | Queue* first; |
| 212 | // The priority level queues of thread ids. | 142 | // The priority level queues of thread ids. |
| 213 | Queue queues[NUM_QUEUES]; | 143 | std::array<Queue, NUM_QUEUES> queues; |
| 214 | }; | 144 | }; |
| 215 | 145 | ||
| 216 | } // namespace | 146 | } // namespace |