parent
d14af73c30
commit
34d91cfa21
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 28f3c35c215ffbe0241685901338fad484660454
|
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef PRIORITY_QUEUE_H
|
||||||
|
#define PRIORITY_QUEUE_H
|
||||||
|
|
||||||
|
#include <spinlock/fas.h>
|
||||||
|
|
||||||
|
#define MAX 4096
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How to get the priority out of the generic element
|
||||||
|
* We assume priority is expressed as an unsigned 64-bit integer (i.e. cycles or
|
||||||
|
* UNIX time in ms). This is used to maintain a read replica of the highest
|
||||||
|
* priority element that can be used to maintain a read replica
|
||||||
|
* @param element
|
||||||
|
* @returns priority (a u64)
|
||||||
|
**/
|
||||||
|
typedef unsigned long long int (*priority_queue_get_priority_t)(void *element);
|
||||||
|
|
||||||
|
// We assume that priority is expressed in terms of a 64 bit unsigned integral
|
||||||
|
struct priority_queue {
|
||||||
|
ck_spinlock_fas_t lock;
|
||||||
|
unsigned long long int highest_priority;
|
||||||
|
void * items[MAX];
|
||||||
|
int first_free;
|
||||||
|
priority_queue_get_priority_t get_priority;
|
||||||
|
};
|
||||||
|
|
||||||
|
void priority_queue_initialize(struct priority_queue *self, priority_queue_get_priority_t get_priority);
|
||||||
|
int priority_queue_enqueue(struct priority_queue *self, void *value);
|
||||||
|
void *priority_queue_dequeue(struct priority_queue *self);
|
||||||
|
int priority_queue_length(struct priority_queue *self);
|
||||||
|
|
||||||
|
#endif /* PRIORITY_QUEUE_H */
|
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef SFRT_SANDBOX_REQUEST_QUEUE_H
|
||||||
|
#define SFRT_SANDBOX_REQUEST_QUEUE_H
|
||||||
|
|
||||||
|
#include <sandbox_request.h>
|
||||||
|
|
||||||
|
void sandbox_request_queue_initialize(void);
|
||||||
|
int sandbox_request_queue_add(sandbox_request_t *);
|
||||||
|
sandbox_request_t *sandbox_request_queue_remove(void);
|
||||||
|
|
||||||
|
#endif /* SFRT_SANDBOX_REQUEST_QUEUE_H */
|
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 85695f3d5903b1cd5b4030efe50db3b4f5f3c928
|
@ -0,0 +1,207 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <priority_queue.h>
|
||||||
|
|
||||||
|
/****************************
|
||||||
|
* Private Helper Functions *
|
||||||
|
****************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a value to the end of the binary heap
|
||||||
|
* @param self the priority queue
|
||||||
|
* @param new_item the value we are adding
|
||||||
|
* @return 0 on success. -1 when priority queue is full
|
||||||
|
**/
|
||||||
|
static inline int
|
||||||
|
priority_queue_append(struct priority_queue *self, void *new_item)
|
||||||
|
{
|
||||||
|
assert(self != NULL);
|
||||||
|
|
||||||
|
if (self->first_free >= MAX) return -1;
|
||||||
|
|
||||||
|
self->items[self->first_free] = new_item;
|
||||||
|
self->first_free++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shifts an appended value upwards to restore heap structure property
|
||||||
|
* @param self the priority queue
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
priority_queue_percolate_up(struct priority_queue *self)
|
||||||
|
{
|
||||||
|
assert(self != NULL);
|
||||||
|
assert(self->get_priority != NULL);
|
||||||
|
|
||||||
|
for (int i = self->first_free - 1;
|
||||||
|
i / 2 != 0 && self->get_priority(self->items[i]) < self->get_priority(self->items[i / 2]); i /= 2) {
|
||||||
|
void *temp = self->items[i / 2];
|
||||||
|
self->items[i / 2] = self->items[i];
|
||||||
|
self->items[i] = temp;
|
||||||
|
// If percolated to highest priority, update highest priority
|
||||||
|
if (i / 2 == 1) self->highest_priority = self->get_priority(self->items[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of a node's smallest child
|
||||||
|
* @param self the priority queue
|
||||||
|
* @param parent_index
|
||||||
|
* @returns the index of the smallest child
|
||||||
|
*/
|
||||||
|
static inline int
|
||||||
|
priority_queue_find_smallest_child(struct priority_queue *self, int parent_index)
|
||||||
|
{
|
||||||
|
assert(self != NULL);
|
||||||
|
assert(parent_index >= 1 && parent_index < self->first_free);
|
||||||
|
assert(self->get_priority != NULL);
|
||||||
|
|
||||||
|
int left_child_index = 2 * parent_index;
|
||||||
|
int right_child_index = 2 * parent_index + 1;
|
||||||
|
assert(self->items[left_child_index] != NULL);
|
||||||
|
|
||||||
|
// If we don't have a right child or the left child is smaller, return it
|
||||||
|
if (right_child_index == self->first_free) {
|
||||||
|
return left_child_index;
|
||||||
|
} else if (self->get_priority(self->items[left_child_index])
|
||||||
|
< self->get_priority(self->items[right_child_index])) {
|
||||||
|
return left_child_index;
|
||||||
|
} else {
|
||||||
|
// Otherwise, return the right child
|
||||||
|
return right_child_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shifts the top of the heap downwards. Used after placing the last value at
|
||||||
|
* the top
|
||||||
|
* @param self the priority queue
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
priority_queue_percolate_down(struct priority_queue *self)
|
||||||
|
{
|
||||||
|
assert(self != NULL);
|
||||||
|
assert(self->get_priority != NULL);
|
||||||
|
|
||||||
|
int parent_index = 1;
|
||||||
|
int left_child_index = 2 * parent_index;
|
||||||
|
while (left_child_index >= 2 && left_child_index < self->first_free) {
|
||||||
|
int smallest_child_index = priority_queue_find_smallest_child(self, parent_index);
|
||||||
|
// Once the parent is equal to or less than its smallest child, break;
|
||||||
|
if (self->get_priority(self->items[parent_index])
|
||||||
|
<= self->get_priority(self->items[smallest_child_index]))
|
||||||
|
break;
|
||||||
|
// Otherwise, swap and continue down the tree
|
||||||
|
void *temp = self->items[smallest_child_index];
|
||||||
|
self->items[smallest_child_index] = self->items[parent_index];
|
||||||
|
self->items[parent_index] = temp;
|
||||||
|
|
||||||
|
parent_index = smallest_child_index;
|
||||||
|
left_child_index = 2 * parent_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************
|
||||||
|
* Public API *
|
||||||
|
*********************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialized the Priority Queue Data structure
|
||||||
|
* @param self the priority_queue to initialize
|
||||||
|
* @param get_priority pointer to a function that returns the priority of an
|
||||||
|
*element
|
||||||
|
**/
|
||||||
|
void
|
||||||
|
priority_queue_initialize(struct priority_queue *self, priority_queue_get_priority_t get_priority)
|
||||||
|
{
|
||||||
|
assert(self != NULL);
|
||||||
|
assert(get_priority != NULL);
|
||||||
|
|
||||||
|
memset(self->items, 0, sizeof(void *) * MAX);
|
||||||
|
|
||||||
|
ck_spinlock_fas_init(&self->lock);
|
||||||
|
self->first_free = 1;
|
||||||
|
self->get_priority = get_priority;
|
||||||
|
|
||||||
|
// We're assuming a min-heap implementation, so set to larget possible value
|
||||||
|
self->highest_priority = ULONG_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param self the priority_queue
|
||||||
|
* @returns the number of elements in the priority queue
|
||||||
|
**/
|
||||||
|
int
|
||||||
|
priority_queue_length(struct priority_queue *self)
|
||||||
|
{
|
||||||
|
assert(self != NULL);
|
||||||
|
|
||||||
|
return self->first_free - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param self - the priority queue we want to add to
|
||||||
|
* @param value - the value we want to add
|
||||||
|
* @returns 0 on success. -1 on full. -2 on unable to take lock
|
||||||
|
**/
|
||||||
|
int
|
||||||
|
priority_queue_enqueue(struct priority_queue *self, void *value)
|
||||||
|
{
|
||||||
|
assert(self != NULL);
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (ck_spinlock_fas_trylock(&self->lock) == false) return -2;
|
||||||
|
|
||||||
|
// Start of Critical Section
|
||||||
|
if (priority_queue_append(self, value) == 0) {
|
||||||
|
if (self->first_free > 2) {
|
||||||
|
priority_queue_percolate_up(self);
|
||||||
|
} else {
|
||||||
|
// If this is the first element we add, update the highest priority
|
||||||
|
self->highest_priority = self->get_priority(value);
|
||||||
|
}
|
||||||
|
rc = 0;
|
||||||
|
} else {
|
||||||
|
// Priority Queue is full
|
||||||
|
rc = -1;
|
||||||
|
}
|
||||||
|
// End of Critical Section
|
||||||
|
ck_spinlock_fas_unlock(&self->lock);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param self - the priority queue we want to add to
|
||||||
|
* @returns The head of the priority queue or NULL when empty
|
||||||
|
**/
|
||||||
|
void *
|
||||||
|
priority_queue_dequeue(struct priority_queue *self)
|
||||||
|
{
|
||||||
|
assert(self != NULL);
|
||||||
|
assert(self->get_priority != NULL);
|
||||||
|
// If first_free is 1, we're empty
|
||||||
|
if (self->first_free == 1) return NULL;
|
||||||
|
|
||||||
|
if (ck_spinlock_fas_trylock(&self->lock) == false) return NULL;
|
||||||
|
// Start of Critical Section
|
||||||
|
void *min = self->items[1];
|
||||||
|
self->items[1] = self->items[self->first_free - 1];
|
||||||
|
self->items[self->first_free - 1] = NULL;
|
||||||
|
self->first_free--;
|
||||||
|
assert(self->first_free == 1 || self->items[self->first_free - 1] != NULL);
|
||||||
|
// Because of 1-based indices, first_free is 2 when there is only one element
|
||||||
|
if (self->first_free > 2) priority_queue_percolate_down(self);
|
||||||
|
|
||||||
|
if (self->first_free > 1) {
|
||||||
|
self->highest_priority = self->get_priority(self->items[1]);
|
||||||
|
} else {
|
||||||
|
self->highest_priority = ULONG_MAX;
|
||||||
|
}
|
||||||
|
ck_spinlock_fas_unlock(&self->lock);
|
||||||
|
// End of Critical Section
|
||||||
|
return min;
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
#include <sandbox_request_queue.h>
|
||||||
|
#include <priority_queue.h>
|
||||||
|
|
||||||
|
enum scheduling_policy
|
||||||
|
{
|
||||||
|
FIFO,
|
||||||
|
PS
|
||||||
|
};
|
||||||
|
|
||||||
|
enum scheduling_policy sandbox_request_queue_policy = FIFO;
|
||||||
|
|
||||||
|
// FIFO Globals
|
||||||
|
struct deque_sandbox *runtime_global_deque;
|
||||||
|
pthread_mutex_t runtime_global_deque_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
// PS Globals
|
||||||
|
struct priority_queue sandbox_request_queue_ps;
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
sandbox_request_queue_fifo_initialize(void)
|
||||||
|
{
|
||||||
|
// Allocate and Initialize the global deque
|
||||||
|
runtime_global_deque = (struct deque_sandbox *)malloc(sizeof(struct deque_sandbox));
|
||||||
|
assert(runtime_global_deque);
|
||||||
|
// Note: Below is a Macro
|
||||||
|
deque_init_sandbox(runtime_global_deque, RUNTIME_MAX_SANDBOX_REQUEST_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
sandbox_request_queue_ps_initialize(void)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sandbox_request_queue_initialize(void)
|
||||||
|
{
|
||||||
|
switch (sandbox_request_queue_policy) {
|
||||||
|
case FIFO:
|
||||||
|
return sandbox_request_queue_fifo_initialize();
|
||||||
|
case PS:
|
||||||
|
return sandbox_request_queue_ps_initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a sandbox request to the global deque
|
||||||
|
* @param sandbox_request
|
||||||
|
**/
|
||||||
|
static inline int
|
||||||
|
sandbox_request_queue_fifo_add(sandbox_request_t *sandbox_request)
|
||||||
|
{
|
||||||
|
int return_code;
|
||||||
|
|
||||||
|
// TODO: Running the runtime and listener cores on a single shared core is untested
|
||||||
|
// We are unsure if the locking behavior is correct, so there may be deadlocks
|
||||||
|
#if NCORES == 1
|
||||||
|
pthread_mutex_lock(&runtime_global_deque_mutex);
|
||||||
|
#endif
|
||||||
|
return_code = deque_push_sandbox(runtime_global_deque, &sandbox_request);
|
||||||
|
#if NCORES == 1
|
||||||
|
pthread_mutex_unlock(&runtime_global_deque_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return return_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
sandbox_request_queue_ps_add(sandbox_request_t *sandbox_request)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a sandbox request to the global deque
|
||||||
|
* @param sandbox_request
|
||||||
|
**/
|
||||||
|
int
|
||||||
|
sandbox_request_queue_add(sandbox_request_t *sandbox_request)
|
||||||
|
{
|
||||||
|
switch (sandbox_request_queue_policy) {
|
||||||
|
case FIFO:
|
||||||
|
return sandbox_request_queue_fifo_add(sandbox_request);
|
||||||
|
case PS:
|
||||||
|
return sandbox_request_queue_ps_add(sandbox_request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stealing from the dequeue is a lock-free, cross-core "pop", which removes the element from the end opposite to
|
||||||
|
* "pop". Because the producer and consumer (the core stealine the sandbox request) modify different ends,
|
||||||
|
* no locks are required, and coordination is achieved by instead retrying on inconsistent indices.
|
||||||
|
*
|
||||||
|
* Relevant Read: https://www.dre.vanderbilt.edu/~schmidt/PDF/work-stealing-dequeue.pdf
|
||||||
|
*
|
||||||
|
* TODO: Notice the mutex_lock for NCORES == 1 in both push/pop functions and steal calling 'pop' for NCORES == 1.
|
||||||
|
* Ideally you don't call steal for same core consumption but I just made the steal API wrap that logic. Which is
|
||||||
|
* perhaps not good. We might just add the #if in the scheduling code which should explicitly call "pop" for single core
|
||||||
|
* and add an assert in "steal" function for NCORES == 1.
|
||||||
|
*
|
||||||
|
* @returns A Sandbox Request or NULL
|
||||||
|
**/
|
||||||
|
static inline sandbox_request_t *
|
||||||
|
sandbox_request_queue_fifo_remove(void)
|
||||||
|
{
|
||||||
|
sandbox_request_t *sandbox_request = NULL;
|
||||||
|
|
||||||
|
#if NCORES == 1
|
||||||
|
pthread_mutex_lock(&runtime_global_deque_mutex);
|
||||||
|
return_code = deque_pop_sandbox(runtime_global_deque, sandbox_request);
|
||||||
|
pthread_mutex_unlock(&runtime_global_deque_mutex);
|
||||||
|
#else
|
||||||
|
int return_code = deque_steal_sandbox(runtime_global_deque, &sandbox_request);
|
||||||
|
#endif
|
||||||
|
if (return_code) sandbox_request = NULL;
|
||||||
|
return sandbox_request;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline sandbox_request_t *
|
||||||
|
sandbox_request_queue_ps_remove(void)
|
||||||
|
{
|
||||||
|
sandbox_request_t *sandbox_request = NULL;
|
||||||
|
// TODO
|
||||||
|
return sandbox_request;
|
||||||
|
}
|
||||||
|
|
||||||
|
sandbox_request_t *
|
||||||
|
sandbox_request_queue_remove(void)
|
||||||
|
{
|
||||||
|
switch (sandbox_request_queue_policy) {
|
||||||
|
case FIFO:
|
||||||
|
return sandbox_request_queue_fifo_remove();
|
||||||
|
case PS:
|
||||||
|
return sandbox_request_queue_ps_remove();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue