@ -0,0 +1,442 @@ | |||||
#define _GNU_SOURCE | |||||
#include "erl_nif.h" | |||||
#include <assert.h> | |||||
#include <stdio.h> | |||||
#include <stdint.h> | |||||
#include <string.h> | |||||
#include <time.h> | |||||
// #include "fifo.h" | |||||
#include "lifo.h" | |||||
typedef struct { | |||||
ERL_NIF_TERM ok; | |||||
ERL_NIF_TERM error; | |||||
ERL_NIF_TERM fifo; | |||||
ERL_NIF_TERM lifo; | |||||
ERL_NIF_TERM ttl; | |||||
ERL_NIF_TERM max_size; | |||||
} atoms_t; | |||||
typedef struct { | |||||
ErlNifResourceType *queue; | |||||
atoms_t atoms; | |||||
} priv_t; | |||||
typedef struct { | |||||
union { | |||||
fifo_handle_t fifo; | |||||
lifo_handle_t lifo; | |||||
} handle; | |||||
ErlNifBinary data; | |||||
struct timespec added; | |||||
} item_t; | |||||
typedef enum { | |||||
QTYPE_FIFO = 0, | |||||
QTYPE_LIFO | |||||
} queue_type_t; | |||||
typedef struct queue { | |||||
union { | |||||
fifo_t fifo; | |||||
lifo_t lifo; | |||||
} queue; | |||||
uint64_t ttl; | |||||
uint64_t max_size; | |||||
void (*push) (struct queue *inst, item_t *item); | |||||
item_t* (*pop) (struct queue *inst); | |||||
void (*free) (struct queue *inst); | |||||
uint64_t (*size) (struct queue *inst); | |||||
void (*cleanup) (struct queue *inst); | |||||
} queue_t; | |||||
// returns tuple {error, atom()} | |||||
static inline ERL_NIF_TERM | |||||
make_error(ErlNifEnv* env, const char *error) { | |||||
priv_t *priv = (priv_t *) enif_priv_data(env); | |||||
return enif_make_tuple2(env, priv->atoms.error, enif_make_atom(env, error)); | |||||
} | |||||
// returns time diff in milliseconds | |||||
static inline int64_t | |||||
tdiff(struct timespec *t2, struct timespec *t1) { | |||||
return (t2->tv_sec * 1000 + t2->tv_nsec / 1000000UL) - | |||||
(t1->tv_sec * 1000 + t1->tv_nsec / 1000000UL); | |||||
} | |||||
static inline void | |||||
gettime(struct timespec *tp) { | |||||
int rc = clock_gettime(CLOCK_MONOTONIC_RAW, tp); | |||||
assert(rc == 0); | |||||
} | |||||
/******************************************************************************/ | |||||
/* FIFO callbacks */ | |||||
/******************************************************************************/ | |||||
static void | |||||
cleanup_fifo(queue_t *inst) { | |||||
struct timespec now; | |||||
gettime(&now); | |||||
for (;;) { | |||||
item_t *item = NULL; | |||||
__fifo_peak(&inst->queue.fifo, item, handle.fifo); | |||||
if (item == NULL) | |||||
return; | |||||
int64_t diff = tdiff(&now, &item->added); | |||||
if (diff < inst->ttl) { | |||||
return; | |||||
} else { | |||||
__fifo_pop(&inst->queue.fifo, item, handle.fifo); | |||||
enif_release_binary(&item->data); | |||||
enif_free(item); | |||||
} | |||||
} | |||||
} | |||||
static void | |||||
push_fifo(queue_t *inst, item_t *item) { | |||||
__fifo_push(&inst->queue.fifo, item, handle.fifo); | |||||
} | |||||
static item_t * | |||||
pop_fifo(queue_t *inst) { | |||||
item_t *item = NULL; | |||||
if (inst->ttl > 0) { | |||||
struct timespec now; | |||||
gettime(&now); | |||||
for (;;) { | |||||
__fifo_pop(&inst->queue.fifo, item, handle.fifo); | |||||
if (item == NULL) | |||||
return NULL; | |||||
int64_t diff = tdiff(&now, &item->added); | |||||
if (diff < inst->ttl) { | |||||
return item; | |||||
} else { | |||||
enif_release_binary(&item->data); | |||||
enif_free(item); | |||||
} | |||||
} | |||||
} else { | |||||
__fifo_pop(&inst->queue.fifo, item, handle.fifo); | |||||
} | |||||
return item; | |||||
} | |||||
static void | |||||
free_fifo(queue_t *inst) { | |||||
item_t *item; | |||||
for(;;) { | |||||
__fifo_pop(&inst->queue.fifo, item, handle.fifo); | |||||
if (item == NULL) | |||||
return; | |||||
enif_release_binary(&item->data); | |||||
enif_free(item); | |||||
} | |||||
} | |||||
static uint64_t | |||||
size_fifo(queue_t *inst) { | |||||
return fifo_length(&inst->queue.fifo); | |||||
} | |||||
/******************************************************************************/ | |||||
/* LIFO callbacks */ | |||||
/******************************************************************************/ | |||||
static void | |||||
cleanup_lifo(queue_t *inst) { | |||||
struct timespec now; | |||||
gettime(&now); | |||||
for(;;) { | |||||
item_t *item = inst->queue.lifo.tail; | |||||
if (item == NULL) | |||||
return; | |||||
int64_t diff = tdiff(&now, &item->added); | |||||
if (diff < inst->ttl) { | |||||
return; | |||||
} else { | |||||
item_t *prev = item->handle.lifo.prev; | |||||
if (prev != NULL) | |||||
prev->handle.lifo.next = NULL; | |||||
inst->queue.lifo.tail = prev; | |||||
enif_release_binary(&item->data); | |||||
enif_free(item); | |||||
} | |||||
} | |||||
} | |||||
static void | |||||
push_lifo(queue_t *inst, item_t *item) { | |||||
__lifo_push(&inst->queue.lifo, item, handle.lifo); | |||||
} | |||||
static item_t * | |||||
pop_lifo(queue_t *inst) { | |||||
item_t *item = NULL; | |||||
if (inst->ttl > 0) | |||||
cleanup_lifo(inst); | |||||
__lifo_pop(&inst->queue.lifo, item, handle.lifo); | |||||
return item; | |||||
} | |||||
static void | |||||
free_lifo(queue_t *inst) { | |||||
item_t *item; | |||||
for(;;) { | |||||
__lifo_pop(&inst->queue.lifo, item, handle.lifo); | |||||
if (item == NULL) | |||||
return; | |||||
enif_release_binary(&item->data); | |||||
enif_free(item); | |||||
} | |||||
} | |||||
static uint64_t | |||||
size_lifo(queue_t *inst) { | |||||
return lifo_length(&inst->queue.lifo); | |||||
} | |||||
/****************************************************************************** | |||||
** NIFs | |||||
*******************************************************************************/ | |||||
static ERL_NIF_TERM | |||||
new_queue(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
if (!enif_is_list(env, argv[0])) | |||||
return enif_make_badarg(env); | |||||
priv_t *priv = (priv_t *) enif_priv_data(env); | |||||
queue_type_t qtype = QTYPE_FIFO; | |||||
unsigned long ttl = 0; | |||||
unsigned long max_size = 0; | |||||
ERL_NIF_TERM settings_list = argv[0]; | |||||
ERL_NIF_TERM head; | |||||
// parses proplist [fifo, lifo, {ttl, non_neg_integer()}, {max_size, non_neg_integer()}] | |||||
while(enif_get_list_cell(env, settings_list, &head, &settings_list)) | |||||
{ | |||||
const ERL_NIF_TERM *items; | |||||
int arity; | |||||
if (enif_is_atom(env, head)) { | |||||
if (enif_is_identical(head, priv->atoms.fifo)) { | |||||
qtype = QTYPE_FIFO; | |||||
} else if (enif_is_identical(head, priv->atoms.lifo)) { | |||||
qtype = QTYPE_LIFO; | |||||
} else { | |||||
return enif_make_badarg(env); | |||||
} | |||||
} else if (enif_get_tuple(env, head, &arity, &items) && arity == 2) { | |||||
if (enif_is_identical(items[0], priv->atoms.ttl)) { | |||||
if (!enif_get_ulong(env, items[1], &ttl)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
} else if (enif_is_identical(items[0], priv->atoms.max_size)) { | |||||
if (!enif_get_ulong(env, items[1], &max_size)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
} else { | |||||
return enif_make_badarg(env); | |||||
} | |||||
} else { | |||||
return enif_make_badarg(env); | |||||
} | |||||
} | |||||
queue_t *inst = (queue_t *) enif_alloc_resource(priv->queue, sizeof(*inst)); | |||||
if (inst == NULL) | |||||
return make_error(env, "enif_alloc_resource"); | |||||
inst->ttl = ttl; | |||||
inst->max_size = max_size; | |||||
switch (qtype) { | |||||
case QTYPE_FIFO: | |||||
fifo_init(&inst->queue.fifo); | |||||
inst->push = &push_fifo; | |||||
inst->pop = &pop_fifo; | |||||
inst->free = &free_fifo; | |||||
inst->size = &size_fifo; | |||||
inst->cleanup = &cleanup_fifo; | |||||
break; | |||||
case QTYPE_LIFO: | |||||
lifo_init(&inst->queue.lifo); | |||||
inst->push = &push_lifo; | |||||
inst->pop = &pop_lifo; | |||||
inst->free = &free_lifo; | |||||
inst->size = &size_lifo; | |||||
inst->cleanup = &cleanup_lifo; | |||||
break; | |||||
} | |||||
ERL_NIF_TERM result = enif_make_resource(env, inst); | |||||
enif_release_resource(inst); | |||||
return enif_make_tuple2(env, priv->atoms.ok, result); | |||||
} | |||||
static ERL_NIF_TERM | |||||
push_item(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
priv_t *priv = (priv_t *) enif_priv_data(env); | |||||
queue_t *inst; | |||||
if (!enif_get_resource(env, argv[0], priv->queue, (void**) &inst)) | |||||
return enif_make_badarg(env); | |||||
// todo: check an owner of the queue | |||||
ErlNifBinary bin; | |||||
if (!enif_inspect_binary(env, argv[1], &bin)) | |||||
return enif_make_badarg(env); | |||||
if (inst->ttl > 0) { | |||||
inst->cleanup(inst); | |||||
} | |||||
if (inst->max_size > 0 && inst->size(inst) >= inst->max_size) { | |||||
return enif_make_tuple2(env, priv->atoms.error, priv->atoms.max_size); | |||||
} | |||||
item_t *item = (item_t *) enif_alloc(sizeof(*item)); | |||||
if (item == NULL) | |||||
return make_error(env, "enif_alloc"); | |||||
if (!enif_alloc_binary(bin.size, &item->data)) { | |||||
enif_free(item); | |||||
return make_error(env, "enif_alloc_binary"); | |||||
} | |||||
memcpy(item->data.data, bin.data, bin.size); | |||||
if (inst->ttl > 0) { | |||||
gettime(&item->added); | |||||
} | |||||
inst->push(inst, item); | |||||
return priv->atoms.ok; | |||||
} | |||||
static ERL_NIF_TERM | |||||
pop_item(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
priv_t *priv = (priv_t *) enif_priv_data(env); | |||||
queue_t *inst; | |||||
item_t *item; | |||||
if (!enif_get_resource(env, argv[0], priv->queue, (void**) &inst)) | |||||
return enif_make_badarg(env); | |||||
// todo: check an owner of the queue | |||||
item = inst->pop(inst); | |||||
if (item == NULL) | |||||
return enif_make_list(env, 0); | |||||
ERL_NIF_TERM result = enif_make_binary(env, &item->data); | |||||
enif_free(item); | |||||
return enif_make_list1(env, result); | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
priv_t *priv = (priv_t *) enif_priv_data(env); | |||||
queue_t *inst; | |||||
if (!enif_get_resource(env, argv[0], priv->queue, (void**) &inst)) | |||||
return enif_make_badarg(env); | |||||
return enif_make_uint64(env, inst->size(inst)); | |||||
} | |||||
/****************************************************************************** | |||||
** NIF initialization | |||||
*******************************************************************************/ | |||||
static void | |||||
enq_queue_free(ErlNifEnv* env, void* obj) { | |||||
queue_t *inst = obj; | |||||
inst->free(inst); | |||||
} | |||||
static priv_t * | |||||
make_priv(ErlNifEnv *env) { | |||||
priv_t *priv = enif_alloc(sizeof(*priv)); | |||||
if (priv == NULL) | |||||
return NULL; | |||||
ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; | |||||
priv->queue = enif_open_resource_type(env, NULL, "enq_queue", enq_queue_free, flags, NULL); | |||||
priv->atoms.ok = enif_make_atom(env, "ok"); | |||||
priv->atoms.error = enif_make_atom(env, "error"); | |||||
priv->atoms.fifo = enif_make_atom(env, "fifo"); | |||||
priv->atoms.lifo = enif_make_atom(env, "lifo"); | |||||
priv->atoms.ttl = enif_make_atom(env, "ttl"); | |||||
priv->atoms.max_size = enif_make_atom(env, "max_size"); | |||||
return priv; | |||||
} | |||||
static int | |||||
enq_nif_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) { | |||||
*priv_data = make_priv(env); | |||||
return 0; | |||||
} | |||||
static int | |||||
enq_nif_upgrade(ErlNifEnv *env, void **priv_data, void **old_priv_data, ERL_NIF_TERM load_info) { | |||||
*priv_data = make_priv(env); | |||||
return 0; | |||||
} | |||||
static ErlNifFunc enq_nif_funcs[] = { | |||||
{"new", 1, new_queue}, | |||||
{"push", 2, push_item}, | |||||
{"pop", 1, pop_item}, | |||||
{"size", 1, queue_size}, | |||||
}; | |||||
ERL_NIF_INIT(enq_nif, enq_nif_funcs, enq_nif_load, NULL, enq_nif_upgrade, NULL) |
@ -0,0 +1,71 @@ | |||||
#ifndef _FIFO_H | |||||
#define _FIFO_H | |||||
/* Main FIFO structure. Allocate memory for it yourself. */ | |||||
typedef struct fifo_t { | |||||
void *head; | |||||
void *tail; | |||||
unsigned long long count; | |||||
} fifo_t; | |||||
typedef struct fifo_handle_t { | |||||
void *next; | |||||
} fifo_handle_t; | |||||
/* Initializes fifo structure. */ | |||||
#define fifo_init(fifo) \ | |||||
do { \ | |||||
fifo_t *__q = fifo; \ | |||||
__q->head = NULL; \ | |||||
__q->tail = NULL; \ | |||||
__q->count = 0; \ | |||||
} while (0) | |||||
#define __fifo_push(fifo, p, h) \ | |||||
do { \ | |||||
fifo_t *__q = fifo; \ | |||||
__typeof__ (p) e = p; \ | |||||
e->h.next = NULL; \ | |||||
if (__q->tail == NULL) { \ | |||||
__q->head = e; \ | |||||
} else { \ | |||||
__typeof__ (e) t = __q->tail; \ | |||||
t->h.next = e; \ | |||||
} \ | |||||
__q->tail = e; \ | |||||
__q->count++; \ | |||||
} while (0) | |||||
/* Puts an element to the queue. */ | |||||
#define fifo_push(fifo, p) __fifo_push (fifo, p, fifo_handle) | |||||
#define __fifo_pop(fifo, p, h) \ | |||||
do { \ | |||||
fifo_t *__q = fifo; \ | |||||
p = __q->head; \ | |||||
if (p != NULL) { \ | |||||
__q->count--; \ | |||||
__q->head = p->h.next; \ | |||||
if (__q->tail == p) \ | |||||
__q->tail = NULL; \ | |||||
} \ | |||||
} while (0) | |||||
/* Pops the first element out of the queue. */ | |||||
#define fifo_pop(fifo, p) __fifo_pop (fifo, p, fifo_handle) | |||||
#define __fifo_peak(fifo, p, h) \ | |||||
do { \ | |||||
p = (fifo)->head; \ | |||||
} while (0) | |||||
/* Returns the first elemnt of the queue without removing. */ | |||||
#define fifo_peak(fifo, p) __fifo_peak (fifo, p, fifo_handle) | |||||
/* Returns the length of the queue. */ | |||||
#define fifo_length(fifo) ((fifo)->count) | |||||
/* Returns true if the queue is empty. */ | |||||
#define fifo_empty(fifo) ((fifo)->count == 0) | |||||
#endif /* _FIFO_H */ |
@ -0,0 +1,63 @@ | |||||
#ifndef _LIFO_H | |||||
#define _LIFO_H | |||||
typedef struct lifo_t { | |||||
void *head; | |||||
void *tail; | |||||
unsigned long long count; | |||||
} lifo_t; | |||||
typedef struct lifo_handle_t { | |||||
void *next; | |||||
void *prev; | |||||
} lifo_handle_t; | |||||
#define lifo_init(lifo) \ | |||||
do { \ | |||||
lifo_t *__q = lifo; \ | |||||
__q->head = NULL; \ | |||||
__q->tail = NULL; \ | |||||
__q->count = 0; \ | |||||
} while (0) | |||||
#define __lifo_push(lifo, p, h) \ | |||||
do { \ | |||||
lifo_t *__q = lifo; \ | |||||
__typeof__ (p) e = p; \ | |||||
e->h.next = __q->head; \ | |||||
e->h.prev = NULL; \ | |||||
if (__q->head == NULL) { \ | |||||
__q->tail = e; \ | |||||
} else { \ | |||||
__typeof__ (e) t = __q->head; \ | |||||
t->h.prev = e; \ | |||||
} \ | |||||
__q->head = e; \ | |||||
__q->count++; \ | |||||
} while (0) | |||||
#define lifo_push(lifo, p) __lifo_push (lifo, p, lifo_handle) | |||||
#define __lifo_pop(lifo, p, h) \ | |||||
do { \ | |||||
lifo_t *__q = lifo; \ | |||||
p = __q->head; \ | |||||
if (p != NULL) { \ | |||||
__q->count--; \ | |||||
__q->head = p->h.next; \ | |||||
if (__q->head != NULL) { \ | |||||
__typeof__ (p) t = __q->head; \ | |||||
t->h.prev = NULL; \ | |||||
} else { \ | |||||
__q->tail = NULL; \ | |||||
} \ | |||||
} \ | |||||
} while (0) | |||||
#define lifo_pop(lifo, p) __lifo_pop (lifo, p, lifo_handle) | |||||
#define lifo_length(lifo) ((lifo)->count) | |||||
#define lifo_empty(lifo) ((lifo)->count == 0) | |||||
#endif /* _LIFO_H */ |
@ -0,0 +1,12 @@ | |||||
{port_specs, [ | |||||
{"../../priv/enq_nif.so", ["*.c"]} | |||||
]}. | |||||
% {port_env, [ | |||||
% {"LDFLAGS", "$ERL_LDFLAGS -lrt"}, | |||||
% {"CFLAGS", "$CFLAGS --std=gnu99 -Wall -O3"} | |||||
% ]}. | |||||
@ -0,0 +1,80 @@ | |||||
#include <erl_nif.h> | |||||
/* | |||||
This function expects a list of list of tuples of type {int, _}. | |||||
It filters the tuples, using the first int field as a key, | |||||
and removing duplicating keys with precedence given the the order | |||||
in which they were seen (first given precedence). | |||||
*/ | |||||
static ERL_NIF_TERM | |||||
bitmap_filter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
size_t seen_forklift_id[3000] = { 0 }; | |||||
if(argc != 1) | |||||
{ | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_is_list(env, argv[0])) | |||||
{ | |||||
return enif_make_badarg(env); | |||||
} | |||||
ERL_NIF_TERM ret = enif_make_list(env, 0); | |||||
ERL_NIF_TERM outer_list = argv[0]; | |||||
ERL_NIF_TERM inner_list; | |||||
ERL_NIF_TERM inner_head; | |||||
const ERL_NIF_TERM* tuple_elems; | |||||
int num_elems; | |||||
unsigned int key; | |||||
while(enif_get_list_cell(env, outer_list, &inner_list, &outer_list)) | |||||
{ | |||||
if(!enif_is_list(env, inner_list)) | |||||
{ | |||||
return enif_make_badarg(env); | |||||
} | |||||
while(enif_get_list_cell(env, inner_list, &inner_head, &inner_list)) | |||||
{ | |||||
if(!enif_get_tuple(env, inner_head, &num_elems, &tuple_elems)) | |||||
{ | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(num_elems != 2) | |||||
{ | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, tuple_elems[0], &key)) | |||||
{ | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(key >= 3000) | |||||
{ | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!seen_forklift_id[key]) | |||||
{ | |||||
seen_forklift_id[key] = 1; | |||||
ret = enif_make_list_cell(env, inner_head, ret); | |||||
} | |||||
} | |||||
} | |||||
return ret; | |||||
} | |||||
static ErlNifFunc nif_funcs[] = | |||||
{ | |||||
{"filter", 1, bitmap_filter, 0} | |||||
}; | |||||
ERL_NIF_INIT(bitmap_filter, nif_funcs, NULL, NULL, NULL, NULL) |
@ -1,9 +1,9 @@ | |||||
{port_specs, [ | {port_specs, [ | ||||
{"../../priv/cq.so", [ | |||||
"*.c", | |||||
"*.cc" | |||||
{"../../priv/bitmap_filter.so", [ | |||||
"*.c" | |||||
]} | ]} | ||||
]}. | ]}. | ||||
%{port_specs, [{"../../priv/granderl.so", []}]}. | |||||
%% {port_env, [ | %% {port_env, [ | ||||
%% {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", | %% {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", |
@ -0,0 +1,448 @@ | |||||
#include "erl_nif.h" | |||||
ErlNifResourceType* bsn_type; | |||||
ERL_NIF_TERM ATOM_TRUE, ATOM_FALSE; | |||||
/* | |||||
typedef struct { | |||||
unsigned size; | |||||
unsigned char* data; | |||||
} ErlNifBinary; | |||||
*/ | |||||
struct bsn_elem_struct { | |||||
ErlNifBinary bin; | |||||
struct bsn_elem_struct* next; | |||||
}; | |||||
typedef struct bsn_elem_struct bsn_elem; | |||||
typedef bsn_elem* bsn_list; | |||||
typedef struct { | |||||
unsigned int count; /* count of elements */ | |||||
unsigned int max; /* count of slots */ | |||||
ErlNifMutex *mutex; | |||||
bsn_list* list; | |||||
} bsn_res; | |||||
inline static ERL_NIF_TERM bool_to_term(int value) { | |||||
return value ? ATOM_TRUE : ATOM_FALSE; | |||||
} | |||||
/* Calculate the sum of chars. */ | |||||
unsigned int | |||||
private_hash(const ErlNifBinary* b, unsigned int max) | |||||
{ | |||||
unsigned char* ptr; | |||||
unsigned int i, sum = 0; | |||||
ptr = b->data; | |||||
i = b->size; | |||||
for (; i; i--, ptr++) | |||||
sum += *ptr; | |||||
return sum % max; | |||||
} | |||||
inline void | |||||
private_clear_elem(bsn_elem* el) | |||||
{ | |||||
enif_release_binary(&(el->bin)); | |||||
enif_free(el); | |||||
} | |||||
inline void | |||||
private_chain_clear_all(bsn_elem* ptr) | |||||
{ | |||||
bsn_elem* next; | |||||
while (ptr != NULL) { | |||||
next = ptr->next; | |||||
private_clear_elem(ptr); | |||||
ptr = next; | |||||
} | |||||
} | |||||
inline int | |||||
private_compare(ErlNifBinary* b1, ErlNifBinary* b2) | |||||
{ | |||||
unsigned char* p1; | |||||
unsigned char* p2; | |||||
unsigned len; | |||||
if (b1->size != b2->size) | |||||
return 0; | |||||
p1 = b1->data; | |||||
p2 = b2->data; | |||||
len = b1->size; | |||||
while (len) { | |||||
if ((*p1) != (*p2)) | |||||
return 0; | |||||
len--; p1++; p2++; | |||||
} | |||||
return 1; | |||||
} | |||||
/* Skip existing elements. If the element bin is not found, return last element. | |||||
* If el.bin == bin, return el. */ | |||||
bsn_elem* | |||||
private_chain_shift(bsn_elem* ptr, ErlNifBinary* bin, int* num_ptr) | |||||
{ | |||||
(*num_ptr)++; | |||||
if ((ptr) == NULL) | |||||
return ptr; | |||||
while (1) { | |||||
if (private_compare(&(ptr->bin), bin)) { | |||||
/* found an equal binary. Invert num */ | |||||
(*num_ptr) *= -1; | |||||
return ptr; | |||||
} | |||||
if ((ptr->next) == NULL) | |||||
return ptr; | |||||
ptr = ptr->next; | |||||
(*num_ptr)++; | |||||
} | |||||
} | |||||
/* Append the element `el' to the chain `chain' */ | |||||
void | |||||
private_chain_append(bsn_elem** chain, bsn_elem* el, int* num_ptr) | |||||
{ | |||||
bsn_elem* last; | |||||
if ((*chain) == NULL) { | |||||
/* The new element is last */ | |||||
*chain = el; | |||||
} else { | |||||
last = private_chain_shift(*chain, &(el->bin), num_ptr); | |||||
if ((*num_ptr) < 0) { | |||||
/* Element was already added. */ | |||||
private_clear_elem(el); | |||||
} else { | |||||
last->next = el; | |||||
} | |||||
} | |||||
} | |||||
bsn_elem* | |||||
private_chain_shift_clear(bsn_elem** ptr, ErlNifBinary* bin, int* num_ptr) | |||||
{ | |||||
bsn_elem** prev = NULL; | |||||
bsn_elem* el; | |||||
while ((*ptr) != NULL) { | |||||
if (private_compare(&((*ptr)->bin), bin)) { | |||||
(*num_ptr) *= -1; | |||||
/* found an equal binary. Delete elem. Invert num */ | |||||
if (prev == NULL) { | |||||
el = *ptr; | |||||
(*ptr) = (*ptr)->next; | |||||
return el; | |||||
} | |||||
*prev = (*ptr)->next; | |||||
return *ptr; | |||||
} | |||||
prev = ptr; | |||||
el = *ptr; | |||||
ptr = (bsn_elem**) &(el->next); | |||||
(*num_ptr)++; | |||||
} | |||||
return NULL; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
unsigned int max; | |||||
bsn_list* ptr; | |||||
bsn_res* r; | |||||
if (!(enif_get_uint(env, argv[0], &max) && (max>0))) | |||||
return enif_make_badarg(env); | |||||
ptr = enif_alloc(sizeof(bsn_list) * max); | |||||
if (ptr == NULL) | |||||
return enif_make_badarg(env); | |||||
r = (bsn_res*) enif_alloc_resource(bsn_type, sizeof(bsn_res)); | |||||
r->mutex = enif_mutex_create("Mutex for the BSN writer"); | |||||
r->count = 0; | |||||
r->max = max; | |||||
r->list = ptr; | |||||
for (; max; max--, ptr++) | |||||
*ptr = NULL; | |||||
return enif_make_resource(env, r); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_add(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
bsn_res* r; | |||||
unsigned int pos; | |||||
int num = 0; | |||||
bsn_elem* elem_ptr; | |||||
if (!(enif_get_resource(env, argv[0], bsn_type, (void**) &r) | |||||
&& enif_inspect_binary(env, argv[1], &bin))) | |||||
return enif_make_badarg(env); | |||||
enif_realloc_binary(&bin, bin.size); | |||||
pos = private_hash(&bin, r->max); | |||||
elem_ptr = enif_alloc(sizeof(bsn_elem)); | |||||
if (elem_ptr == NULL) | |||||
return enif_make_badarg(env); | |||||
elem_ptr->next = NULL; | |||||
elem_ptr->bin = bin; | |||||
enif_mutex_lock(r->mutex); | |||||
private_chain_append(&(r->list[pos]), elem_ptr, &num); | |||||
if (num >= 0) | |||||
(r->count)++; | |||||
enif_mutex_unlock(r->mutex); | |||||
/* Already added */ | |||||
if (num < 0) | |||||
enif_release_binary(&(bin)); | |||||
return enif_make_int(env, num); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_search(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
bsn_res* r; | |||||
unsigned int pos; | |||||
int num = 0; | |||||
if (!(enif_get_resource(env, argv[0], bsn_type, (void**) &r) | |||||
&& enif_inspect_binary(env, argv[1], &bin))) | |||||
return enif_make_badarg(env); | |||||
pos = private_hash(&bin, r->max); | |||||
enif_mutex_lock(r->mutex); | |||||
private_chain_shift(r->list[pos], &bin, &num); | |||||
enif_mutex_unlock(r->mutex); | |||||
return enif_make_int(env, num); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_clear(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
bsn_res* r; | |||||
unsigned int pos; | |||||
int num = 0; | |||||
bsn_elem* elem_ptr; | |||||
if (!(enif_get_resource(env, argv[0], bsn_type, (void**) &r) | |||||
&& enif_inspect_binary(env, argv[1], &bin))) | |||||
return enif_make_badarg(env); | |||||
pos = private_hash(&bin, r->max); | |||||
enif_mutex_lock(r->mutex); | |||||
elem_ptr = private_chain_shift_clear(&(r->list[pos]), &bin, &num); | |||||
if (elem_ptr != NULL) { | |||||
private_clear_elem(elem_ptr); | |||||
(r->count)--; | |||||
} | |||||
enif_mutex_unlock(r->mutex); | |||||
return enif_make_int(env, num); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_all_chain(ErlNifEnv* env, bsn_elem* e, ERL_NIF_TERM tail) | |||||
{ | |||||
ERL_NIF_TERM head; | |||||
ErlNifBinary bin; | |||||
while (e != NULL) { | |||||
bin = e->bin; | |||||
enif_realloc_binary(&bin, bin.size); | |||||
head = enif_make_binary(env, &bin); | |||||
tail = enif_make_list_cell(env, head, tail); | |||||
e = e->next; | |||||
} | |||||
return tail; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_chains(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
bsn_res* r; | |||||
unsigned int max; | |||||
bsn_list* ptr; | |||||
ERL_NIF_TERM tail, head; | |||||
if (!enif_get_resource(env, argv[0], bsn_type, (void**) &r)) | |||||
return enif_make_badarg(env); | |||||
tail = enif_make_list(env, 0); | |||||
ptr = r->list; | |||||
enif_mutex_lock(r->mutex); | |||||
max = r->max; | |||||
while (max) { | |||||
head = enif_make_list(env, 0); | |||||
head = bsn_all_chain(env, *ptr, head); | |||||
tail = enif_make_list_cell(env, head, tail); | |||||
ptr++; | |||||
max--; | |||||
} | |||||
enif_mutex_unlock(r->mutex); | |||||
return tail; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_all(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
bsn_res* r; | |||||
unsigned int max; | |||||
bsn_list* ptr; | |||||
ERL_NIF_TERM list; | |||||
if (!enif_get_resource(env, argv[0], bsn_type, (void**) &r)) | |||||
return enif_make_badarg(env); | |||||
list = enif_make_list(env, 0); | |||||
ptr = r->list; | |||||
enif_mutex_lock(r->mutex); | |||||
max = r->max; | |||||
while (max) { | |||||
list = bsn_all_chain(env, *ptr, list); | |||||
ptr++; | |||||
max--; | |||||
} | |||||
enif_mutex_unlock(r->mutex); | |||||
return list; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
bsn_res* r; | |||||
if (!enif_get_resource(env, argv[0], bsn_type, (void**) &r)) | |||||
return enif_make_badarg(env); | |||||
return enif_make_int(env, r->count); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_hash(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
unsigned int max; | |||||
if (!(enif_inspect_binary(env, argv[0], &bin) | |||||
&& enif_get_uint(env, argv[1], &max) && (max>0))) | |||||
return enif_make_badarg(env); | |||||
return enif_make_uint(env, | |||||
private_hash(&bin, max)); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_compare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary b1, b2; | |||||
if (!(enif_inspect_binary(env, argv[0], &b1) | |||||
&& enif_inspect_binary(env, argv[1], &b2))) | |||||
return enif_make_badarg(env); | |||||
return bool_to_term(private_compare(&b1, &b2)); | |||||
} | |||||
void private_clear_all(bsn_res* r) | |||||
{ | |||||
unsigned int max; | |||||
bsn_list* ptr; | |||||
max = r->max; | |||||
ptr = r->list; | |||||
while (max) { | |||||
private_chain_clear_all(*ptr); | |||||
ptr++; | |||||
max--; | |||||
} | |||||
} | |||||
void | |||||
bsn_type_dtor(ErlNifEnv* env, void* obj) | |||||
{ | |||||
bsn_res* r = (bsn_res*) obj; | |||||
private_clear_all(r); | |||||
enif_mutex_destroy(r->mutex); | |||||
enif_free(r->list); | |||||
} | |||||
int | |||||
on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) | |||||
{ | |||||
ATOM_TRUE = enif_make_atom(env, "true"); | |||||
ATOM_FALSE = enif_make_atom(env, "false"); | |||||
ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | | |||||
ERL_NIF_RT_TAKEOVER); | |||||
bsn_type = enif_open_resource_type(env, NULL, "bsn_type", | |||||
bsn_type_dtor, flags, NULL); | |||||
if (bsn_type == NULL) return 1; | |||||
return 0; | |||||
} | |||||
int | |||||
on_upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) | |||||
{ | |||||
return 0; | |||||
} | |||||
static ErlNifFunc nif_functions[] = { | |||||
{"new", 1, bsn_new}, | |||||
{"add", 2, bsn_add}, | |||||
{"all", 1, bsn_all}, | |||||
{"chains", 1, bsn_chains}, | |||||
{"in", 2, bsn_search}, | |||||
{"clear", 2, bsn_clear}, | |||||
{"count", 1, bsn_count}, | |||||
{"hash", 2, bsn_hash}, | |||||
{"compare", 2, bsn_compare}, | |||||
}; | |||||
ERL_NIF_INIT(bsn_ext, nif_functions, &on_load, &on_load, &on_upgrade, NULL); |
@ -0,0 +1,331 @@ | |||||
#include "erl_nif.h" | |||||
ErlNifResourceType* bsn_type; | |||||
ERL_NIF_TERM ATOM_TRUE, ATOM_FALSE, ATOM_NO_MORE; | |||||
struct bsn_elem_struct { | |||||
ErlNifBinary bin; | |||||
unsigned int hash; | |||||
}; | |||||
typedef struct bsn_elem_struct bsn_elem; | |||||
typedef struct { | |||||
unsigned int count; /* count of elements */ | |||||
unsigned int max; /* count of slots */ | |||||
ErlNifMutex *mutex; | |||||
bsn_elem* list; | |||||
unsigned int (*next_pos) | |||||
(void*, unsigned int, unsigned int); | |||||
} bsn_res; | |||||
inline static ERL_NIF_TERM bool_to_term(int value) { | |||||
return value ? ATOM_TRUE : ATOM_FALSE; | |||||
} | |||||
unsigned int next_pos_linear(bsn_res* r, unsigned int hash, unsigned int step) { | |||||
return (hash + step) % (r->max); | |||||
} | |||||
unsigned int next_pos_quadric(bsn_res* r, unsigned int hash, unsigned int step) { | |||||
return (hash + (step*step)) % (r->max); | |||||
} | |||||
/* Calculate the sum of chars. */ | |||||
unsigned int | |||||
private_hash(const ErlNifBinary* b, unsigned int max) | |||||
{ | |||||
unsigned char* ptr; | |||||
unsigned int i, sum = 0; | |||||
ptr = b->data; | |||||
i = b->size; | |||||
for (; i; i--, ptr++) | |||||
sum += *ptr; | |||||
return sum % max; | |||||
} | |||||
inline int | |||||
private_compare(ErlNifBinary* b1, ErlNifBinary* b2) | |||||
{ | |||||
unsigned char* p1; | |||||
unsigned char* p2; | |||||
unsigned len; | |||||
if (b1->size != b2->size) | |||||
return 0; | |||||
p1 = b1->data; | |||||
p2 = b2->data; | |||||
len = b1->size; | |||||
while (len) { | |||||
if ((*p1) != (*p2)) | |||||
return 0; | |||||
len--; p1++; p2++; | |||||
} | |||||
return 1; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
int max; /* This value will be set by a client: | |||||
if (max<0) -> use quadric algorithm */ | |||||
bsn_elem* ptr; | |||||
bsn_res* r; | |||||
if (!enif_get_int(env, argv[0], &max) || (max == 0)) | |||||
return enif_make_badarg(env); | |||||
r = (bsn_res*) enif_alloc_resource(bsn_type, sizeof(bsn_res)); | |||||
r->mutex = enif_mutex_create("Mutex for the BSN writer"); | |||||
r->count = 0; | |||||
/* Select an algorithm */ | |||||
if (max>0) { | |||||
r->next_pos = &next_pos_linear; | |||||
} else if (max<0) { | |||||
r->next_pos = &next_pos_quadric; | |||||
max *= -1; | |||||
} | |||||
/* Now max is cells' count in the array. */ | |||||
r->max = (unsigned int) max; | |||||
ptr = enif_alloc(sizeof(bsn_elem) * max); | |||||
if (ptr == NULL) | |||||
return enif_make_badarg(env); | |||||
r->list = ptr; | |||||
for (; max; max--, ptr++) | |||||
ptr->hash = r->max; | |||||
return enif_make_resource(env, r); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_add(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
bsn_res* r; | |||||
unsigned int pos, hash, max; | |||||
int num = 0; | |||||
bsn_elem* elem_ptr; | |||||
if (!(enif_get_resource(env, argv[0], bsn_type, (void**) &r) | |||||
&& enif_inspect_binary(env, argv[1], &bin))) | |||||
return enif_make_badarg(env); | |||||
enif_realloc_binary(&bin, bin.size); | |||||
hash = pos = private_hash(&bin, r->max); | |||||
enif_mutex_lock(r->mutex); | |||||
max = r->max; | |||||
while (num < max) { | |||||
elem_ptr = &(r->list[pos]); | |||||
/* Found free space */ | |||||
if (elem_ptr->hash == max) { | |||||
elem_ptr->bin = bin; | |||||
elem_ptr->hash = hash; | |||||
break; | |||||
} | |||||
/* Found elem */ | |||||
if ((elem_ptr->hash == hash) | |||||
&& private_compare(&bin, &(elem_ptr->bin))) { | |||||
num *= -1; | |||||
break; | |||||
} | |||||
pos = (r->next_pos)(r, hash, num); | |||||
num++; | |||||
} | |||||
if ((num >= 0) && (num < max)) | |||||
(r->count)++; | |||||
enif_mutex_unlock(r->mutex); | |||||
/* Error: already added or owerflow */ | |||||
if (!((num >= 0) && (num < max))) | |||||
enif_release_binary(&bin); | |||||
if (num >= max) | |||||
return ATOM_NO_MORE; | |||||
return enif_make_int(env, num); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_search(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
bsn_res* r; | |||||
unsigned int pos, max, hash; | |||||
int num = 1; | |||||
bsn_elem* elem_ptr; | |||||
if (!(enif_get_resource(env, argv[0], bsn_type, (void**) &r) | |||||
&& enif_inspect_binary(env, argv[1], &bin))) | |||||
return enif_make_badarg(env); | |||||
hash = pos = private_hash(&bin, r->max); | |||||
enif_mutex_lock(r->mutex); | |||||
max = r->max; | |||||
while (num < max) { | |||||
elem_ptr = &(r->list[pos]); | |||||
/* Found free space */ | |||||
if (elem_ptr->hash == max) { | |||||
break; | |||||
} | |||||
/* Found elem */ | |||||
if ((elem_ptr->hash == hash) | |||||
&& private_compare(&bin, &(elem_ptr->bin))) { | |||||
num *= -1; | |||||
break; | |||||
} | |||||
pos = (r->next_pos)(r, hash, num); | |||||
num++; | |||||
} | |||||
enif_mutex_unlock(r->mutex); | |||||
return enif_make_int(env, num); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_clear(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
return enif_make_badarg(env); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_all(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
bsn_res* r; | |||||
unsigned int max, pos = 0; | |||||
ERL_NIF_TERM head, tail; | |||||
ErlNifBinary bin; | |||||
bsn_elem* elem_ptr; | |||||
if (!enif_get_resource(env, argv[0], bsn_type, (void**) &r)) | |||||
return enif_make_badarg(env); | |||||
tail = enif_make_list(env, 0); | |||||
enif_mutex_lock(r->mutex); | |||||
max = r->max; | |||||
elem_ptr = r->list; | |||||
do { | |||||
if (elem_ptr->hash != max) { | |||||
bin = elem_ptr->bin; | |||||
enif_realloc_binary(&bin, bin.size); | |||||
head = enif_make_binary(env, &bin); | |||||
tail = enif_make_list_cell(env, head, tail); | |||||
} | |||||
elem_ptr++; | |||||
pos++; | |||||
} while (pos < max); | |||||
enif_mutex_unlock(r->mutex); | |||||
return tail; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
bsn_res* r; | |||||
if (!enif_get_resource(env, argv[0], bsn_type, (void**) &r)) | |||||
return enif_make_badarg(env); | |||||
return enif_make_int(env, r->count); | |||||
} | |||||
void private_clear_all(bsn_res* r) | |||||
{ | |||||
unsigned int max, num; | |||||
bsn_elem* ptr; | |||||
num = max = r->max; | |||||
ptr = r->list; | |||||
while (num) { | |||||
if (ptr->hash != max) { | |||||
enif_release_binary(&(ptr->bin)); | |||||
} | |||||
ptr++; | |||||
num--; | |||||
} | |||||
} | |||||
void | |||||
bsn_type_dtor(ErlNifEnv* env, void* obj) | |||||
{ | |||||
bsn_res* r = (bsn_res*) obj; | |||||
private_clear_all(r); | |||||
enif_mutex_destroy(r->mutex); | |||||
enif_free(r->list); | |||||
} | |||||
int | |||||
on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) | |||||
{ | |||||
ATOM_TRUE = enif_make_atom(env, "true"); | |||||
ATOM_FALSE = enif_make_atom(env, "false"); | |||||
ATOM_NO_MORE = enif_make_atom(env, "no_more"); | |||||
ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | | |||||
ERL_NIF_RT_TAKEOVER); | |||||
bsn_type = enif_open_resource_type(env, NULL, "bsn_type", | |||||
bsn_type_dtor, flags, NULL); | |||||
if (bsn_type == NULL) return 1; | |||||
return 0; | |||||
} | |||||
int | |||||
on_upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) | |||||
{ | |||||
return 0; | |||||
} | |||||
static ErlNifFunc nif_functions[] = { | |||||
{"new", 1, bsn_new}, | |||||
{"add", 2, bsn_add}, | |||||
{"all", 1, bsn_all}, | |||||
{"in", 2, bsn_search}, | |||||
{"clear", 2, bsn_clear}, | |||||
{"count", 1, bsn_count}, | |||||
}; | |||||
ERL_NIF_INIT(bsn_int, nif_functions, &on_load, &on_load, &on_upgrade, NULL); |
@ -0,0 +1,448 @@ | |||||
#include "erl_nif.h" | |||||
ErlNifResourceType* bsn_type; | |||||
ERL_NIF_TERM ATOM_TRUE, ATOM_FALSE; | |||||
/* | |||||
typedef struct { | |||||
unsigned size; | |||||
unsigned char* data; | |||||
} ErlNifBinary; | |||||
*/ | |||||
struct bsn_elem_struct { | |||||
ErlNifBinary bin; | |||||
struct bsn_elem_struct* next; | |||||
}; | |||||
typedef struct bsn_elem_struct bsn_elem; | |||||
typedef bsn_elem* bsn_list; | |||||
typedef struct { | |||||
unsigned int count; /* count of elements */ | |||||
unsigned int max; /* count of slots */ | |||||
ErlNifMutex *mutex; | |||||
bsn_list* list; | |||||
} bsn_res; | |||||
inline static ERL_NIF_TERM bool_to_term(int value) { | |||||
return value ? ATOM_TRUE : ATOM_FALSE; | |||||
} | |||||
/* Calculate the sum of chars. */ | |||||
unsigned int | |||||
private_hash(const ErlNifBinary* b, unsigned int max) | |||||
{ | |||||
unsigned char* ptr; | |||||
unsigned int i, sum = 0; | |||||
ptr = b->data; | |||||
i = b->size; | |||||
for (; i; i--, ptr++) | |||||
sum += *ptr; | |||||
return sum % max; | |||||
} | |||||
inline void | |||||
private_clear_elem(bsn_elem* el) | |||||
{ | |||||
enif_release_binary(&(el->bin)); | |||||
enif_free(el); | |||||
} | |||||
inline void | |||||
private_chain_clear_all(bsn_elem* ptr) | |||||
{ | |||||
bsn_elem* next; | |||||
while (ptr != NULL) { | |||||
next = ptr->next; | |||||
private_clear_elem(ptr); | |||||
ptr = next; | |||||
} | |||||
} | |||||
inline int | |||||
private_compare(ErlNifBinary* b1, ErlNifBinary* b2) | |||||
{ | |||||
unsigned char* p1; | |||||
unsigned char* p2; | |||||
unsigned len; | |||||
if (b1->size != b2->size) | |||||
return 0; | |||||
p1 = b1->data; | |||||
p2 = b2->data; | |||||
len = b1->size; | |||||
while (len) { | |||||
if ((*p1) != (*p2)) | |||||
return 0; | |||||
len--; p1++; p2++; | |||||
} | |||||
return 1; | |||||
} | |||||
/* Skip existing elements. If the element bin is not found, return last element. | |||||
* If el.bin == bin, return el. */ | |||||
bsn_elem* | |||||
private_chain_shift(bsn_elem* ptr, ErlNifBinary* bin, int* num_ptr) | |||||
{ | |||||
(*num_ptr)++; | |||||
if ((ptr) == NULL) | |||||
return ptr; | |||||
while (1) { | |||||
if (private_compare(&(ptr->bin), bin)) { | |||||
/* found an equal binary. Invert num */ | |||||
(*num_ptr) *= -1; | |||||
return ptr; | |||||
} | |||||
if ((ptr->next) == NULL) | |||||
return ptr; | |||||
ptr = ptr->next; | |||||
(*num_ptr)++; | |||||
} | |||||
} | |||||
/* Append the element `el' to the chain `chain' */ | |||||
void | |||||
private_chain_append(bsn_elem** chain, bsn_elem* el, int* num_ptr) | |||||
{ | |||||
bsn_elem* last; | |||||
if ((*chain) == NULL) { | |||||
/* The new element is last */ | |||||
*chain = el; | |||||
} else { | |||||
last = private_chain_shift(*chain, &(el->bin), num_ptr); | |||||
if ((*num_ptr) < 0) { | |||||
/* Element was already added. */ | |||||
private_clear_elem(el); | |||||
} else { | |||||
last->next = el; | |||||
} | |||||
} | |||||
} | |||||
bsn_elem* | |||||
private_chain_shift_clear(bsn_elem** ptr, ErlNifBinary* bin, int* num_ptr) | |||||
{ | |||||
bsn_elem** prev = NULL; | |||||
bsn_elem* el; | |||||
while ((*ptr) != NULL) { | |||||
if (private_compare(&((*ptr)->bin), bin)) { | |||||
(*num_ptr) *= -1; | |||||
/* found an equal binary. Delete elem. Invert num */ | |||||
if (prev == NULL) { | |||||
el = *ptr; | |||||
(*ptr) = (*ptr)->next; | |||||
return el; | |||||
} | |||||
*prev = (*ptr)->next; | |||||
return *ptr; | |||||
} | |||||
prev = ptr; | |||||
el = *ptr; | |||||
ptr = (bsn_elem**) &(el->next); | |||||
(*num_ptr)++; | |||||
} | |||||
return NULL; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
unsigned int max; | |||||
bsn_list* ptr; | |||||
bsn_res* r; | |||||
if (!(enif_get_uint(env, argv[0], &max) && (max>0))) | |||||
return enif_make_badarg(env); | |||||
ptr = enif_alloc(sizeof(bsn_list) * max); | |||||
if (ptr == NULL) | |||||
return enif_make_badarg(env); | |||||
r = (bsn_res*) enif_alloc_resource(bsn_type, sizeof(bsn_res)); | |||||
r->mutex = enif_mutex_create("Mutex for the BSN writer"); | |||||
r->count = 0; | |||||
r->max = max; | |||||
r->list = ptr; | |||||
for (; max; max--, ptr++) | |||||
*ptr = NULL; | |||||
return enif_make_resource(env, r); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_add(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
bsn_res* r; | |||||
unsigned int pos; | |||||
int num = 0; | |||||
bsn_elem* elem_ptr; | |||||
if (!(enif_get_resource(env, argv[0], bsn_type, (void**) &r) | |||||
&& enif_inspect_binary(env, argv[1], &bin))) | |||||
return enif_make_badarg(env); | |||||
enif_realloc_binary(&bin, bin.size); | |||||
pos = private_hash(&bin, r->max); | |||||
elem_ptr = enif_alloc(sizeof(bsn_elem)); | |||||
if (elem_ptr == NULL) | |||||
return enif_make_badarg(env); | |||||
elem_ptr->next = NULL; | |||||
elem_ptr->bin = bin; | |||||
enif_mutex_lock(r->mutex); | |||||
private_chain_append(&(r->list[pos]), elem_ptr, &num); | |||||
if (num >= 0) | |||||
(r->count)++; | |||||
enif_mutex_unlock(r->mutex); | |||||
/* Already added */ | |||||
if (num < 0) | |||||
enif_release_binary(&(bin)); | |||||
return enif_make_int(env, num); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_search(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
bsn_res* r; | |||||
unsigned int pos; | |||||
int num = 0; | |||||
if (!(enif_get_resource(env, argv[0], bsn_type, (void**) &r) | |||||
&& enif_inspect_binary(env, argv[1], &bin))) | |||||
return enif_make_badarg(env); | |||||
pos = private_hash(&bin, r->max); | |||||
enif_mutex_lock(r->mutex); | |||||
private_chain_shift(r->list[pos], &bin, &num); | |||||
enif_mutex_unlock(r->mutex); | |||||
return enif_make_int(env, num); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_clear(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
bsn_res* r; | |||||
unsigned int pos; | |||||
int num = 0; | |||||
bsn_elem* elem_ptr; | |||||
if (!(enif_get_resource(env, argv[0], bsn_type, (void**) &r) | |||||
&& enif_inspect_binary(env, argv[1], &bin))) | |||||
return enif_make_badarg(env); | |||||
pos = private_hash(&bin, r->max); | |||||
enif_mutex_lock(r->mutex); | |||||
elem_ptr = private_chain_shift_clear(&(r->list[pos]), &bin, &num); | |||||
if (elem_ptr != NULL) { | |||||
private_clear_elem(elem_ptr); | |||||
(r->count)--; | |||||
} | |||||
enif_mutex_unlock(r->mutex); | |||||
return enif_make_int(env, num); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_all_chain(ErlNifEnv* env, bsn_elem* e, ERL_NIF_TERM tail) | |||||
{ | |||||
ERL_NIF_TERM head; | |||||
ErlNifBinary bin; | |||||
while (e != NULL) { | |||||
bin = e->bin; | |||||
enif_realloc_binary(&bin, bin.size); | |||||
head = enif_make_binary(env, &bin); | |||||
tail = enif_make_list_cell(env, head, tail); | |||||
e = e->next; | |||||
} | |||||
return tail; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_chains(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
bsn_res* r; | |||||
unsigned int max; | |||||
bsn_list* ptr; | |||||
ERL_NIF_TERM tail, head; | |||||
if (!enif_get_resource(env, argv[0], bsn_type, (void**) &r)) | |||||
return enif_make_badarg(env); | |||||
tail = enif_make_list(env, 0); | |||||
ptr = r->list; | |||||
enif_mutex_lock(r->mutex); | |||||
max = r->max; | |||||
while (max) { | |||||
head = enif_make_list(env, 0); | |||||
head = bsn_all_chain(env, *ptr, head); | |||||
tail = enif_make_list_cell(env, head, tail); | |||||
ptr++; | |||||
max--; | |||||
} | |||||
enif_mutex_unlock(r->mutex); | |||||
return tail; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_all(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
bsn_res* r; | |||||
unsigned int max; | |||||
bsn_list* ptr; | |||||
ERL_NIF_TERM list; | |||||
if (!enif_get_resource(env, argv[0], bsn_type, (void**) &r)) | |||||
return enif_make_badarg(env); | |||||
list = enif_make_list(env, 0); | |||||
ptr = r->list; | |||||
enif_mutex_lock(r->mutex); | |||||
max = r->max; | |||||
while (max) { | |||||
list = bsn_all_chain(env, *ptr, list); | |||||
ptr++; | |||||
max--; | |||||
} | |||||
enif_mutex_unlock(r->mutex); | |||||
return list; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
bsn_res* r; | |||||
if (!enif_get_resource(env, argv[0], bsn_type, (void**) &r)) | |||||
return enif_make_badarg(env); | |||||
return enif_make_int(env, r->count); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_hash(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
unsigned int max; | |||||
if (!(enif_inspect_binary(env, argv[0], &bin) | |||||
&& enif_get_uint(env, argv[1], &max) && (max>0))) | |||||
return enif_make_badarg(env); | |||||
return enif_make_uint(env, | |||||
private_hash(&bin, max)); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_compare(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary b1, b2; | |||||
if (!(enif_inspect_binary(env, argv[0], &b1) | |||||
&& enif_inspect_binary(env, argv[1], &b2))) | |||||
return enif_make_badarg(env); | |||||
return bool_to_term(private_compare(&b1, &b2)); | |||||
} | |||||
void private_clear_all(bsn_res* r) | |||||
{ | |||||
unsigned int max; | |||||
bsn_list* ptr; | |||||
max = r->max; | |||||
ptr = r->list; | |||||
while (max) { | |||||
private_chain_clear_all(*ptr); | |||||
ptr++; | |||||
max--; | |||||
} | |||||
} | |||||
void | |||||
bsn_type_dtor(ErlNifEnv* env, void* obj) | |||||
{ | |||||
bsn_res* r = (bsn_res*) obj; | |||||
private_clear_all(r); | |||||
enif_mutex_destroy(r->mutex); | |||||
enif_free(r->list); | |||||
} | |||||
int | |||||
on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) | |||||
{ | |||||
ATOM_TRUE = enif_make_atom(env, "true"); | |||||
ATOM_FALSE = enif_make_atom(env, "false"); | |||||
ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | | |||||
ERL_NIF_RT_TAKEOVER); | |||||
bsn_type = enif_open_resource_type(env, NULL, "bsn_type", | |||||
bsn_type_dtor, flags, NULL); | |||||
if (bsn_type == NULL) return 1; | |||||
return 0; | |||||
} | |||||
int | |||||
on_upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) | |||||
{ | |||||
return 0; | |||||
} | |||||
static ErlNifFunc nif_functions[] = { | |||||
{"new", 1, bsn_new}, | |||||
{"add", 2, bsn_add}, | |||||
{"all", 1, bsn_all}, | |||||
{"chains", 1, bsn_chains}, | |||||
{"in", 2, bsn_search}, | |||||
{"clear", 2, bsn_clear}, | |||||
{"count", 1, bsn_count}, | |||||
{"hash", 2, bsn_hash}, | |||||
{"compare", 2, bsn_compare}, | |||||
}; | |||||
ERL_NIF_INIT(bsn_ext, nif_functions, &on_load, &on_load, &on_upgrade, NULL); |
@ -0,0 +1,331 @@ | |||||
#include "erl_nif.h" | |||||
ErlNifResourceType* bsn_type; | |||||
ERL_NIF_TERM ATOM_TRUE, ATOM_FALSE, ATOM_NO_MORE; | |||||
struct bsn_elem_struct { | |||||
ErlNifBinary bin; | |||||
unsigned int hash; | |||||
}; | |||||
typedef struct bsn_elem_struct bsn_elem; | |||||
typedef struct { | |||||
unsigned int count; /* count of elements */ | |||||
unsigned int max; /* count of slots */ | |||||
ErlNifMutex *mutex; | |||||
bsn_elem* list; | |||||
unsigned int (*next_pos) | |||||
(void*, unsigned int, unsigned int); | |||||
} bsn_res; | |||||
inline static ERL_NIF_TERM bool_to_term(int value) { | |||||
return value ? ATOM_TRUE : ATOM_FALSE; | |||||
} | |||||
unsigned int next_pos_linear(bsn_res* r, unsigned int hash, unsigned int step) { | |||||
return (hash + step) % (r->max); | |||||
} | |||||
unsigned int next_pos_quadric(bsn_res* r, unsigned int hash, unsigned int step) { | |||||
return (hash + (step*step)) % (r->max); | |||||
} | |||||
/* Calculate the sum of chars. */ | |||||
unsigned int | |||||
private_hash(const ErlNifBinary* b, unsigned int max) | |||||
{ | |||||
unsigned char* ptr; | |||||
unsigned int i, sum = 0; | |||||
ptr = b->data; | |||||
i = b->size; | |||||
for (; i; i--, ptr++) | |||||
sum += *ptr; | |||||
return sum % max; | |||||
} | |||||
inline int | |||||
private_compare(ErlNifBinary* b1, ErlNifBinary* b2) | |||||
{ | |||||
unsigned char* p1; | |||||
unsigned char* p2; | |||||
unsigned len; | |||||
if (b1->size != b2->size) | |||||
return 0; | |||||
p1 = b1->data; | |||||
p2 = b2->data; | |||||
len = b1->size; | |||||
while (len) { | |||||
if ((*p1) != (*p2)) | |||||
return 0; | |||||
len--; p1++; p2++; | |||||
} | |||||
return 1; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
int max; /* This value will be set by a client: | |||||
if (max<0) -> use quadric algorithm */ | |||||
bsn_elem* ptr; | |||||
bsn_res* r; | |||||
if (!enif_get_int(env, argv[0], &max) || (max == 0)) | |||||
return enif_make_badarg(env); | |||||
r = (bsn_res*) enif_alloc_resource(bsn_type, sizeof(bsn_res)); | |||||
r->mutex = enif_mutex_create("Mutex for the BSN writer"); | |||||
r->count = 0; | |||||
/* Select an algorithm */ | |||||
if (max>0) { | |||||
r->next_pos = &next_pos_linear; | |||||
} else if (max<0) { | |||||
r->next_pos = &next_pos_quadric; | |||||
max *= -1; | |||||
} | |||||
/* Now max is cells' count in the array. */ | |||||
r->max = (unsigned int) max; | |||||
ptr = enif_alloc(sizeof(bsn_elem) * max); | |||||
if (ptr == NULL) | |||||
return enif_make_badarg(env); | |||||
r->list = ptr; | |||||
for (; max; max--, ptr++) | |||||
ptr->hash = r->max; | |||||
return enif_make_resource(env, r); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_add(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
bsn_res* r; | |||||
unsigned int pos, hash, max; | |||||
int num = 0; | |||||
bsn_elem* elem_ptr; | |||||
if (!(enif_get_resource(env, argv[0], bsn_type, (void**) &r) | |||||
&& enif_inspect_binary(env, argv[1], &bin))) | |||||
return enif_make_badarg(env); | |||||
enif_realloc_binary(&bin, bin.size); | |||||
hash = pos = private_hash(&bin, r->max); | |||||
enif_mutex_lock(r->mutex); | |||||
max = r->max; | |||||
while (num < max) { | |||||
elem_ptr = &(r->list[pos]); | |||||
/* Found free space */ | |||||
if (elem_ptr->hash == max) { | |||||
elem_ptr->bin = bin; | |||||
elem_ptr->hash = hash; | |||||
break; | |||||
} | |||||
/* Found elem */ | |||||
if ((elem_ptr->hash == hash) | |||||
&& private_compare(&bin, &(elem_ptr->bin))) { | |||||
num *= -1; | |||||
break; | |||||
} | |||||
pos = (r->next_pos)(r, hash, num); | |||||
num++; | |||||
} | |||||
if ((num >= 0) && (num < max)) | |||||
(r->count)++; | |||||
enif_mutex_unlock(r->mutex); | |||||
/* Error: already added or owerflow */ | |||||
if (!((num >= 0) && (num < max))) | |||||
enif_release_binary(&bin); | |||||
if (num >= max) | |||||
return ATOM_NO_MORE; | |||||
return enif_make_int(env, num); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_search(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary bin; | |||||
bsn_res* r; | |||||
unsigned int pos, max, hash; | |||||
int num = 1; | |||||
bsn_elem* elem_ptr; | |||||
if (!(enif_get_resource(env, argv[0], bsn_type, (void**) &r) | |||||
&& enif_inspect_binary(env, argv[1], &bin))) | |||||
return enif_make_badarg(env); | |||||
hash = pos = private_hash(&bin, r->max); | |||||
enif_mutex_lock(r->mutex); | |||||
max = r->max; | |||||
while (num < max) { | |||||
elem_ptr = &(r->list[pos]); | |||||
/* Found free space */ | |||||
if (elem_ptr->hash == max) { | |||||
break; | |||||
} | |||||
/* Found elem */ | |||||
if ((elem_ptr->hash == hash) | |||||
&& private_compare(&bin, &(elem_ptr->bin))) { | |||||
num *= -1; | |||||
break; | |||||
} | |||||
pos = (r->next_pos)(r, hash, num); | |||||
num++; | |||||
} | |||||
enif_mutex_unlock(r->mutex); | |||||
return enif_make_int(env, num); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_clear(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
return enif_make_badarg(env); | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_all(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
bsn_res* r; | |||||
unsigned int max, pos = 0; | |||||
ERL_NIF_TERM head, tail; | |||||
ErlNifBinary bin; | |||||
bsn_elem* elem_ptr; | |||||
if (!enif_get_resource(env, argv[0], bsn_type, (void**) &r)) | |||||
return enif_make_badarg(env); | |||||
tail = enif_make_list(env, 0); | |||||
enif_mutex_lock(r->mutex); | |||||
max = r->max; | |||||
elem_ptr = r->list; | |||||
do { | |||||
if (elem_ptr->hash != max) { | |||||
bin = elem_ptr->bin; | |||||
enif_realloc_binary(&bin, bin.size); | |||||
head = enif_make_binary(env, &bin); | |||||
tail = enif_make_list_cell(env, head, tail); | |||||
} | |||||
elem_ptr++; | |||||
pos++; | |||||
} while (pos < max); | |||||
enif_mutex_unlock(r->mutex); | |||||
return tail; | |||||
} | |||||
static ERL_NIF_TERM | |||||
bsn_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
bsn_res* r; | |||||
if (!enif_get_resource(env, argv[0], bsn_type, (void**) &r)) | |||||
return enif_make_badarg(env); | |||||
return enif_make_int(env, r->count); | |||||
} | |||||
void private_clear_all(bsn_res* r) | |||||
{ | |||||
unsigned int max, num; | |||||
bsn_elem* ptr; | |||||
num = max = r->max; | |||||
ptr = r->list; | |||||
while (num) { | |||||
if (ptr->hash != max) { | |||||
enif_release_binary(&(ptr->bin)); | |||||
} | |||||
ptr++; | |||||
num--; | |||||
} | |||||
} | |||||
void | |||||
bsn_type_dtor(ErlNifEnv* env, void* obj) | |||||
{ | |||||
bsn_res* r = (bsn_res*) obj; | |||||
private_clear_all(r); | |||||
enif_mutex_destroy(r->mutex); | |||||
enif_free(r->list); | |||||
} | |||||
int | |||||
on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) | |||||
{ | |||||
ATOM_TRUE = enif_make_atom(env, "true"); | |||||
ATOM_FALSE = enif_make_atom(env, "false"); | |||||
ATOM_NO_MORE = enif_make_atom(env, "no_more"); | |||||
ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | | |||||
ERL_NIF_RT_TAKEOVER); | |||||
bsn_type = enif_open_resource_type(env, NULL, "bsn_type", | |||||
bsn_type_dtor, flags, NULL); | |||||
if (bsn_type == NULL) return 1; | |||||
return 0; | |||||
} | |||||
int | |||||
on_upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) | |||||
{ | |||||
return 0; | |||||
} | |||||
static ErlNifFunc nif_functions[] = { | |||||
{"new", 1, bsn_new}, | |||||
{"add", 2, bsn_add}, | |||||
{"all", 1, bsn_all}, | |||||
{"in", 2, bsn_search}, | |||||
{"clear", 2, bsn_clear}, | |||||
{"count", 1, bsn_count}, | |||||
}; | |||||
ERL_NIF_INIT(bsn_int, nif_functions, &on_load, &on_load, &on_upgrade, NULL); |
@ -0,0 +1,318 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
// use this file except in compliance with the License. You may obtain a copy of | |||||
// the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
// License for the specific language governing permissions and limitations under | |||||
// the License. | |||||
#include <assert.h> | |||||
#include <stdint.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include "hqueue.h" | |||||
struct hqueue | |||||
{ | |||||
int version; | |||||
uint32_t idx; | |||||
uint32_t max_elems; | |||||
uint32_t heap_size; | |||||
hqnode_t* heap; // one based index | |||||
}; | |||||
struct hqnode | |||||
{ | |||||
double priority; | |||||
void* value; | |||||
}; | |||||
static inline void | |||||
hqueue_exchange(hqueue_t* hqueue, int i, int j) | |||||
{ | |||||
hqnode_t tmp; | |||||
tmp = hqueue->heap[i]; | |||||
hqueue->heap[i] = hqueue->heap[j]; | |||||
hqueue->heap[j] = tmp; | |||||
return; | |||||
} | |||||
static inline int | |||||
hqueue_less(hqueue_t* hqueue, int i, int j) | |||||
{ | |||||
return hqueue->heap[i].priority < hqueue->heap[j].priority; | |||||
} | |||||
static void | |||||
hqueue_fix_up(hqueue_t* hqueue, int k) | |||||
{ | |||||
while(k > 1 && hqueue_less(hqueue, k/2, k)) { | |||||
hqueue_exchange(hqueue, k/2, k); | |||||
k = k/2; | |||||
} | |||||
return; | |||||
} | |||||
static void | |||||
hqueue_fix_down(hqueue_t* hqueue, int k) | |||||
{ | |||||
int j; | |||||
int n = hqueue->idx; | |||||
while(2*k <= n) { | |||||
j = 2*k; | |||||
if(j < n && hqueue_less(hqueue, j, j+1)) { | |||||
j++; | |||||
} | |||||
if(!hqueue_less(hqueue, k, j)) { | |||||
break; | |||||
} | |||||
hqueue_exchange(hqueue, k, j); | |||||
k = j; | |||||
} | |||||
return; | |||||
} | |||||
hqueue_t* | |||||
hqueue_new(uint32_t max_elems, uint32_t heap_size) | |||||
{ | |||||
hqueue_t* hqueue = NULL; | |||||
size_t total_heap_size; | |||||
if(max_elems == 0 || heap_size == 0) { | |||||
return NULL; | |||||
} | |||||
if(max_elems < heap_size) { | |||||
heap_size = max_elems; | |||||
} | |||||
hqueue = HQUEUE_ALLOC(sizeof(hqueue_t)); | |||||
if(hqueue == NULL) { | |||||
return NULL; | |||||
} | |||||
memset(hqueue, '\0', sizeof(hqueue_t)); | |||||
hqueue->version = HQ_VERSION; | |||||
hqueue->max_elems = max_elems; | |||||
hqueue->heap_size = heap_size; | |||||
hqueue->idx = 0; | |||||
total_heap_size = sizeof(hqnode_t) * (hqueue->heap_size+1); | |||||
hqueue->heap = (hqnode_t*) HQUEUE_ALLOC(total_heap_size); | |||||
if(hqueue->heap == NULL ) { | |||||
HQUEUE_FREE(hqueue); | |||||
return NULL; | |||||
} | |||||
memset(hqueue->heap, '\0', total_heap_size); | |||||
return hqueue; | |||||
} | |||||
void | |||||
hqueue_free(hqueue_t* hqueue) | |||||
{ | |||||
HQUEUE_FREE(hqueue->heap); | |||||
HQUEUE_FREE(hqueue); | |||||
return; | |||||
} | |||||
void | |||||
hqueue_free2(hqueue_t* hqueue, void (*free_node)(void* node)) | |||||
{ | |||||
uint32_t i; | |||||
for(i = 1; i < hqueue->heap_size + 1; i++) { | |||||
if(i <= hqueue->idx) { | |||||
free_node(hqueue->heap[i].value); | |||||
} else { | |||||
assert(hqueue->heap[i].value == NULL && "inactive elements must be NULL"); | |||||
} | |||||
} | |||||
hqueue_free(hqueue); | |||||
return; | |||||
} | |||||
// Extraction order is undefined for entries with duplicate priorities | |||||
int | |||||
hqueue_extract_max(hqueue_t* hqueue, double* priority, void** value) | |||||
{ | |||||
if(hqueue->idx <= 0) { | |||||
return 0; | |||||
} | |||||
hqueue_exchange(hqueue, 1, hqueue->idx); | |||||
*priority = hqueue->heap[hqueue->idx].priority; | |||||
*value = hqueue->heap[hqueue->idx].value; | |||||
hqueue->heap[hqueue->idx].value = NULL; | |||||
hqueue->idx--; // heap uses one based index, so we decrement after | |||||
hqueue_fix_down(hqueue, 1); | |||||
return 1; | |||||
} | |||||
void | |||||
hqueue_get_elem(hqueue_t* hqueue, uint32_t idx, double *priority, void** value) | |||||
{ | |||||
*priority = hqueue->heap[idx].priority; | |||||
*value = hqueue->heap[idx].value; | |||||
return; | |||||
} | |||||
static int | |||||
hqueue_maybe_resize(hqueue_t* hqueue) | |||||
{ | |||||
uint32_t min_resize; | |||||
if(hqueue->idx + 1 > hqueue->heap_size) { | |||||
if(hqueue->idx * HQ_SCALE_FACTOR > hqueue->max_elems) { | |||||
min_resize = hqueue->max_elems; | |||||
} else { | |||||
min_resize = hqueue->idx * HQ_SCALE_FACTOR; | |||||
} | |||||
return hqueue_resize_heap(hqueue, min_resize); | |||||
} | |||||
return 1; | |||||
} | |||||
int | |||||
hqueue_insert(hqueue_t* hqueue, double priority, void* value) | |||||
{ | |||||
if(hqueue->idx >= hqueue->max_elems) { | |||||
return 0; | |||||
} | |||||
if(!hqueue_maybe_resize(hqueue)) { | |||||
return 0; | |||||
} | |||||
hqueue->idx++; // heap uses one based index, so we increment first | |||||
hqueue->heap[hqueue->idx].priority = priority; | |||||
hqueue->heap[hqueue->idx].value = value; | |||||
hqueue_fix_up(hqueue, hqueue->idx); | |||||
return 1; | |||||
} | |||||
uint32_t | |||||
hqueue_size(hqueue_t* hqueue) | |||||
{ | |||||
return hqueue->idx; | |||||
} | |||||
uint32_t | |||||
hqueue_heap_size(hqueue_t* hqueue) | |||||
{ | |||||
return hqueue->heap_size; | |||||
} | |||||
uint32_t | |||||
hqueue_max_elems(hqueue_t* hqueue) | |||||
{ | |||||
return hqueue->max_elems; | |||||
} | |||||
void | |||||
hqueue_scale_by(hqueue_t* hqueue, double factor) | |||||
{ | |||||
uint32_t i; | |||||
for(i = 1; i <= hqueue->idx && i <= hqueue->heap_size; i++) { | |||||
hqueue->heap[i].priority *= factor; | |||||
} | |||||
return; | |||||
} | |||||
uint32_t | |||||
hqueue_resize_heap(hqueue_t* hqueue, uint32_t new_heap_size) | |||||
{ | |||||
uint32_t old_heap_size; | |||||
size_t total_heap_size; | |||||
hqnode_t* tmp_heap; | |||||
uint32_t i; | |||||
if(hqueue->idx > new_heap_size) { | |||||
return 0; | |||||
} | |||||
total_heap_size = sizeof(hqnode_t) * (new_heap_size+1); | |||||
old_heap_size = hqueue->heap_size; | |||||
if((tmp_heap = (hqnode_t*) HQUEUE_ALLOC(total_heap_size)) == NULL) { | |||||
return 0; | |||||
} | |||||
memset(tmp_heap, '\0', total_heap_size); | |||||
for(i = 1; i <= hqueue->idx && i <= old_heap_size; i++) { | |||||
if(i <= hqueue->idx) { | |||||
tmp_heap[i] = hqueue->heap[i]; | |||||
hqueue->heap[i].value = NULL; | |||||
} else { | |||||
assert(hqueue->heap[i].value == NULL && | |||||
"unexpected NULL element during heap resize"); | |||||
} | |||||
} | |||||
HQUEUE_FREE(hqueue->heap); | |||||
hqueue->heap = tmp_heap; | |||||
hqueue->heap_size = new_heap_size; | |||||
return old_heap_size; | |||||
} | |||||
int | |||||
hqueue_set_max_elems(hqueue_t* hqueue, uint32_t new_max_elems) | |||||
{ | |||||
uint32_t old_max_elems; | |||||
if(hqueue->heap_size > new_max_elems) { | |||||
if(!hqueue_resize_heap(hqueue, new_max_elems)) { | |||||
return 0; | |||||
} | |||||
} | |||||
old_max_elems = hqueue->max_elems; | |||||
hqueue->max_elems = new_max_elems; | |||||
return old_max_elems; | |||||
} |
@ -0,0 +1,5 @@ | |||||
c_src/hqueue.o: c_src/hqueue.c c_src/hqueue.h \ | |||||
/usr/lib/erlang/erts-10.6.2/include/erl_nif.h \ | |||||
/usr/lib/erlang/erts-10.6.2/include/erl_drv_nif.h \ | |||||
/usr/lib/erlang/erts-10.6.2/include/erl_int_sizes_config.h \ | |||||
/usr/lib/erlang/erts-10.6.2/include/erl_nif_api_funcs.h |
@ -0,0 +1,60 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
// use this file except in compliance with the License. You may obtain a copy of | |||||
// the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
// License for the specific language governing permissions and limitations under | |||||
// the License. | |||||
#pragma once | |||||
#include <stdint.h> | |||||
#define HQ_VERSION 0 | |||||
#define HQ_SCALE_FACTOR 2 // heap expansion scale factor | |||||
// Override the default memory allocator to use the Erlang versions. | |||||
// This bubbles up memory usage for the NIF into Erlang stats. | |||||
#ifdef HQ_ENIF_ALLOC | |||||
#include "erl_nif.h" | |||||
#define HQUEUE_ALLOC enif_alloc | |||||
#define HQUEUE_FREE enif_free | |||||
#else | |||||
#define HQUEUE_ALLOC malloc | |||||
#define HQUEUE_FREE free | |||||
#endif | |||||
typedef struct hqnode hqnode_t; | |||||
typedef struct hqueue hqueue_t; | |||||
hqueue_t* hqueue_new(uint32_t max_elems, uint32_t heap_size); | |||||
void hqueue_free(hqueue_t* hqueue); | |||||
void hqueue_free2(hqueue_t* hqueue, void (*free_node)(void* node)); | |||||
int hqueue_insert(hqueue_t* hqueue, double priority, void* val); | |||||
int hqueue_extract_max(hqueue_t* hqueue, double* priority, void** value); | |||||
void hqueue_get_elem(hqueue_t* hqueue, uint32_t idx, double *priority, | |||||
void** value); | |||||
uint32_t hqueue_size(hqueue_t* hqueue); | |||||
uint32_t hqueue_heap_size(hqueue_t* hqueue); | |||||
uint32_t hqueue_max_elems(hqueue_t* hqueue); | |||||
int hqueue_set_max_elems(hqueue_t* hqueue, uint32_t new_max_elems); | |||||
void hqueue_scale_by(hqueue_t* hqueue, double factor); | |||||
uint32_t hqueue_resize_heap(hqueue_t* hqueue, uint32_t new_heap_size); |
@ -0,0 +1,601 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
// use this file except in compliance with the License. You may obtain a copy of | |||||
// the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
// License for the specific language governing permissions and limitations under | |||||
// the License. | |||||
#include <assert.h> | |||||
#include <string.h> | |||||
#include <stdio.h> | |||||
#include "hqueue.h" | |||||
typedef struct | |||||
{ | |||||
ERL_NIF_TERM atom_ok; | |||||
ERL_NIF_TERM atom_error; | |||||
ERL_NIF_TERM atom_value; | |||||
ERL_NIF_TERM atom_empty; | |||||
ERL_NIF_TERM atom_full; | |||||
ERL_NIF_TERM atom_max_elems; | |||||
ERL_NIF_TERM atom_heap_size; | |||||
ERL_NIF_TERM atom_too_small; | |||||
ErlNifResourceType* res_hqueue; | |||||
} hqueue_priv; | |||||
typedef struct | |||||
{ | |||||
ErlNifEnv* env; | |||||
ERL_NIF_TERM value; | |||||
} hqnode_nif_t; | |||||
typedef struct | |||||
{ | |||||
int version; | |||||
uint64_t gen; | |||||
hqueue_t* hqueue; | |||||
ErlNifPid p; | |||||
} hqueue_nif_t; | |||||
static const uint32_t default_max_elems = UINT32_MAX-1; | |||||
static const uint32_t default_heap_size = 1024; | |||||
static inline ERL_NIF_TERM | |||||
make_atom(ErlNifEnv* env, const char* name) | |||||
{ | |||||
ERL_NIF_TERM ret; | |||||
if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) { | |||||
return ret; | |||||
} | |||||
return enif_make_atom(env, name); | |||||
} | |||||
static inline ERL_NIF_TERM | |||||
make_ok(ErlNifEnv* env, hqueue_priv* priv, ERL_NIF_TERM value) | |||||
{ | |||||
return enif_make_tuple2(env, priv->atom_ok, value); | |||||
} | |||||
static inline ERL_NIF_TERM | |||||
make_error(ErlNifEnv* env, hqueue_priv* priv, ERL_NIF_TERM reason) | |||||
{ | |||||
return enif_make_tuple2(env, priv->atom_error, reason); | |||||
} | |||||
static inline int | |||||
check_pid(ErlNifEnv* env, hqueue_nif_t* hqueue_nif) | |||||
{ | |||||
ErlNifPid pid; | |||||
enif_self(env, &pid); | |||||
if(enif_compare(pid.pid, hqueue_nif->p.pid) == 0) { | |||||
return 1; | |||||
} | |||||
return 0; | |||||
} | |||||
void | |||||
hqueue_nif_node_free(hqnode_nif_t* hqnode_nif) | |||||
{ | |||||
enif_free_env(hqnode_nif->env); | |||||
enif_free(hqnode_nif); | |||||
return; | |||||
} | |||||
void | |||||
hqueue_nif_node_free_ext(void* node) | |||||
{ | |||||
hqueue_nif_node_free((hqnode_nif_t*) node); | |||||
return; | |||||
} | |||||
hqnode_nif_t* | |||||
hqueue_nif_node_alloc() | |||||
{ | |||||
hqnode_nif_t* node = (hqnode_nif_t*) enif_alloc(sizeof(hqnode_nif_t*)); | |||||
memset(node, 0, sizeof(hqnode_nif_t)); | |||||
node->env = enif_alloc_env(); | |||||
return node; | |||||
} | |||||
static int | |||||
get_uint_param(ErlNifEnv* env, ERL_NIF_TERM value, ERL_NIF_TERM atom, uint32_t* p) | |||||
{ | |||||
const ERL_NIF_TERM* tuple; | |||||
int arity; | |||||
if(!enif_get_tuple(env, value, &arity, &tuple)) { | |||||
return 0; | |||||
} | |||||
if(arity != 2) { | |||||
return 0; | |||||
} | |||||
if(enif_compare(tuple[0], atom) != 0) { | |||||
return 0; | |||||
} | |||||
if(!enif_get_uint(env, tuple[1], p)) { | |||||
return 0; | |||||
} | |||||
return 1; | |||||
} | |||||
static inline hqueue_nif_t* | |||||
hqueue_nif_create_int(ErlNifEnv* env, hqueue_priv* priv, uint32_t max_elems, | |||||
uint32_t heap_size) | |||||
{ | |||||
hqueue_nif_t* hqueue_nif = NULL; | |||||
assert(priv != NULL && "missing private data member"); | |||||
hqueue_nif = (hqueue_nif_t*) enif_alloc_resource( | |||||
priv->res_hqueue, sizeof(hqueue_nif_t)); | |||||
memset(hqueue_nif, 0, sizeof(hqueue_nif_t)); | |||||
hqueue_nif->version = HQ_VERSION; | |||||
hqueue_nif->hqueue = hqueue_new(max_elems, heap_size); | |||||
if(hqueue_nif->hqueue == NULL ) { | |||||
enif_release_resource(hqueue_nif); | |||||
return NULL; | |||||
} | |||||
enif_self(env, &(hqueue_nif->p)); | |||||
return hqueue_nif; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
ERL_NIF_TERM opts; | |||||
ERL_NIF_TERM value; | |||||
uint32_t max_elems = default_max_elems; | |||||
uint32_t heap_size = default_heap_size; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
opts = argv[0]; | |||||
if(!enif_is_list(env, opts)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
while(enif_get_list_cell(env, opts, &value, &opts)) { | |||||
if(get_uint_param(env, value, priv->atom_max_elems, &max_elems)) { | |||||
continue; | |||||
} else if(get_uint_param(env, value, priv->atom_heap_size, &heap_size)) { | |||||
continue; | |||||
} else { | |||||
return enif_make_badarg(env); | |||||
} | |||||
} | |||||
hqueue_nif = hqueue_nif_create_int(env, priv, max_elems, heap_size); | |||||
if(hqueue_nif == NULL) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_resource(env, hqueue_nif); | |||||
enif_release_resource(hqueue_nif); | |||||
return make_ok(env, priv, ret); | |||||
} | |||||
static void | |||||
hqueue_nif_free(ErlNifEnv* env, void* obj) | |||||
{ | |||||
hqueue_nif_t* hqueue_nif = (hqueue_nif_t*) obj; | |||||
hqueue_free2(hqueue_nif->hqueue, hqueue_nif_node_free_ext); | |||||
return; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_extract_max(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
hqnode_nif_t* hqnode_nif; | |||||
double tmp_priority; | |||||
ERL_NIF_TERM ret; | |||||
ERL_NIF_TERM priority; | |||||
ERL_NIF_TERM value; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!hqueue_extract_max(hqueue_nif->hqueue, &tmp_priority, (void**) &hqnode_nif)) { | |||||
return make_error(env, priv, priv->atom_empty); | |||||
} | |||||
priority = enif_make_double(env, tmp_priority); | |||||
value = enif_make_copy(env, hqnode_nif->value); | |||||
ret = enif_make_tuple2(env, priority, value); | |||||
hqueue_nif_node_free(hqnode_nif); | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_insert(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
hqnode_nif_t* hqnode_nif; | |||||
ERL_NIF_TERM ret; | |||||
double priority; | |||||
if(argc != 3) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_double(env, argv[1], &priority)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(priority < 0.0) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
hqnode_nif = hqueue_nif_node_alloc(); | |||||
hqnode_nif->value = enif_make_copy(hqnode_nif->env, argv[2]); | |||||
if (!hqueue_insert(hqueue_nif->hqueue, priority, (void*) hqnode_nif)) { | |||||
return make_error(env, priv, priv->atom_full); | |||||
} | |||||
ret = priv->atom_ok; | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_uint64(env, hqueue_size(hqueue_nif->hqueue)); | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_heap_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_uint64(env, hqueue_heap_size(hqueue_nif->hqueue)); | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_max_elems(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_uint64(env, hqueue_max_elems(hqueue_nif->hqueue)); | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_to_list(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
hqueue_t* hqueue; | |||||
hqnode_nif_t* hqnode_nif; | |||||
double tmp_priority; | |||||
ERL_NIF_TERM ret = enif_make_list(env, 0); | |||||
ERL_NIF_TERM priority; | |||||
ERL_NIF_TERM value; | |||||
ERL_NIF_TERM tuple; | |||||
uint32_t i; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
hqueue = hqueue_nif->hqueue; | |||||
for (i = 1; i <= hqueue_size(hqueue); i++) { | |||||
hqueue_get_elem(hqueue, i, &tmp_priority, (void **) &hqnode_nif); | |||||
priority = enif_make_double(env, tmp_priority); | |||||
value = enif_make_copy(env, hqnode_nif->value); | |||||
tuple = enif_make_tuple2(env, priority, value); | |||||
ret = enif_make_list_cell(env, tuple, ret); | |||||
} | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_scale_by(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
double factor; | |||||
if(argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_double(env, argv[1], &factor)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(factor < 0.0) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
hqueue_scale_by(hqueue_nif->hqueue, factor); | |||||
ret = priv->atom_ok; | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_resize_heap(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
uint32_t new_heap_size; | |||||
uint32_t old_heap_size; | |||||
if(argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &new_heap_size)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(hqueue_size(hqueue_nif->hqueue) > new_heap_size) { | |||||
return make_error(env, priv, priv->atom_too_small); | |||||
} | |||||
if((old_heap_size = hqueue_resize_heap(hqueue_nif->hqueue, new_heap_size)) == 0) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_uint64(env, old_heap_size); | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_set_max_elems(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
uint32_t new_max_elems; | |||||
uint32_t old_max_elems; | |||||
if(argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &new_max_elems)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(hqueue_size(hqueue_nif->hqueue) > new_max_elems) { | |||||
return make_error(env, priv, priv->atom_too_small); | |||||
} | |||||
if ((old_max_elems = hqueue_set_max_elems(hqueue_nif->hqueue, new_max_elems)) == 0) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_uint64(env, old_max_elems); | |||||
return ret; | |||||
} | |||||
static int | |||||
load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) | |||||
{ | |||||
int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; | |||||
ErlNifResourceType* res; | |||||
hqueue_priv* new_priv = (hqueue_priv*) enif_alloc(sizeof(hqueue_priv)); | |||||
if(new_priv == NULL) { | |||||
return 1; | |||||
} | |||||
res = enif_open_resource_type( | |||||
env, NULL, "hqueue", hqueue_nif_free, flags, NULL); | |||||
if(res == NULL) { | |||||
enif_free(new_priv); | |||||
return 1; | |||||
} | |||||
new_priv->res_hqueue = res; | |||||
new_priv->atom_ok = make_atom(env, "ok"); | |||||
new_priv->atom_error = make_atom(env, "error"); | |||||
new_priv->atom_value = make_atom(env, "value"); | |||||
new_priv->atom_empty = make_atom(env, "empty"); | |||||
new_priv->atom_full = make_atom(env, "full"); | |||||
new_priv->atom_max_elems = make_atom(env, "max_elems"); | |||||
new_priv->atom_heap_size = make_atom(env, "heap_size"); | |||||
new_priv->atom_too_small = make_atom(env, "too_small"); | |||||
*priv = (void*) new_priv; | |||||
return 0; | |||||
} | |||||
static int | |||||
upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) | |||||
{ | |||||
return load(env, priv, info); | |||||
} | |||||
static void | |||||
unload(ErlNifEnv* env, void* priv) | |||||
{ | |||||
enif_free(priv); | |||||
return; | |||||
} | |||||
static ErlNifFunc funcs[] = { | |||||
{"new", 1, hqueue_nif_new}, | |||||
{"extract_max", 1, hqueue_nif_extract_max}, | |||||
{"insert", 3, hqueue_nif_insert}, | |||||
{"size", 1, hqueue_nif_size}, | |||||
{"heap_size", 1, hqueue_nif_heap_size}, | |||||
{"max_elems", 1, hqueue_nif_max_elems}, | |||||
{"set_max_elems", 2, hqueue_nif_set_max_elems}, | |||||
{"to_list", 1, hqueue_nif_to_list}, | |||||
{"scale_by", 2, hqueue_nif_scale_by}, | |||||
{"resize_heap", 2, hqueue_nif_resize_heap} | |||||
}; | |||||
ERL_NIF_INIT(hqueue, funcs, &load, NULL, &upgrade, &unload); |
@ -0,0 +1,5 @@ | |||||
c_src/hqueue_nif.o: c_src/hqueue_nif.c c_src/hqueue.h \ | |||||
/usr/lib/erlang/erts-10.6.2/include/erl_nif.h \ | |||||
/usr/lib/erlang/erts-10.6.2/include/erl_drv_nif.h \ | |||||
/usr/lib/erlang/erts-10.6.2/include/erl_int_sizes_config.h \ | |||||
/usr/lib/erlang/erts-10.6.2/include/erl_nif_api_funcs.h |
@ -0,0 +1,72 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
// use this file except in compliance with the License. You may obtain a copy of | |||||
// the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
// License for the specific language governing permissions and limitations under | |||||
// the License. | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <assert.h> | |||||
#include "hqueue.h" | |||||
// Simple test script to stress the public HQueue API. | |||||
// Primary use case is for running this under Valgrind. | |||||
int main(void) | |||||
{ | |||||
int str_len = 100; | |||||
int iterations = 1000; | |||||
uint32_t max_elems = 1024; | |||||
uint32_t heap_size = 64; | |||||
hqueue_t* hq = hqueue_new(max_elems, heap_size); | |||||
double priority; | |||||
double priority_res; | |||||
char* val; | |||||
char* val_res; | |||||
int i; | |||||
assert(max_elems == hqueue_max_elems(hq)); | |||||
assert(heap_size == hqueue_heap_size(hq)); | |||||
for(i = 0; i < iterations; i++) { | |||||
priority = 1234.4321 * i; | |||||
val = (char*) malloc(str_len + 1); | |||||
if(val == NULL) { | |||||
return 1; | |||||
} | |||||
assert(hqueue_size(hq) == i); | |||||
if(snprintf(val, str_len + 1, "Fun string #%d\n", i)) { | |||||
if(!hqueue_insert(hq, priority, val)) { | |||||
return 1; | |||||
} | |||||
} else { | |||||
return 1; | |||||
} | |||||
} | |||||
hqueue_scale_by(hq, 3.7); | |||||
// Added 1000 elements, so heap size should have expanded to 1024 | |||||
assert(max_elems == hqueue_max_elems(hq)); | |||||
assert(max_elems == hqueue_heap_size(hq)); | |||||
if(!hqueue_extract_max(hq, &priority_res, (void**) &val_res)) { | |||||
return 1; | |||||
} | |||||
free(val_res); | |||||
hqueue_free2(hq, free); | |||||
return 0; | |||||
} | |||||
@ -0,0 +1,318 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
// use this file except in compliance with the License. You may obtain a copy of | |||||
// the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
// License for the specific language governing permissions and limitations under | |||||
// the License. | |||||
#include <assert.h> | |||||
#include <stdint.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include "hqueue.h" | |||||
struct hqueue | |||||
{ | |||||
int version; | |||||
uint32_t idx; | |||||
uint32_t max_elems; | |||||
uint32_t heap_size; | |||||
hqnode_t* heap; // one based index | |||||
}; | |||||
struct hqnode | |||||
{ | |||||
double priority; | |||||
void* value; | |||||
}; | |||||
static inline void | |||||
hqueue_exchange(hqueue_t* hqueue, int i, int j) | |||||
{ | |||||
hqnode_t tmp; | |||||
tmp = hqueue->heap[i]; | |||||
hqueue->heap[i] = hqueue->heap[j]; | |||||
hqueue->heap[j] = tmp; | |||||
return; | |||||
} | |||||
static inline int | |||||
hqueue_less(hqueue_t* hqueue, int i, int j) | |||||
{ | |||||
return hqueue->heap[i].priority < hqueue->heap[j].priority; | |||||
} | |||||
static void | |||||
hqueue_fix_up(hqueue_t* hqueue, int k) | |||||
{ | |||||
while(k > 1 && hqueue_less(hqueue, k/2, k)) { | |||||
hqueue_exchange(hqueue, k/2, k); | |||||
k = k/2; | |||||
} | |||||
return; | |||||
} | |||||
static void | |||||
hqueue_fix_down(hqueue_t* hqueue, int k) | |||||
{ | |||||
int j; | |||||
int n = hqueue->idx; | |||||
while(2*k <= n) { | |||||
j = 2*k; | |||||
if(j < n && hqueue_less(hqueue, j, j+1)) { | |||||
j++; | |||||
} | |||||
if(!hqueue_less(hqueue, k, j)) { | |||||
break; | |||||
} | |||||
hqueue_exchange(hqueue, k, j); | |||||
k = j; | |||||
} | |||||
return; | |||||
} | |||||
hqueue_t* | |||||
hqueue_new(uint32_t max_elems, uint32_t heap_size) | |||||
{ | |||||
hqueue_t* hqueue = NULL; | |||||
size_t total_heap_size; | |||||
if(max_elems == 0 || heap_size == 0) { | |||||
return NULL; | |||||
} | |||||
if(max_elems < heap_size) { | |||||
heap_size = max_elems; | |||||
} | |||||
hqueue = HQUEUE_ALLOC(sizeof(hqueue_t)); | |||||
if(hqueue == NULL) { | |||||
return NULL; | |||||
} | |||||
memset(hqueue, '\0', sizeof(hqueue_t)); | |||||
hqueue->version = HQ_VERSION; | |||||
hqueue->max_elems = max_elems; | |||||
hqueue->heap_size = heap_size; | |||||
hqueue->idx = 0; | |||||
total_heap_size = sizeof(hqnode_t) * (hqueue->heap_size+1); | |||||
hqueue->heap = (hqnode_t*) HQUEUE_ALLOC(total_heap_size); | |||||
if(hqueue->heap == NULL ) { | |||||
HQUEUE_FREE(hqueue); | |||||
return NULL; | |||||
} | |||||
memset(hqueue->heap, '\0', total_heap_size); | |||||
return hqueue; | |||||
} | |||||
void | |||||
hqueue_free(hqueue_t* hqueue) | |||||
{ | |||||
HQUEUE_FREE(hqueue->heap); | |||||
HQUEUE_FREE(hqueue); | |||||
return; | |||||
} | |||||
void | |||||
hqueue_free2(hqueue_t* hqueue, void (*free_node)(void* node)) | |||||
{ | |||||
uint32_t i; | |||||
for(i = 1; i < hqueue->heap_size + 1; i++) { | |||||
if(i <= hqueue->idx) { | |||||
free_node(hqueue->heap[i].value); | |||||
} else { | |||||
assert(hqueue->heap[i].value == NULL && "inactive elements must be NULL"); | |||||
} | |||||
} | |||||
hqueue_free(hqueue); | |||||
return; | |||||
} | |||||
// Extraction order is undefined for entries with duplicate priorities | |||||
int | |||||
hqueue_extract_max(hqueue_t* hqueue, double* priority, void** value) | |||||
{ | |||||
if(hqueue->idx <= 0) { | |||||
return 0; | |||||
} | |||||
hqueue_exchange(hqueue, 1, hqueue->idx); | |||||
*priority = hqueue->heap[hqueue->idx].priority; | |||||
*value = hqueue->heap[hqueue->idx].value; | |||||
hqueue->heap[hqueue->idx].value = NULL; | |||||
hqueue->idx--; // heap uses one based index, so we decrement after | |||||
hqueue_fix_down(hqueue, 1); | |||||
return 1; | |||||
} | |||||
void | |||||
hqueue_get_elem(hqueue_t* hqueue, uint32_t idx, double *priority, void** value) | |||||
{ | |||||
*priority = hqueue->heap[idx].priority; | |||||
*value = hqueue->heap[idx].value; | |||||
return; | |||||
} | |||||
static int | |||||
hqueue_maybe_resize(hqueue_t* hqueue) | |||||
{ | |||||
uint32_t min_resize; | |||||
if(hqueue->idx + 1 > hqueue->heap_size) { | |||||
if(hqueue->idx * HQ_SCALE_FACTOR > hqueue->max_elems) { | |||||
min_resize = hqueue->max_elems; | |||||
} else { | |||||
min_resize = hqueue->idx * HQ_SCALE_FACTOR; | |||||
} | |||||
return hqueue_resize_heap(hqueue, min_resize); | |||||
} | |||||
return 1; | |||||
} | |||||
int | |||||
hqueue_insert(hqueue_t* hqueue, double priority, void* value) | |||||
{ | |||||
if(hqueue->idx >= hqueue->max_elems) { | |||||
return 0; | |||||
} | |||||
if(!hqueue_maybe_resize(hqueue)) { | |||||
return 0; | |||||
} | |||||
hqueue->idx++; // heap uses one based index, so we increment first | |||||
hqueue->heap[hqueue->idx].priority = priority; | |||||
hqueue->heap[hqueue->idx].value = value; | |||||
hqueue_fix_up(hqueue, hqueue->idx); | |||||
return 1; | |||||
} | |||||
uint32_t | |||||
hqueue_size(hqueue_t* hqueue) | |||||
{ | |||||
return hqueue->idx; | |||||
} | |||||
uint32_t | |||||
hqueue_heap_size(hqueue_t* hqueue) | |||||
{ | |||||
return hqueue->heap_size; | |||||
} | |||||
uint32_t | |||||
hqueue_max_elems(hqueue_t* hqueue) | |||||
{ | |||||
return hqueue->max_elems; | |||||
} | |||||
void | |||||
hqueue_scale_by(hqueue_t* hqueue, double factor) | |||||
{ | |||||
uint32_t i; | |||||
for(i = 1; i <= hqueue->idx && i <= hqueue->heap_size; i++) { | |||||
hqueue->heap[i].priority *= factor; | |||||
} | |||||
return; | |||||
} | |||||
uint32_t | |||||
hqueue_resize_heap(hqueue_t* hqueue, uint32_t new_heap_size) | |||||
{ | |||||
uint32_t old_heap_size; | |||||
size_t total_heap_size; | |||||
hqnode_t* tmp_heap; | |||||
uint32_t i; | |||||
if(hqueue->idx > new_heap_size) { | |||||
return 0; | |||||
} | |||||
total_heap_size = sizeof(hqnode_t) * (new_heap_size+1); | |||||
old_heap_size = hqueue->heap_size; | |||||
if((tmp_heap = (hqnode_t*) HQUEUE_ALLOC(total_heap_size)) == NULL) { | |||||
return 0; | |||||
} | |||||
memset(tmp_heap, '\0', total_heap_size); | |||||
for(i = 1; i <= hqueue->idx && i <= old_heap_size; i++) { | |||||
if(i <= hqueue->idx) { | |||||
tmp_heap[i] = hqueue->heap[i]; | |||||
hqueue->heap[i].value = NULL; | |||||
} else { | |||||
assert(hqueue->heap[i].value == NULL && | |||||
"unexpected NULL element during heap resize"); | |||||
} | |||||
} | |||||
HQUEUE_FREE(hqueue->heap); | |||||
hqueue->heap = tmp_heap; | |||||
hqueue->heap_size = new_heap_size; | |||||
return old_heap_size; | |||||
} | |||||
int | |||||
hqueue_set_max_elems(hqueue_t* hqueue, uint32_t new_max_elems) | |||||
{ | |||||
uint32_t old_max_elems; | |||||
if(hqueue->heap_size > new_max_elems) { | |||||
if(!hqueue_resize_heap(hqueue, new_max_elems)) { | |||||
return 0; | |||||
} | |||||
} | |||||
old_max_elems = hqueue->max_elems; | |||||
hqueue->max_elems = new_max_elems; | |||||
return old_max_elems; | |||||
} |
@ -0,0 +1,60 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
// use this file except in compliance with the License. You may obtain a copy of | |||||
// the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
// License for the specific language governing permissions and limitations under | |||||
// the License. | |||||
#pragma once | |||||
#include <stdint.h> | |||||
#define HQ_VERSION 0 | |||||
#define HQ_SCALE_FACTOR 2 // heap expansion scale factor | |||||
// Override the default memory allocator to use the Erlang versions. | |||||
// This bubbles up memory usage for the NIF into Erlang stats. | |||||
#ifdef HQ_ENIF_ALLOC | |||||
#include "erl_nif.h" | |||||
#define HQUEUE_ALLOC enif_alloc | |||||
#define HQUEUE_FREE enif_free | |||||
#else | |||||
#define HQUEUE_ALLOC malloc | |||||
#define HQUEUE_FREE free | |||||
#endif | |||||
typedef struct hqnode hqnode_t; | |||||
typedef struct hqueue hqueue_t; | |||||
hqueue_t* hqueue_new(uint32_t max_elems, uint32_t heap_size); | |||||
void hqueue_free(hqueue_t* hqueue); | |||||
void hqueue_free2(hqueue_t* hqueue, void (*free_node)(void* node)); | |||||
int hqueue_insert(hqueue_t* hqueue, double priority, void* val); | |||||
int hqueue_extract_max(hqueue_t* hqueue, double* priority, void** value); | |||||
void hqueue_get_elem(hqueue_t* hqueue, uint32_t idx, double *priority, | |||||
void** value); | |||||
uint32_t hqueue_size(hqueue_t* hqueue); | |||||
uint32_t hqueue_heap_size(hqueue_t* hqueue); | |||||
uint32_t hqueue_max_elems(hqueue_t* hqueue); | |||||
int hqueue_set_max_elems(hqueue_t* hqueue, uint32_t new_max_elems); | |||||
void hqueue_scale_by(hqueue_t* hqueue, double factor); | |||||
uint32_t hqueue_resize_heap(hqueue_t* hqueue, uint32_t new_heap_size); |
@ -0,0 +1,601 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
// use this file except in compliance with the License. You may obtain a copy of | |||||
// the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
// License for the specific language governing permissions and limitations under | |||||
// the License. | |||||
#include <assert.h> | |||||
#include <string.h> | |||||
#include <stdio.h> | |||||
#include "hqueue.h" | |||||
typedef struct | |||||
{ | |||||
ERL_NIF_TERM atom_ok; | |||||
ERL_NIF_TERM atom_error; | |||||
ERL_NIF_TERM atom_value; | |||||
ERL_NIF_TERM atom_empty; | |||||
ERL_NIF_TERM atom_full; | |||||
ERL_NIF_TERM atom_max_elems; | |||||
ERL_NIF_TERM atom_heap_size; | |||||
ERL_NIF_TERM atom_too_small; | |||||
ErlNifResourceType* res_hqueue; | |||||
} hqueue_priv; | |||||
typedef struct | |||||
{ | |||||
ErlNifEnv* env; | |||||
ERL_NIF_TERM value; | |||||
} hqnode_nif_t; | |||||
typedef struct | |||||
{ | |||||
int version; | |||||
uint64_t gen; | |||||
hqueue_t* hqueue; | |||||
ErlNifPid p; | |||||
} hqueue_nif_t; | |||||
static const uint32_t default_max_elems = UINT32_MAX-1; | |||||
static const uint32_t default_heap_size = 1024; | |||||
static inline ERL_NIF_TERM | |||||
make_atom(ErlNifEnv* env, const char* name) | |||||
{ | |||||
ERL_NIF_TERM ret; | |||||
if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) { | |||||
return ret; | |||||
} | |||||
return enif_make_atom(env, name); | |||||
} | |||||
static inline ERL_NIF_TERM | |||||
make_ok(ErlNifEnv* env, hqueue_priv* priv, ERL_NIF_TERM value) | |||||
{ | |||||
return enif_make_tuple2(env, priv->atom_ok, value); | |||||
} | |||||
static inline ERL_NIF_TERM | |||||
make_error(ErlNifEnv* env, hqueue_priv* priv, ERL_NIF_TERM reason) | |||||
{ | |||||
return enif_make_tuple2(env, priv->atom_error, reason); | |||||
} | |||||
static inline int | |||||
check_pid(ErlNifEnv* env, hqueue_nif_t* hqueue_nif) | |||||
{ | |||||
ErlNifPid pid; | |||||
enif_self(env, &pid); | |||||
if(enif_compare(pid.pid, hqueue_nif->p.pid) == 0) { | |||||
return 1; | |||||
} | |||||
return 0; | |||||
} | |||||
void | |||||
hqueue_nif_node_free(hqnode_nif_t* hqnode_nif) | |||||
{ | |||||
enif_free_env(hqnode_nif->env); | |||||
enif_free(hqnode_nif); | |||||
return; | |||||
} | |||||
void | |||||
hqueue_nif_node_free_ext(void* node) | |||||
{ | |||||
hqueue_nif_node_free((hqnode_nif_t*) node); | |||||
return; | |||||
} | |||||
hqnode_nif_t* | |||||
hqueue_nif_node_alloc() | |||||
{ | |||||
hqnode_nif_t* node = (hqnode_nif_t*) enif_alloc(sizeof(hqnode_nif_t*)); | |||||
memset(node, 0, sizeof(hqnode_nif_t)); | |||||
node->env = enif_alloc_env(); | |||||
return node; | |||||
} | |||||
static int | |||||
get_uint_param(ErlNifEnv* env, ERL_NIF_TERM value, ERL_NIF_TERM atom, uint32_t* p) | |||||
{ | |||||
const ERL_NIF_TERM* tuple; | |||||
int arity; | |||||
if(!enif_get_tuple(env, value, &arity, &tuple)) { | |||||
return 0; | |||||
} | |||||
if(arity != 2) { | |||||
return 0; | |||||
} | |||||
if(enif_compare(tuple[0], atom) != 0) { | |||||
return 0; | |||||
} | |||||
if(!enif_get_uint(env, tuple[1], p)) { | |||||
return 0; | |||||
} | |||||
return 1; | |||||
} | |||||
static inline hqueue_nif_t* | |||||
hqueue_nif_create_int(ErlNifEnv* env, hqueue_priv* priv, uint32_t max_elems, | |||||
uint32_t heap_size) | |||||
{ | |||||
hqueue_nif_t* hqueue_nif = NULL; | |||||
assert(priv != NULL && "missing private data member"); | |||||
hqueue_nif = (hqueue_nif_t*) enif_alloc_resource( | |||||
priv->res_hqueue, sizeof(hqueue_nif_t)); | |||||
memset(hqueue_nif, 0, sizeof(hqueue_nif_t)); | |||||
hqueue_nif->version = HQ_VERSION; | |||||
hqueue_nif->hqueue = hqueue_new(max_elems, heap_size); | |||||
if(hqueue_nif->hqueue == NULL ) { | |||||
enif_release_resource(hqueue_nif); | |||||
return NULL; | |||||
} | |||||
enif_self(env, &(hqueue_nif->p)); | |||||
return hqueue_nif; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
ERL_NIF_TERM opts; | |||||
ERL_NIF_TERM value; | |||||
uint32_t max_elems = default_max_elems; | |||||
uint32_t heap_size = default_heap_size; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
opts = argv[0]; | |||||
if(!enif_is_list(env, opts)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
while(enif_get_list_cell(env, opts, &value, &opts)) { | |||||
if(get_uint_param(env, value, priv->atom_max_elems, &max_elems)) { | |||||
continue; | |||||
} else if(get_uint_param(env, value, priv->atom_heap_size, &heap_size)) { | |||||
continue; | |||||
} else { | |||||
return enif_make_badarg(env); | |||||
} | |||||
} | |||||
hqueue_nif = hqueue_nif_create_int(env, priv, max_elems, heap_size); | |||||
if(hqueue_nif == NULL) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_resource(env, hqueue_nif); | |||||
enif_release_resource(hqueue_nif); | |||||
return make_ok(env, priv, ret); | |||||
} | |||||
static void | |||||
hqueue_nif_free(ErlNifEnv* env, void* obj) | |||||
{ | |||||
hqueue_nif_t* hqueue_nif = (hqueue_nif_t*) obj; | |||||
hqueue_free2(hqueue_nif->hqueue, hqueue_nif_node_free_ext); | |||||
return; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_extract_max(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
hqnode_nif_t* hqnode_nif; | |||||
double tmp_priority; | |||||
ERL_NIF_TERM ret; | |||||
ERL_NIF_TERM priority; | |||||
ERL_NIF_TERM value; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!hqueue_extract_max(hqueue_nif->hqueue, &tmp_priority, (void**) &hqnode_nif)) { | |||||
return make_error(env, priv, priv->atom_empty); | |||||
} | |||||
priority = enif_make_double(env, tmp_priority); | |||||
value = enif_make_copy(env, hqnode_nif->value); | |||||
ret = enif_make_tuple2(env, priority, value); | |||||
hqueue_nif_node_free(hqnode_nif); | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_insert(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
hqnode_nif_t* hqnode_nif; | |||||
ERL_NIF_TERM ret; | |||||
double priority; | |||||
if(argc != 3) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_double(env, argv[1], &priority)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(priority < 0.0) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
hqnode_nif = hqueue_nif_node_alloc(); | |||||
hqnode_nif->value = enif_make_copy(hqnode_nif->env, argv[2]); | |||||
if (!hqueue_insert(hqueue_nif->hqueue, priority, (void*) hqnode_nif)) { | |||||
return make_error(env, priv, priv->atom_full); | |||||
} | |||||
ret = priv->atom_ok; | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_uint64(env, hqueue_size(hqueue_nif->hqueue)); | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_heap_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_uint64(env, hqueue_heap_size(hqueue_nif->hqueue)); | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_max_elems(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_uint64(env, hqueue_max_elems(hqueue_nif->hqueue)); | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_to_list(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
hqueue_t* hqueue; | |||||
hqnode_nif_t* hqnode_nif; | |||||
double tmp_priority; | |||||
ERL_NIF_TERM ret = enif_make_list(env, 0); | |||||
ERL_NIF_TERM priority; | |||||
ERL_NIF_TERM value; | |||||
ERL_NIF_TERM tuple; | |||||
uint32_t i; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
hqueue = hqueue_nif->hqueue; | |||||
for (i = 1; i <= hqueue_size(hqueue); i++) { | |||||
hqueue_get_elem(hqueue, i, &tmp_priority, (void **) &hqnode_nif); | |||||
priority = enif_make_double(env, tmp_priority); | |||||
value = enif_make_copy(env, hqnode_nif->value); | |||||
tuple = enif_make_tuple2(env, priority, value); | |||||
ret = enif_make_list_cell(env, tuple, ret); | |||||
} | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_scale_by(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
double factor; | |||||
if(argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_double(env, argv[1], &factor)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(factor < 0.0) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
hqueue_scale_by(hqueue_nif->hqueue, factor); | |||||
ret = priv->atom_ok; | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_resize_heap(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
uint32_t new_heap_size; | |||||
uint32_t old_heap_size; | |||||
if(argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &new_heap_size)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(hqueue_size(hqueue_nif->hqueue) > new_heap_size) { | |||||
return make_error(env, priv, priv->atom_too_small); | |||||
} | |||||
if((old_heap_size = hqueue_resize_heap(hqueue_nif->hqueue, new_heap_size)) == 0) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_uint64(env, old_heap_size); | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
hqueue_nif_set_max_elems(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
hqueue_priv* priv = enif_priv_data(env); | |||||
hqueue_nif_t* hqueue_nif; | |||||
ERL_NIF_TERM ret; | |||||
uint32_t new_max_elems; | |||||
uint32_t old_max_elems; | |||||
if(argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hqueue, (void**) &hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, hqueue_nif)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &new_max_elems)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(hqueue_size(hqueue_nif->hqueue) > new_max_elems) { | |||||
return make_error(env, priv, priv->atom_too_small); | |||||
} | |||||
if ((old_max_elems = hqueue_set_max_elems(hqueue_nif->hqueue, new_max_elems)) == 0) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_uint64(env, old_max_elems); | |||||
return ret; | |||||
} | |||||
static int | |||||
load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) | |||||
{ | |||||
int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; | |||||
ErlNifResourceType* res; | |||||
hqueue_priv* new_priv = (hqueue_priv*) enif_alloc(sizeof(hqueue_priv)); | |||||
if(new_priv == NULL) { | |||||
return 1; | |||||
} | |||||
res = enif_open_resource_type( | |||||
env, NULL, "hqueue", hqueue_nif_free, flags, NULL); | |||||
if(res == NULL) { | |||||
enif_free(new_priv); | |||||
return 1; | |||||
} | |||||
new_priv->res_hqueue = res; | |||||
new_priv->atom_ok = make_atom(env, "ok"); | |||||
new_priv->atom_error = make_atom(env, "error"); | |||||
new_priv->atom_value = make_atom(env, "value"); | |||||
new_priv->atom_empty = make_atom(env, "empty"); | |||||
new_priv->atom_full = make_atom(env, "full"); | |||||
new_priv->atom_max_elems = make_atom(env, "max_elems"); | |||||
new_priv->atom_heap_size = make_atom(env, "heap_size"); | |||||
new_priv->atom_too_small = make_atom(env, "too_small"); | |||||
*priv = (void*) new_priv; | |||||
return 0; | |||||
} | |||||
static int | |||||
upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) | |||||
{ | |||||
return load(env, priv, info); | |||||
} | |||||
static void | |||||
unload(ErlNifEnv* env, void* priv) | |||||
{ | |||||
enif_free(priv); | |||||
return; | |||||
} | |||||
static ErlNifFunc funcs[] = { | |||||
{"new", 1, hqueue_nif_new}, | |||||
{"extract_max", 1, hqueue_nif_extract_max}, | |||||
{"insert", 3, hqueue_nif_insert}, | |||||
{"size", 1, hqueue_nif_size}, | |||||
{"heap_size", 1, hqueue_nif_heap_size}, | |||||
{"max_elems", 1, hqueue_nif_max_elems}, | |||||
{"set_max_elems", 2, hqueue_nif_set_max_elems}, | |||||
{"to_list", 1, hqueue_nif_to_list}, | |||||
{"scale_by", 2, hqueue_nif_scale_by}, | |||||
{"resize_heap", 2, hqueue_nif_resize_heap} | |||||
}; | |||||
ERL_NIF_INIT(hqueue, funcs, &load, NULL, &upgrade, &unload); |
@ -0,0 +1,13 @@ | |||||
{port_specs, [ | |||||
{"../../priv/hqueue.so", ["hqueue*.c"]} | |||||
]}. | |||||
{port_env, [ | |||||
{"(linux|solaris|darwin|freebsd)", "CFLAGS", "$CFLAGS -g -Wall -Werror -DHQ_ENIF_ALLOC -O3"}, | |||||
{"win32", "CFLAGS", "$CFLAGS /O2 /DNDEBUG /DHQ_ENIF_ALLOC /Dinline=__inline /Wall"} | |||||
%% {".*", "CFLAGS", "$CFLAGS -g -Wall -Werror -Wextra"} | |||||
]}. | |||||
@ -0,0 +1,72 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
// use this file except in compliance with the License. You may obtain a copy of | |||||
// the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
// License for the specific language governing permissions and limitations under | |||||
// the License. | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <assert.h> | |||||
#include "hqueue.h" | |||||
// Simple test script to stress the public HQueue API. | |||||
// Primary use case is for running this under Valgrind. | |||||
int main(void) | |||||
{ | |||||
int str_len = 100; | |||||
int iterations = 1000; | |||||
uint32_t max_elems = 1024; | |||||
uint32_t heap_size = 64; | |||||
hqueue_t* hq = hqueue_new(max_elems, heap_size); | |||||
double priority; | |||||
double priority_res; | |||||
char* val; | |||||
char* val_res; | |||||
int i; | |||||
assert(max_elems == hqueue_max_elems(hq)); | |||||
assert(heap_size == hqueue_heap_size(hq)); | |||||
for(i = 0; i < iterations; i++) { | |||||
priority = 1234.4321 * i; | |||||
val = (char*) malloc(str_len + 1); | |||||
if(val == NULL) { | |||||
return 1; | |||||
} | |||||
assert(hqueue_size(hq) == i); | |||||
if(snprintf(val, str_len + 1, "Fun string #%d\n", i)) { | |||||
if(!hqueue_insert(hq, priority, val)) { | |||||
return 1; | |||||
} | |||||
} else { | |||||
return 1; | |||||
} | |||||
} | |||||
hqueue_scale_by(hq, 3.7); | |||||
// Added 1000 elements, so heap size should have expanded to 1024 | |||||
assert(max_elems == hqueue_max_elems(hq)); | |||||
assert(max_elems == hqueue_heap_size(hq)); | |||||
if(!hqueue_extract_max(hq, &priority_res, (void**) &val_res)) { | |||||
return 1; | |||||
} | |||||
free(val_res); | |||||
hqueue_free2(hq, free); | |||||
return 0; | |||||
} | |||||
@ -1,564 +0,0 @@ | |||||
#include <stdio.h> | |||||
#include <unistd.h> | |||||
#include "erl_nif.h" | |||||
#include "cq_nif.h" | |||||
/* #ifndef ERL_NIF_DIRTY_SCHEDULER_SUPPORT | |||||
# error Requires dirty schedulers | |||||
#endif */ | |||||
ERL_NIF_TERM | |||||
mk_atom(ErlNifEnv* env, const char* atom) | |||||
{ | |||||
ERL_NIF_TERM ret; | |||||
if(!enif_make_existing_atom(env, atom, &ret, ERL_NIF_LATIN1)) | |||||
return enif_make_atom(env, atom); | |||||
return ret; | |||||
} | |||||
ERL_NIF_TERM | |||||
mk_error(ErlNifEnv* env, const char* mesg) | |||||
{ | |||||
return enif_make_tuple2(env, mk_atom(env, "error"), mk_atom(env, mesg)); | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
cq_t *q = enif_alloc_resource(CQ_RESOURCE, sizeof(cq_t)); | |||||
if (q == NULL) | |||||
return mk_error(env, "priv_alloc_error"); | |||||
ERL_NIF_TERM ret = enif_make_resource(env, q); | |||||
/* enif_release_resource(ret); */ | |||||
uint32_t queue_id = 0; | |||||
uint32_t queue_size = 0; | |||||
uint32_t overflow_size = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id) || | |||||
!enif_get_uint(env, argv[1], &queue_size) || | |||||
!enif_get_uint(env, argv[2], &overflow_size)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "bad_queue_id"); | |||||
/* TODO: Check that queue_size is power of 2 */ | |||||
if (QUEUES[queue_id] != NULL) | |||||
return mk_error(env, "queue_id_already_exists"); | |||||
q->id = queue_id; | |||||
q->queue_size = queue_size; | |||||
q->overflow_size = overflow_size; | |||||
q->tail = 0; | |||||
q->head = 0; | |||||
q->slots_states = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->slots_terms = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->slots_envs = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->overflow_terms = calloc(q->overflow_size, CACHE_LINE_SIZE); | |||||
q->overflow_envs = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->push_queue = new_queue(); | |||||
q->pop_queue = new_queue(); | |||||
/* TODO: Check calloc return */ | |||||
for (int i = 0; i < q->queue_size; i++) { | |||||
ErlNifEnv *slot_env = enif_alloc_env(); | |||||
q->slots_envs[i*CACHE_LINE_SIZE] = slot_env; | |||||
//q->overflow_envs[i*CACHE_LINE_SIZE] = (ErlNifEnv *) enif_alloc_env(); | |||||
} | |||||
QUEUES[q->id] = q; | |||||
return enif_make_tuple2(env, mk_atom(env, "ok"), ret); | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_free(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
/* TODO: Free all the things! */ | |||||
QUEUES[queue_id] = NULL; | |||||
return enif_make_atom(env, "ok"); | |||||
} | |||||
/* Push to the head of the queue. */ | |||||
static ERL_NIF_TERM | |||||
queue_push(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
/* Load the queue */ | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
if (q->id != queue_id) | |||||
return mk_error(env, "not_identical_queue_id"); | |||||
for (int i = 0; i < q->queue_size; i++) { | |||||
fprintf(stderr, "queue slot %d, index %d, state %d\n", | |||||
i, i*CACHE_LINE_SIZE, q->slots_states[i*CACHE_LINE_SIZE]); | |||||
} | |||||
/* If there's consumers waiting, the queue must be empty and we | |||||
should directly pick a consumer to notify. */ | |||||
ErlNifPid *waiting_consumer; | |||||
int dequeue_ret = dequeue(q->pop_queue, &waiting_consumer); | |||||
if (dequeue_ret) { | |||||
ErlNifEnv *msg_env = enif_alloc_env(); | |||||
ERL_NIF_TERM copy = enif_make_copy(msg_env, argv[1]); | |||||
ERL_NIF_TERM tuple = enif_make_tuple2(msg_env, mk_atom(env, "pop"), copy); | |||||
if (enif_send(env, waiting_consumer, msg_env, tuple)) { | |||||
enif_free_env(msg_env); | |||||
return mk_atom(env, "ok"); | |||||
} else { | |||||
return mk_error(env, "notify_failed"); | |||||
} | |||||
} | |||||
/* Increment head and attempt to claim the slot by marking it as | |||||
busy. This ensures no other thread will attempt to modify this | |||||
slot. If we cannot lock it, another thread must have */ | |||||
uint64_t head = __sync_add_and_fetch(&q->head, 1); | |||||
size_t size = q->queue_size; | |||||
while (1) { | |||||
uint64_t index = SLOT_INDEX(head, size); | |||||
uint64_t ret = __sync_val_compare_and_swap(&q->slots_states[index], | |||||
STATE_EMPTY, | |||||
STATE_WRITE); | |||||
switch (ret) { | |||||
case STATE_EMPTY: | |||||
head = __sync_add_and_fetch(&q->head, 1); | |||||
case STATE_WRITE: | |||||
/* We acquired the write lock, go ahead with the write. */ | |||||
break; | |||||
case STATE_FULL: | |||||
/* We have caught up with the tail and the buffer is | |||||
full. Block the producer until a consumer reads the | |||||
item. */ | |||||
return mk_error(env, "full_not_implemented"); | |||||
} | |||||
} | |||||
/* If head catches up with tail, the queue is full. Add to | |||||
overflow instead */ | |||||
/* Copy term to slot-specific temporary process env. */ | |||||
ERL_NIF_TERM copy = enif_make_copy(q->slots_envs[SLOT_INDEX(head, size)], argv[1]); | |||||
q->slots_terms[SLOT_INDEX(head, size)] = copy; | |||||
__sync_synchronize(); /* Or compiler memory barrier? */ | |||||
/* TODO: Do we need to collect garbage? */ | |||||
/* Mark the slot ready to be consumed */ | |||||
if (__sync_bool_compare_and_swap(&q->slots_states[SLOT_INDEX(head, size)], | |||||
STATE_WRITE, | |||||
STATE_FULL)) { | |||||
return mk_atom(env, "ok"); | |||||
} else { | |||||
return mk_error(env, "could_not_update_slots_after_insert"); | |||||
} | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_async_pop(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
/* Load queue */ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
if (q->id != queue_id) | |||||
return mk_error(env, "not_identical_queue_id"); | |||||
uint64_t qsize = q->queue_size; | |||||
uint64_t tail = q->tail; | |||||
uint64_t num_busy = 0; | |||||
/* Walk the buffer starting the tail position until we are either | |||||
able to consume a term or find an empty slot. */ | |||||
while (1) { | |||||
uint64_t index = SLOT_INDEX(tail, qsize); | |||||
uint64_t ret = __sync_val_compare_and_swap(&q->slots_states[index], | |||||
STATE_FULL, | |||||
STATE_READ); | |||||
if (ret == STATE_READ) { | |||||
/* We were able to mark the term as read in progress. We | |||||
now have an exclusive lock. */ | |||||
break; | |||||
} else if (ret == STATE_WRITE) { | |||||
/* We found an item with a write in progress. If that | |||||
thread progresses, it will eventually mark the slot as | |||||
full. We can spin until that happens. | |||||
This can take an arbitrary amount of time and multiple | |||||
reading threads will compete for the same slot. | |||||
Instead we add the caller to the queue of blocking | |||||
consumers. When the next producer comes it will "help" | |||||
this thread by calling enif_send on the current | |||||
in-progress term *and* handle it's own terms. If | |||||
there's no new push to the queue, this will block | |||||
forever. */ | |||||
return mk_atom(env, "write_in_progress_not_implemented"); | |||||
} else if (ret == STATE_EMPTY) { | |||||
/* We found an empty item. Queue must be empty. Add | |||||
calling Erlang consumer process to queue of waiting | |||||
processes. When the next producer comes along, it first | |||||
checks the waiting consumers and calls enif_send | |||||
instead of writing to the slots. */ | |||||
ErlNifPid *pid = enif_alloc(sizeof(ErlNifPid)); | |||||
pid = enif_self(env, pid); | |||||
enqueue(q->pop_queue, pid); | |||||
return mk_atom(env, "wait_for_msg"); | |||||
} else { | |||||
tail = __sync_add_and_fetch(&q->tail, 1); | |||||
} | |||||
} | |||||
/* Copy term into calling process env. The NIF env can now be | |||||
gargbage collected. */ | |||||
ERL_NIF_TERM copy = enif_make_copy(env, q->slots_terms[SLOT_INDEX(tail, qsize)]); | |||||
/* Mark the slot as free. Note: We don't increment the tail | |||||
position here, as another thread also walking the buffer might | |||||
have incremented it multiple times */ | |||||
q->slots_terms[SLOT_INDEX(tail, qsize)] = 0; | |||||
if (__sync_bool_compare_and_swap(&q->slots_states[SLOT_INDEX(tail, qsize)], | |||||
STATE_READ, | |||||
STATE_EMPTY)) { | |||||
return enif_make_tuple2(env, mk_atom(env, "ok"), copy); | |||||
} else { | |||||
return mk_error(env, "could_not_update_slots_after_pop"); | |||||
} | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_debug(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
ERL_NIF_TERM *slots_states = enif_alloc(sizeof(ERL_NIF_TERM) * q->queue_size); | |||||
ERL_NIF_TERM *slots_terms = enif_alloc(sizeof(ERL_NIF_TERM) * q->queue_size); | |||||
for (int i = 0; i < q->queue_size; i++) { | |||||
slots_states[i] = enif_make_int(env, q->slots_states[i * CACHE_LINE_SIZE]); | |||||
if (q->slots_terms[i * CACHE_LINE_SIZE] == 0) { | |||||
slots_terms[i] = mk_atom(env, "null"); | |||||
} else { | |||||
slots_terms[i] = enif_make_copy(env, q->slots_terms[i * CACHE_LINE_SIZE]); | |||||
} | |||||
} | |||||
return enif_make_tuple4(env, | |||||
enif_make_uint64(env, q->tail), | |||||
enif_make_uint64(env, q->head), | |||||
enif_make_list_from_array(env, slots_states, q->queue_size), | |||||
enif_make_list_from_array(env, slots_terms, q->queue_size)); | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_debug_poppers(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
uint64_t pop_queue_size = 0; | |||||
cq_node_t *node = q->pop_queue->head; | |||||
if (node->value == NULL) { | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
while (node != NULL) { | |||||
pop_queue_size++; | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
ERL_NIF_TERM *pop_queue_pids = enif_alloc(sizeof(ERL_NIF_TERM) * pop_queue_size); | |||||
node = q->pop_queue->head; | |||||
node = Q_PTR(node); | |||||
if (node->value == NULL) { | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
uint64_t i = 0; | |||||
while (node != NULL) { | |||||
if (node->value == 0) { | |||||
pop_queue_pids[i] = mk_atom(env, "null"); | |||||
} | |||||
else { | |||||
pop_queue_pids[i] = enif_make_pid(env, node->value); | |||||
} | |||||
i++; | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
ERL_NIF_TERM list = enif_make_list_from_array(env, pop_queue_pids, pop_queue_size); | |||||
enif_free(pop_queue_pids); | |||||
return list; | |||||
} | |||||
static ERL_NIF_TERM | |||||
print_bits(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint64_t *p1 = malloc(8); | |||||
*p1 = 0; | |||||
for (int bit = 63; bit >= 0; bit--) { | |||||
uint64_t power = 1 << bit; | |||||
//uint64_t byte = *p1; | |||||
uint64_t byte = p1; | |||||
fprintf(stderr, "%d", (byte & power) >> bit); | |||||
} | |||||
fprintf(stderr, "\n"); | |||||
//enif_free(p1); | |||||
return mk_atom(env, "ok"); | |||||
} | |||||
void free_resource(ErlNifEnv* env, void* arg) | |||||
{ | |||||
//cq_t *cq = (cq_t *) arg; | |||||
fprintf(stderr, "free_resource\n"); | |||||
} | |||||
cq_queue_t * new_queue() | |||||
{ | |||||
cq_queue_t *queue = enif_alloc(sizeof(cq_queue_t)); | |||||
cq_node_t *node = enif_alloc(sizeof(cq_node_t)); | |||||
node->next = NULL; | |||||
//node->env = NULL; | |||||
node->value = NULL; | |||||
queue->head = node; | |||||
queue->tail = node; | |||||
return queue; | |||||
} | |||||
void enqueue(cq_queue_t *queue, ErlNifPid *pid) | |||||
{ | |||||
cq_node_t *node = enif_alloc(sizeof(cq_node_t)); | |||||
//node->env = enif_alloc_env(); | |||||
//node->term = enif_make_copy(node->env, term); | |||||
node->value = pid; | |||||
node->next = NULL; | |||||
fprintf(stderr, "node %lu\n", node); | |||||
cq_node_t *tail = NULL; | |||||
uint64_t tail_count = 0; | |||||
while (1) { | |||||
tail = queue->tail; | |||||
cq_node_t *tail_ptr = Q_PTR(tail); | |||||
tail_count = Q_COUNT(tail); | |||||
cq_node_t *next = tail->next; | |||||
cq_node_t *next_ptr = Q_PTR(next); | |||||
uint64_t next_count = Q_COUNT(next); | |||||
if (tail == queue->tail) { | |||||
fprintf(stderr, "tail == queue->tail\n"); | |||||
if (next_ptr == NULL) { | |||||
fprintf(stderr, "next_ptr == NULL\n"); | |||||
if (__sync_bool_compare_and_swap(&tail_ptr->next, | |||||
next, | |||||
Q_SET_COUNT(node, next_count+1))) | |||||
fprintf(stderr, "CAS(tail_ptr->next, next, (node, next_count+1)) -> true\n"); | |||||
break; | |||||
} else { | |||||
__sync_bool_compare_and_swap(&queue->tail, | |||||
tail, | |||||
Q_SET_COUNT(next_ptr, next_count+1)); | |||||
fprintf(stderr, "CAS(queue->tail, tail, (next_ptr, next_count+1))\n"); | |||||
} | |||||
} | |||||
} | |||||
cq_node_t *node_with_count = Q_SET_COUNT(node, tail_count+1); | |||||
int ret = __sync_bool_compare_and_swap(&queue->tail, | |||||
tail, | |||||
node_with_count); | |||||
fprintf(stderr, "CAS(queue->tail, tail, %lu) -> %d\n", node_with_count, ret); | |||||
} | |||||
int dequeue(cq_queue_t *queue, ErlNifPid **pid) | |||||
{ | |||||
fprintf(stderr, "dequeue\n"); | |||||
cq_node_t *head, *head_ptr, *tail, *tail_ptr, *next, *next_ptr; | |||||
while (1) { | |||||
head = queue->head; | |||||
head_ptr = Q_PTR(head); | |||||
tail = queue->tail; | |||||
tail_ptr = Q_PTR(tail); | |||||
next = head->next; | |||||
next_ptr = Q_PTR(next); | |||||
fprintf(stderr, "head %lu, tail %lu, next %lu\n", head, tail, next); | |||||
if (head == queue->head) { | |||||
if (head_ptr == tail_ptr) { | |||||
if (next_ptr == NULL) { | |||||
return 0; /* Queue is empty */ | |||||
} | |||||
fprintf(stderr, "CAS(queue->tail, tail, (next_ptr, tail+1))\n"); | |||||
__sync_bool_compare_and_swap(&queue->tail, | |||||
tail, | |||||
Q_SET_COUNT(next_ptr, Q_COUNT(tail)+1)); | |||||
} else { | |||||
fprintf(stderr, "next->value %lu\n", next_ptr->value); | |||||
*pid = next_ptr->value; | |||||
fprintf(stderr, "CAS(queue->head, head, (next_ptr, head+1))\n"); | |||||
if (__sync_bool_compare_and_swap(&queue->head, | |||||
head, | |||||
Q_SET_COUNT(next_ptr, Q_COUNT(head)+1))) | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
// free pid | |||||
//enif_free(Q_PTR(head)); | |||||
return 1; | |||||
} | |||||
int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { | |||||
/* Initialize global array mapping id to cq_t ptr */ | |||||
QUEUES = (cq_t **) calloc(8, sizeof(cq_t **)); | |||||
if (QUEUES == NULL) | |||||
return -1; | |||||
ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER); | |||||
CQ_RESOURCE = enif_open_resource_type(env, "cq", "cq", | |||||
&free_resource, flags, NULL); | |||||
if (CQ_RESOURCE == NULL) | |||||
return -1; | |||||
return 0; | |||||
} | |||||
static ErlNifFunc nif_funcs[] = { | |||||
{"new" , 3, queue_new}, | |||||
{"free" , 1, queue_free}, | |||||
{"push" , 2, queue_push}, | |||||
{"async_pop", 1, queue_async_pop}, | |||||
{"debug" , 1, queue_debug}, | |||||
{"debug_poppers", 1, queue_debug_poppers}, | |||||
{"print_bits", 0, print_bits} | |||||
}; | |||||
ERL_NIF_INIT(cq, nif_funcs, load, NULL, NULL, NULL); |
@ -1,71 +0,0 @@ | |||||
#include <stdint.h> | |||||
#include "erl_nif.h" | |||||
#define CACHE_LINE_SIZE 64 | |||||
#define SLOT_INDEX(__index, __size) __index & (__size - 1) | |||||
#define Q_MASK 3L | |||||
#define Q_PTR(__ptr) (cq_node_t *) (((uint64_t)__ptr) & (~Q_MASK)) | |||||
#define Q_COUNT(__ptr) ((uint64_t) __ptr & Q_MASK) | |||||
#define Q_SET_COUNT(__ptr, __val) (cq_node_t *) ((uint64_t) __ptr | (__val & Q_MASK)) | |||||
#define STATE_EMPTY 0 | |||||
#define STATE_WRITE 1 | |||||
#define STATE_READ 2 | |||||
#define STATE_FULL 3 | |||||
ErlNifResourceType* CQ_RESOURCE; | |||||
typedef struct cq_node cq_node_t; | |||||
struct cq_node { | |||||
ErlNifEnv *env; | |||||
//ERL_NIF_TERM term; | |||||
ErlNifPid *value; | |||||
cq_node_t *next; | |||||
}; | |||||
typedef struct cq_queue { | |||||
cq_node_t *head; | |||||
cq_node_t *tail; | |||||
} cq_queue_t; | |||||
// TODO: Add padding between the fields | |||||
typedef struct cq { | |||||
uint32_t id; | |||||
uint64_t queue_size; | |||||
uint64_t overflow_size; | |||||
uint64_t head; | |||||
uint64_t tail; | |||||
uint8_t *slots_states; | |||||
ERL_NIF_TERM *slots_terms; | |||||
ErlNifEnv **slots_envs; | |||||
cq_queue_t *push_queue; | |||||
cq_queue_t *pop_queue; | |||||
uint8_t *overflow_states; | |||||
ERL_NIF_TERM *overflow_terms; | |||||
ErlNifEnv **overflow_envs; | |||||
} cq_t; | |||||
cq_t **QUEUES = NULL; /* Initialized on nif load */ | |||||
ERL_NIF_TERM mk_atom(ErlNifEnv* env, const char* atom); | |||||
ERL_NIF_TERM mk_error(ErlNifEnv* env, const char* msg); | |||||
int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); | |||||
void free_resource(ErlNifEnv*, void*); | |||||
cq_queue_t* new_queue(void); | |||||
void enqueue(cq_queue_t *q, ErlNifPid *pid); |
@ -1,564 +0,0 @@ | |||||
#include <stdio.h> | |||||
#include <unistd.h> | |||||
#include "erl_nif.h" | |||||
#include "cq_nif.h" | |||||
/* #ifndef ERL_NIF_DIRTY_SCHEDULER_SUPPORT | |||||
# error Requires dirty schedulers | |||||
#endif */ | |||||
ERL_NIF_TERM | |||||
mk_atom(ErlNifEnv* env, const char* atom) | |||||
{ | |||||
ERL_NIF_TERM ret; | |||||
if(!enif_make_existing_atom(env, atom, &ret, ERL_NIF_LATIN1)) | |||||
return enif_make_atom(env, atom); | |||||
return ret; | |||||
} | |||||
ERL_NIF_TERM | |||||
mk_error(ErlNifEnv* env, const char* mesg) | |||||
{ | |||||
return enif_make_tuple2(env, mk_atom(env, "error"), mk_atom(env, mesg)); | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
cq_t *q = enif_alloc_resource(CQ_RESOURCE, sizeof(cq_t)); | |||||
if (q == NULL) | |||||
return mk_error(env, "priv_alloc_error"); | |||||
ERL_NIF_TERM ret = enif_make_resource(env, q); | |||||
/* enif_release_resource(ret); */ | |||||
uint32_t queue_id = 0; | |||||
uint32_t queue_size = 0; | |||||
uint32_t overflow_size = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id) || | |||||
!enif_get_uint(env, argv[1], &queue_size) || | |||||
!enif_get_uint(env, argv[2], &overflow_size)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "bad_queue_id"); | |||||
/* TODO: Check that queue_size is power of 2 */ | |||||
if (QUEUES[queue_id] != NULL) | |||||
return mk_error(env, "queue_id_already_exists"); | |||||
q->id = queue_id; | |||||
q->queue_size = queue_size; | |||||
q->overflow_size = overflow_size; | |||||
q->tail = 0; | |||||
q->head = 0; | |||||
q->slots_states = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->slots_terms = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->slots_envs = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->overflow_terms = calloc(q->overflow_size, CACHE_LINE_SIZE); | |||||
q->overflow_envs = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->push_queue = new_queue(); | |||||
q->pop_queue = new_queue(); | |||||
/* TODO: Check calloc return */ | |||||
for (int i = 0; i < q->queue_size; i++) { | |||||
ErlNifEnv *slot_env = enif_alloc_env(); | |||||
q->slots_envs[i*CACHE_LINE_SIZE] = slot_env; | |||||
//q->overflow_envs[i*CACHE_LINE_SIZE] = (ErlNifEnv *) enif_alloc_env(); | |||||
} | |||||
QUEUES[q->id] = q; | |||||
return enif_make_tuple2(env, mk_atom(env, "ok"), ret); | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_free(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
/* TODO: Free all the things! */ | |||||
QUEUES[queue_id] = NULL; | |||||
return enif_make_atom(env, "ok"); | |||||
} | |||||
/* Push to the head of the queue. */ | |||||
static ERL_NIF_TERM | |||||
queue_push(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
/* Load the queue */ | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
if (q->id != queue_id) | |||||
return mk_error(env, "not_identical_queue_id"); | |||||
for (int i = 0; i < q->queue_size; i++) { | |||||
fprintf(stderr, "queue slot %d, index %d, state %d\n", | |||||
i, i*CACHE_LINE_SIZE, q->slots_states[i*CACHE_LINE_SIZE]); | |||||
} | |||||
/* If there's consumers waiting, the queue must be empty and we | |||||
should directly pick a consumer to notify. */ | |||||
ErlNifPid *waiting_consumer; | |||||
int dequeue_ret = dequeue(q->pop_queue, &waiting_consumer); | |||||
if (dequeue_ret) { | |||||
ErlNifEnv *msg_env = enif_alloc_env(); | |||||
ERL_NIF_TERM copy = enif_make_copy(msg_env, argv[1]); | |||||
ERL_NIF_TERM tuple = enif_make_tuple2(msg_env, mk_atom(env, "pop"), copy); | |||||
if (enif_send(env, waiting_consumer, msg_env, tuple)) { | |||||
enif_free_env(msg_env); | |||||
return mk_atom(env, "ok"); | |||||
} else { | |||||
return mk_error(env, "notify_failed"); | |||||
} | |||||
} | |||||
/* Increment head and attempt to claim the slot by marking it as | |||||
busy. This ensures no other thread will attempt to modify this | |||||
slot. If we cannot lock it, another thread must have */ | |||||
uint64_t head = __sync_add_and_fetch(&q->head, 1); | |||||
size_t size = q->queue_size; | |||||
while (1) { | |||||
uint64_t index = SLOT_INDEX(head, size); | |||||
uint64_t ret = __sync_val_compare_and_swap(&q->slots_states[index], | |||||
STATE_EMPTY, | |||||
STATE_WRITE); | |||||
switch (ret) { | |||||
case STATE_EMPTY: | |||||
head = __sync_add_and_fetch(&q->head, 1); | |||||
case STATE_WRITE: | |||||
/* We acquired the write lock, go ahead with the write. */ | |||||
break; | |||||
case STATE_FULL: | |||||
/* We have caught up with the tail and the buffer is | |||||
full. Block the producer until a consumer reads the | |||||
item. */ | |||||
return mk_error(env, "full_not_implemented"); | |||||
} | |||||
} | |||||
/* If head catches up with tail, the queue is full. Add to | |||||
overflow instead */ | |||||
/* Copy term to slot-specific temporary process env. */ | |||||
ERL_NIF_TERM copy = enif_make_copy(q->slots_envs[SLOT_INDEX(head, size)], argv[1]); | |||||
q->slots_terms[SLOT_INDEX(head, size)] = copy; | |||||
__sync_synchronize(); /* Or compiler memory barrier? */ | |||||
/* TODO: Do we need to collect garbage? */ | |||||
/* Mark the slot ready to be consumed */ | |||||
if (__sync_bool_compare_and_swap(&q->slots_states[SLOT_INDEX(head, size)], | |||||
STATE_WRITE, | |||||
STATE_FULL)) { | |||||
return mk_atom(env, "ok"); | |||||
} else { | |||||
return mk_error(env, "could_not_update_slots_after_insert"); | |||||
} | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_async_pop(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
/* Load queue */ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
if (q->id != queue_id) | |||||
return mk_error(env, "not_identical_queue_id"); | |||||
uint64_t qsize = q->queue_size; | |||||
uint64_t tail = q->tail; | |||||
uint64_t num_busy = 0; | |||||
/* Walk the buffer starting the tail position until we are either | |||||
able to consume a term or find an empty slot. */ | |||||
while (1) { | |||||
uint64_t index = SLOT_INDEX(tail, qsize); | |||||
uint64_t ret = __sync_val_compare_and_swap(&q->slots_states[index], | |||||
STATE_FULL, | |||||
STATE_READ); | |||||
if (ret == STATE_READ) { | |||||
/* We were able to mark the term as read in progress. We | |||||
now have an exclusive lock. */ | |||||
break; | |||||
} else if (ret == STATE_WRITE) { | |||||
/* We found an item with a write in progress. If that | |||||
thread progresses, it will eventually mark the slot as | |||||
full. We can spin until that happens. | |||||
This can take an arbitrary amount of time and multiple | |||||
reading threads will compete for the same slot. | |||||
Instead we add the caller to the queue of blocking | |||||
consumers. When the next producer comes it will "help" | |||||
this thread by calling enif_send on the current | |||||
in-progress term *and* handle it's own terms. If | |||||
there's no new push to the queue, this will block | |||||
forever. */ | |||||
return mk_atom(env, "write_in_progress_not_implemented"); | |||||
} else if (ret == STATE_EMPTY) { | |||||
/* We found an empty item. Queue must be empty. Add | |||||
calling Erlang consumer process to queue of waiting | |||||
processes. When the next producer comes along, it first | |||||
checks the waiting consumers and calls enif_send | |||||
instead of writing to the slots. */ | |||||
ErlNifPid *pid = enif_alloc(sizeof(ErlNifPid)); | |||||
pid = enif_self(env, pid); | |||||
enqueue(q->pop_queue, pid); | |||||
return mk_atom(env, "wait_for_msg"); | |||||
} else { | |||||
tail = __sync_add_and_fetch(&q->tail, 1); | |||||
} | |||||
} | |||||
/* Copy term into calling process env. The NIF env can now be | |||||
gargbage collected. */ | |||||
ERL_NIF_TERM copy = enif_make_copy(env, q->slots_terms[SLOT_INDEX(tail, qsize)]); | |||||
/* Mark the slot as free. Note: We don't increment the tail | |||||
position here, as another thread also walking the buffer might | |||||
have incremented it multiple times */ | |||||
q->slots_terms[SLOT_INDEX(tail, qsize)] = 0; | |||||
if (__sync_bool_compare_and_swap(&q->slots_states[SLOT_INDEX(tail, qsize)], | |||||
STATE_READ, | |||||
STATE_EMPTY)) { | |||||
return enif_make_tuple2(env, mk_atom(env, "ok"), copy); | |||||
} else { | |||||
return mk_error(env, "could_not_update_slots_after_pop"); | |||||
} | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_debug(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
ERL_NIF_TERM *slots_states = enif_alloc(sizeof(ERL_NIF_TERM) * q->queue_size); | |||||
ERL_NIF_TERM *slots_terms = enif_alloc(sizeof(ERL_NIF_TERM) * q->queue_size); | |||||
for (int i = 0; i < q->queue_size; i++) { | |||||
slots_states[i] = enif_make_int(env, q->slots_states[i * CACHE_LINE_SIZE]); | |||||
if (q->slots_terms[i * CACHE_LINE_SIZE] == 0) { | |||||
slots_terms[i] = mk_atom(env, "null"); | |||||
} else { | |||||
slots_terms[i] = enif_make_copy(env, q->slots_terms[i * CACHE_LINE_SIZE]); | |||||
} | |||||
} | |||||
return enif_make_tuple4(env, | |||||
enif_make_uint64(env, q->tail), | |||||
enif_make_uint64(env, q->head), | |||||
enif_make_list_from_array(env, slots_states, q->queue_size), | |||||
enif_make_list_from_array(env, slots_terms, q->queue_size)); | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_debug_poppers(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
uint64_t pop_queue_size = 0; | |||||
cq_node_t *node = q->pop_queue->head; | |||||
if (node->value == NULL) { | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
while (node != NULL) { | |||||
pop_queue_size++; | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
ERL_NIF_TERM *pop_queue_pids = enif_alloc(sizeof(ERL_NIF_TERM) * pop_queue_size); | |||||
node = q->pop_queue->head; | |||||
node = Q_PTR(node); | |||||
if (node->value == NULL) { | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
uint64_t i = 0; | |||||
while (node != NULL) { | |||||
if (node->value == 0) { | |||||
pop_queue_pids[i] = mk_atom(env, "null"); | |||||
} | |||||
else { | |||||
pop_queue_pids[i] = enif_make_pid(env, node->value); | |||||
} | |||||
i++; | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
ERL_NIF_TERM list = enif_make_list_from_array(env, pop_queue_pids, pop_queue_size); | |||||
enif_free(pop_queue_pids); | |||||
return list; | |||||
} | |||||
static ERL_NIF_TERM | |||||
print_bits(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint64_t *p1 = malloc(8); | |||||
*p1 = 0; | |||||
for (int bit = 63; bit >= 0; bit--) { | |||||
uint64_t power = 1 << bit; | |||||
//uint64_t byte = *p1; | |||||
uint64_t byte = p1; | |||||
fprintf(stderr, "%d", (byte & power) >> bit); | |||||
} | |||||
fprintf(stderr, "\n"); | |||||
//enif_free(p1); | |||||
return mk_atom(env, "ok"); | |||||
} | |||||
void free_resource(ErlNifEnv* env, void* arg) | |||||
{ | |||||
//cq_t *cq = (cq_t *) arg; | |||||
fprintf(stderr, "free_resource\n"); | |||||
} | |||||
cq_queue_t * new_queue() | |||||
{ | |||||
cq_queue_t *queue = enif_alloc(sizeof(cq_queue_t)); | |||||
cq_node_t *node = enif_alloc(sizeof(cq_node_t)); | |||||
node->next = NULL; | |||||
//node->env = NULL; | |||||
node->value = NULL; | |||||
queue->head = node; | |||||
queue->tail = node; | |||||
return queue; | |||||
} | |||||
void enqueue(cq_queue_t *queue, ErlNifPid *pid) | |||||
{ | |||||
cq_node_t *node = enif_alloc(sizeof(cq_node_t)); | |||||
//node->env = enif_alloc_env(); | |||||
//node->term = enif_make_copy(node->env, term); | |||||
node->value = pid; | |||||
node->next = NULL; | |||||
fprintf(stderr, "node %lu\n", node); | |||||
cq_node_t *tail = NULL; | |||||
uint64_t tail_count = 0; | |||||
while (1) { | |||||
tail = queue->tail; | |||||
cq_node_t *tail_ptr = Q_PTR(tail); | |||||
tail_count = Q_COUNT(tail); | |||||
cq_node_t *next = tail->next; | |||||
cq_node_t *next_ptr = Q_PTR(next); | |||||
uint64_t next_count = Q_COUNT(next); | |||||
if (tail == queue->tail) { | |||||
fprintf(stderr, "tail == queue->tail\n"); | |||||
if (next_ptr == NULL) { | |||||
fprintf(stderr, "next_ptr == NULL\n"); | |||||
if (__sync_bool_compare_and_swap(&tail_ptr->next, | |||||
next, | |||||
Q_SET_COUNT(node, next_count+1))) | |||||
fprintf(stderr, "CAS(tail_ptr->next, next, (node, next_count+1)) -> true\n"); | |||||
break; | |||||
} else { | |||||
__sync_bool_compare_and_swap(&queue->tail, | |||||
tail, | |||||
Q_SET_COUNT(next_ptr, next_count+1)); | |||||
fprintf(stderr, "CAS(queue->tail, tail, (next_ptr, next_count+1))\n"); | |||||
} | |||||
} | |||||
} | |||||
cq_node_t *node_with_count = Q_SET_COUNT(node, tail_count+1); | |||||
int ret = __sync_bool_compare_and_swap(&queue->tail, | |||||
tail, | |||||
node_with_count); | |||||
fprintf(stderr, "CAS(queue->tail, tail, %lu) -> %d\n", node_with_count, ret); | |||||
} | |||||
int dequeue(cq_queue_t *queue, ErlNifPid **pid) | |||||
{ | |||||
fprintf(stderr, "dequeue\n"); | |||||
cq_node_t *head, *head_ptr, *tail, *tail_ptr, *next, *next_ptr; | |||||
while (1) { | |||||
head = queue->head; | |||||
head_ptr = Q_PTR(head); | |||||
tail = queue->tail; | |||||
tail_ptr = Q_PTR(tail); | |||||
next = head->next; | |||||
next_ptr = Q_PTR(next); | |||||
fprintf(stderr, "head %lu, tail %lu, next %lu\n", head, tail, next); | |||||
if (head == queue->head) { | |||||
if (head_ptr == tail_ptr) { | |||||
if (next_ptr == NULL) { | |||||
return 0; /* Queue is empty */ | |||||
} | |||||
fprintf(stderr, "CAS(queue->tail, tail, (next_ptr, tail+1))\n"); | |||||
__sync_bool_compare_and_swap(&queue->tail, | |||||
tail, | |||||
Q_SET_COUNT(next_ptr, Q_COUNT(tail)+1)); | |||||
} else { | |||||
fprintf(stderr, "next->value %lu\n", next_ptr->value); | |||||
*pid = next_ptr->value; | |||||
fprintf(stderr, "CAS(queue->head, head, (next_ptr, head+1))\n"); | |||||
if (__sync_bool_compare_and_swap(&queue->head, | |||||
head, | |||||
Q_SET_COUNT(next_ptr, Q_COUNT(head)+1))) | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
// free pid | |||||
//enif_free(Q_PTR(head)); | |||||
return 1; | |||||
} | |||||
int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { | |||||
/* Initialize global array mapping id to cq_t ptr */ | |||||
QUEUES = (cq_t **) calloc(8, sizeof(cq_t **)); | |||||
if (QUEUES == NULL) | |||||
return -1; | |||||
ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER); | |||||
CQ_RESOURCE = enif_open_resource_type(env, "cq", "cq", | |||||
&free_resource, flags, NULL); | |||||
if (CQ_RESOURCE == NULL) | |||||
return -1; | |||||
return 0; | |||||
} | |||||
static ErlNifFunc nif_funcs[] = { | |||||
{"new" , 3, queue_new}, | |||||
{"free" , 1, queue_free}, | |||||
{"push" , 2, queue_push}, | |||||
{"async_pop", 1, queue_async_pop}, | |||||
{"debug" , 1, queue_debug}, | |||||
{"debug_poppers", 1, queue_debug_poppers}, | |||||
{"print_bits", 0, print_bits} | |||||
}; | |||||
ERL_NIF_INIT(cq, nif_funcs, load, NULL, NULL, NULL); |
@ -1,71 +0,0 @@ | |||||
#include <stdint.h> | |||||
#include "erl_nif.h" | |||||
#define CACHE_LINE_SIZE 64 | |||||
#define SLOT_INDEX(__index, __size) __index & (__size - 1) | |||||
#define Q_MASK 3L | |||||
#define Q_PTR(__ptr) (cq_node_t *) (((uint64_t)__ptr) & (~Q_MASK)) | |||||
#define Q_COUNT(__ptr) ((uint64_t) __ptr & Q_MASK) | |||||
#define Q_SET_COUNT(__ptr, __val) (cq_node_t *) ((uint64_t) __ptr | (__val & Q_MASK)) | |||||
#define STATE_EMPTY 0 | |||||
#define STATE_WRITE 1 | |||||
#define STATE_READ 2 | |||||
#define STATE_FULL 3 | |||||
ErlNifResourceType* CQ_RESOURCE; | |||||
typedef struct cq_node cq_node_t; | |||||
struct cq_node { | |||||
ErlNifEnv *env; | |||||
//ERL_NIF_TERM term; | |||||
ErlNifPid *value; | |||||
cq_node_t *next; | |||||
}; | |||||
typedef struct cq_queue { | |||||
cq_node_t *head; | |||||
cq_node_t *tail; | |||||
} cq_queue_t; | |||||
// TODO: Add padding between the fields | |||||
typedef struct cq { | |||||
uint32_t id; | |||||
uint64_t queue_size; | |||||
uint64_t overflow_size; | |||||
uint64_t head; | |||||
uint64_t tail; | |||||
uint8_t *slots_states; | |||||
ERL_NIF_TERM *slots_terms; | |||||
ErlNifEnv **slots_envs; | |||||
cq_queue_t *push_queue; | |||||
cq_queue_t *pop_queue; | |||||
uint8_t *overflow_states; | |||||
ERL_NIF_TERM *overflow_terms; | |||||
ErlNifEnv **overflow_envs; | |||||
} cq_t; | |||||
cq_t **QUEUES = NULL; /* Initialized on nif load */ | |||||
ERL_NIF_TERM mk_atom(ErlNifEnv* env, const char* atom); | |||||
ERL_NIF_TERM mk_error(ErlNifEnv* env, const char* msg); | |||||
int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); | |||||
void free_resource(ErlNifEnv*, void*); | |||||
cq_queue_t* new_queue(void); | |||||
void enqueue(cq_queue_t *q, ErlNifPid *pid); |
@ -1,26 +0,0 @@ | |||||
{port_specs, [ | |||||
{"../../priv/cq1.so", [ | |||||
"*.c", | |||||
"*.cc" | |||||
]} | |||||
]}. | |||||
%% {port_env, [ | |||||
%% {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", | |||||
%% "CFLAGS", "$CFLAGS -Ic_src/ -g -Wall -flto -Werror -O3"}, | |||||
%% {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", | |||||
%% "CXXFLAGS", "$CXXFLAGS -Ic_src/ -g -Wall -flto -Werror -O3"}, | |||||
%% | |||||
%% {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)", | |||||
%% "LDFLAGS", "$LDFLAGS -flto -lstdc++"}, | |||||
%% | |||||
%% %% OS X Leopard flags for 64-bit | |||||
%% {"darwin9.*-64$", "CXXFLAGS", "-m64"}, | |||||
%% {"darwin9.*-64$", "LDFLAGS", "-arch x86_64"}, | |||||
%% | |||||
%% %% OS X Snow Leopard flags for 32-bit | |||||
%% {"darwin10.*-32$", "CXXFLAGS", "-m32"}, | |||||
%% {"darwin10.*-32$", "LDFLAGS", "-arch i386"}, | |||||
%% | |||||
%% {"win32", "CXXFLAGS", "$CXXFLAGS /O2 /DNDEBUG"} | |||||
%% ]}. |
@ -1,564 +0,0 @@ | |||||
#include <stdio.h> | |||||
#include <unistd.h> | |||||
#include "erl_nif.h" | |||||
#include "cq_nif.h" | |||||
/* #ifndef ERL_NIF_DIRTY_SCHEDULER_SUPPORT | |||||
# error Requires dirty schedulers | |||||
#endif */ | |||||
ERL_NIF_TERM | |||||
mk_atom(ErlNifEnv* env, const char* atom) | |||||
{ | |||||
ERL_NIF_TERM ret; | |||||
if(!enif_make_existing_atom(env, atom, &ret, ERL_NIF_LATIN1)) | |||||
return enif_make_atom(env, atom); | |||||
return ret; | |||||
} | |||||
ERL_NIF_TERM | |||||
mk_error(ErlNifEnv* env, const char* mesg) | |||||
{ | |||||
return enif_make_tuple2(env, mk_atom(env, "error"), mk_atom(env, mesg)); | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
cq_t *q = enif_alloc_resource(CQ_RESOURCE, sizeof(cq_t)); | |||||
if (q == NULL) | |||||
return mk_error(env, "priv_alloc_error"); | |||||
ERL_NIF_TERM ret = enif_make_resource(env, q); | |||||
/* enif_release_resource(ret); */ | |||||
uint32_t queue_id = 0; | |||||
uint32_t queue_size = 0; | |||||
uint32_t overflow_size = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id) || | |||||
!enif_get_uint(env, argv[1], &queue_size) || | |||||
!enif_get_uint(env, argv[2], &overflow_size)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "bad_queue_id"); | |||||
/* TODO: Check that queue_size is power of 2 */ | |||||
if (QUEUES[queue_id] != NULL) | |||||
return mk_error(env, "queue_id_already_exists"); | |||||
q->id = queue_id; | |||||
q->queue_size = queue_size; | |||||
q->overflow_size = overflow_size; | |||||
q->tail = 0; | |||||
q->head = 0; | |||||
q->slots_states = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->slots_terms = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->slots_envs = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->overflow_terms = calloc(q->overflow_size, CACHE_LINE_SIZE); | |||||
q->overflow_envs = calloc(q->queue_size, CACHE_LINE_SIZE); | |||||
q->push_queue = new_queue(); | |||||
q->pop_queue = new_queue(); | |||||
/* TODO: Check calloc return */ | |||||
for (int i = 0; i < q->queue_size; i++) { | |||||
ErlNifEnv *slot_env = enif_alloc_env(); | |||||
q->slots_envs[i*CACHE_LINE_SIZE] = slot_env; | |||||
//q->overflow_envs[i*CACHE_LINE_SIZE] = (ErlNifEnv *) enif_alloc_env(); | |||||
} | |||||
QUEUES[q->id] = q; | |||||
return enif_make_tuple2(env, mk_atom(env, "ok"), ret); | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_free(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
/* TODO: Free all the things! */ | |||||
QUEUES[queue_id] = NULL; | |||||
return enif_make_atom(env, "ok"); | |||||
} | |||||
/* Push to the head of the queue. */ | |||||
static ERL_NIF_TERM | |||||
queue_push(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
/* Load the queue */ | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
if (q->id != queue_id) | |||||
return mk_error(env, "not_identical_queue_id"); | |||||
for (int i = 0; i < q->queue_size; i++) { | |||||
fprintf(stderr, "queue slot %d, index %d, state %d\n", | |||||
i, i*CACHE_LINE_SIZE, q->slots_states[i*CACHE_LINE_SIZE]); | |||||
} | |||||
/* If there's consumers waiting, the queue must be empty and we | |||||
should directly pick a consumer to notify. */ | |||||
ErlNifPid *waiting_consumer; | |||||
int dequeue_ret = dequeue(q->pop_queue, &waiting_consumer); | |||||
if (dequeue_ret) { | |||||
ErlNifEnv *msg_env = enif_alloc_env(); | |||||
ERL_NIF_TERM copy = enif_make_copy(msg_env, argv[1]); | |||||
ERL_NIF_TERM tuple = enif_make_tuple2(msg_env, mk_atom(env, "pop"), copy); | |||||
if (enif_send(env, waiting_consumer, msg_env, tuple)) { | |||||
enif_free_env(msg_env); | |||||
return mk_atom(env, "ok"); | |||||
} else { | |||||
return mk_error(env, "notify_failed"); | |||||
} | |||||
} | |||||
/* Increment head and attempt to claim the slot by marking it as | |||||
busy. This ensures no other thread will attempt to modify this | |||||
slot. If we cannot lock it, another thread must have */ | |||||
uint64_t head = __sync_add_and_fetch(&q->head, 1); | |||||
size_t size = q->queue_size; | |||||
while (1) { | |||||
uint64_t index = SLOT_INDEX(head, size); | |||||
uint64_t ret = __sync_val_compare_and_swap(&q->slots_states[index], | |||||
STATE_EMPTY, | |||||
STATE_WRITE); | |||||
switch (ret) { | |||||
case STATE_EMPTY: | |||||
head = __sync_add_and_fetch(&q->head, 1); | |||||
case STATE_WRITE: | |||||
/* We acquired the write lock, go ahead with the write. */ | |||||
break; | |||||
case STATE_FULL: | |||||
/* We have caught up with the tail and the buffer is | |||||
full. Block the producer until a consumer reads the | |||||
item. */ | |||||
return mk_error(env, "full_not_implemented"); | |||||
} | |||||
} | |||||
/* If head catches up with tail, the queue is full. Add to | |||||
overflow instead */ | |||||
/* Copy term to slot-specific temporary process env. */ | |||||
ERL_NIF_TERM copy = enif_make_copy(q->slots_envs[SLOT_INDEX(head, size)], argv[1]); | |||||
q->slots_terms[SLOT_INDEX(head, size)] = copy; | |||||
__sync_synchronize(); /* Or compiler memory barrier? */ | |||||
/* TODO: Do we need to collect garbage? */ | |||||
/* Mark the slot ready to be consumed */ | |||||
if (__sync_bool_compare_and_swap(&q->slots_states[SLOT_INDEX(head, size)], | |||||
STATE_WRITE, | |||||
STATE_FULL)) { | |||||
return mk_atom(env, "ok"); | |||||
} else { | |||||
return mk_error(env, "could_not_update_slots_after_insert"); | |||||
} | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_async_pop(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
/* Load queue */ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
if (q->id != queue_id) | |||||
return mk_error(env, "not_identical_queue_id"); | |||||
uint64_t qsize = q->queue_size; | |||||
uint64_t tail = q->tail; | |||||
uint64_t num_busy = 0; | |||||
/* Walk the buffer starting the tail position until we are either | |||||
able to consume a term or find an empty slot. */ | |||||
while (1) { | |||||
uint64_t index = SLOT_INDEX(tail, qsize); | |||||
uint64_t ret = __sync_val_compare_and_swap(&q->slots_states[index], | |||||
STATE_FULL, | |||||
STATE_READ); | |||||
if (ret == STATE_READ) { | |||||
/* We were able to mark the term as read in progress. We | |||||
now have an exclusive lock. */ | |||||
break; | |||||
} else if (ret == STATE_WRITE) { | |||||
/* We found an item with a write in progress. If that | |||||
thread progresses, it will eventually mark the slot as | |||||
full. We can spin until that happens. | |||||
This can take an arbitrary amount of time and multiple | |||||
reading threads will compete for the same slot. | |||||
Instead we add the caller to the queue of blocking | |||||
consumers. When the next producer comes it will "help" | |||||
this thread by calling enif_send on the current | |||||
in-progress term *and* handle it's own terms. If | |||||
there's no new push to the queue, this will block | |||||
forever. */ | |||||
return mk_atom(env, "write_in_progress_not_implemented"); | |||||
} else if (ret == STATE_EMPTY) { | |||||
/* We found an empty item. Queue must be empty. Add | |||||
calling Erlang consumer process to queue of waiting | |||||
processes. When the next producer comes along, it first | |||||
checks the waiting consumers and calls enif_send | |||||
instead of writing to the slots. */ | |||||
ErlNifPid *pid = enif_alloc(sizeof(ErlNifPid)); | |||||
pid = enif_self(env, pid); | |||||
enqueue(q->pop_queue, pid); | |||||
return mk_atom(env, "wait_for_msg"); | |||||
} else { | |||||
tail = __sync_add_and_fetch(&q->tail, 1); | |||||
} | |||||
} | |||||
/* Copy term into calling process env. The NIF env can now be | |||||
gargbage collected. */ | |||||
ERL_NIF_TERM copy = enif_make_copy(env, q->slots_terms[SLOT_INDEX(tail, qsize)]); | |||||
/* Mark the slot as free. Note: We don't increment the tail | |||||
position here, as another thread also walking the buffer might | |||||
have incremented it multiple times */ | |||||
q->slots_terms[SLOT_INDEX(tail, qsize)] = 0; | |||||
if (__sync_bool_compare_and_swap(&q->slots_states[SLOT_INDEX(tail, qsize)], | |||||
STATE_READ, | |||||
STATE_EMPTY)) { | |||||
return enif_make_tuple2(env, mk_atom(env, "ok"), copy); | |||||
} else { | |||||
return mk_error(env, "could_not_update_slots_after_pop"); | |||||
} | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_debug(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
ERL_NIF_TERM *slots_states = enif_alloc(sizeof(ERL_NIF_TERM) * q->queue_size); | |||||
ERL_NIF_TERM *slots_terms = enif_alloc(sizeof(ERL_NIF_TERM) * q->queue_size); | |||||
for (int i = 0; i < q->queue_size; i++) { | |||||
slots_states[i] = enif_make_int(env, q->slots_states[i * CACHE_LINE_SIZE]); | |||||
if (q->slots_terms[i * CACHE_LINE_SIZE] == 0) { | |||||
slots_terms[i] = mk_atom(env, "null"); | |||||
} else { | |||||
slots_terms[i] = enif_make_copy(env, q->slots_terms[i * CACHE_LINE_SIZE]); | |||||
} | |||||
} | |||||
return enif_make_tuple4(env, | |||||
enif_make_uint64(env, q->tail), | |||||
enif_make_uint64(env, q->head), | |||||
enif_make_list_from_array(env, slots_states, q->queue_size), | |||||
enif_make_list_from_array(env, slots_terms, q->queue_size)); | |||||
} | |||||
static ERL_NIF_TERM | |||||
queue_debug_poppers(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t queue_id = 0; | |||||
if (!enif_get_uint(env, argv[0], &queue_id)) | |||||
return mk_error(env, "badarg"); | |||||
if (queue_id > 8) | |||||
return mk_error(env, "badarg"); | |||||
cq_t *q = QUEUES[queue_id]; | |||||
if (q == NULL) | |||||
return mk_error(env, "bad_queue_id"); | |||||
uint64_t pop_queue_size = 0; | |||||
cq_node_t *node = q->pop_queue->head; | |||||
if (node->value == NULL) { | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
while (node != NULL) { | |||||
pop_queue_size++; | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
ERL_NIF_TERM *pop_queue_pids = enif_alloc(sizeof(ERL_NIF_TERM) * pop_queue_size); | |||||
node = q->pop_queue->head; | |||||
node = Q_PTR(node); | |||||
if (node->value == NULL) { | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
uint64_t i = 0; | |||||
while (node != NULL) { | |||||
if (node->value == 0) { | |||||
pop_queue_pids[i] = mk_atom(env, "null"); | |||||
} | |||||
else { | |||||
pop_queue_pids[i] = enif_make_pid(env, node->value); | |||||
} | |||||
i++; | |||||
node = node->next; | |||||
node = Q_PTR(node); | |||||
} | |||||
ERL_NIF_TERM list = enif_make_list_from_array(env, pop_queue_pids, pop_queue_size); | |||||
enif_free(pop_queue_pids); | |||||
return list; | |||||
} | |||||
static ERL_NIF_TERM | |||||
print_bits(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint64_t *p1 = malloc(8); | |||||
*p1 = 0; | |||||
for (int bit = 63; bit >= 0; bit--) { | |||||
uint64_t power = 1 << bit; | |||||
//uint64_t byte = *p1; | |||||
uint64_t byte = p1; | |||||
fprintf(stderr, "%d", (byte & power) >> bit); | |||||
} | |||||
fprintf(stderr, "\n"); | |||||
//enif_free(p1); | |||||
return mk_atom(env, "ok"); | |||||
} | |||||
void free_resource(ErlNifEnv* env, void* arg) | |||||
{ | |||||
//cq_t *cq = (cq_t *) arg; | |||||
fprintf(stderr, "free_resource\n"); | |||||
} | |||||
cq_queue_t * new_queue() | |||||
{ | |||||
cq_queue_t *queue = enif_alloc(sizeof(cq_queue_t)); | |||||
cq_node_t *node = enif_alloc(sizeof(cq_node_t)); | |||||
node->next = NULL; | |||||
//node->env = NULL; | |||||
node->value = NULL; | |||||
queue->head = node; | |||||
queue->tail = node; | |||||
return queue; | |||||
} | |||||
void enqueue(cq_queue_t *queue, ErlNifPid *pid) | |||||
{ | |||||
cq_node_t *node = enif_alloc(sizeof(cq_node_t)); | |||||
//node->env = enif_alloc_env(); | |||||
//node->term = enif_make_copy(node->env, term); | |||||
node->value = pid; | |||||
node->next = NULL; | |||||
fprintf(stderr, "node %lu\n", node); | |||||
cq_node_t *tail = NULL; | |||||
uint64_t tail_count = 0; | |||||
while (1) { | |||||
tail = queue->tail; | |||||
cq_node_t *tail_ptr = Q_PTR(tail); | |||||
tail_count = Q_COUNT(tail); | |||||
cq_node_t *next = tail->next; | |||||
cq_node_t *next_ptr = Q_PTR(next); | |||||
uint64_t next_count = Q_COUNT(next); | |||||
if (tail == queue->tail) { | |||||
fprintf(stderr, "tail == queue->tail\n"); | |||||
if (next_ptr == NULL) { | |||||
fprintf(stderr, "next_ptr == NULL\n"); | |||||
if (__sync_bool_compare_and_swap(&tail_ptr->next, | |||||
next, | |||||
Q_SET_COUNT(node, next_count+1))) | |||||
fprintf(stderr, "CAS(tail_ptr->next, next, (node, next_count+1)) -> true\n"); | |||||
break; | |||||
} else { | |||||
__sync_bool_compare_and_swap(&queue->tail, | |||||
tail, | |||||
Q_SET_COUNT(next_ptr, next_count+1)); | |||||
fprintf(stderr, "CAS(queue->tail, tail, (next_ptr, next_count+1))\n"); | |||||
} | |||||
} | |||||
} | |||||
cq_node_t *node_with_count = Q_SET_COUNT(node, tail_count+1); | |||||
int ret = __sync_bool_compare_and_swap(&queue->tail, | |||||
tail, | |||||
node_with_count); | |||||
fprintf(stderr, "CAS(queue->tail, tail, %lu) -> %d\n", node_with_count, ret); | |||||
} | |||||
int dequeue(cq_queue_t *queue, ErlNifPid **pid) | |||||
{ | |||||
fprintf(stderr, "dequeue\n"); | |||||
cq_node_t *head, *head_ptr, *tail, *tail_ptr, *next, *next_ptr; | |||||
while (1) { | |||||
head = queue->head; | |||||
head_ptr = Q_PTR(head); | |||||
tail = queue->tail; | |||||
tail_ptr = Q_PTR(tail); | |||||
next = head->next; | |||||
next_ptr = Q_PTR(next); | |||||
fprintf(stderr, "head %lu, tail %lu, next %lu\n", head, tail, next); | |||||
if (head == queue->head) { | |||||
if (head_ptr == tail_ptr) { | |||||
if (next_ptr == NULL) { | |||||
return 0; /* Queue is empty */ | |||||
} | |||||
fprintf(stderr, "CAS(queue->tail, tail, (next_ptr, tail+1))\n"); | |||||
__sync_bool_compare_and_swap(&queue->tail, | |||||
tail, | |||||
Q_SET_COUNT(next_ptr, Q_COUNT(tail)+1)); | |||||
} else { | |||||
fprintf(stderr, "next->value %lu\n", next_ptr->value); | |||||
*pid = next_ptr->value; | |||||
fprintf(stderr, "CAS(queue->head, head, (next_ptr, head+1))\n"); | |||||
if (__sync_bool_compare_and_swap(&queue->head, | |||||
head, | |||||
Q_SET_COUNT(next_ptr, Q_COUNT(head)+1))) | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
// free pid | |||||
//enif_free(Q_PTR(head)); | |||||
return 1; | |||||
} | |||||
int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { | |||||
/* Initialize global array mapping id to cq_t ptr */ | |||||
QUEUES = (cq_t **) calloc(8, sizeof(cq_t **)); | |||||
if (QUEUES == NULL) | |||||
return -1; | |||||
ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER); | |||||
CQ_RESOURCE = enif_open_resource_type(env, "cq", "cq", | |||||
&free_resource, flags, NULL); | |||||
if (CQ_RESOURCE == NULL) | |||||
return -1; | |||||
return 0; | |||||
} | |||||
static ErlNifFunc nif_funcs[] = { | |||||
{"new" , 3, queue_new}, | |||||
{"free" , 1, queue_free}, | |||||
{"push" , 2, queue_push}, | |||||
{"async_pop", 1, queue_async_pop}, | |||||
{"debug" , 1, queue_debug}, | |||||
{"debug_poppers", 1, queue_debug_poppers}, | |||||
{"print_bits", 0, print_bits} | |||||
}; | |||||
ERL_NIF_INIT(cq, nif_funcs, load, NULL, NULL, NULL); |
@ -1,71 +0,0 @@ | |||||
#include <stdint.h> | |||||
#include "erl_nif.h" | |||||
#define CACHE_LINE_SIZE 64 | |||||
#define SLOT_INDEX(__index, __size) __index & (__size - 1) | |||||
#define Q_MASK 3L | |||||
#define Q_PTR(__ptr) (cq_node_t *) (((uint64_t)__ptr) & (~Q_MASK)) | |||||
#define Q_COUNT(__ptr) ((uint64_t) __ptr & Q_MASK) | |||||
#define Q_SET_COUNT(__ptr, __val) (cq_node_t *) ((uint64_t) __ptr | (__val & Q_MASK)) | |||||
#define STATE_EMPTY 0 | |||||
#define STATE_WRITE 1 | |||||
#define STATE_READ 2 | |||||
#define STATE_FULL 3 | |||||
ErlNifResourceType* CQ_RESOURCE; | |||||
typedef struct cq_node cq_node_t; | |||||
struct cq_node { | |||||
ErlNifEnv *env; | |||||
//ERL_NIF_TERM term; | |||||
ErlNifPid *value; | |||||
cq_node_t *next; | |||||
}; | |||||
typedef struct cq_queue { | |||||
cq_node_t *head; | |||||
cq_node_t *tail; | |||||
} cq_queue_t; | |||||
// TODO: Add padding between the fields | |||||
typedef struct cq { | |||||
uint32_t id; | |||||
uint64_t queue_size; | |||||
uint64_t overflow_size; | |||||
uint64_t head; | |||||
uint64_t tail; | |||||
uint8_t *slots_states; | |||||
ERL_NIF_TERM *slots_terms; | |||||
ErlNifEnv **slots_envs; | |||||
cq_queue_t *push_queue; | |||||
cq_queue_t *pop_queue; | |||||
uint8_t *overflow_states; | |||||
ERL_NIF_TERM *overflow_terms; | |||||
ErlNifEnv **overflow_envs; | |||||
} cq_t; | |||||
cq_t **QUEUES = NULL; /* Initialized on nif load */ | |||||
ERL_NIF_TERM mk_atom(ErlNifEnv* env, const char* atom); | |||||
ERL_NIF_TERM mk_error(ErlNifEnv* env, const char* msg); | |||||
int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); | |||||
void free_resource(ErlNifEnv*, void*); | |||||
cq_queue_t* new_queue(void); | |||||
void enqueue(cq_queue_t *q, ErlNifPid *pid); |
@ -0,0 +1,80 @@ | |||||
PROJECT = enlfq | |||||
CXXFLAGS = -std=c++11 -O2 -Wextra -Werror -Wno-missing-field-initializers -fno-rtti -fno-exceptions | |||||
LDLIBS = -lstdc++ | |||||
# Based on c_src.mk from erlang.mk by Loic Hoguin <essen@ninenines.eu> | |||||
CURDIR := $(shell pwd) | |||||
BASEDIR := $(abspath $(CURDIR)/..) | |||||
PROJECT ?= $(notdir $(BASEDIR)) | |||||
PROJECT := $(strip $(PROJECT)) | |||||
ERTS_INCLUDE_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~ts/erts-~ts/include/\", [code:root_dir(), erlang:system_info(version)]).") | |||||
ERL_INTERFACE_INCLUDE_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~ts\", [code:lib_dir(erl_interface, include)]).") | |||||
ERL_INTERFACE_LIB_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~ts\", [code:lib_dir(erl_interface, lib)]).") | |||||
C_SRC_DIR = $(CURDIR) | |||||
C_SRC_OUTPUT ?= $(CURDIR)/../priv/$(PROJECT).so | |||||
# System type and C compiler/flags. | |||||
UNAME_SYS := $(shell uname -s) | |||||
ifeq ($(UNAME_SYS), Darwin) | |||||
CC ?= cc | |||||
CFLAGS ?= -O3 -std=c99 -arch x86_64 -finline-functions -Wall -Wmissing-prototypes | |||||
CXXFLAGS ?= -O3 -arch x86_64 -finline-functions -Wall | |||||
LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress | |||||
else ifeq ($(UNAME_SYS), FreeBSD) | |||||
CC ?= cc | |||||
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes | |||||
CXXFLAGS ?= -O3 -finline-functions -Wall | |||||
else ifeq ($(UNAME_SYS), Linux) | |||||
CC ?= gcc | |||||
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes | |||||
CXXFLAGS ?= -O3 -finline-functions -Wall | |||||
endif | |||||
CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) | |||||
CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) | |||||
LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -lerl_interface -lei | |||||
LDFLAGS += -shared | |||||
# Verbosity. | |||||
c_verbose_0 = @echo " C " $(?F); | |||||
c_verbose = $(c_verbose_$(V)) | |||||
cpp_verbose_0 = @echo " CPP " $(?F); | |||||
cpp_verbose = $(cpp_verbose_$(V)) | |||||
link_verbose_0 = @echo " LD " $(@F); | |||||
link_verbose = $(link_verbose_$(V)) | |||||
SOURCES := $(shell find $(C_SRC_DIR) -type f \( -name "*.c" -o -name "*.C" -o -name "*.cc" -o -name "*.cpp" \)) | |||||
OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) | |||||
COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c | |||||
COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c | |||||
$(C_SRC_OUTPUT): $(OBJECTS) | |||||
@mkdir -p $(BASEDIR)/priv/ | |||||
$(link_verbose) $(CC) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $(C_SRC_OUTPUT) | |||||
%.o: %.c | |||||
$(COMPILE_C) $(OUTPUT_OPTION) $< | |||||
%.o: %.cc | |||||
$(COMPILE_CPP) $(OUTPUT_OPTION) $< | |||||
%.o: %.C | |||||
$(COMPILE_CPP) $(OUTPUT_OPTION) $< | |||||
%.o: %.cpp | |||||
$(COMPILE_CPP) $(OUTPUT_OPTION) $< | |||||
clean: | |||||
@rm -f $(C_SRC_OUTPUT) $(OBJECTS) |
@ -0,0 +1,84 @@ | |||||
#include "enlfq.h" | |||||
#include "enlfq_nif.h" | |||||
#include "nif_utils.h" | |||||
#include "concurrentqueue.h" | |||||
struct q_item { | |||||
ErlNifEnv *env; | |||||
ERL_NIF_TERM term; | |||||
}; | |||||
struct squeue { | |||||
moodycamel::ConcurrentQueue<q_item> *queue; | |||||
}; | |||||
void nif_enlfq_free(ErlNifEnv *, void *obj) { | |||||
squeue *inst = static_cast<squeue *>(obj); | |||||
if (inst != nullptr) { | |||||
q_item item; | |||||
while (inst->queue->try_dequeue(item)) { | |||||
enif_free_env(item.env); | |||||
} | |||||
delete inst->queue; | |||||
} | |||||
} | |||||
ERL_NIF_TERM nif_enlfq_new(ErlNifEnv *env, int, const ERL_NIF_TERM *) { | |||||
shared_data *data = static_cast<shared_data *>(enif_priv_data(env)); | |||||
squeue *qinst = static_cast<squeue *>(enif_alloc_resource(data->resQueueInstance, sizeof(squeue))); | |||||
qinst->queue = new moodycamel::ConcurrentQueue<q_item>; | |||||
if (qinst == NULL) | |||||
return make_error(env, "enif_alloc_resource failed"); | |||||
ERL_NIF_TERM term = enif_make_resource(env, qinst); | |||||
enif_release_resource(qinst); | |||||
return enif_make_tuple2(env, ATOMS.atomOk, term); | |||||
} | |||||
ERL_NIF_TERM nif_enlfq_push(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { | |||||
shared_data *data = static_cast<shared_data *>(enif_priv_data(env)); | |||||
squeue *inst; | |||||
if (!enif_get_resource(env, argv[0], data->resQueueInstance, (void **) &inst)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
q_item item; | |||||
item.env = enif_alloc_env(); | |||||
item.term = enif_make_copy(item.env, argv[1]); | |||||
inst->queue->enqueue(item); | |||||
return ATOMS.atomTrue; | |||||
} | |||||
ERL_NIF_TERM nif_enlfq_pop(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]) { | |||||
shared_data *data = static_cast<shared_data *>(enif_priv_data(env)); | |||||
squeue *inst = NULL; | |||||
if (!enif_get_resource(env, argv[0], data->resQueueInstance, (void **) &inst)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ERL_NIF_TERM term; | |||||
q_item item; | |||||
if (inst->queue->try_dequeue(item)) { | |||||
term = enif_make_copy(env, item.term); | |||||
enif_free_env(item.env); | |||||
return enif_make_tuple2(env, ATOMS.atomOk, term); | |||||
} else { | |||||
return ATOMS.atomEmpty; | |||||
} | |||||
} |
@ -0,0 +1,10 @@ | |||||
#pragma once | |||||
#include "erl_nif.h" | |||||
extern "C" { | |||||
void nif_enlfq_free(ErlNifEnv *env, void *obj); | |||||
ERL_NIF_TERM nif_enlfq_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
ERL_NIF_TERM nif_enlfq_push(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
ERL_NIF_TERM nif_enlfq_pop(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
} |
@ -0,0 +1,57 @@ | |||||
#include <string.h> | |||||
#include "enlfq_nif.h" | |||||
#include "enlfq.h" | |||||
#include "nif_utils.h" | |||||
const char kAtomOk[] = "ok"; | |||||
const char kAtomError[] = "error"; | |||||
const char kAtomTrue[] = "true"; | |||||
//const char kAtomFalse[] = "false"; | |||||
//const char kAtomUndefined[] = "undefined"; | |||||
const char kAtomEmpty[] = "empty"; | |||||
atoms ATOMS; | |||||
void open_resources(ErlNifEnv *env, shared_data *data) { | |||||
ErlNifResourceFlags flags = static_cast<ErlNifResourceFlags>(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER); | |||||
data->resQueueInstance = enif_open_resource_type(env, NULL, "enlfq_instance", nif_enlfq_free, flags, NULL); | |||||
} | |||||
int on_nif_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM) { | |||||
ATOMS.atomOk = make_atom(env, kAtomOk); | |||||
ATOMS.atomError = make_atom(env, kAtomError); | |||||
ATOMS.atomTrue = make_atom(env, kAtomTrue); | |||||
// ATOMS.atomFalse = make_atom(env, kAtomFalse); | |||||
// ATOMS.atomUndefined = make_atom(env, kAtomUndefined); | |||||
ATOMS.atomEmpty = make_atom(env, kAtomEmpty); | |||||
shared_data *data = static_cast<shared_data *>(enif_alloc(sizeof(shared_data))); | |||||
open_resources(env, data); | |||||
*priv_data = data; | |||||
return 0; | |||||
} | |||||
void on_nif_unload(ErlNifEnv *, void *priv_data) { | |||||
shared_data *data = static_cast<shared_data *>(priv_data); | |||||
enif_free(data); | |||||
} | |||||
int on_nif_upgrade(ErlNifEnv *env, void **priv, void **, ERL_NIF_TERM) { | |||||
shared_data *data = static_cast<shared_data *>(enif_alloc(sizeof(shared_data))); | |||||
open_resources(env, data); | |||||
*priv = data; | |||||
return 0; | |||||
} | |||||
static ErlNifFunc nif_funcs[] = | |||||
{ | |||||
{"new", 0, nif_enlfq_new}, | |||||
{"push", 2, nif_enlfq_push}, | |||||
{"pop", 1, nif_enlfq_pop} | |||||
}; | |||||
ERL_NIF_INIT(enlfq, nif_funcs, on_nif_load, NULL, on_nif_upgrade, on_nif_unload) | |||||
@ -0,0 +1,19 @@ | |||||
#pragma once | |||||
#include "erl_nif.h" | |||||
struct atoms | |||||
{ | |||||
ERL_NIF_TERM atomOk; | |||||
ERL_NIF_TERM atomError; | |||||
ERL_NIF_TERM atomTrue; | |||||
// ERL_NIF_TERM atomFalse; | |||||
// ERL_NIF_TERM atomUndefined; | |||||
ERL_NIF_TERM atomEmpty; | |||||
}; | |||||
struct shared_data | |||||
{ | |||||
ErlNifResourceType* resQueueInstance; | |||||
}; | |||||
extern atoms ATOMS; |
@ -0,0 +1,27 @@ | |||||
#include "nif_utils.h" | |||||
#include "enlfq_nif.h" | |||||
#include <string.h> | |||||
ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name) | |||||
{ | |||||
ERL_NIF_TERM ret; | |||||
if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) | |||||
return ret; | |||||
return enif_make_atom(env, name); | |||||
} | |||||
ERL_NIF_TERM make_binary(ErlNifEnv* env, const char* buff, size_t length) | |||||
{ | |||||
ERL_NIF_TERM term; | |||||
unsigned char *destination_buffer = enif_make_new_binary(env, length, &term); | |||||
memcpy(destination_buffer, buff, length); | |||||
return term; | |||||
} | |||||
ERL_NIF_TERM make_error(ErlNifEnv* env, const char* error) | |||||
{ | |||||
return enif_make_tuple2(env, ATOMS.atomError, make_binary(env, error, strlen(error))); | |||||
} |
@ -0,0 +1,6 @@ | |||||
#pragma once | |||||
#include "erl_nif.h" | |||||
ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name); | |||||
ERL_NIF_TERM make_error(ErlNifEnv* env, const char* error); | |||||
ERL_NIF_TERM make_binary(ErlNifEnv* env, const char* buff, size_t length); |
@ -0,0 +1,7 @@ | |||||
{port_specs, [ | |||||
{"../../priv/enlfq.so", ["*.cc"]} | |||||
]}. | |||||
@ -0,0 +1,172 @@ | |||||
#include "etsq.h" | |||||
ErlNifRWLock *qinfo_map_rwlock; | |||||
QInfoMap qinfo_map; | |||||
// Function finds the queue from map and returns QueueInfo | |||||
// Not thread safe. | |||||
QueueInfo* get_q_info(char* name) | |||||
{ | |||||
//std::cout<<"Info: "<< name<<std::endl; | |||||
QInfoMap::iterator iter = qinfo_map.find(name); | |||||
if (iter != qinfo_map.end()) | |||||
{ | |||||
//std::cout<<" Fetched "; | |||||
return iter->second; | |||||
} | |||||
return NULL; | |||||
} | |||||
void new_q(char* name) | |||||
{ | |||||
//std::cout<<"Create: " << name<<std::endl; | |||||
WriteLock write_lock(qinfo_map_rwlock); | |||||
QueueInfo *queue_info = new QueueInfo(name); | |||||
qinfo_map.insert(QInfoMapPair(name, queue_info)); | |||||
//std::cout<<"Created: " << name<<std::endl; | |||||
} | |||||
bool push(char* name, ErlTerm *erl_term) | |||||
{ | |||||
QueueInfo *pqueue_info = NULL; | |||||
ReadLock read_lock(qinfo_map_rwlock); | |||||
if (NULL != (pqueue_info = get_q_info(name))) | |||||
{ | |||||
Mutex mutex(pqueue_info->pmutex); | |||||
pqueue_info->queue.push(erl_term); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
// Returns new ErlTerm. Caller should delete it | |||||
ErlTerm* pop(char* name, bool read_only) | |||||
{ | |||||
QueueInfo *pqueue_info = NULL; | |||||
ReadLock read_lock(qinfo_map_rwlock); | |||||
if (NULL != (pqueue_info = get_q_info(name))) | |||||
{ | |||||
Mutex mutex(pqueue_info->pmutex); | |||||
if (!pqueue_info->queue.empty()) | |||||
{ | |||||
ErlTerm *erl_term = pqueue_info->queue.front(); | |||||
if(read_only) | |||||
{ | |||||
return new ErlTerm(erl_term); | |||||
} | |||||
pqueue_info->queue.pop(); | |||||
return erl_term; | |||||
} | |||||
return new ErlTerm("empty"); | |||||
} | |||||
return NULL; | |||||
} | |||||
static ERL_NIF_TERM new_queue(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
int size = 100; | |||||
char *name = new char(size); | |||||
enif_get_atom(env, argv[0], name, size, ERL_NIF_LATIN1); | |||||
{ | |||||
QueueInfo *pqueue_info = NULL; | |||||
ReadLock read_lock(qinfo_map_rwlock); | |||||
if (NULL != (pqueue_info = get_q_info(name))) | |||||
{ | |||||
return enif_make_error(env, "already_exists"); | |||||
} | |||||
} | |||||
new_q(name); | |||||
return enif_make_atom(env, "ok"); | |||||
} | |||||
static ERL_NIF_TERM info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
int size = 100; | |||||
char name[100]; | |||||
enif_get_atom(env, argv[0], name, size, ERL_NIF_LATIN1); | |||||
int queue_size = 0; | |||||
{ | |||||
QueueInfo *pqueue_info = NULL; | |||||
ReadLock read_lock(qinfo_map_rwlock); | |||||
if (NULL == (pqueue_info = get_q_info(name))) | |||||
return enif_make_badarg(env); | |||||
queue_size = pqueue_info->queue.size(); | |||||
} | |||||
return enif_make_list2(env, | |||||
enif_make_tuple2(env, enif_make_atom(env, "name"), enif_make_atom(env, name)), | |||||
enif_make_tuple2(env, enif_make_atom(env, "size"), enif_make_int(env, queue_size))); | |||||
} | |||||
static ERL_NIF_TERM push_back(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
int size = 100; | |||||
char name[100]; | |||||
enif_get_atom(env, argv[0], name, size, ERL_NIF_LATIN1); | |||||
ErlTerm *erl_term = new ErlTerm(argv[1]); | |||||
if (push(name, erl_term)) | |||||
return enif_make_atom(env, "ok"); | |||||
delete erl_term; | |||||
return enif_make_badarg(env); | |||||
} | |||||
static ERL_NIF_TERM pop_front(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
int size = 100; | |||||
char name[100]; | |||||
enif_get_atom(env, argv[0], name, size, ERL_NIF_LATIN1); | |||||
ErlTerm *erl_term = NULL; | |||||
if (NULL == (erl_term = pop(name, false))) | |||||
return enif_make_badarg(env); | |||||
ERL_NIF_TERM return_term = enif_make_copy(env, erl_term->term); | |||||
delete erl_term; | |||||
return return_term; | |||||
} | |||||
static ERL_NIF_TERM get_front(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
int size = 100; | |||||
char name[100]; | |||||
enif_get_atom(env, argv[0], name, size, ERL_NIF_LATIN1); | |||||
ErlTerm *erl_term = NULL; | |||||
if (NULL == (erl_term = pop(name, true))) | |||||
return enif_make_badarg(env); | |||||
ERL_NIF_TERM return_term = enif_make_copy(env, erl_term->term); | |||||
delete erl_term; | |||||
return return_term; | |||||
} | |||||
static int is_ok_load_info(ErlNifEnv* env, ERL_NIF_TERM load_info) | |||||
{ | |||||
int i; | |||||
return enif_get_int(env, load_info, &i) && i == 1; | |||||
} | |||||
static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) | |||||
{ | |||||
if (!is_ok_load_info(env, load_info)) | |||||
return -1; | |||||
qinfo_map_rwlock = enif_rwlock_create((char*)"qinfo"); | |||||
return 0; | |||||
} | |||||
static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) | |||||
{ | |||||
if (!is_ok_load_info(env, load_info)) | |||||
return -1; | |||||
return 0; | |||||
} | |||||
static void unload(ErlNifEnv* env, void* priv_data) | |||||
{ | |||||
enif_rwlock_destroy(qinfo_map_rwlock); | |||||
} | |||||
static ErlNifFunc nif_funcs[] = { | |||||
{"new", 1, new_queue}, | |||||
{"info", 1, info}, | |||||
{"push_back", 2, push_back}, | |||||
{"pop_front", 1, pop_front}, | |||||
{"get_front", 1, get_front} | |||||
}; | |||||
ERL_NIF_INIT(etsq, nif_funcs, load, NULL, upgrade, unload) |
@ -0,0 +1,130 @@ | |||||
/* | |||||
* etsq.h | |||||
* | |||||
* Created on: Mar 21, 2016 | |||||
* Author: Vinod | |||||
*/ | |||||
#ifndef ETSQ_H_ | |||||
#define ETSQ_H_ | |||||
#include <iostream> // std::cin, std::cout | |||||
#include <map> // std::map | |||||
#include <queue> // std::queue | |||||
#include <string.h> | |||||
#include "erl_nif.h" | |||||
#define enif_make_error(env, error) enif_make_tuple2(env, \ | |||||
enif_make_atom(env, "error"), enif_make_atom(env, error)) | |||||
struct cmp_str | |||||
{ | |||||
bool operator()(char *a, char *b) const | |||||
{ | |||||
return strcmp(a, b) < 0; | |||||
} | |||||
}; | |||||
class ErlTerm | |||||
{ | |||||
public: | |||||
ErlNifEnv *term_env; | |||||
ERL_NIF_TERM term; | |||||
public: | |||||
ErlTerm(ERL_NIF_TERM erl_nif_term) | |||||
{ | |||||
term_env = enif_alloc_env(); | |||||
this->term = enif_make_copy(term_env, erl_nif_term); | |||||
} | |||||
ErlTerm(ErlTerm *erl_term) | |||||
{ | |||||
term_env = enif_alloc_env(); | |||||
this->term = enif_make_copy(term_env, erl_term->term); | |||||
} | |||||
ErlTerm(int value) | |||||
{ | |||||
term_env = enif_alloc_env(); | |||||
this->term = enif_make_int(term_env, value); | |||||
} | |||||
ErlTerm(const char *error) | |||||
{ | |||||
term_env = enif_alloc_env(); | |||||
this->term = enif_make_error(term_env, error); | |||||
} | |||||
~ErlTerm() | |||||
{ | |||||
enif_free_env(term_env); | |||||
term_env = NULL; | |||||
} | |||||
}; | |||||
typedef std::queue<ErlTerm*> ErlQueue; | |||||
class QueueInfo | |||||
{ | |||||
public: | |||||
ErlNifMutex* pmutex; | |||||
ErlQueue queue; | |||||
public: | |||||
QueueInfo(char* name) | |||||
{ | |||||
pmutex = enif_mutex_create(name); | |||||
} | |||||
~QueueInfo() | |||||
{ | |||||
enif_mutex_destroy(pmutex); | |||||
} | |||||
}; | |||||
typedef std::map<char *, QueueInfo*, cmp_str> QInfoMap; | |||||
typedef std::pair<char *, QueueInfo*> QInfoMapPair; | |||||
// Class to handle Read lock | |||||
class ReadLock | |||||
{ | |||||
ErlNifRWLock *pread_lock; | |||||
public: | |||||
ReadLock(ErlNifRWLock *pread_lock) | |||||
{ | |||||
this->pread_lock = pread_lock; | |||||
enif_rwlock_rlock(this->pread_lock); | |||||
}; | |||||
~ReadLock() | |||||
{ | |||||
enif_rwlock_runlock(pread_lock); | |||||
}; | |||||
}; | |||||
// Class to handle Write lock | |||||
class WriteLock | |||||
{ | |||||
ErlNifRWLock *pwrite_lock; | |||||
public: | |||||
WriteLock(ErlNifRWLock *pwrite_lock) | |||||
{ | |||||
this->pwrite_lock = pwrite_lock; | |||||
enif_rwlock_rwlock(this->pwrite_lock); | |||||
}; | |||||
~WriteLock() | |||||
{ | |||||
enif_rwlock_rwunlock(pwrite_lock); | |||||
}; | |||||
}; | |||||
// Class to handle Mutex lock and unlock | |||||
class Mutex | |||||
{ | |||||
ErlNifMutex *pmtx; | |||||
public: | |||||
Mutex(ErlNifMutex *pmtx) | |||||
{ | |||||
this->pmtx = pmtx; | |||||
enif_mutex_lock(this->pmtx); | |||||
}; | |||||
~Mutex() | |||||
{ | |||||
enif_mutex_unlock(pmtx); | |||||
}; | |||||
}; | |||||
#endif /* ETSQ_H_ */ |
@ -0,0 +1,7 @@ | |||||
{port_specs, [ | |||||
{"../../priv/etsq.so", ["*.cpp"]} | |||||
]}. | |||||
@ -0,0 +1,103 @@ | |||||
#include <iostream> | |||||
#include <algorithm> | |||||
#include <string.h> | |||||
class Binary { | |||||
public: | |||||
unsigned char *bin; | |||||
size_t size; | |||||
bool allocated; | |||||
Binary() : bin(NULL), size(0), allocated(false) { } | |||||
Binary(const char *data) { | |||||
bin = (unsigned char *) data; | |||||
size = strlen(data); | |||||
allocated = false; | |||||
} | |||||
Binary(const Binary &b) { | |||||
bin = b.bin; | |||||
size = b.size; | |||||
allocated = false; | |||||
} | |||||
~Binary() { | |||||
if (allocated) { | |||||
delete bin; | |||||
} | |||||
} | |||||
operator std::string() { | |||||
return (const char *) bin; | |||||
} | |||||
friend std::ostream & operator<<(std::ostream & str, Binary const &b) { | |||||
return str << b.bin; | |||||
} | |||||
bool operator<(const Binary &b) { | |||||
if(size < b.size) { | |||||
return true; | |||||
} else if (size > b.size) { | |||||
return false; | |||||
} else { | |||||
return memcmp(bin,b.bin,size) < 0; | |||||
} | |||||
} | |||||
bool operator<(Binary &b) { | |||||
if(size < b.size) { | |||||
return true; | |||||
} else if (size > b.size) { | |||||
return false; | |||||
} else { | |||||
return memcmp(bin,b.bin,size) < 0; | |||||
} | |||||
} | |||||
bool operator>(const Binary &b) { | |||||
if(size > b.size) { | |||||
return true; | |||||
} else if (size < b.size) { | |||||
return false; | |||||
} else { | |||||
return memcmp(bin,b.bin,size) > 0; | |||||
} | |||||
} | |||||
bool operator== (const Binary &b) { | |||||
if (size == b.size ) { | |||||
return memcmp(bin,b.bin, std::min(size, b.size)) == 0; | |||||
} else { | |||||
return false; | |||||
} | |||||
} | |||||
operator std::string() const { | |||||
return (const char*) bin; | |||||
} | |||||
Binary& set_data(const char *data) { | |||||
bin = (unsigned char *) data; | |||||
size = strlen(data); | |||||
return *this; | |||||
} | |||||
void copy(char *inbin, size_t insize) { | |||||
bin = (unsigned char *) operator new(insize); | |||||
allocated = true; | |||||
size = insize; | |||||
memcpy(bin, inbin, size); | |||||
} | |||||
}; | |||||
inline bool operator < (const Binary &a, const Binary &b) { | |||||
if(a.size < b.size) { | |||||
return true; | |||||
} else if (a.size > b.size) { | |||||
return false; | |||||
} else { | |||||
return memcmp(a.bin,b.bin, std::min(a.size, b.size)) < 0; | |||||
} | |||||
} | |||||
@ -0,0 +1,349 @@ | |||||
// Copyright 2013 Google Inc. All Rights Reserved. | |||||
// | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
#ifndef UTIL_BTREE_BTREE_CONTAINER_H__ | |||||
#define UTIL_BTREE_BTREE_CONTAINER_H__ | |||||
#include <iosfwd> | |||||
#include <utility> | |||||
#include "btree.h" | |||||
namespace btree { | |||||
// A common base class for btree_set, btree_map, btree_multiset and | |||||
// btree_multimap. | |||||
template <typename Tree> | |||||
class btree_container { | |||||
typedef btree_container<Tree> self_type; | |||||
public: | |||||
typedef typename Tree::params_type params_type; | |||||
typedef typename Tree::key_type key_type; | |||||
typedef typename Tree::value_type value_type; | |||||
typedef typename Tree::key_compare key_compare; | |||||
typedef typename Tree::allocator_type allocator_type; | |||||
typedef typename Tree::pointer pointer; | |||||
typedef typename Tree::const_pointer const_pointer; | |||||
typedef typename Tree::reference reference; | |||||
typedef typename Tree::const_reference const_reference; | |||||
typedef typename Tree::size_type size_type; | |||||
typedef typename Tree::difference_type difference_type; | |||||
typedef typename Tree::iterator iterator; | |||||
typedef typename Tree::const_iterator const_iterator; | |||||
typedef typename Tree::reverse_iterator reverse_iterator; | |||||
typedef typename Tree::const_reverse_iterator const_reverse_iterator; | |||||
public: | |||||
// Default constructor. | |||||
btree_container(const key_compare &comp, const allocator_type &alloc) | |||||
: tree_(comp, alloc) { | |||||
} | |||||
// Copy constructor. | |||||
btree_container(const self_type &x) | |||||
: tree_(x.tree_) { | |||||
} | |||||
// Iterator routines. | |||||
iterator begin() { return tree_.begin(); } | |||||
const_iterator begin() const { return tree_.begin(); } | |||||
iterator end() { return tree_.end(); } | |||||
const_iterator end() const { return tree_.end(); } | |||||
reverse_iterator rbegin() { return tree_.rbegin(); } | |||||
const_reverse_iterator rbegin() const { return tree_.rbegin(); } | |||||
reverse_iterator rend() { return tree_.rend(); } | |||||
const_reverse_iterator rend() const { return tree_.rend(); } | |||||
// Lookup routines. | |||||
iterator lower_bound(const key_type &key) { | |||||
return tree_.lower_bound(key); | |||||
} | |||||
const_iterator lower_bound(const key_type &key) const { | |||||
return tree_.lower_bound(key); | |||||
} | |||||
iterator upper_bound(const key_type &key) { | |||||
return tree_.upper_bound(key); | |||||
} | |||||
const_iterator upper_bound(const key_type &key) const { | |||||
return tree_.upper_bound(key); | |||||
} | |||||
std::pair<iterator,iterator> equal_range(const key_type &key) { | |||||
return tree_.equal_range(key); | |||||
} | |||||
std::pair<const_iterator,const_iterator> equal_range(const key_type &key) const { | |||||
return tree_.equal_range(key); | |||||
} | |||||
// Utility routines. | |||||
void clear() { | |||||
tree_.clear(); | |||||
} | |||||
void swap(self_type &x) { | |||||
tree_.swap(x.tree_); | |||||
} | |||||
void dump(std::ostream &os) const { | |||||
tree_.dump(os); | |||||
} | |||||
void verify() const { | |||||
tree_.verify(); | |||||
} | |||||
// Size routines. | |||||
size_type size() const { return tree_.size(); } | |||||
size_type max_size() const { return tree_.max_size(); } | |||||
bool empty() const { return tree_.empty(); } | |||||
size_type height() const { return tree_.height(); } | |||||
size_type internal_nodes() const { return tree_.internal_nodes(); } | |||||
size_type leaf_nodes() const { return tree_.leaf_nodes(); } | |||||
size_type nodes() const { return tree_.nodes(); } | |||||
size_type bytes_used() const { return tree_.bytes_used(); } | |||||
static double average_bytes_per_value() { | |||||
return Tree::average_bytes_per_value(); | |||||
} | |||||
double fullness() const { return tree_.fullness(); } | |||||
double overhead() const { return tree_.overhead(); } | |||||
bool operator==(const self_type& x) const { | |||||
if (size() != x.size()) { | |||||
return false; | |||||
} | |||||
for (const_iterator i = begin(), xi = x.begin(); i != end(); ++i, ++xi) { | |||||
if (*i != *xi) { | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
bool operator!=(const self_type& other) const { | |||||
return !operator==(other); | |||||
} | |||||
protected: | |||||
Tree tree_; | |||||
}; | |||||
template <typename T> | |||||
inline std::ostream& operator<<(std::ostream &os, const btree_container<T> &b) { | |||||
b.dump(os); | |||||
return os; | |||||
} | |||||
// A common base class for btree_set and safe_btree_set. | |||||
template <typename Tree> | |||||
class btree_unique_container : public btree_container<Tree> { | |||||
typedef btree_unique_container<Tree> self_type; | |||||
typedef btree_container<Tree> super_type; | |||||
public: | |||||
typedef typename Tree::key_type key_type; | |||||
typedef typename Tree::value_type value_type; | |||||
typedef typename Tree::size_type size_type; | |||||
typedef typename Tree::key_compare key_compare; | |||||
typedef typename Tree::allocator_type allocator_type; | |||||
typedef typename Tree::iterator iterator; | |||||
typedef typename Tree::const_iterator const_iterator; | |||||
public: | |||||
// Default constructor. | |||||
btree_unique_container(const key_compare &comp = key_compare(), | |||||
const allocator_type &alloc = allocator_type()) | |||||
: super_type(comp, alloc) { | |||||
} | |||||
// Copy constructor. | |||||
btree_unique_container(const self_type &x) | |||||
: super_type(x) { | |||||
} | |||||
// Range constructor. | |||||
template <class InputIterator> | |||||
btree_unique_container(InputIterator b, InputIterator e, | |||||
const key_compare &comp = key_compare(), | |||||
const allocator_type &alloc = allocator_type()) | |||||
: super_type(comp, alloc) { | |||||
insert(b, e); | |||||
} | |||||
// Lookup routines. | |||||
iterator find(const key_type &key) { | |||||
return this->tree_.find_unique(key); | |||||
} | |||||
const_iterator find(const key_type &key) const { | |||||
return this->tree_.find_unique(key); | |||||
} | |||||
size_type count(const key_type &key) const { | |||||
return this->tree_.count_unique(key); | |||||
} | |||||
// Insertion routines. | |||||
std::pair<iterator,bool> insert(const value_type &x) { | |||||
return this->tree_.insert_unique(x); | |||||
} | |||||
iterator insert(iterator position, const value_type &x) { | |||||
return this->tree_.insert_unique(position, x); | |||||
} | |||||
template <typename InputIterator> | |||||
void insert(InputIterator b, InputIterator e) { | |||||
this->tree_.insert_unique(b, e); | |||||
} | |||||
// Deletion routines. | |||||
int erase(const key_type &key) { | |||||
return this->tree_.erase_unique(key); | |||||
} | |||||
// Erase the specified iterator from the btree. The iterator must be valid | |||||
// (i.e. not equal to end()). Return an iterator pointing to the node after | |||||
// the one that was erased (or end() if none exists). | |||||
iterator erase(const iterator &iter) { | |||||
return this->tree_.erase(iter); | |||||
} | |||||
void erase(const iterator &first, const iterator &last) { | |||||
this->tree_.erase(first, last); | |||||
} | |||||
}; | |||||
// A common base class for btree_map and safe_btree_map. | |||||
template <typename Tree> | |||||
class btree_map_container : public btree_unique_container<Tree> { | |||||
typedef btree_map_container<Tree> self_type; | |||||
typedef btree_unique_container<Tree> super_type; | |||||
public: | |||||
typedef typename Tree::key_type key_type; | |||||
typedef typename Tree::data_type data_type; | |||||
typedef typename Tree::value_type value_type; | |||||
typedef typename Tree::mapped_type mapped_type; | |||||
typedef typename Tree::key_compare key_compare; | |||||
typedef typename Tree::allocator_type allocator_type; | |||||
private: | |||||
// A pointer-like object which only generates its value when | |||||
// dereferenced. Used by operator[] to avoid constructing an empty data_type | |||||
// if the key already exists in the map. | |||||
struct generate_value { | |||||
generate_value(const key_type &k) | |||||
: key(k) { | |||||
} | |||||
value_type operator*() const { | |||||
return std::make_pair(key, data_type()); | |||||
} | |||||
const key_type &key; | |||||
}; | |||||
public: | |||||
// Default constructor. | |||||
btree_map_container(const key_compare &comp = key_compare(), | |||||
const allocator_type &alloc = allocator_type()) | |||||
: super_type(comp, alloc) { | |||||
} | |||||
// Copy constructor. | |||||
btree_map_container(const self_type &x) | |||||
: super_type(x) { | |||||
} | |||||
// Range constructor. | |||||
template <class InputIterator> | |||||
btree_map_container(InputIterator b, InputIterator e, | |||||
const key_compare &comp = key_compare(), | |||||
const allocator_type &alloc = allocator_type()) | |||||
: super_type(b, e, comp, alloc) { | |||||
} | |||||
// Insertion routines. | |||||
data_type& operator[](const key_type &key) { | |||||
return this->tree_.insert_unique(key, generate_value(key)).first->second; | |||||
} | |||||
}; | |||||
// A common base class for btree_multiset and btree_multimap. | |||||
template <typename Tree> | |||||
class btree_multi_container : public btree_container<Tree> { | |||||
typedef btree_multi_container<Tree> self_type; | |||||
typedef btree_container<Tree> super_type; | |||||
public: | |||||
typedef typename Tree::key_type key_type; | |||||
typedef typename Tree::value_type value_type; | |||||
typedef typename Tree::size_type size_type; | |||||
typedef typename Tree::key_compare key_compare; | |||||
typedef typename Tree::allocator_type allocator_type; | |||||
typedef typename Tree::iterator iterator; | |||||
typedef typename Tree::const_iterator const_iterator; | |||||
public: | |||||
// Default constructor. | |||||
btree_multi_container(const key_compare &comp = key_compare(), | |||||
const allocator_type &alloc = allocator_type()) | |||||
: super_type(comp, alloc) { | |||||
} | |||||
// Copy constructor. | |||||
btree_multi_container(const self_type &x) | |||||
: super_type(x) { | |||||
} | |||||
// Range constructor. | |||||
template <class InputIterator> | |||||
btree_multi_container(InputIterator b, InputIterator e, | |||||
const key_compare &comp = key_compare(), | |||||
const allocator_type &alloc = allocator_type()) | |||||
: super_type(comp, alloc) { | |||||
insert(b, e); | |||||
} | |||||
// Lookup routines. | |||||
iterator find(const key_type &key) { | |||||
return this->tree_.find_multi(key); | |||||
} | |||||
const_iterator find(const key_type &key) const { | |||||
return this->tree_.find_multi(key); | |||||
} | |||||
size_type count(const key_type &key) const { | |||||
return this->tree_.count_multi(key); | |||||
} | |||||
// Insertion routines. | |||||
iterator insert(const value_type &x) { | |||||
return this->tree_.insert_multi(x); | |||||
} | |||||
iterator insert(iterator position, const value_type &x) { | |||||
return this->tree_.insert_multi(position, x); | |||||
} | |||||
template <typename InputIterator> | |||||
void insert(InputIterator b, InputIterator e) { | |||||
this->tree_.insert_multi(b, e); | |||||
} | |||||
// Deletion routines. | |||||
int erase(const key_type &key) { | |||||
return this->tree_.erase_multi(key); | |||||
} | |||||
// Erase the specified iterator from the btree. The iterator must be valid | |||||
// (i.e. not equal to end()). Return an iterator pointing to the node after | |||||
// the one that was erased (or end() if none exists). | |||||
iterator erase(const iterator &iter) { | |||||
return this->tree_.erase(iter); | |||||
} | |||||
void erase(const iterator &first, const iterator &last) { | |||||
this->tree_.erase(first, last); | |||||
} | |||||
}; | |||||
} // namespace btree | |||||
#endif // UTIL_BTREE_BTREE_CONTAINER_H__ |
@ -0,0 +1,130 @@ | |||||
// Copyright 2013 Google Inc. All Rights Reserved. | |||||
// | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
// | |||||
// A btree_map<> implements the STL unique sorted associative container | |||||
// interface and the pair associative container interface (a.k.a map<>) using a | |||||
// btree. A btree_multimap<> implements the STL multiple sorted associative | |||||
// container interface and the pair associtive container interface (a.k.a | |||||
// multimap<>) using a btree. See btree.h for details of the btree | |||||
// implementation and caveats. | |||||
#ifndef UTIL_BTREE_BTREE_MAP_H__ | |||||
#define UTIL_BTREE_BTREE_MAP_H__ | |||||
#include <algorithm> | |||||
#include <functional> | |||||
#include <memory> | |||||
#include <string> | |||||
#include <utility> | |||||
#include "btree.h" | |||||
#include "btree_container.h" | |||||
namespace btree { | |||||
// The btree_map class is needed mainly for its constructors. | |||||
template <typename Key, typename Value, | |||||
typename Compare = std::less<Key>, | |||||
typename Alloc = std::allocator<std::pair<const Key, Value> >, | |||||
int TargetNodeSize = 256> | |||||
class btree_map : public btree_map_container< | |||||
btree<btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize> > > { | |||||
typedef btree_map<Key, Value, Compare, Alloc, TargetNodeSize> self_type; | |||||
typedef btree_map_params< | |||||
Key, Value, Compare, Alloc, TargetNodeSize> params_type; | |||||
typedef btree<params_type> btree_type; | |||||
typedef btree_map_container<btree_type> super_type; | |||||
public: | |||||
typedef typename btree_type::key_compare key_compare; | |||||
typedef typename btree_type::allocator_type allocator_type; | |||||
public: | |||||
// Default constructor. | |||||
btree_map(const key_compare &comp = key_compare(), | |||||
const allocator_type &alloc = allocator_type()) | |||||
: super_type(comp, alloc) { | |||||
} | |||||
// Copy constructor. | |||||
btree_map(const self_type &x) | |||||
: super_type(x) { | |||||
} | |||||
// Range constructor. | |||||
template <class InputIterator> | |||||
btree_map(InputIterator b, InputIterator e, | |||||
const key_compare &comp = key_compare(), | |||||
const allocator_type &alloc = allocator_type()) | |||||
: super_type(b, e, comp, alloc) { | |||||
} | |||||
}; | |||||
template <typename K, typename V, typename C, typename A, int N> | |||||
inline void swap(btree_map<K, V, C, A, N> &x, | |||||
btree_map<K, V, C, A, N> &y) { | |||||
x.swap(y); | |||||
} | |||||
// The btree_multimap class is needed mainly for its constructors. | |||||
template <typename Key, typename Value, | |||||
typename Compare = std::less<Key>, | |||||
typename Alloc = std::allocator<std::pair<const Key, Value> >, | |||||
int TargetNodeSize = 256> | |||||
class btree_multimap : public btree_multi_container< | |||||
btree<btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize> > > { | |||||
typedef btree_multimap<Key, Value, Compare, Alloc, TargetNodeSize> self_type; | |||||
typedef btree_map_params< | |||||
Key, Value, Compare, Alloc, TargetNodeSize> params_type; | |||||
typedef btree<params_type> btree_type; | |||||
typedef btree_multi_container<btree_type> super_type; | |||||
public: | |||||
typedef typename btree_type::key_compare key_compare; | |||||
typedef typename btree_type::allocator_type allocator_type; | |||||
typedef typename btree_type::data_type data_type; | |||||
typedef typename btree_type::mapped_type mapped_type; | |||||
public: | |||||
// Default constructor. | |||||
btree_multimap(const key_compare &comp = key_compare(), | |||||
const allocator_type &alloc = allocator_type()) | |||||
: super_type(comp, alloc) { | |||||
} | |||||
// Copy constructor. | |||||
btree_multimap(const self_type &x) | |||||
: super_type(x) { | |||||
} | |||||
// Range constructor. | |||||
template <class InputIterator> | |||||
btree_multimap(InputIterator b, InputIterator e, | |||||
const key_compare &comp = key_compare(), | |||||
const allocator_type &alloc = allocator_type()) | |||||
: super_type(b, e, comp, alloc) { | |||||
} | |||||
}; | |||||
template <typename K, typename V, typename C, typename A, int N> | |||||
inline void swap(btree_multimap<K, V, C, A, N> &x, | |||||
btree_multimap<K, V, C, A, N> &y) { | |||||
x.swap(y); | |||||
} | |||||
} // namespace btree | |||||
#endif // UTIL_BTREE_BTREE_MAP_H__ |
@ -0,0 +1,619 @@ | |||||
#include <string> | |||||
#include <iostream> | |||||
#include <vector> | |||||
#include "erl_nif.h" | |||||
#include "erlterm.h" | |||||
#include "lru.h" | |||||
using namespace std; | |||||
namespace { /* anonymous namespace starts */ | |||||
typedef struct _obj_resource { | |||||
bool allocated; | |||||
void *object; | |||||
ErlNifMutex *emtx; | |||||
} object_resource; | |||||
ErlNifResourceFlags resource_flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER); | |||||
ErlNifResourceType* lruResource; | |||||
ErlNifResourceType* iteratorResource; | |||||
/* atoms */ | |||||
ERL_NIF_TERM atom_ok; | |||||
ERL_NIF_TERM atom_key; | |||||
ERL_NIF_TERM atom_error; | |||||
ERL_NIF_TERM atom_invalid; | |||||
ERL_NIF_TERM atom_value; | |||||
ERL_NIF_TERM atom_max_size; | |||||
ERL_NIF_TERM atom_tab; | |||||
ERL_NIF_TERM atom_lru_old; | |||||
void lru_dtor(ErlNifEnv* env, void *lru); | |||||
void iterator_dtor(ErlNifEnv* env, void *it); | |||||
int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info){ | |||||
lruResource = enif_open_resource_type(env, | |||||
"btreelru_nif", | |||||
"lru", | |||||
lru_dtor, | |||||
resource_flags, | |||||
NULL); | |||||
iteratorResource = enif_open_resource_type(env, | |||||
"btreelru_nif", | |||||
"iterator", | |||||
iterator_dtor, | |||||
resource_flags, | |||||
NULL); | |||||
atom_ok = enif_make_atom(env, "ok"); | |||||
atom_key = enif_make_atom(env, "key"); | |||||
atom_error = enif_make_atom(env, "error"); | |||||
atom_invalid = enif_make_atom(env, "invalid"); | |||||
atom_value = enif_make_atom(env, "value"); | |||||
atom_max_size = enif_make_atom(env, "max_size"); | |||||
atom_tab = enif_make_atom(env, "tab"); | |||||
atom_lru_old = enif_make_atom(env, "lru_old"); | |||||
return 0; | |||||
} | |||||
int reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info){ | |||||
return 0; | |||||
} | |||||
int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data,ERL_NIF_TERM load_info){ | |||||
return 0; | |||||
} | |||||
void lru_dtor(ErlNifEnv* env, void* _lru_btree) { | |||||
object_resource *lru_btree = (object_resource*) _lru_btree; | |||||
if (lru_btree->allocated) | |||||
delete (LRUBtree<ErlTerm,ErlTerm>*) lru_btree->object; | |||||
} | |||||
void iterator_dtor(ErlNifEnv* env, void* _lru_iterator) { | |||||
object_resource *lru_iterator = (object_resource*) _lru_iterator; | |||||
if (lru_iterator->allocated) | |||||
delete (LRUBtree<ErlTerm,ErlTerm>::iterator*) lru_iterator->object; | |||||
} | |||||
void node_free(LRUBtree<ErlTerm,ErlTerm> *bt_lru, LRUNode<ErlTerm,ErlTerm> *node) { | |||||
enif_free_env((ErlNifEnv*)node->kvenv); | |||||
return; | |||||
} | |||||
void node_kickout(LRUBtree<ErlTerm,ErlTerm> *bt_lru, LRUNode<ErlTerm,ErlTerm> *node, void *currenv) { | |||||
ErlNifEnv *env = (ErlNifEnv *) currenv; | |||||
if (bt_lru->pid_set) { | |||||
enif_send(env, &bt_lru->pid, NULL, enif_make_tuple3(env, atom_lru_old, node->key.t, node->data.t)); | |||||
} | |||||
return; | |||||
} | |||||
ERL_NIF_TERM next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
LRUNode<ErlTerm,ErlTerm> *node; | |||||
ErlTerm key; | |||||
ErlTerm value; | |||||
if (argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
key.t = argv[1]; | |||||
node = bt_lru->get(key); | |||||
if (!node) | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
node = node->next; | |||||
if (!node) | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
key.t = enif_make_copy(env, node->key.t); | |||||
value.t = enif_make_copy(env, node->data.t); | |||||
return enif_make_tuple2(env, key.t, value.t); | |||||
} | |||||
ERL_NIF_TERM prev(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
LRUNode<ErlTerm,ErlTerm> *node; | |||||
ErlTerm key; | |||||
ErlTerm value; | |||||
if (argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
key.t = argv[1]; | |||||
node = bt_lru->get(key); | |||||
if (!node) | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
node = node->prev; | |||||
if (!node) | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
key.t = enif_make_copy(env, node->key.t); | |||||
value.t = enif_make_copy(env, node->data.t); | |||||
return enif_make_tuple2(env, key.t, value.t); | |||||
} | |||||
ERL_NIF_TERM create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
unsigned long max_size; | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
ERL_NIF_TERM lru_term; | |||||
/* get max_size */ | |||||
if (enif_get_ulong(env, argv[0], &max_size) < 1){ | |||||
return enif_make_tuple2(env, atom_error, atom_max_size); | |||||
} | |||||
if (!(bt_lru = new LRUBtree<ErlTerm,ErlTerm>(max_size, node_free, node_kickout))) { | |||||
return enif_make_tuple2(env, atom_error, enif_make_atom(env, "alloction")); | |||||
} | |||||
lru = (object_resource *) enif_alloc_resource(lruResource, sizeof(object_resource)); | |||||
lru->object = bt_lru; | |||||
lru->allocated = true; | |||||
lru_term = enif_make_resource(env, lru); | |||||
enif_release_resource(lru); | |||||
return enif_make_tuple2(env, atom_ok, lru_term); | |||||
} | |||||
ERL_NIF_TERM seek(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
object_resource *it; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
LRUBtree<ErlTerm,ErlTerm>::iterator *bt_it_; | |||||
LRUBtree<ErlTerm,ErlTerm>::iterator bt_it; | |||||
ErlTerm key; | |||||
ERL_NIF_TERM it_term; | |||||
ERL_NIF_TERM kv; | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
key.t = argv[1]; | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *)lru->object; | |||||
bt_it = bt_lru->bmap.lower_bound(key); | |||||
if ( bt_it == bt_lru->bmap.end() ) { | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
} | |||||
bt_it_ = new LRUBtree<ErlTerm,ErlTerm>::iterator; | |||||
*bt_it_ = bt_it; | |||||
it = (object_resource *) enif_alloc_resource(iteratorResource, sizeof(object_resource)); | |||||
it->object = bt_it_; | |||||
it->allocated = true; | |||||
it_term = enif_make_resource(env, it); | |||||
enif_release_resource(it); | |||||
kv = enif_make_tuple2(env, bt_it->second->key.t, bt_it->second->data.t); | |||||
return enif_make_tuple2(env, kv, it_term); | |||||
} | |||||
ERL_NIF_TERM iterate_next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
object_resource *it; | |||||
LRUBtree<ErlTerm,ErlTerm>::iterator *bt_it_; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
ERL_NIF_TERM kv; | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[1], iteratorResource, (void **) &it)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *)lru->object; | |||||
bt_it_ = (LRUBtree<ErlTerm,ErlTerm>::iterator *) it->object; | |||||
if (bt_it_ == NULL) | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
(*bt_it_)++; | |||||
if ( *bt_it_ == bt_lru->bmap.end() ) { | |||||
it->allocated = false; | |||||
delete bt_it_; | |||||
it->object = NULL; | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
} | |||||
kv = enif_make_tuple2(env, (*bt_it_)->second->key.t, (*bt_it_)->second->data.t); | |||||
return enif_make_tuple2(env, atom_ok, kv); | |||||
} | |||||
ERL_NIF_TERM close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
if (argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *)lru->object; | |||||
lru->allocated = false; | |||||
delete bt_lru; | |||||
return atom_ok; | |||||
} | |||||
ERL_NIF_TERM read(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
LRUNode<ErlTerm,ErlTerm> *node; | |||||
ErlTerm key; | |||||
ERL_NIF_TERM kv; | |||||
if (argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
key.t = argv[1]; | |||||
node = bt_lru->get(key); | |||||
if (!node) | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
kv = enif_make_tuple2(env, enif_make_copy(env, node->key.t), enif_make_copy(env, node->data.t)); | |||||
return enif_make_tuple2(env, atom_ok, kv); | |||||
} | |||||
ERL_NIF_TERM remove(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
ErlTerm key; | |||||
if (argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
key.t = argv[1]; | |||||
bt_lru->erase(key); | |||||
return atom_ok; | |||||
} | |||||
ERL_NIF_TERM oldest(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
LRUNode<ErlTerm,ErlTerm> *node; | |||||
ERL_NIF_TERM key; | |||||
ERL_NIF_TERM value; | |||||
if (argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
node = bt_lru->getOldest(); | |||||
if (!node) | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
key = enif_make_copy(env, node->key.t); | |||||
value = enif_make_copy(env, node->data.t); | |||||
return enif_make_tuple2(env, key, value); | |||||
} | |||||
ERL_NIF_TERM latest(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
LRUNode<ErlTerm,ErlTerm> *node; | |||||
ERL_NIF_TERM key; | |||||
ERL_NIF_TERM value; | |||||
if (argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
// last is "last in" in the lru | |||||
node = bt_lru->getLatest(); | |||||
if (!node) | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
key = enif_make_copy(env, node->key.t); | |||||
value = enif_make_copy(env, node->data.t); | |||||
return enif_make_tuple2(env, key, value); | |||||
} | |||||
ERL_NIF_TERM last(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
LRUNode<ErlTerm,ErlTerm> *node; | |||||
ERL_NIF_TERM key; | |||||
ERL_NIF_TERM value; | |||||
if (argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
node = bt_lru->bmap.rbegin()->second; | |||||
if (!node) | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
key = enif_make_copy(env, node->key.t); | |||||
value = enif_make_copy(env, node->data.t); | |||||
return enif_make_tuple2(env, key, value); | |||||
} | |||||
ERL_NIF_TERM first(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
LRUNode<ErlTerm,ErlTerm> *node; | |||||
ERL_NIF_TERM key; | |||||
ERL_NIF_TERM value; | |||||
if (argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
node = bt_lru->bmap.begin()->second; | |||||
if (!node) | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
key = enif_make_copy(env, node->key.t); | |||||
value = enif_make_copy(env, node->data.t); | |||||
return enif_make_tuple2(env, key, value); | |||||
} | |||||
ERL_NIF_TERM write(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
ErlTerm key; | |||||
ErlTerm value; | |||||
ErlNifEnv *kv_env; | |||||
size_t size; | |||||
if (argc != 3) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm, ErlTerm> *) lru->object; | |||||
kv_env = enif_alloc_env(); | |||||
key.t = enif_make_copy(kv_env, argv[1]); | |||||
value.t = enif_make_copy(kv_env, argv[2]); | |||||
/* do not use the size of term | |||||
size = enif_size_term(key.t); | |||||
size += enif_size_term(value.t); | |||||
*/ | |||||
/* size based on entries */ | |||||
size = 1; | |||||
bt_lru->put(key, value, kv_env, env, size); | |||||
return atom_ok; | |||||
} | |||||
ERL_NIF_TERM register_pid(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
if (argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
if (!enif_get_local_pid(env, argv[1], &(bt_lru->pid))) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru->pid_set = true; | |||||
return atom_ok; | |||||
} | |||||
ERL_NIF_TERM unregister_pid(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
if (argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
bt_lru->pid_set = false; | |||||
return atom_ok; | |||||
} | |||||
ERL_NIF_TERM get_registered_pid(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
if (argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
if (!bt_lru->pid_set) { | |||||
return enif_make_tuple2(env, atom_error, atom_invalid); | |||||
} | |||||
return enif_make_pid(env, &(bt_lru->pid)); | |||||
} | |||||
ERL_NIF_TERM get_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
if (argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
return enif_make_ulong(env, bt_lru->getSize()); | |||||
} | |||||
ERL_NIF_TERM get_max_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
if (argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
return enif_make_ulong(env, bt_lru->getMaxSize()); | |||||
} | |||||
ERL_NIF_TERM set_max_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { | |||||
object_resource *lru; | |||||
unsigned long max_size; | |||||
LRUBtree<ErlTerm,ErlTerm> *bt_lru; | |||||
if (argc != 2) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if (!enif_get_resource(env, argv[0], lruResource, (void **) &lru)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
/* get max_size */ | |||||
if (enif_get_ulong(env, argv[1], &max_size) < 1){ | |||||
return enif_make_tuple2(env, atom_error, atom_max_size); | |||||
} | |||||
bt_lru = (LRUBtree<ErlTerm,ErlTerm> *) lru->object; | |||||
bt_lru->setMaxSize(max_size); | |||||
return atom_ok; | |||||
} | |||||
ErlNifFunc nif_funcs[] = { | |||||
{"create", 1, create}, | |||||
{"close", 1, close, ERL_NIF_DIRTY_JOB_IO_BOUND}, | |||||
{"register_pid", 2, register_pid}, | |||||
{"unregister_pid", 1, unregister_pid}, | |||||
{"get_registered_pid", 1, get_registered_pid}, | |||||
{"get_size", 1, get_size}, | |||||
{"get_max_size", 1, get_max_size}, | |||||
{"set_max_size", 2, set_max_size}, | |||||
{"oldest", 1, oldest}, | |||||
{"latest", 1, latest}, | |||||
{"last", 1, last}, | |||||
{"first", 1, first}, | |||||
{"read", 2, read}, | |||||
{"next", 2, next}, | |||||
{"prev", 2, prev}, | |||||
{"seek", 2, seek}, | |||||
{"iterate_next", 2, iterate_next}, | |||||
{"remove", 2, remove}, | |||||
{"write", 3, write} | |||||
}; | |||||
} /* anonymouse namespace ends */ | |||||
ERL_NIF_INIT(btree_lru, nif_funcs, load, reload, upgrade, NULL) |
@ -0,0 +1,71 @@ | |||||
#include "erl_nif.h" | |||||
class ErlTerm { | |||||
public: | |||||
ERL_NIF_TERM t; | |||||
static void *operator new(size_t size) { | |||||
return enif_alloc(size); | |||||
} | |||||
static void operator delete(void *block) { | |||||
enif_free(block); | |||||
} | |||||
bool operator< (const ErlTerm &term) { | |||||
if (enif_compare(t, term.t) < 0) | |||||
return true; | |||||
return false; | |||||
} | |||||
bool operator< (ErlTerm &term) { | |||||
if (enif_compare(t, term.t) < 0) | |||||
return true; | |||||
return false; | |||||
} | |||||
bool operator> (const ErlTerm &term) { | |||||
if (enif_compare(t, term.t) > 0) | |||||
return true; | |||||
return false; | |||||
} | |||||
bool operator> (ErlTerm &term) { | |||||
if (enif_compare(t, term.t) > 0) | |||||
return true; | |||||
return false; | |||||
} | |||||
bool operator== (const ErlTerm &term) { | |||||
if (enif_compare(t, term.t) == 0) | |||||
return true; | |||||
return false; | |||||
} | |||||
bool operator== (ErlTerm &term) { | |||||
if (enif_compare(t, term.t) == 0) | |||||
return true; | |||||
return false; | |||||
} | |||||
}; | |||||
inline bool operator < (const ErlTerm &a, const ErlTerm &b) { | |||||
if (enif_compare(a.t, b.t) < 0) | |||||
return true; | |||||
return false; | |||||
} | |||||
#if 0 | |||||
// extend std::hash to understand ErlTerm used by hashmap not btree | |||||
namespace std { | |||||
template <> | |||||
struct hash<ErlTerm> | |||||
{ | |||||
size_t operator()(const ErlTerm& term) const | |||||
{ | |||||
return (size_t) enif_hash_term(term.t); | |||||
} | |||||
}; | |||||
} | |||||
#endif |
@ -0,0 +1,266 @@ | |||||
#include "btree_map.h" | |||||
#include <algorithm> | |||||
#include <iostream> | |||||
#include "murmurhash2.h" | |||||
#include "binary.h" | |||||
#include "erl_nif.h" | |||||
// extend std::hash to understand Binary type | |||||
namespace std { | |||||
template <> | |||||
struct hash<Binary> | |||||
{ | |||||
size_t operator()(const Binary& b) const | |||||
{ | |||||
return MurmurHash2(b.bin, b.size, 4242); | |||||
} | |||||
}; | |||||
} | |||||
template <typename K, typename V> | |||||
struct LRUNode | |||||
{ | |||||
K key; | |||||
V data; | |||||
void *kvenv; | |||||
LRUNode<K,V> *prev; | |||||
LRUNode<K,V> *next; | |||||
size_t size; | |||||
LRUNode(void *kvenv = NULL, size_t size=0) : kvenv(kvenv), prev(NULL), next(NULL), size(size) { } | |||||
/* | |||||
static void *LRUNode<ErlTerm,ErlTerm>::operator new(size_t size) { | |||||
return enif_alloc(size); | |||||
} | |||||
static void operator delete(void *block) { | |||||
enif_free(block); | |||||
} | |||||
*/ | |||||
void printChain() { | |||||
LRUNode<K,V>* node; | |||||
int i=11; | |||||
std::cout << "("; | |||||
for(node = this; node && i; node = node->next, i--) { | |||||
std::cout << node->key << " -> "; | |||||
} | |||||
if (node) { | |||||
std::cout << " loop detection end "; | |||||
} else { | |||||
std::cout << " end "; | |||||
} | |||||
std::cout << ")" << std::endl; | |||||
} | |||||
void printNextPrevKey() { | |||||
std::cout << "("; | |||||
printNextKey(); | |||||
printPrevKey(); | |||||
std::cout << ")"; | |||||
} | |||||
void printNextKey() { | |||||
if (next) { | |||||
std::cout << "next key " << next->key << " "; | |||||
} | |||||
} | |||||
void printPrevKey() { | |||||
if (prev) { | |||||
std::cout << "prev key " << prev->key << " "; | |||||
} | |||||
} | |||||
}; | |||||
template <class K,class V> | |||||
class LRUBtree { | |||||
private: | |||||
LRUNode<K,V> *oldest; | |||||
LRUNode<K,V> *latest; | |||||
unsigned long size; | |||||
unsigned long max_size; | |||||
void (*node_free)(LRUBtree<K,V> *lru, LRUNode<K,V> *node); | |||||
void (*node_kickout)(LRUBtree<K,V> *lru, LRUNode<K,V> *node, void *call_env); | |||||
typedef btree::btree_map<K, LRUNode<K,V>*> LRUBtree_map; | |||||
public: | |||||
LRUBtree_map bmap; | |||||
bool pid_set = false; | |||||
ErlNifPid pid; | |||||
typedef typename LRUBtree_map::iterator iterator; | |||||
typedef typename LRUBtree_map::reverse_iterator reverse_iterator; | |||||
void printLatest() { | |||||
if (latest) { | |||||
std::cout << " latest " << latest->key; | |||||
} else { | |||||
std::cout << " no data in lru "; | |||||
} | |||||
} | |||||
private: | |||||
LRUNode<K,V>* erase(LRUNode<K,V> *node) { | |||||
if (node->next) { | |||||
node->next->prev = node->prev; | |||||
} | |||||
if (node->prev) { | |||||
node->prev->next = node->next; | |||||
} | |||||
if (node == oldest) { | |||||
oldest = node->prev; | |||||
} | |||||
if (node == latest) { | |||||
latest = node->next; | |||||
} | |||||
if (node_free) { | |||||
node_free(this, node); | |||||
} | |||||
node->next = NULL; | |||||
node->prev = NULL; | |||||
return node; | |||||
} | |||||
void printOldest() { | |||||
if(oldest) { | |||||
std::cout << " oldest " << oldest->key; | |||||
} else { | |||||
std::cout << " no data in lru "; | |||||
} | |||||
} | |||||
void check_size(void *call_env) { | |||||
if (size > max_size) { | |||||
if (oldest) { // remove check if oldest exist and rely on max_size always being positive | |||||
if (node_kickout) | |||||
node_kickout(this, oldest, call_env); | |||||
erase(oldest->key); | |||||
} | |||||
} | |||||
} | |||||
#define SIZE_100MB 100*1024*1024 | |||||
public: | |||||
LRUBtree(unsigned long max_size = SIZE_100MB, | |||||
void (*node_free)(LRUBtree<K,V> *lru, LRUNode<K,V> *node) = NULL, | |||||
void (*node_kickout)(LRUBtree<K,V> *lru, LRUNode<K,V> *node, void *call_env) = NULL) | |||||
: oldest(NULL), latest(NULL), size(0), max_size(max_size), node_free(node_free), | |||||
node_kickout(node_kickout) { } | |||||
~LRUBtree() { | |||||
LRUNode<K,V> *node; | |||||
LRUNode<K,V> *next; | |||||
node = latest; | |||||
while(node) { | |||||
if (node_free) { | |||||
node_free(this, node); | |||||
} | |||||
next = node->next; | |||||
delete node; | |||||
node = next; | |||||
} | |||||
} | |||||
void printSize() { | |||||
std::cout << "size " << size << std::endl; | |||||
} | |||||
unsigned long getSize() { | |||||
return size; | |||||
} | |||||
unsigned long getMaxSize() { | |||||
return max_size; | |||||
} | |||||
void setMaxSize(unsigned long max_size) { | |||||
this->max_size = max_size; | |||||
} | |||||
void erase(K key) { | |||||
LRUNode<K,V> *node; | |||||
if ((node = bmap[key])) { | |||||
erase(node); | |||||
bmap.erase(key); | |||||
size -= node->size; | |||||
delete node; | |||||
} | |||||
} | |||||
inline void put(K key, V data, | |||||
void *kvenv = NULL, void *call_env = NULL, | |||||
size_t size = 1) { | |||||
LRUNode<K,V> *node; | |||||
this->size += size; | |||||
check_size(call_env); | |||||
// overwrite already existing key | |||||
if ((node = bmap[key])) { | |||||
this->size -= node->size; | |||||
erase(node); | |||||
node->kvenv = kvenv; | |||||
node->next = latest; | |||||
node->size = size; | |||||
if (node->next) { | |||||
node->next->prev = node; | |||||
} | |||||
if (!oldest) { | |||||
oldest = node; | |||||
} | |||||
latest = node; | |||||
node->key = key; | |||||
node->data = data; | |||||
} | |||||
else if (!oldest) { | |||||
node = new LRUNode<K,V>; | |||||
node->key = key; | |||||
node->data = data; | |||||
node->kvenv = kvenv; | |||||
node->size = size; | |||||
oldest = node; | |||||
latest = node; | |||||
bmap[node->key] = node; | |||||
} | |||||
else { | |||||
node = new LRUNode<K,V>; | |||||
node->key = key; | |||||
node->data = data; | |||||
node->kvenv = kvenv; | |||||
node->size = size; | |||||
latest->prev = node; | |||||
node->next = latest; | |||||
latest = node; | |||||
bmap[node->key] = node; | |||||
} | |||||
} | |||||
LRUNode<K,V>* get(K key) { | |||||
return bmap[key]; | |||||
} | |||||
LRUNode<K,V>* getOldest() { | |||||
return oldest; | |||||
} | |||||
LRUNode<K,V>* getLatest() { | |||||
return latest; | |||||
} | |||||
LRUNode<K,V>* getNext(LRUNode<K,V> *node) { | |||||
return node->next; | |||||
} | |||||
LRUNode<K,V>* getPrev(LRUNode<K,V> *node) { | |||||
return node->prev; | |||||
} | |||||
}; | |||||
@ -0,0 +1,73 @@ | |||||
//----------------------------------------------------------------------------- | |||||
// MurmurHash2, by Austin Appleby | |||||
// Note - This code makes a few assumptions about how your machine behaves - | |||||
// 1. We can read a 4-byte value from any address without crashing | |||||
// 2. sizeof(int) == 4 | |||||
// And it has a few limitations - | |||||
// 1. It will not wo | |||||
// | |||||
// rk incrementally. | |||||
// 2. It will not produce the same results on little-endian and big-endian | |||||
// machines. | |||||
unsigned int MurmurHash2 ( const void * key, int len, unsigned int seed ) | |||||
{ | |||||
// 'm' and 'r' are mixing constants generated offline. | |||||
// They're not really 'magic', they just happen to work well. | |||||
const unsigned int m = 0x5bd1e995; | |||||
const int r = 24; | |||||
// Initialize the hash to a 'random' value | |||||
unsigned int h = seed ^ len; | |||||
// Mix 4 bytes at a time into the hash | |||||
const unsigned char * data = (const unsigned char *)key; | |||||
while(len >= 4) | |||||
{ | |||||
unsigned int k = *(unsigned int *)data; | |||||
k *= m; | |||||
k ^= k >> r; | |||||
k *= m; | |||||
h *= m; | |||||
h ^= k; | |||||
data += 4; | |||||
len -= 4; | |||||
} | |||||
// Handle the last few bytes of the input array | |||||
switch(len) | |||||
{ | |||||
case 3: h ^= data[2] << 16; | |||||
case 2: h ^= data[1] << 8; | |||||
case 1: h ^= data[0]; | |||||
h *= m; | |||||
}; | |||||
// Do a few final mixes of t | |||||
// | |||||
// | |||||
// | |||||
// he hash to ensure the last few | |||||
// bytes are well-incorporated. | |||||
h ^= h >> 13; | |||||
h *= m; | |||||
h ^= h >> 15; | |||||
return h; | |||||
} | |||||
@ -0,0 +1,7 @@ | |||||
{port_specs, [ | |||||
{"../../priv/btreelru_nif.so", ["btreelru_nif.cpp"]} | |||||
]}. | |||||
@ -0,0 +1,90 @@ | |||||
#include "erl_nif.h" | |||||
#define A_OK(env) enif_make_atom(env, "ok") | |||||
#define assert_badarg(S, Env) if (! S) { return enif_make_badarg(env); } | |||||
static ErlNifResourceType* array_handle = NULL; | |||||
static void array_handle_cleanup(ErlNifEnv* env, void* arg) {} | |||||
static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) | |||||
{ | |||||
ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; | |||||
array_handle = enif_open_resource_type(env, "native_array_nif", "array_handle", | |||||
&array_handle_cleanup, flags, 0); | |||||
// 用于存储指针的数组, 最多1000个array | |||||
*priv = enif_alloc(1000 * sizeof(void*)); | |||||
return 0; | |||||
} | |||||
static void unload(ErlNifEnv* env, void* priv) | |||||
{ | |||||
enif_free(priv); | |||||
} | |||||
static ERL_NIF_TERM new_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
// 取参数 | |||||
int refindex; | |||||
assert_badarg(enif_get_int(env, argv[0], &refindex), env); | |||||
// 取参数length | |||||
unsigned long length; | |||||
assert_badarg(enif_get_ulong(env, argv[1], &length), env); | |||||
// 分配内存 | |||||
// unsigned char* ref = enif_alloc_resource(array_handle, length); | |||||
unsigned char* ref = enif_alloc(length); | |||||
// 保存指针 | |||||
*((unsigned char**)enif_priv_data(env) + refindex) = ref; | |||||
return A_OK(env); | |||||
} | |||||
static ERL_NIF_TERM get_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
// 取参数ref | |||||
int refindex; | |||||
assert_badarg(enif_get_int(env, argv[0], &refindex), env); | |||||
unsigned char* ref = *((unsigned char**)enif_priv_data(env) + refindex); | |||||
assert_badarg(ref, env); | |||||
// 取参数offset | |||||
unsigned long offset; | |||||
assert_badarg(enif_get_ulong(env, argv[1], &offset), env); | |||||
return enif_make_int(env, (int)(*(ref + offset - 1))); | |||||
} | |||||
static ERL_NIF_TERM put_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
// 取参数ref | |||||
int refindex; | |||||
assert_badarg(enif_get_int(env, argv[0], &refindex), env); | |||||
unsigned char* ref = *((unsigned char**)enif_priv_data(env) + refindex); | |||||
// 取参数offset | |||||
unsigned long offset; | |||||
assert_badarg(enif_get_ulong(env, argv[1], &offset), env); | |||||
// 取参数newval | |||||
unsigned int newval; | |||||
assert_badarg(enif_get_uint(env, argv[2], &newval), env); | |||||
// 赋值 | |||||
*(ref + offset - 1) = (unsigned char)newval; | |||||
return A_OK(env); | |||||
} | |||||
static ERL_NIF_TERM delete_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
// 取参数ref | |||||
int refindex; | |||||
assert_badarg(enif_get_int(env, argv[0], &refindex), env); | |||||
unsigned char* ref = *((unsigned char**)enif_priv_data(env) + refindex); | |||||
//enif_release_resource(ref); | |||||
enif_free(ref); | |||||
return A_OK(env); | |||||
} | |||||
static ErlNifFunc nif_funcs[] = { | |||||
{"new", 2, new_nif}, | |||||
{"get", 2, get_nif}, | |||||
{"put", 3, put_nif}, | |||||
{"delete", 1, delete_nif}, | |||||
}; | |||||
ERL_NIF_INIT(native_array, nif_funcs, &load, NULL, NULL, &unload) | |||||
@ -0,0 +1,7 @@ | |||||
{port_specs, [ | |||||
{"../../priv/native_array_nif.so", ["*.c"]} | |||||
]}. | |||||
@ -0,0 +1,905 @@ | |||||
#include "NeuralTable.h" | |||||
/* !!!! A NOTE ON KEYS !!!! | |||||
* Keys should be integer values passed from the erlang emulator, | |||||
* and should be generated by a hashing function. There is no easy | |||||
* way to hash an erlang term from a NIF, but ERTS is more than | |||||
* capable of doing so. | |||||
* | |||||
* Additionally, this workaround means that traditional collision | |||||
* handling mechanisms for hash tables will not work without | |||||
* special consideration. For instance, to compare keys as you | |||||
* would by storing linked lists, you must retrieve the stored | |||||
* tuple and call enif_compare or enif_is_identical on the key | |||||
* elements of each tuple. | |||||
*/ | |||||
table_set NeuralTable::tables; | |||||
atomic<bool> NeuralTable::running(true); | |||||
ErlNifMutex *NeuralTable::table_mutex; | |||||
NeuralTable::NeuralTable(unsigned int kp) { | |||||
for (int i = 0; i < BUCKET_COUNT; ++i) { | |||||
ErlNifEnv *env = enif_alloc_env(); | |||||
env_buckets[i] = env; | |||||
locks[i] = enif_rwlock_create("neural_table"); | |||||
garbage_cans[i] = 0; | |||||
reclaimable[i] = enif_make_list(env, 0); | |||||
} | |||||
start_gc(); | |||||
start_batch(); | |||||
key_pos = kp; | |||||
} | |||||
NeuralTable::~NeuralTable() { | |||||
stop_batch(); | |||||
stop_gc(); | |||||
for (int i = 0; i < BUCKET_COUNT; ++i) { | |||||
enif_rwlock_destroy(locks[i]); | |||||
enif_free_env(env_buckets[i]); | |||||
} | |||||
} | |||||
/* ================================================================ | |||||
* MakeTable | |||||
* Allocates a new table, assuming a unique atom identifier. This | |||||
* table is stored in a static container. All interactions with | |||||
* the table must be performed through the static class API. | |||||
*/ | |||||
ERL_NIF_TERM NeuralTable::MakeTable(ErlNifEnv *env, ERL_NIF_TERM name, ERL_NIF_TERM key_pos) { | |||||
char *atom; | |||||
string key; | |||||
unsigned int len = 0, | |||||
pos = 0; | |||||
ERL_NIF_TERM ret; | |||||
// Allocate space for the name of the table | |||||
enif_get_atom_length(env, name, &len, ERL_NIF_LATIN1); | |||||
atom = (char*)enif_alloc(len + 1); | |||||
// Fetch the value of the atom and store it in a string (because I can, that's why) | |||||
enif_get_atom(env, name, atom, len + 1, ERL_NIF_LATIN1); | |||||
key = atom; | |||||
// Deallocate that space | |||||
enif_free(atom); | |||||
// Get the key position value | |||||
enif_get_uint(env, key_pos, &pos); | |||||
enif_mutex_lock(table_mutex); | |||||
if (NeuralTable::tables.find(key) != NeuralTable::tables.end()) { | |||||
// Table already exists? Bad monkey! | |||||
ret = enif_make_badarg(env); | |||||
} else { | |||||
// All good. Make the table | |||||
NeuralTable::tables[key] = new NeuralTable(pos); | |||||
ret = enif_make_atom(env, "ok"); | |||||
} | |||||
enif_mutex_unlock(table_mutex); | |||||
return ret; | |||||
} | |||||
/* ================================================================ | |||||
* GetTable | |||||
* Retrieves a handle to the table referenced by name, assuming | |||||
* such a table exists. If not, throw badarg. | |||||
*/ | |||||
NeuralTable* NeuralTable::GetTable(ErlNifEnv *env, ERL_NIF_TERM name) { | |||||
char *atom = NULL; | |||||
string key; | |||||
unsigned len = 0; | |||||
NeuralTable *ret = NULL; | |||||
table_set::const_iterator it; | |||||
// Allocate space for the table name | |||||
enif_get_atom_length(env, name, &len, ERL_NIF_LATIN1); | |||||
atom = (char*)enif_alloc(len + 1); | |||||
// Copy the table name into a string | |||||
enif_get_atom(env, name, atom, len + 1, ERL_NIF_LATIN1); | |||||
key = atom; | |||||
// Deallocate that space | |||||
enif_free(atom); | |||||
// Look for the table and return its pointer if found | |||||
it = NeuralTable::tables.find(key); | |||||
if (it != NeuralTable::tables.end()) { | |||||
ret = it->second; | |||||
} | |||||
return ret; | |||||
} | |||||
/* ================================================================ | |||||
* Insert | |||||
* Inserts a tuple into the table with key. | |||||
*/ | |||||
ERL_NIF_TERM NeuralTable::Insert(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM object) { | |||||
NeuralTable *tb; | |||||
ERL_NIF_TERM ret, old; | |||||
unsigned long int entry_key = 0; | |||||
// Grab table or bail. | |||||
tb = GetTable(env, table); | |||||
if (tb == NULL) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
// Get key value. | |||||
enif_get_ulong(env, key, &entry_key); | |||||
// Lock the key. | |||||
tb->rwlock(entry_key); | |||||
// Attempt to lookup the value. If nonempty, increment | |||||
// discarded term counter and return a copy of the | |||||
// old value | |||||
if (tb->find(entry_key, old)) { | |||||
tb->reclaim(entry_key, old); | |||||
ret = enif_make_tuple2(env, enif_make_atom(env, "ok"), enif_make_copy(env, old)); | |||||
} else { | |||||
ret = enif_make_atom(env, "ok"); | |||||
} | |||||
// Write that shit out | |||||
tb->put(entry_key, object); | |||||
// Oh, and unlock the key if you would. | |||||
tb->rwunlock(entry_key); | |||||
return ret; | |||||
} | |||||
/* ================================================================ | |||||
* InsertNew | |||||
* Inserts a tuple into the table with key, assuming there is not | |||||
* a value with key already. Returns true if there was no value | |||||
* for key, or false if there was. | |||||
*/ | |||||
ERL_NIF_TERM NeuralTable::InsertNew(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM object) { | |||||
NeuralTable *tb; | |||||
ERL_NIF_TERM ret, old; | |||||
unsigned long int entry_key = 0; | |||||
// Get the table or bail | |||||
tb = GetTable(env, table); | |||||
if (tb == NULL) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
// Get the key value | |||||
enif_get_ulong(env, key, &entry_key); | |||||
// Get write lock for the key | |||||
tb->rwlock(entry_key); | |||||
if (tb->find(entry_key, old)) { | |||||
// Key was found. Return false and do not insert | |||||
ret = enif_make_atom(env, "false"); | |||||
} else { | |||||
// Key was not found. Return true and insert | |||||
tb->put(entry_key, object); | |||||
ret = enif_make_atom(env, "true"); | |||||
} | |||||
// Release write lock for the key | |||||
tb->rwunlock(entry_key); | |||||
return ret; | |||||
} | |||||
/* ================================================================ | |||||
* Increment | |||||
* Processes a list of update operations. Each operation specifies | |||||
* a position in the stored tuple to update and an integer to add | |||||
* to it. | |||||
*/ | |||||
ERL_NIF_TERM NeuralTable::Increment(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM ops) { | |||||
NeuralTable *tb; | |||||
ERL_NIF_TERM ret, old; | |||||
ERL_NIF_TERM it; | |||||
unsigned long int entry_key = 0; | |||||
// Get table handle or bail | |||||
tb = GetTable(env, table); | |||||
if (tb == NULL) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
// Get key value | |||||
enif_get_ulong(env, key, &entry_key); | |||||
// Acquire read/write lock for key | |||||
tb->rwlock(entry_key); | |||||
// Try to read the value as it is | |||||
if (tb->find(entry_key, old)) { | |||||
// Value exists | |||||
ERL_NIF_TERM op_cell; | |||||
const ERL_NIF_TERM *tb_tpl; | |||||
const ERL_NIF_TERM *op_tpl; | |||||
ERL_NIF_TERM *new_tpl; | |||||
ErlNifEnv *bucket_env = tb->get_env(entry_key); | |||||
unsigned long int pos = 0; | |||||
long int incr = 0; | |||||
unsigned int ops_length = 0; | |||||
int op_arity = 0, | |||||
tb_arity = 0; | |||||
// Expand tuple to work on elements | |||||
enif_get_tuple(bucket_env, old, &tb_arity, &tb_tpl); | |||||
// Allocate space for a copy the contents of the table | |||||
// tuple and copy it in. All changes are to be made to | |||||
// the copy of the tuple. | |||||
new_tpl = (ERL_NIF_TERM*)enif_alloc(sizeof(ERL_NIF_TERM) * tb_arity); | |||||
memcpy(new_tpl, tb_tpl, sizeof(ERL_NIF_TERM) * tb_arity); | |||||
// Create empty list cell for return value. | |||||
ret = enif_make_list(env, 0); | |||||
// Set iterator to first cell of ops | |||||
it = ops; | |||||
while(!enif_is_empty_list(env, it)) { | |||||
long int value = 0; | |||||
enif_get_list_cell(env, it, &op_cell, &it); // op_cell = hd(it), it = tl(it) | |||||
enif_get_tuple(env, op_cell, &op_arity, &op_tpl); // op_arity = tuple_size(op_cell), op_tpl = [TplPos1, TplPos2] | |||||
enif_get_ulong(env, op_tpl[0], &pos); // pos = (uint64)op_tpl[0] | |||||
enif_get_long(env, op_tpl[1], &incr); // incr = (uint64)op_tpl[1] | |||||
// Is the operation trying to modify a nonexistant | |||||
// position? | |||||
if (pos <= 0 || pos > tb_arity) { | |||||
ret = enif_make_badarg(env); | |||||
goto bailout; | |||||
} | |||||
// Is the operation trying to add to a value that's | |||||
// not a number? | |||||
if (!enif_is_number(bucket_env, new_tpl[pos - 1])) { | |||||
ret = enif_make_badarg(env); | |||||
goto bailout; | |||||
} | |||||
// Update the value stored in the tuple. | |||||
enif_get_long(env, new_tpl[pos - 1], &value); | |||||
tb->reclaim(entry_key, new_tpl[pos - 1]); | |||||
new_tpl[pos - 1] = enif_make_long(bucket_env, value + incr); | |||||
// Copy the new value to the head of the return list | |||||
ret = enif_make_list_cell(env, enif_make_copy(env, new_tpl[pos - 1]), ret); | |||||
} | |||||
tb->put(entry_key, enif_make_tuple_from_array(bucket_env, new_tpl, tb_arity)); | |||||
// Bailout allows cancelling the update opertion | |||||
// in case something goes wrong. It must always | |||||
// come after tb->put and before enif_free and | |||||
// rwunlock | |||||
bailout: | |||||
enif_free(new_tpl); | |||||
} else { | |||||
ret = enif_make_badarg(env); | |||||
} | |||||
// Release the rwlock for entry_key | |||||
tb->rwunlock(entry_key); | |||||
return ret; | |||||
} | |||||
/* ================================================================ | |||||
* Unshift | |||||
* Processes a list of update operations. Each update operation is | |||||
* a tuple specifying the position of a list in the stored value to | |||||
* update and a list of values to append. Elements are shifted from | |||||
* the input list to the stored list, so: | |||||
* | |||||
* unshift([a,b,c,d]) results in [d,c,b,a] | |||||
*/ | |||||
ERL_NIF_TERM NeuralTable::Unshift(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM ops) { | |||||
NeuralTable *tb; | |||||
ERL_NIF_TERM ret, old, it; | |||||
unsigned long int entry_key; | |||||
ErlNifEnv *bucket_env; | |||||
tb = GetTable(env, table); | |||||
if (tb == NULL) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
enif_get_ulong(env, key, &entry_key); | |||||
tb->rwlock(entry_key); | |||||
bucket_env = tb->get_env(entry_key); | |||||
if (tb->find(entry_key, old)) { | |||||
const ERL_NIF_TERM *old_tpl, | |||||
*op_tpl; | |||||
ERL_NIF_TERM *new_tpl; | |||||
int tb_arity = 0, | |||||
op_arity = 0; | |||||
unsigned long pos = 0; | |||||
unsigned int new_length = 0; | |||||
ERL_NIF_TERM op, | |||||
unshift, | |||||
copy_it, | |||||
copy_val; | |||||
enif_get_tuple(bucket_env, old, &tb_arity, &old_tpl); | |||||
new_tpl = (ERL_NIF_TERM*)enif_alloc(sizeof(ERL_NIF_TERM) * tb_arity); | |||||
memcpy(new_tpl, old_tpl, sizeof(ERL_NIF_TERM) * tb_arity); | |||||
it = ops; | |||||
ret = enif_make_list(env, 0); | |||||
while (!enif_is_empty_list(env, it)) { | |||||
// Examine the operation. | |||||
enif_get_list_cell(env, it, &op, &it); // op = hd(it), it = tl(it) | |||||
enif_get_tuple(env, op, &op_arity, &op_tpl); // op_arity = tuple_size(op), op_tpl = [TplPos1, TplPos2] | |||||
enif_get_ulong(env, op_tpl[0], &pos); // Tuple position to modify | |||||
unshift = op_tpl[1]; // Values to unshfit | |||||
// Argument 1 of the operation tuple is position; | |||||
// make sure it's within the bounds of the tuple | |||||
// in the table. | |||||
if (pos <= 0 || pos > tb_arity) { | |||||
ret = enif_make_badarg(env); | |||||
goto bailout; | |||||
} | |||||
// Make sure we were passed a list of things to push | |||||
// onto the posth element of the entry | |||||
if (!enif_is_list(env, unshift)) { | |||||
ret = enif_make_badarg(env); | |||||
} | |||||
// Now iterate over unshift, moving its values to | |||||
// the head of new_tpl[pos - 1] one by one | |||||
copy_it = unshift; | |||||
while (!enif_is_empty_list(env, copy_it)) { | |||||
enif_get_list_cell(env, copy_it, ©_val, ©_it); | |||||
new_tpl[pos - 1] = enif_make_list_cell(bucket_env, enif_make_copy(bucket_env, copy_val), new_tpl[pos - 1]); | |||||
} | |||||
enif_get_list_length(bucket_env, new_tpl[pos - 1], &new_length); | |||||
ret = enif_make_list_cell(env, enif_make_uint(env, new_length), ret); | |||||
} | |||||
tb->put(entry_key, enif_make_tuple_from_array(bucket_env, new_tpl, tb_arity)); | |||||
bailout: | |||||
enif_free(new_tpl); | |||||
} else { | |||||
ret = enif_make_badarg(env); | |||||
} | |||||
tb->rwunlock(entry_key); | |||||
return ret; | |||||
} | |||||
ERL_NIF_TERM NeuralTable::Shift(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM ops) { | |||||
NeuralTable *tb; | |||||
ERL_NIF_TERM ret, old, it; | |||||
unsigned long int entry_key; | |||||
ErlNifEnv *bucket_env; | |||||
tb = GetTable(env, table); | |||||
if (tb == NULL) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
enif_get_ulong(env, key, &entry_key); | |||||
tb->rwlock(entry_key); | |||||
bucket_env = tb->get_env(entry_key); | |||||
if (tb->find(entry_key, old)) { | |||||
const ERL_NIF_TERM *old_tpl; | |||||
const ERL_NIF_TERM *op_tpl; | |||||
ERL_NIF_TERM *new_tpl; | |||||
int tb_arity = 0, | |||||
op_arity = 0; | |||||
unsigned long pos = 0, | |||||
count = 0; | |||||
ERL_NIF_TERM op, list, shifted, reclaim; | |||||
enif_get_tuple(bucket_env, old, &tb_arity, &old_tpl); | |||||
new_tpl = (ERL_NIF_TERM*)enif_alloc(tb_arity * sizeof(ERL_NIF_TERM)); | |||||
memcpy(new_tpl, old_tpl, sizeof(ERL_NIF_TERM) * tb_arity); | |||||
it = ops; | |||||
ret = enif_make_list(env, 0); | |||||
reclaim = enif_make_list(bucket_env, 0); | |||||
while(!enif_is_empty_list(env, it)) { | |||||
enif_get_list_cell(env, it, &op, &it); | |||||
enif_get_tuple(env, op, &op_arity, &op_tpl); | |||||
enif_get_ulong(env, op_tpl[0], &pos); | |||||
enif_get_ulong(env, op_tpl[1], &count); | |||||
if (pos <= 0 || pos > tb_arity) { | |||||
ret = enif_make_badarg(env); | |||||
goto bailout; | |||||
} | |||||
if (!enif_is_list(env, new_tpl[pos -1])) { | |||||
ret = enif_make_badarg(env); | |||||
goto bailout; | |||||
} | |||||
shifted = enif_make_list(env, 0); | |||||
if (count > 0) { | |||||
ERL_NIF_TERM copy_it = new_tpl[pos - 1], | |||||
val; | |||||
int i = 0; | |||||
while (i < count && !enif_is_empty_list(bucket_env, copy_it)) { | |||||
enif_get_list_cell(bucket_env, copy_it, &val, ©_it); | |||||
++i; | |||||
shifted = enif_make_list_cell(env, enif_make_copy(env, val), shifted); | |||||
reclaim = enif_make_list_cell(env, val, reclaim); | |||||
} | |||||
new_tpl[pos - 1] = copy_it; | |||||
} else if (count < 0) { | |||||
ERL_NIF_TERM copy_it = new_tpl[pos - 1], | |||||
val; | |||||
while (!enif_is_empty_list(bucket_env, copy_it)) { | |||||
enif_get_list_cell(bucket_env, copy_it, &val, ©_it); | |||||
shifted = enif_make_list_cell(env, enif_make_copy(env, val), shifted); | |||||
reclaim = enif_make_list_cell(env, val, reclaim); | |||||
} | |||||
new_tpl[pos - 1] = copy_it; | |||||
} | |||||
ret = enif_make_list_cell(env, shifted, ret); | |||||
} | |||||
tb->put(entry_key, enif_make_tuple_from_array(bucket_env, new_tpl, tb_arity)); | |||||
tb->reclaim(entry_key, reclaim); | |||||
bailout: | |||||
enif_free(new_tpl); | |||||
} else { | |||||
ret = enif_make_badarg(env); | |||||
} | |||||
tb->rwunlock(entry_key); | |||||
return ret; | |||||
} | |||||
ERL_NIF_TERM NeuralTable::Swap(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM ops) { | |||||
NeuralTable *tb; | |||||
ERL_NIF_TERM ret, old, it; | |||||
unsigned long int entry_key; | |||||
ErlNifEnv *bucket_env; | |||||
tb = GetTable(env, table); | |||||
if (tb == NULL) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
enif_get_ulong(env, key, &entry_key); | |||||
tb->rwlock(entry_key); | |||||
bucket_env = tb->get_env(entry_key); | |||||
if (tb->find(entry_key, old)) { | |||||
const ERL_NIF_TERM *old_tpl; | |||||
const ERL_NIF_TERM *op_tpl; | |||||
ERL_NIF_TERM *new_tpl; | |||||
int tb_arity = 0, | |||||
op_arity = 0; | |||||
unsigned long pos = 0; | |||||
ERL_NIF_TERM op, list, shifted, reclaim; | |||||
enif_get_tuple(bucket_env, old, &tb_arity, &old_tpl); | |||||
new_tpl = (ERL_NIF_TERM*)enif_alloc(tb_arity * sizeof(ERL_NIF_TERM)); | |||||
memcpy(new_tpl, old_tpl, sizeof(ERL_NIF_TERM) * tb_arity); | |||||
it = ops; | |||||
ret = enif_make_list(env, 0); | |||||
reclaim = enif_make_list(bucket_env, 0); | |||||
while (!enif_is_empty_list(env, it)) { | |||||
enif_get_list_cell(env, it, &op, &it); | |||||
enif_get_tuple(env, op, &op_arity, &op_tpl); | |||||
enif_get_ulong(env, op_tpl[0], &pos); | |||||
if (pos <= 0 || pos > tb_arity) { | |||||
ret = enif_make_badarg(env); | |||||
goto bailout; | |||||
} | |||||
reclaim = enif_make_list_cell(bucket_env, new_tpl[pos - 1], reclaim); | |||||
ret = enif_make_list_cell(env, enif_make_copy(env, new_tpl[pos -1]), ret); | |||||
new_tpl[pos - 1] = enif_make_copy(bucket_env, op_tpl[1]); | |||||
} | |||||
tb->put(entry_key, enif_make_tuple_from_array(bucket_env, new_tpl, tb_arity)); | |||||
tb->reclaim(entry_key, reclaim); | |||||
bailout: | |||||
enif_free(new_tpl); | |||||
} else { | |||||
ret = enif_make_badarg(env); | |||||
} | |||||
tb->rwunlock(entry_key); | |||||
return ret; | |||||
} | |||||
ERL_NIF_TERM NeuralTable::Delete(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key) { | |||||
NeuralTable *tb; | |||||
ERL_NIF_TERM val, ret; | |||||
unsigned long int entry_key; | |||||
tb = GetTable(env, table); | |||||
if (tb == NULL) { return enif_make_badarg(env); } | |||||
enif_get_ulong(env, key, &entry_key); | |||||
tb->rwlock(entry_key); | |||||
if (tb->erase(entry_key, val)) { | |||||
tb->reclaim(entry_key, val); | |||||
ret = enif_make_copy(env, val); | |||||
} else { | |||||
ret = enif_make_atom(env, "undefined"); | |||||
} | |||||
tb->rwunlock(entry_key); | |||||
return ret; | |||||
} | |||||
ERL_NIF_TERM NeuralTable::Empty(ErlNifEnv *env, ERL_NIF_TERM table) { | |||||
NeuralTable *tb; | |||||
int n = 0; | |||||
tb = GetTable(env, table); | |||||
if (tb == NULL) { return enif_make_badarg(env); } | |||||
// First, lock EVERY bucket. We want this to be an isolated operation. | |||||
for (n = 0; n < BUCKET_COUNT; ++n) { | |||||
enif_rwlock_rwlock(tb->locks[n]); | |||||
} | |||||
// Now clear the table | |||||
for (n = 0; n < BUCKET_COUNT; ++n) { | |||||
tb->hash_buckets[n].clear(); | |||||
enif_clear_env(tb->env_buckets[n]); | |||||
tb->garbage_cans[n] = 0; | |||||
tb->reclaimable[n] = enif_make_list(tb->env_buckets[n], 0); | |||||
} | |||||
// Now unlock every bucket. | |||||
for (n = 0; n < BUCKET_COUNT; ++n) { | |||||
enif_rwlock_rwunlock(tb->locks[n]); | |||||
} | |||||
return enif_make_atom(env, "ok"); | |||||
} | |||||
ERL_NIF_TERM NeuralTable::Get(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key) { | |||||
NeuralTable *tb; | |||||
ERL_NIF_TERM ret, val; | |||||
unsigned long int entry_key; | |||||
// Acquire table handle, or quit if the table doesn't exist. | |||||
tb = GetTable(env, table); | |||||
if (tb == NULL) { return enif_make_badarg(env); } | |||||
// Get key value | |||||
enif_get_ulong(env, key, &entry_key); | |||||
// Lock the key | |||||
tb->rlock(entry_key); | |||||
// Read current value | |||||
if (!tb->find(entry_key, val)) { | |||||
ret = enif_make_atom(env, "undefined"); | |||||
} else { | |||||
ret = enif_make_copy(env, val); | |||||
} | |||||
tb->runlock(entry_key); | |||||
return ret; | |||||
} | |||||
ERL_NIF_TERM NeuralTable::Dump(ErlNifEnv *env, ERL_NIF_TERM table) { | |||||
NeuralTable *tb = GetTable(env, table); | |||||
ErlNifPid self; | |||||
ERL_NIF_TERM ret; | |||||
if (tb == NULL) { return enif_make_badarg(env); } | |||||
enif_self(env, &self); | |||||
tb->add_batch_job(self, &NeuralTable::batch_dump); | |||||
return enif_make_atom(env, "$neural_batch_wait"); | |||||
} | |||||
ERL_NIF_TERM NeuralTable::Drain(ErlNifEnv *env, ERL_NIF_TERM table) { | |||||
NeuralTable *tb = GetTable(env, table); | |||||
ErlNifPid self; | |||||
int ret; | |||||
if (tb == NULL) { return enif_make_badarg(env); } | |||||
enif_self(env, &self); | |||||
tb->add_batch_job(self, &NeuralTable::batch_drain); | |||||
return enif_make_atom(env, "$neural_batch_wait"); | |||||
} | |||||
ERL_NIF_TERM NeuralTable::GetKeyPosition(ErlNifEnv *env, ERL_NIF_TERM table) { | |||||
NeuralTable *tb = GetTable(env, table); | |||||
if (tb == NULL) { return enif_make_badarg(env); } | |||||
return enif_make_uint(env, tb->key_pos); | |||||
} | |||||
ERL_NIF_TERM NeuralTable::GarbageCollect(ErlNifEnv *env, ERL_NIF_TERM table) { | |||||
NeuralTable *tb = GetTable(env, table); | |||||
if (tb == NULL) { return enif_make_badarg(env); } | |||||
enif_cond_signal(tb->gc_cond); | |||||
return enif_make_atom(env, "ok"); | |||||
} | |||||
ERL_NIF_TERM NeuralTable::GarbageSize(ErlNifEnv *env, ERL_NIF_TERM table) { | |||||
NeuralTable *tb = GetTable(env, table); | |||||
unsigned long int size = 0; | |||||
if (tb == NULL) { return enif_make_badarg(env); } | |||||
size = tb->garbage_size(); | |||||
return enif_make_ulong(env, size); | |||||
} | |||||
void* NeuralTable::DoGarbageCollection(void *table) { | |||||
NeuralTable *tb = (NeuralTable*)table; | |||||
enif_mutex_lock(tb->gc_mutex); | |||||
while (running.load(memory_order_acquire)) { | |||||
while (running.load(memory_order_acquire) && tb->garbage_size() < RECLAIM_THRESHOLD) { | |||||
enif_cond_wait(tb->gc_cond, tb->gc_mutex); | |||||
} | |||||
tb->gc(); | |||||
} | |||||
enif_mutex_unlock(tb->gc_mutex); | |||||
return NULL; | |||||
} | |||||
void* NeuralTable::DoReclamation(void *table) { | |||||
const int max_eat = 5; | |||||
NeuralTable *tb = (NeuralTable*)table; | |||||
int i = 0, c = 0, t = 0;; | |||||
ERL_NIF_TERM tl, hd; | |||||
ErlNifEnv *env; | |||||
while (running.load(memory_order_acquire)) { | |||||
for (i = 0; i < BUCKET_COUNT; ++i) { | |||||
c = 0; | |||||
t = 0; | |||||
tb->rwlock(i); | |||||
env = tb->get_env(i); | |||||
tl = tb->reclaimable[i]; | |||||
while (c++ < max_eat && !enif_is_empty_list(env, tl)) { | |||||
enif_get_list_cell(env, tl, &hd, &tl); | |||||
tb->garbage_cans[i] += estimate_size(env, hd); | |||||
t += tb->garbage_cans[i]; | |||||
} | |||||
tb->rwunlock(i); | |||||
if (t >= RECLAIM_THRESHOLD) { | |||||
enif_cond_signal(tb->gc_cond); | |||||
} | |||||
} | |||||
#ifdef _WIN32 | |||||
Sleep(50); | |||||
#else | |||||
usleep(50000); | |||||
#endif | |||||
} | |||||
return NULL; | |||||
} | |||||
void* NeuralTable::DoBatchOperations(void *table) { | |||||
NeuralTable *tb = (NeuralTable*)table; | |||||
enif_mutex_lock(tb->batch_mutex); | |||||
while (running.load(memory_order_acquire)) { | |||||
while (running.load(memory_order_acquire) && tb->batch_jobs.empty()) { | |||||
enif_cond_wait(tb->batch_cond, tb->batch_mutex); | |||||
} | |||||
BatchJob job = tb->batch_jobs.front(); | |||||
(tb->*job.fun)(job.pid); | |||||
tb->batch_jobs.pop(); | |||||
} | |||||
enif_mutex_unlock(tb->batch_mutex); | |||||
return NULL; | |||||
} | |||||
void NeuralTable::start_gc() { | |||||
int ret; | |||||
gc_mutex = enif_mutex_create("neural_table_gc"); | |||||
gc_cond = enif_cond_create("neural_table_gc"); | |||||
ret = enif_thread_create("neural_garbage_collector", &gc_tid, NeuralTable::DoGarbageCollection, (void*)this, NULL); | |||||
if (ret != 0) { | |||||
printf("[neural_gc] Can't create GC thread. Error Code: %d\r\n", ret); | |||||
} | |||||
// Start the reclaimer after the garbage collector. | |||||
ret = enif_thread_create("neural_reclaimer", &rc_tid, NeuralTable::DoReclamation, (void*)this, NULL); | |||||
if (ret != 0) { | |||||
printf("[neural_gc] Can't create reclamation thread. Error Code: %d\r\n", ret); | |||||
} | |||||
} | |||||
void NeuralTable::stop_gc() { | |||||
enif_cond_signal(gc_cond); | |||||
// Join the reclaimer before the garbage collector. | |||||
enif_thread_join(rc_tid, NULL); | |||||
enif_thread_join(gc_tid, NULL); | |||||
} | |||||
void NeuralTable::start_batch() { | |||||
int ret; | |||||
batch_mutex = enif_mutex_create("neural_table_batch"); | |||||
batch_cond = enif_cond_create("neural_table_batch"); | |||||
ret = enif_thread_create("neural_batcher", &batch_tid, NeuralTable::DoBatchOperations, (void*)this, NULL); | |||||
if (ret != 0) { | |||||
printf("[neural_batch] Can't create batch thread. Error Code: %d\r\n", ret); | |||||
} | |||||
} | |||||
void NeuralTable::stop_batch() { | |||||
enif_cond_signal(batch_cond); | |||||
enif_thread_join(batch_tid, NULL); | |||||
} | |||||
void NeuralTable::put(unsigned long int key, ERL_NIF_TERM tuple) { | |||||
ErlNifEnv *env = get_env(key); | |||||
hash_buckets[GET_BUCKET(key)][key] = enif_make_copy(env, tuple); | |||||
} | |||||
ErlNifEnv* NeuralTable::get_env(unsigned long int key) { | |||||
return env_buckets[GET_BUCKET(key)]; | |||||
} | |||||
bool NeuralTable::find(unsigned long int key, ERL_NIF_TERM &ret) { | |||||
hash_table *bucket = &hash_buckets[GET_BUCKET(key)]; | |||||
hash_table::iterator it = bucket->find(key); | |||||
if (bucket->end() == it) { | |||||
return false; | |||||
} else { | |||||
ret = it->second; | |||||
return true; | |||||
} | |||||
} | |||||
bool NeuralTable::erase(unsigned long int key, ERL_NIF_TERM &val) { | |||||
hash_table *bucket = &hash_buckets[GET_BUCKET(key)]; | |||||
hash_table::iterator it = bucket->find(key); | |||||
bool ret = false; | |||||
if (it != bucket->end()) { | |||||
ret = true; | |||||
val = it->second; | |||||
bucket->erase(it); | |||||
} | |||||
return ret; | |||||
} | |||||
void NeuralTable::add_batch_job(ErlNifPid pid, BatchFunction fun) { | |||||
BatchJob job; | |||||
job.pid = pid; | |||||
job.fun = fun; | |||||
enif_mutex_lock(batch_mutex); | |||||
batch_jobs.push(job); | |||||
enif_mutex_unlock(batch_mutex); | |||||
enif_cond_signal(batch_cond); | |||||
} | |||||
void NeuralTable::batch_drain(ErlNifPid pid) { | |||||
ErlNifEnv *env = enif_alloc_env(); | |||||
ERL_NIF_TERM msg, value; | |||||
value = enif_make_list(env, 0); | |||||
for (int i = 0; i < BUCKET_COUNT; ++i) { | |||||
enif_rwlock_rwlock(locks[i]); | |||||
for (hash_table::iterator it = hash_buckets[i].begin(); it != hash_buckets[i].end(); ++it) { | |||||
value = enif_make_list_cell(env, enif_make_copy(env, it->second), value); | |||||
} | |||||
enif_clear_env(env_buckets[i]); | |||||
hash_buckets[i].clear(); | |||||
garbage_cans[i] = 0; | |||||
reclaimable[i] = enif_make_list(env_buckets[i], 0); | |||||
enif_rwlock_rwunlock(locks[i]); | |||||
} | |||||
msg = enif_make_tuple2(env, enif_make_atom(env, "$neural_batch_response"), value); | |||||
enif_send(NULL, &pid, env, msg); | |||||
enif_free_env(env); | |||||
} | |||||
void NeuralTable::batch_dump(ErlNifPid pid) { | |||||
ErlNifEnv *env = enif_alloc_env(); | |||||
ERL_NIF_TERM msg, value; | |||||
value = enif_make_list(env, 0); | |||||
for (int i = 0; i < BUCKET_COUNT; ++i) { | |||||
enif_rwlock_rlock(locks[i]); | |||||
for (hash_table::iterator it = hash_buckets[i].begin(); it != hash_buckets[i].end(); ++it) { | |||||
value = enif_make_list_cell(env, enif_make_copy(env, it->second), value); | |||||
} | |||||
enif_rwlock_runlock(locks[i]); | |||||
} | |||||
msg = enif_make_tuple2(env, enif_make_atom(env, "$neural_batch_response"), value); | |||||
enif_send(NULL, &pid, env, msg); | |||||
enif_free_env(env); | |||||
} | |||||
void NeuralTable::reclaim(unsigned long int key, ERL_NIF_TERM term) { | |||||
int bucket = GET_BUCKET(key); | |||||
ErlNifEnv *env = get_env(key); | |||||
reclaimable[bucket] = enif_make_list_cell(env, term, reclaimable[bucket]); | |||||
} | |||||
void NeuralTable::gc() { | |||||
ErlNifEnv *fresh = NULL, | |||||
*old = NULL; | |||||
hash_table *bucket = NULL; | |||||
hash_table::iterator it; | |||||
unsigned int gc_curr = 0; | |||||
for (; gc_curr < BUCKET_COUNT; ++gc_curr) { | |||||
bucket = &hash_buckets[gc_curr]; | |||||
old = env_buckets[gc_curr]; | |||||
fresh = enif_alloc_env(); | |||||
enif_rwlock_rwlock(locks[gc_curr]); | |||||
for (it = bucket->begin(); it != bucket->end(); ++it) { | |||||
it->second = enif_make_copy(fresh, it->second); | |||||
} | |||||
garbage_cans[gc_curr] = 0; | |||||
env_buckets[gc_curr] = fresh; | |||||
reclaimable[gc_curr] = enif_make_list(fresh, 0); | |||||
enif_free_env(old); | |||||
enif_rwlock_rwunlock(locks[gc_curr]); | |||||
} | |||||
} | |||||
unsigned long int NeuralTable::garbage_size() { | |||||
unsigned long int size = 0; | |||||
for (int i = 0; i < BUCKET_COUNT; ++i) { | |||||
enif_rwlock_rlock(locks[i]); | |||||
size += garbage_cans[i]; | |||||
enif_rwlock_runlock(locks[i]); | |||||
} | |||||
return size; | |||||
} |
@ -0,0 +1,121 @@ | |||||
#ifndef NEURALTABLE_H | |||||
#define NEURALTABLE_H | |||||
#include "erl_nif.h" | |||||
#include "neural_utils.h" | |||||
#include <string> | |||||
#include <stdio.h> | |||||
#include <string.h> | |||||
#include <unordered_map> | |||||
#include <queue> | |||||
#include <atomic> | |||||
#ifdef _WIN32 | |||||
#include <windows.h> | |||||
#include <io.h> | |||||
#include <process.h> | |||||
#else | |||||
#include <unistd.h> | |||||
#endif | |||||
#define BUCKET_COUNT 64 | |||||
#define BUCKET_MASK (BUCKET_COUNT - 1) | |||||
#define GET_BUCKET(key) key & BUCKET_MASK | |||||
#define GET_LOCK(key) key & BUCKET_MASK | |||||
#define RECLAIM_THRESHOLD 1048576 | |||||
using namespace std; | |||||
class NeuralTable; | |||||
typedef unordered_map<string, NeuralTable*> table_set; | |||||
typedef unordered_map<unsigned long int, ERL_NIF_TERM> hash_table; | |||||
typedef void (NeuralTable::*BatchFunction)(ErlNifPid pid); | |||||
class NeuralTable { | |||||
public: | |||||
static ERL_NIF_TERM MakeTable(ErlNifEnv *env, ERL_NIF_TERM name, ERL_NIF_TERM keypos); | |||||
static ERL_NIF_TERM Insert(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM object); | |||||
static ERL_NIF_TERM InsertNew(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM object); | |||||
static ERL_NIF_TERM Delete(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key); | |||||
static ERL_NIF_TERM Empty(ErlNifEnv *env, ERL_NIF_TERM table); | |||||
static ERL_NIF_TERM Get(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key); | |||||
static ERL_NIF_TERM Increment(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM ops); | |||||
static ERL_NIF_TERM Shift(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM ops); | |||||
static ERL_NIF_TERM Unshift(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM ops); | |||||
static ERL_NIF_TERM Swap(ErlNifEnv *env, ERL_NIF_TERM table, ERL_NIF_TERM key, ERL_NIF_TERM ops); | |||||
static ERL_NIF_TERM Dump(ErlNifEnv *env, ERL_NIF_TERM table); | |||||
static ERL_NIF_TERM Drain(ErlNifEnv *env, ERL_NIF_TERM table); | |||||
static ERL_NIF_TERM GetKeyPosition(ErlNifEnv *env, ERL_NIF_TERM table); | |||||
static ERL_NIF_TERM GarbageCollect(ErlNifEnv *env, ERL_NIF_TERM table); | |||||
static ERL_NIF_TERM GarbageSize(ErlNifEnv *env, ERL_NIF_TERM table); | |||||
static NeuralTable* GetTable(ErlNifEnv *env, ERL_NIF_TERM name); | |||||
static void* DoGarbageCollection(void *table); | |||||
static void* DoBatchOperations(void *table); | |||||
static void* DoReclamation(void *table); | |||||
static void Initialize() { | |||||
table_mutex = enif_mutex_create("neural_table_maker"); | |||||
} | |||||
static void Shutdown() { | |||||
running = false; | |||||
table_set::iterator it(tables.begin()); | |||||
while (it != tables.end()) { | |||||
delete it->second; | |||||
tables.erase(it); | |||||
it = tables.begin(); | |||||
} | |||||
enif_mutex_destroy(table_mutex); | |||||
} | |||||
void rlock(unsigned long int key) { enif_rwlock_rlock(locks[GET_LOCK(key)]); } | |||||
void runlock(unsigned long int key) { enif_rwlock_runlock(locks[GET_LOCK(key)]); } | |||||
void rwlock(unsigned long int key) { enif_rwlock_rwlock(locks[GET_LOCK(key)]); } | |||||
void rwunlock(unsigned long int key) { enif_rwlock_rwunlock(locks[GET_LOCK(key)]); } | |||||
ErlNifEnv *get_env(unsigned long int key); | |||||
bool erase(unsigned long int key, ERL_NIF_TERM &ret); | |||||
bool find(unsigned long int key, ERL_NIF_TERM &ret); | |||||
void put(unsigned long int key, ERL_NIF_TERM tuple); | |||||
void batch_dump(ErlNifPid pid); | |||||
void batch_drain(ErlNifPid pid); | |||||
void start_gc(); | |||||
void stop_gc(); | |||||
void start_batch(); | |||||
void stop_batch(); | |||||
void gc(); | |||||
void reclaim(unsigned long int key, ERL_NIF_TERM reclaim); | |||||
unsigned long int garbage_size(); | |||||
void add_batch_job(ErlNifPid pid, BatchFunction fun); | |||||
protected: | |||||
static table_set tables; | |||||
static atomic<bool> running; | |||||
static ErlNifMutex *table_mutex; | |||||
struct BatchJob { | |||||
ErlNifPid pid; | |||||
BatchFunction fun; | |||||
}; | |||||
NeuralTable(unsigned int kp); | |||||
~NeuralTable(); | |||||
unsigned int garbage_cans[BUCKET_COUNT]; | |||||
hash_table hash_buckets[BUCKET_COUNT]; | |||||
ErlNifEnv *env_buckets[BUCKET_COUNT]; | |||||
ERL_NIF_TERM reclaimable[BUCKET_COUNT]; | |||||
ErlNifRWLock *locks[BUCKET_COUNT]; | |||||
ErlNifCond *gc_cond; | |||||
ErlNifMutex *gc_mutex; | |||||
ErlNifTid gc_tid; | |||||
ErlNifTid rc_tid; | |||||
ErlNifCond *batch_cond; | |||||
ErlNifMutex *batch_mutex; | |||||
queue<BatchJob> batch_jobs; | |||||
ErlNifTid batch_tid; | |||||
unsigned int key_pos; | |||||
}; | |||||
#endif |
@ -0,0 +1,134 @@ | |||||
#include "erl_nif.h" | |||||
#include "NeuralTable.h" | |||||
#include <stdio.h> | |||||
// Prototypes | |||||
static ERL_NIF_TERM neural_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_put(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_put_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_increment(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_unshift(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_shift(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_swap(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_get(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_delete(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_garbage(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_garbage_size(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_empty(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_drain(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_dump(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ERL_NIF_TERM neural_key_pos(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); | |||||
static ErlNifFunc nif_funcs[] = | |||||
{ | |||||
{"make_table", 2, neural_new}, | |||||
{"do_fetch", 2, neural_get}, | |||||
{"do_delete", 2, neural_delete}, | |||||
{"do_dump", 1, neural_dump}, | |||||
{"do_drain", 1, neural_drain}, | |||||
{"empty", 1, neural_empty}, | |||||
{"insert", 3, neural_put}, | |||||
{"insert_new", 3, neural_put_new}, | |||||
{"do_increment", 3, neural_increment}, | |||||
{"do_unshift", 3, neural_unshift}, | |||||
{"do_shift", 3, neural_shift}, | |||||
{"do_swap", 3, neural_swap}, | |||||
{"garbage", 1, neural_garbage}, | |||||
{"garbage_size", 1, neural_garbage_size}, | |||||
{"key_pos", 1, neural_key_pos} | |||||
}; | |||||
static ERL_NIF_TERM neural_key_pos(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
// This function is directly exposed, so no strict guards or patterns protecting us. | |||||
if (argc != 1 || !enif_is_atom(env, argv[0])) { return enif_make_badarg(env); } | |||||
return NeuralTable::GetKeyPosition(env, argv[0]); | |||||
} | |||||
static ERL_NIF_TERM neural_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
return NeuralTable::MakeTable(env, argv[0], argv[1]); | |||||
} | |||||
static ERL_NIF_TERM neural_put(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
return NeuralTable::Insert(env, argv[0], argv[1], argv[2]); | |||||
} | |||||
static ERL_NIF_TERM neural_put_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
return NeuralTable::InsertNew(env, argv[0], argv[1], argv[2]); | |||||
} | |||||
static ERL_NIF_TERM neural_increment(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
if (!enif_is_atom(env, argv[0]) || !enif_is_number(env, argv[1]) || !enif_is_list(env, argv[2])) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
return NeuralTable::Increment(env, argv[0], argv[1], argv[2]); | |||||
} | |||||
static ERL_NIF_TERM neural_shift(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
return NeuralTable::Shift(env, argv[0], argv[1], argv[2]); | |||||
} | |||||
static ERL_NIF_TERM neural_unshift(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
return NeuralTable::Unshift(env, argv[0], argv[1], argv[2]); | |||||
} | |||||
static ERL_NIF_TERM neural_swap(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]){ | |||||
return NeuralTable::Swap(env, argv[0], argv[1], argv[2]); | |||||
} | |||||
static ERL_NIF_TERM neural_get(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
return NeuralTable::Get(env, argv[0], argv[1]); | |||||
} | |||||
static ERL_NIF_TERM neural_delete(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
return NeuralTable::Delete(env, argv[0], argv[1]); | |||||
} | |||||
static ERL_NIF_TERM neural_empty(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
if (!enif_is_atom(env, argv[0])) { return enif_make_badarg(env); } | |||||
return NeuralTable::Empty(env, argv[0]); | |||||
} | |||||
static ERL_NIF_TERM neural_dump(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
if (!enif_is_atom(env, argv[0])) { return enif_make_badarg(env); } | |||||
return NeuralTable::Dump(env, argv[0]); | |||||
} | |||||
static ERL_NIF_TERM neural_drain(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
if (!enif_is_atom(env, argv[0])) { return enif_make_badarg(env); } | |||||
return NeuralTable::Drain(env, argv[0]); | |||||
} | |||||
static ERL_NIF_TERM neural_garbage(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
if (!enif_is_atom(env, argv[0])) { return enif_make_badarg(env); } | |||||
return NeuralTable::GarbageCollect(env, argv[0]); | |||||
} | |||||
static ERL_NIF_TERM neural_garbage_size(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
if (!enif_is_atom(env, argv[0])) { return enif_make_badarg(env); } | |||||
return NeuralTable::GarbageSize(env, argv[0]); | |||||
} | |||||
static void neural_resource_cleanup(ErlNifEnv* env, void* arg) | |||||
{ | |||||
/* Delete any dynamically allocated memory stored in neural_handle */ | |||||
/* neural_handle* handle = (neural_handle*)arg; */ | |||||
} | |||||
static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) | |||||
{ | |||||
NeuralTable::Initialize(); | |||||
return 0; | |||||
} | |||||
static void on_unload(ErlNifEnv *env, void *priv_data) { | |||||
NeuralTable::Shutdown(); | |||||
} | |||||
ERL_NIF_INIT(neural, nif_funcs, &on_load, NULL, NULL, &on_unload); |
@ -0,0 +1,46 @@ | |||||
#include "neural_utils.h" | |||||
unsigned long int estimate_size(ErlNifEnv *env, ERL_NIF_TERM term) { | |||||
if (enif_is_atom(env, term)) { | |||||
return WORD_SIZE; | |||||
} | |||||
// Treating all numbers like longs. | |||||
if (enif_is_number(env, term)) { | |||||
return 2 * WORD_SIZE; | |||||
} | |||||
if (enif_is_binary(env, term)) { | |||||
ErlNifBinary bin; | |||||
enif_inspect_binary(env, term, &bin); | |||||
return bin.size + (6 * WORD_SIZE); | |||||
} | |||||
if (enif_is_list(env, term)) { | |||||
unsigned long int size = 0; | |||||
ERL_NIF_TERM it, curr; | |||||
it = term; | |||||
size += WORD_SIZE; | |||||
while (!enif_is_empty_list(env, it)) { | |||||
enif_get_list_cell(env, it, &curr, &it); | |||||
size += estimate_size(env, curr) + WORD_SIZE; | |||||
} | |||||
return size; | |||||
} | |||||
if (enif_is_tuple(env, term)) { | |||||
unsigned long int size = 0; | |||||
const ERL_NIF_TERM *tpl; | |||||
int arity; | |||||
enif_get_tuple(env, term, &arity, &tpl); | |||||
for (int i = 0; i < arity; ++i) { | |||||
size += estimate_size(env, tpl[i]); | |||||
} | |||||
return size; | |||||
} | |||||
// Return 1 word by default | |||||
return WORD_SIZE; | |||||
} | |||||
@ -0,0 +1,9 @@ | |||||
#ifndef NEURAL_UTILS_H | |||||
#define NEURAL_UTILS_H | |||||
#include "erl_nif.h" | |||||
#define WORD_SIZE sizeof(int) | |||||
unsigned long int estimate_size(ErlNifEnv *env, ERL_NIF_TERM term); | |||||
#endif |
@ -0,0 +1,14 @@ | |||||
{port_specs, [ | |||||
{"../../priv/neural.so", ["*.cpp"]} | |||||
]}. | |||||
{port_env, [ | |||||
{".*", "CXXFLAGS", "$CXXFLAGS -std=c++11 -O3"}, | |||||
{".*", "LDFLAGS", "$LDFLAGS -lstdc++ -shared"} | |||||
]}. | |||||
@ -0,0 +1,20 @@ | |||||
-module(bitmap_filter). | |||||
-export([init/0, filter/1]). | |||||
-on_load(init/0). | |||||
init() -> | |||||
PrivDir = case code:priv_dir(?MODULE) of | |||||
{error, _} -> | |||||
EbinDir = filename:dirname(code:which(?MODULE)), | |||||
AppPath = filename:dirname(EbinDir), | |||||
filename:join(AppPath, "priv"); | |||||
Path -> | |||||
Path | |||||
end, | |||||
erlang:load_nif(filename:join(PrivDir, "bitmap_filter"), 0). | |||||
% Hack - overriden by init, which is called in on_load. | |||||
% I couldn't find another way that the compiler or code load didn't complain about. | |||||
filter(DefaultArgs) -> | |||||
DefaultArgs. |
@ -0,0 +1,77 @@ | |||||
-module(bsn). | |||||
%% API | |||||
-export([hash/2, compare/2]). | |||||
-export([new/2, add/2, all/1, chains/1, in/2, count/1, clear/2]). | |||||
-ifdef(TEST). | |||||
-include_lib("eunit/include/eunit.hrl"). | |||||
%-include_lib("triq/include/triq.hrl"). | |||||
-endif. | |||||
%% Create new resource, `CellCount' is the size of the painters' store. | |||||
new('int_quadric', CellsCount) when CellsCount > 0 -> | |||||
{'bsn_int', bsn_int:new(-CellsCount)}; | |||||
new('int_linear', CellsCount) when CellsCount > 0 -> | |||||
{'bsn_int', bsn_int:new(CellsCount)}; | |||||
new('ext', CellsCount) when CellsCount > 0 -> | |||||
{'bsn_ext', bsn_ext:new(CellsCount)}. | |||||
%% Add new element. | |||||
%% If the result is a negative integer | |||||
%% then object was already added. | |||||
%% We found this object with (result) steps. | |||||
%% | |||||
%% If the result is a positive integer | |||||
%% then object was added after (result) elements. | |||||
add({Type, Res}, Bin) -> | |||||
Type:add(Res, Bin). | |||||
all({Type, Res}) -> | |||||
Type:all(Res). | |||||
chains({Type, Res}) -> | |||||
Type:chains(Res). | |||||
%% Add new element. | |||||
%% If the result is a negative integer | |||||
%% then object was found with (-result) steps. | |||||
%% | |||||
%% If the result is a positive integer | |||||
%% then object was not found with (result) steps. | |||||
in({Type, Res}, Bin) -> | |||||
Type:in(Res, Bin). | |||||
clear({Type, Res}, Bin) -> | |||||
Type:clear(Res, Bin). | |||||
%% Return the count of elements stored in this resource. | |||||
count({Type, Res}) -> | |||||
Type:count(Res). | |||||
%% Calculate the hash of the binary | |||||
hash(Bin, Max) -> | |||||
bsn_ext:hash(Bin, Max). | |||||
compare(Bin1, Bin2) -> | |||||
bsn_ext:compare(Bin1, Bin2). | |||||
-ifdef(TEST). | |||||
-ifdef(FORALL). | |||||
prop_compare_test_() -> | |||||
{"Binary compare testing.", | |||||
{timeout, 60, | |||||
fun() -> triq:check(prop_compare()) end}}. | |||||
prop_compare() -> | |||||
?FORALL({Xs},{binary()}, | |||||
compare(Xs, Xs)). | |||||
-endif. | |||||
-endif. |
@ -0,0 +1,56 @@ | |||||
-module(bsn_ext). | |||||
-on_load(init/0). | |||||
-export([init/0]). | |||||
%% API | |||||
-export([hash/2, compare/2]). | |||||
-export([new/1, add/2, all/1, chains/1, in/2, count/1, clear/2]). | |||||
-define(NIF_NOT_LOADED, erlang:nif_error(nif_not_loaded)). | |||||
init() -> | |||||
erlang:load_nif(code:priv_dir('bsn')++"/bsn_ext", 0). | |||||
%% Create new resource, `CellCount' is the size of the painters' store. | |||||
new(CellsCount) -> | |||||
?NIF_NOT_LOADED. | |||||
%% Add new element. | |||||
%% If the result is a negative integer | |||||
%% then object was already added. | |||||
%% We found this object with (result) steps. | |||||
%% | |||||
%% If the result is a positive integer | |||||
%% then object was added after (result) elements. | |||||
add(Res, Bin) -> | |||||
?NIF_NOT_LOADED. | |||||
all(Res) -> | |||||
?NIF_NOT_LOADED. | |||||
chains(Res) -> | |||||
?NIF_NOT_LOADED. | |||||
%% Add new element. | |||||
%% If the result is a negative integer | |||||
%% then object was found with (-result) steps. | |||||
%% | |||||
%% If the result is a positive integer | |||||
%% then object was not found with (result) steps. | |||||
in(Res, Bin) -> | |||||
?NIF_NOT_LOADED. | |||||
%% Return the count of elements stored in this resource. | |||||
count(Res) -> | |||||
?NIF_NOT_LOADED. | |||||
%% Calculate the hash of the binary | |||||
hash(Bin, Max) -> | |||||
?NIF_NOT_LOADED. | |||||
compare(Bin1, Bin2) -> | |||||
?NIF_NOT_LOADED. | |||||
clear(Res, Bin) -> | |||||
?NIF_NOT_LOADED. |
@ -0,0 +1,45 @@ | |||||
-module(bsn_int). | |||||
-on_load(init/0). | |||||
-export([init/0]). | |||||
%% API | |||||
-export([new/1, add/2, all/1, in/2, count/1, clear/2]). | |||||
-define(NIF_NOT_LOADED, erlang:nif_error(nif_not_loaded)). | |||||
init() -> | |||||
erlang:load_nif(code:priv_dir('bsn')++"/bsn_int", 0). | |||||
%% Create new resource, `CellCount' is the size of the painters' store. | |||||
new(CellsCount) -> | |||||
?NIF_NOT_LOADED. | |||||
%% Add new element. | |||||
%% If the result is a negative integer | |||||
%% then object was already added. | |||||
%% We found this object with (result) steps. | |||||
%% | |||||
%% If the result is a positive integer | |||||
%% then object was added after (result) elements. | |||||
add(Res, Bin) -> | |||||
?NIF_NOT_LOADED. | |||||
all(Res) -> | |||||
?NIF_NOT_LOADED. | |||||
%% Add new element. | |||||
%% If the result is a negative integer | |||||
%% then object was found with (-result) steps. | |||||
%% | |||||
%% If the result is a positive integer | |||||
%% then object was not found with (result) steps. | |||||
in(Res, Bin) -> | |||||
?NIF_NOT_LOADED. | |||||
%% Return the count of elements stored in this resource. | |||||
count(Res) -> | |||||
?NIF_NOT_LOADED. | |||||
clear(Res, Bin) -> | |||||
?NIF_NOT_LOADED. |
@ -0,0 +1,236 @@ | |||||
-module(bsn_measure). | |||||
-export([test/0, test2/0, test3/0, print/0]). | |||||
-export([gen/2, check_type/4]). | |||||
-export([check_type/3, get_type/3, test_type/2]). | |||||
-export([check_degrade/0, test_filled/1]). | |||||
-ifndef(TEST). | |||||
-define(TEST, e). | |||||
-endif. | |||||
-ifdef(TEST). | |||||
-include_lib("eunit/include/eunit.hrl"). | |||||
%-include_lib("triq/include/triq.hrl"). | |||||
-endif. | |||||
% InOutK is (success / failure) checks. | |||||
% Return {TestCases, Elements}. | |||||
gen(ElemCount, InOutK) | |||||
when ElemCount>0 -> | |||||
Nums = lists:seq(0, erlang:round(ElemCount*100)), | |||||
filter(ElemCount, InOutK, Nums, [], []). | |||||
filter(EC, InOutK, [H|T], AllAcc, ElemAcc) | |||||
when EC>0 -> | |||||
case random:uniform() of | |||||
X when X<InOutK -> | |||||
filter(EC-1, InOutK, | |||||
T, [H|AllAcc], [H|ElemAcc]); | |||||
_X -> | |||||
filter(EC, InOutK, | |||||
T, [H|AllAcc], ElemAcc) | |||||
end; | |||||
filter(_ElemCount, _InOutK, _Acc, AllAcc, ElemAcc) -> | |||||
{AllAcc, ElemAcc}. | |||||
check_type(Type, Size, InOutK) -> | |||||
check_type(fun average/1, Type, Size, InOutK). | |||||
get_type(Type, Size, InOutK) -> | |||||
check_type(fun(X) -> X end, Type, Size, InOutK). | |||||
check_type(OutF, Type, Size, InOutK) -> | |||||
% Build resourse | |||||
F = fun() -> bsn:new(Type, Size) end, | |||||
[do_check(OutF, F, Size, InOutK, 0.1), | |||||
do_check(OutF, F, Size, InOutK, 0.25), | |||||
do_check(OutF, F, Size, InOutK, 0.5), | |||||
do_check(OutF, F, Size, InOutK, 0.75), | |||||
do_check(OutF, F, Size, InOutK, 0.9), | |||||
do_check(OutF, F, Size, InOutK, 1)]. | |||||
do_check(OutF, F, Size, InOutK, CapacityK) -> | |||||
Res = F(), | |||||
ElemCount = Size * CapacityK, | |||||
{CaseList, ElemList} = gen(ElemCount, InOutK), | |||||
fill_values(Res, ElemList), | |||||
VaList = check_values(Res, CaseList, []), | |||||
{MissList, InNegList} = lists:partition(fun(X) -> X>0 end, VaList), | |||||
InList = lists:map(fun erlang:'-'/1, InNegList), | |||||
AllList = InList ++ MissList, | |||||
{CapacityK, | |||||
{size, Size}, | |||||
{real_count, bsn:count(Res)}, | |||||
{miss, OutF(MissList)}, | |||||
{in, OutF(InList)}, | |||||
{all, OutF(AllList)}}. | |||||
average([]) -> | |||||
false; | |||||
average([X|Tail]) -> | |||||
average1(Tail, X, 1). | |||||
% @private | |||||
average1([X|Tail], Sum, Count) -> | |||||
average1(Tail, Sum + X, Count + 1); | |||||
average1([], Sum, Count) -> | |||||
round4(Sum / Count). | |||||
round4(X) when is_number(X) -> | |||||
erlang:round(X * 1000) / 1000; | |||||
round4(X) -> | |||||
X. | |||||
check_values(Res, [H|T], Acc) -> | |||||
X = bsn:in(Res, integer_to_binary(H)), | |||||
check_values(Res, T, [X|Acc]); | |||||
check_values(_Res, [], Acc) -> | |||||
Acc. | |||||
fill_values(Res, [H|T]) -> | |||||
case bsn:add(Res, integer_to_binary(H)) of | |||||
no_more -> | |||||
Res; | |||||
X -> | |||||
fill_values(Res, T) | |||||
end; | |||||
fill_values(Res, []) -> | |||||
Res. | |||||
fill_values(Res, [H|T], Acc) -> | |||||
case bsn:add(Res, integer_to_binary(H)) of | |||||
no_more -> | |||||
Acc; | |||||
X -> | |||||
fill_values(Res, T, [H|Acc]) | |||||
end; | |||||
fill_values(_Res, [], Acc) -> | |||||
Acc. | |||||
integer_to_binary(X) -> | |||||
erlang:list_to_binary(erlang:integer_to_list(X)). | |||||
test() -> | |||||
[{ext, check_type(ext, 100, 0.5)} | |||||
,{int_linear, check_type(int_linear, 100, 0.5)} | |||||
,{int_quadric, check_type(int_quadric, 100, 0.5)}]. | |||||
%% All values. | |||||
test2() -> | |||||
[{ext, get_type(ext, 100, 0.5)} | |||||
,{int_linear, get_type(int_linear, 100, 0.5)} | |||||
,{int_quadric, get_type(int_quadric, 100, 0.5)}]. | |||||
%% Counts of values. | |||||
test3() -> | |||||
F = fun anal_values/1, | |||||
[{ext, check_type(F, ext, 100, 0.5)} | |||||
,{int_linear, check_type(F, int_linear, 100, 0.5)} | |||||
,{int_quadric, check_type(F, int_quadric, 100, 0.5)}]. | |||||
print() -> | |||||
do_print(test3()). | |||||
do_print([{Type, Vals}|T]) -> | |||||
io:format("Type ~w~n", [Type]), | |||||
lists:map(fun({K, | |||||
{real_count,RC}, | |||||
{miss, M}, | |||||
{in, I}, | |||||
{all, A}}) -> | |||||
io:format("K=~w, RC=~w~n", [K, RC]), | |||||
io:format("count,miss,in,all\n"), | |||||
print_mia(lists:seq(1, 100), M, I, A), | |||||
io:format("\n") | |||||
end, Vals), | |||||
do_print(T); | |||||
do_print([]) -> | |||||
ok. | |||||
print_mia([H|T], [{H,0}|T1], [{H,0}|T2], [{H,0}|T3]) -> | |||||
print_mia(T, T1, T2, T3); | |||||
print_mia([H|T], [{H,C1}|T1], [{H,C2}|T2], [{H,C3}|T3]) -> | |||||
io:format("~w,~w,~w,~w\n", [H, C1, C2, C3]), | |||||
print_mia(T, T1, T2, T3); | |||||
print_mia([H|_]=L, [{X,_}|_]=L1, L2, L3) | |||||
when X =/= H -> | |||||
print_mia(L, [{H,0}|L1], L2, L3); | |||||
print_mia([H|_]=L, [], L2, L3) -> | |||||
print_mia(L, [{H,0}], L2, L3); | |||||
print_mia([H|_]=L, L1, [{X,_}|_]=L2, L3) | |||||
when X =/= H -> | |||||
print_mia(L, L1, [{H,0}|L2], L3); | |||||
print_mia([H|_]=L, L1, [], L3) -> | |||||
print_mia(L, L1, [{H,0}], L3); | |||||
print_mia([H|_]=L, L1, L2, L3) -> | |||||
print_mia(L, L1, L2, [{H,0}|L3]); | |||||
print_mia([], _, _, _) -> | |||||
ok. | |||||
anal_values(L) -> | |||||
do_anal(lists:sort(L), 1, []). | |||||
do_anal([H,H|T], C, Acc) -> | |||||
do_anal([H|T], C+1, Acc); | |||||
do_anal([OldH|T], C, Acc) -> | |||||
do_anal(T, 1, [{OldH, C}|Acc]); | |||||
do_anal([], C, Acc) -> | |||||
lists:reverse(Acc). | |||||
avg(L) -> do_avg(L, 0, 0). | |||||
do_avg([H|T], Cnt, Sum) -> | |||||
do_avg(T, Cnt+1, Sum+H); | |||||
do_avg([], Cnt, Sum) -> | |||||
Sum / Cnt. | |||||
check_degrade() -> | |||||
[do_check_degrade(ext) | |||||
,do_check_degrade(int_linear) | |||||
,do_check_degrade(int_quadric) | |||||
]. | |||||
do_check_degrade(Type) -> | |||||
OutF = fun avg/1, | |||||
[Type, | |||||
lists:map(fun(Size) -> | |||||
F = fun() -> bsn:new(Type, Size) end, | |||||
do_check(OutF, F, Size, 0.5, 1) | |||||
end, [10, 100, 500, 1000, 5000, 10000])]. | |||||
test_filled(ElemCount) -> | |||||
Res = bsn:new(ext, ElemCount), | |||||
{CaseList, ElemList} = gen(ElemCount, 1), | |||||
Vals = fill_values(Res, ElemList, []), | |||||
{bsn_ext, R} = Res, | |||||
R. | |||||
-ifdef(TEST). | |||||
do_test_() -> | |||||
[?_assert(test_type(bsn:new(ext, 100), 100)) | |||||
,?_assert(test_type(bsn:new(int_linear, 100), 100)) | |||||
,?_assert(test_type(bsn:new(int_quadric, 100), 100)) | |||||
]. | |||||
-endif. | |||||
test_type(Res, ElemCount) -> | |||||
{CaseList, ElemList} = gen(ElemCount, 1), | |||||
Vals = fill_values(Res, ElemList, []), | |||||
%Vals = ElemList, | |||||
lists:all(fun(X) -> bsn:in(Res, integer_to_binary(X)) < 0 end, Vals). |
@ -0,0 +1,160 @@ | |||||
% Licensed under the Apache License, Version 2.0 (the "License"); you may not | |||||
% use this file except in compliance with the License. You may obtain a copy of | |||||
% the License at | |||||
% | |||||
% http:%www.apache.org/licenses/LICENSE-2.0 | |||||
% | |||||
% Unless required by applicable law or agreed to in writing, software | |||||
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||||
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||||
% License for the specific language governing permissions and limitations under | |||||
% the License. | |||||
-module(hqueue). | |||||
-on_load(init/0). | |||||
-export([ | |||||
new/0, | |||||
new/1, | |||||
extract_max/1, | |||||
insert/3, | |||||
from_list/1, | |||||
from_list/2, | |||||
to_list/1, | |||||
heap_size/1, | |||||
info/1, | |||||
is_empty/1, | |||||
max_elems/1, | |||||
size/1, | |||||
resize_heap/2, | |||||
scale_by/2, | |||||
set_max_elems/2 | |||||
]). | |||||
-define(NOT_LOADED, not_loaded(?LINE)). | |||||
-type hqueue() :: term(). | |||||
-type hqueue_priority() :: float(). %% this should be non_neg_float() | |||||
-type hqueue_val() :: term(). | |||||
-type hqueue_elem() :: {hqueue_priority(), hqueue_val()}. | |||||
-type hqueue_option() :: {max_elems, pos_integer()} | |||||
| {heap_size, pos_integer()}. | |||||
-type hqueue_stat() :: {max_elems, pos_integer()} | |||||
| {heap_size, pos_integer()} | |||||
| {size, non_neg_integer()}. | |||||
-export_type([hqueue/0]). | |||||
-spec new() -> {ok, hqueue()}. | |||||
new() -> | |||||
new([]). | |||||
-spec new([hqueue_option()]) -> {ok, hqueue()}. | |||||
new(_Options) -> | |||||
?NOT_LOADED. | |||||
%% Extraction order is undefined for entries with duplicate priorities | |||||
-spec extract_max(hqueue()) -> hqueue_elem() | {error, empty}. | |||||
extract_max(_HQ) -> | |||||
?NOT_LOADED. | |||||
-spec insert(hqueue(), hqueue_priority(), hqueue_val()) -> ok | {error, full}. | |||||
insert(_HQ, _Priority, _Val) -> | |||||
?NOT_LOADED. | |||||
-spec size(hqueue()) -> integer(). | |||||
size(_HQ) -> | |||||
?NOT_LOADED. | |||||
-spec max_elems(hqueue()) -> integer(). | |||||
max_elems(_HQ) -> | |||||
?NOT_LOADED. | |||||
%% Returns old max elems or error if NewMaxElems < size(HQ) | |||||
-spec set_max_elems(hqueue(), pos_integer()) -> pos_integer() | |||||
| {error, too_small}. | |||||
set_max_elems(_HQ, _NewMaxElems) -> | |||||
?NOT_LOADED. | |||||
-spec is_empty(hqueue()) -> boolean(). | |||||
is_empty(HQ) -> | |||||
hqueue:size(HQ) =:= 0. | |||||
-spec to_list(hqueue()) -> [hqueue_elem()]. | |||||
to_list(_HQ) -> | |||||
?NOT_LOADED. | |||||
-spec from_list([hqueue_elem()]) -> {ok, hqueue()}. | |||||
from_list(Elems) -> | |||||
from_list(Elems, []). | |||||
-spec from_list([hqueue_elem()], [hqueue_option()]) -> {ok, hqueue()}. | |||||
from_list(Elems, Options) -> | |||||
{ok, HQ} = ?MODULE:new(Options), | |||||
lists:foreach(fun({Priority, Val}) -> | |||||
?MODULE:insert(HQ, Priority, Val) | |||||
end, Elems), | |||||
{ok, HQ}. | |||||
-spec scale_by(hqueue(), float()) -> ok. | |||||
scale_by(_HQ, _Factor) -> | |||||
?NOT_LOADED. | |||||
%% Returns old heap size or error if NewHeapSize < size(HQ) | |||||
-spec resize_heap(hqueue(), pos_integer()) -> pos_integer() | |||||
| {error, too_small}. | |||||
resize_heap(_HQ, _NewHeapSize) -> | |||||
?NOT_LOADED. | |||||
-spec heap_size(hqueue()) -> pos_integer(). | |||||
heap_size(_HQ) -> | |||||
?NOT_LOADED. | |||||
-spec info(hqueue()) -> [hqueue_stat()]. | |||||
info(HQ) -> | |||||
[ | |||||
{heap_size, hqueue:heap_size(HQ)}, | |||||
{max_elems, hqueue:max_elems(HQ)}, | |||||
{size, hqueue:size(HQ)} | |||||
]. | |||||
init() -> | |||||
PrivDir = case code:priv_dir(?MODULE) of | |||||
{error, _} -> | |||||
EbinDir = filename:dirname(code:which(?MODULE)), | |||||
AppPath = filename:dirname(EbinDir), | |||||
filename:join(AppPath, "priv"); | |||||
Path -> | |||||
Path | |||||
end, | |||||
erlang:load_nif(filename:join(PrivDir, "hqueue"), 0). | |||||
not_loaded(Line) -> | |||||
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). |
@ -0,0 +1,51 @@ | |||||
-module(enlfq). | |||||
-on_load(load_nif/0). | |||||
-define(NOT_LOADED, not_loaded(?LINE)). | |||||
%% API exports | |||||
-export([new/0, push/2, pop/1]). | |||||
%%==================================================================== | |||||
%% API functions | |||||
%%==================================================================== | |||||
-spec(new() -> {ok, QueueRef :: reference()} | badarg | {error, Reason :: binary()}). | |||||
new() -> | |||||
?NOT_LOADED. | |||||
-spec(push(QueueRef :: reference(), Data :: any()) -> | |||||
true | {error, Reason :: binary()}). | |||||
push(_QueueRef, _Data) -> | |||||
?NOT_LOADED. | |||||
-spec(pop(QueueRef :: reference()) -> | |||||
{ok, Data :: any()} | empty | {error, Reason :: binary()}). | |||||
pop(_QueueRef) -> | |||||
?NOT_LOADED. | |||||
%%==================================================================== | |||||
%% Internal functions | |||||
%%==================================================================== | |||||
%% nif functions | |||||
load_nif() -> | |||||
SoName = get_priv_path(?MODULE), | |||||
io:format(<<"Loading library: ~p ~n">>, [SoName]), | |||||
ok = erlang:load_nif(SoName, 0). | |||||
get_priv_path(File) -> | |||||
case code:priv_dir(?MODULE) of | |||||
{error, bad_name} -> | |||||
Ebin = filename:dirname(code:which(?MODULE)), | |||||
filename:join([filename:dirname(Ebin), "priv", File]); | |||||
Dir -> | |||||
filename:join(Dir, File) | |||||
end. | |||||
not_loaded(Line) -> | |||||
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). |
@ -0,0 +1,71 @@ | |||||
-module(benchmark). | |||||
-author("silviu.caragea"). | |||||
-export([ | |||||
benchmark_serial/2, | |||||
benchmark_concurrent/3 | |||||
]). | |||||
benchmark_serial(Elements, MaxPriority) -> | |||||
rand:uniform(), %just to init the seed | |||||
{ok, Q} = enlfq:new(), | |||||
{T0, ok} = timer:tc(fun() -> insert_none(Elements, MaxPriority) end), | |||||
{T1, ok} = timer:tc(fun() -> insert_item(Elements, Q, MaxPriority) end), | |||||
{T2, ok} = timer:tc(fun() -> remove_item(Q) end), | |||||
T0Ms = T0/1000, | |||||
T1Ms = T1/1000, | |||||
T2Ms = T2/1000, | |||||
io:format(<<"insert overhead: ~p ms insert time: ~p ms pop time: ~p ms ~n">>, [T0Ms, T1Ms, T2Ms]). | |||||
benchmark_concurrent(Procs, Elements, MaxPriority) -> | |||||
{ok, Q} = enlfq:new(), | |||||
ElsPerProcess = round(Elements/Procs), | |||||
InsertNoneWorkFun = fun() -> | |||||
insert_none(ElsPerProcess, MaxPriority) | |||||
end, | |||||
InsertWorkFun = fun() -> | |||||
insert_item(ElsPerProcess, Q, MaxPriority) | |||||
end, | |||||
RemoveWorkFun = fun() -> | |||||
remove_item(Q) | |||||
end, | |||||
{T0, _} = timer:tc(fun()-> multi_spawn:do_work(InsertNoneWorkFun, Procs) end), | |||||
{T1, _} = timer:tc(fun()-> multi_spawn:do_work(InsertWorkFun, Procs) end), | |||||
{T2, _} = timer:tc(fun()-> multi_spawn:do_work(RemoveWorkFun, Procs) end), | |||||
T0Ms = T0/1000, | |||||
T1Ms = T1/1000, | |||||
T2Ms = T2/1000, | |||||
io:format(<<"insert overhead: ~p ms insert time: ~p ms pop time: ~p ms ~n">>, [T0Ms, T1Ms, T2Ms]). | |||||
insert_item(0, _Q, _Max) -> | |||||
ok; | |||||
insert_item(N, Q, Max) -> | |||||
%% El = rand:uniform(Max), | |||||
true = enlfq:push(Q,{}), | |||||
insert_item(N-1, Q, Max). | |||||
remove_item(Q) -> | |||||
case enlfq:pop(Q) of | |||||
empty -> | |||||
ok; | |||||
{ok, _} -> | |||||
remove_item(Q) | |||||
end. | |||||
insert_none(0, _Max) -> | |||||
ok; | |||||
insert_none(N, Max) -> | |||||
%% rand:uniform(Max), | |||||
insert_none(N-1, Max). | |||||
@ -0,0 +1,23 @@ | |||||
-module(multi_spawn). | |||||
-author("silviu.caragea"). | |||||
-export([do_work/2]). | |||||
do_work(Fun, Count) -> | |||||
process_flag(trap_exit, true), | |||||
spawn_childrens(Fun, Count), | |||||
wait_responses(Count). | |||||
spawn_childrens(_Fun, 0) -> | |||||
ok; | |||||
spawn_childrens(Fun, Count) -> | |||||
spawn_link(Fun), | |||||
spawn_childrens(Fun, Count -1). | |||||
wait_responses(0) -> | |||||
ok; | |||||
wait_responses(Count) -> | |||||
receive | |||||
{'EXIT',_FromPid, _Reason} -> | |||||
wait_responses(Count -1) | |||||
end. |
@ -0,0 +1,159 @@ | |||||
%%%----------------------------------------------------------------------------- | |||||
%%% @author s@shuvatov.ru | |||||
%%% @copyright 2018 Sergei Shuvatov | |||||
%%% @doc | |||||
%%% Native implemented queue with TTL. | |||||
%%% By default queue type is FIFO and TTL is 0 (disabled), size unlimited. | |||||
%%% Usage: | |||||
%%% {ok, Q} = enq:new([fifo, | |||||
%%% {ttl, 10000}, % 10 seconds | |||||
%%% {max_size, 1000}]), % maximum 1000 elements | |||||
%%% ok = enq:push(Q, test), % push atom 'test' to the queue | |||||
%%% [test] = enq:pop(Q), % pop one element from the queue | |||||
%%% [] = enq:pop(Q), % pop returns empty list if the queue is empty | |||||
%%% % pushed item can be any term | |||||
%%% ok = enq:push(Q, fun() -> io:format("some important job~n") end), | |||||
%%% 1 = enq:size(Q), % you can take length of the queue as efficiently as O(1) | |||||
%%% @end | |||||
%%%----------------------------------------------------------------------------- | |||||
-module(enq). | |||||
-author("Sergei Shuvatov"). | |||||
%% API | |||||
-export([new/0, | |||||
new/1, | |||||
push/2, | |||||
pop/1, | |||||
size/1]). | |||||
-export_type([queue/0, option/0, error/0]). | |||||
-type queue() :: reference(). | |||||
-type option() :: fifo | | |||||
lifo | | |||||
{ttl, Microseconds :: non_neg_integer()} | | |||||
{max_size, Count :: non_neg_integer()}. | |||||
-type error() :: max_size. | |||||
%%============================================================================== | |||||
%% API | |||||
%%============================================================================== | |||||
%% Same as enq:new([fifo, {ttl, 0}]). | |||||
-spec new() -> {ok, enq:queue()} | {error, enq:error()}. | |||||
new() -> | |||||
new([]). | |||||
%% Returns a new queue or error in case of memory allocation error. | |||||
-spec new([option()]) -> {ok, enq:queue()} | {error, enq:error()}. | |||||
new(Options) -> | |||||
enq_nif:new(Options). | |||||
%% Pushes Item on top (LIFO) or tail (FIFO) of Queue. | |||||
-spec push(Queue :: enq:queue(), Item :: any()) -> ok | {error, enq:error()}. | |||||
push(Queue, Item) -> | |||||
enq_nif:push(Queue, erlang:term_to_binary(Item)). | |||||
%% Returns next item from the Queue. | |||||
-spec pop(Queue :: enq:queue()) -> [] | [any()]. | |||||
pop(Queue) -> | |||||
[ erlang:binary_to_term(I) || I <- enq_nif:pop(Queue) ]. | |||||
%% Returns Queue length. Speed does not depend on number of elements. | |||||
-spec size(Queue :: enq:queue()) -> non_neg_integer(). | |||||
size(Queue) -> | |||||
enq_nif:size(Queue). | |||||
%%============================================================================== | |||||
%% Tests | |||||
%%============================================================================== | |||||
-ifdef(TEST). | |||||
-include_lib("eunit/include/eunit.hrl"). | |||||
-define(log(F, A), io:format(standard_error, "~p:line ~p: " F "~n", [?FILE, ?LINE | A])). | |||||
-define(log(F), ?log(F, [])). | |||||
fifo_test() -> | |||||
fifo_test(1000000). | |||||
fifo_test(N) -> | |||||
{ok, Q} = enq:new(), | |||||
T1 = erlang:timestamp(), | |||||
% fill the queue with N elements | |||||
fill(Q, N), | |||||
Diff1 = timer:now_diff(erlang:timestamp(), T1), | |||||
?log("FIFO fill time: ~p ms", [Diff1 / 1000]), | |||||
% ensure that size of queue matches N | |||||
N = enq:size(Q), | |||||
T2 = erlang:timestamp(), | |||||
% pop all elements | |||||
fifo_pop_all(Q, N), | |||||
Diff2 = timer:now_diff(erlang:timestamp(), T2), | |||||
?log("FIFO pop time: ~p ms", [Diff2 / 1000]), | |||||
% size of the queue must be 0 | |||||
0 = enq:size(Q). | |||||
fill(_Q, 0) -> | |||||
ok; | |||||
fill(Q, N) -> | |||||
ok = enq:push(Q, N), | |||||
fill(Q, N - 1). | |||||
fifo_pop_all(Q, 0) -> | |||||
[] = enq:pop(Q); | |||||
fifo_pop_all(Q, N) -> | |||||
[N] = enq:pop(Q), | |||||
fifo_pop_all(Q, N - 1). | |||||
ttl_test() -> | |||||
{ok, Q} = enq:new([{ttl, 100}]), | |||||
enq:push(Q, test), | |||||
timer:sleep(95), | |||||
[test] = enq:pop(Q), | |||||
[] = enq:pop(Q), | |||||
enq:push(Q, test), | |||||
timer:sleep(105), | |||||
[] = enq:pop(Q). | |||||
lifo_test() -> | |||||
lifo_test(1000000). | |||||
lifo_test(N) -> | |||||
{ok, Q} = enq:new([lifo]), | |||||
T1 = erlang:timestamp(), | |||||
% fill the queue with N elements | |||||
fill(Q, N), | |||||
Diff1 = timer:now_diff(erlang:timestamp(), T1), | |||||
?log("LIFO fill time: ~p ms", [Diff1 / 1000]), | |||||
% ensure that size of queue matches N | |||||
N = enq:size(Q), | |||||
T2 = erlang:timestamp(), | |||||
% pop all elements | |||||
lifo_pop_all(Q, N), | |||||
Diff2 = timer:now_diff(erlang:timestamp(), T2), | |||||
?log("LIFO pop time: ~p ms", [Diff2 / 1000]), | |||||
% size of the queue must be 0 | |||||
0 = enq:size(Q). | |||||
lifo_pop_all(Q, N) -> | |||||
lifo_pop_all(Q, 1, N). | |||||
lifo_pop_all(Q, I, N) when I > N -> | |||||
[] = enq:pop(Q); | |||||
lifo_pop_all(Q, I, N) -> | |||||
[I] = enq:pop(Q), | |||||
lifo_pop_all(Q, I + 1, N). | |||||
max_size_test() -> | |||||
{ok, Q} = enq:new([{ttl, 100}, {max_size, 1}]), | |||||
ok = enq:push(Q, test), | |||||
timer:sleep(50), | |||||
{error, max_size} = enq:push(Q, 123), | |||||
timer:sleep(55), | |||||
ok = enq:push(Q, 321), | |||||
[321] = enq:pop(Q), | |||||
[] = enq:pop(Q). | |||||
-endif. % TEST |
@ -0,0 +1,63 @@ | |||||
%%%------------------------------------------------------------------- | |||||
%%% @author s@shuvatov.ru | |||||
%%% @copyright 2018 Sergei Shuvatov | |||||
%%%------------------------------------------------------------------- | |||||
-module(enq_nif). | |||||
-author("Sergei Shuvatov"). | |||||
%% API | |||||
-export([new/1, | |||||
push/2, | |||||
pop/1, | |||||
size/1]). | |||||
-on_load(load_nif/0). | |||||
-define(app, enq). | |||||
-define(log(F, A), io:format(standard_error, "~p:~p: " F, [?MODULE, ?LINE | A])). | |||||
-define(not_loaded(), not_loaded(?LINE)). | |||||
%%============================================================================== | |||||
%% API | |||||
%%============================================================================== | |||||
new(_Options) -> | |||||
?not_loaded(). | |||||
push(_Queue, _Item) -> | |||||
?not_loaded(). | |||||
pop(_Queue) -> | |||||
?not_loaded(). | |||||
size(_Queue) -> | |||||
?not_loaded(). | |||||
%%============================================================================== | |||||
%% Internal functions | |||||
%%============================================================================== | |||||
load_nif() -> | |||||
SoName = get_priv_path(?MODULE), | |||||
% ?log("Loading library: ~p ~n", [SoName]), | |||||
ok = erlang:load_nif(SoName, 0). | |||||
get_priv_path(File) -> | |||||
case code:priv_dir(get_app()) of | |||||
{error, bad_name} -> | |||||
Ebin = filename:dirname(code:which(?MODULE)), | |||||
filename:join([filename:dirname(Ebin), "priv", File]); | |||||
Dir -> | |||||
filename:join(Dir, File) | |||||
end. | |||||
get_app() -> | |||||
case application:get_application(?MODULE) of | |||||
{ok, App} -> | |||||
App; | |||||
_ -> | |||||
?app | |||||
end. | |||||
not_loaded(Line) -> | |||||
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). |
@ -0,0 +1,103 @@ | |||||
%% @author vinod | |||||
%% @doc @todo Add description to ets_queue. | |||||
-module(etsq). | |||||
-on_load(load_nif/0). | |||||
-export([load_nif/0, | |||||
new/1, | |||||
info/1, | |||||
push/2, | |||||
pop/1, | |||||
front/1]). | |||||
%% ==================================================================== | |||||
%% API functions | |||||
%% ==================================================================== | |||||
-define(LIB_BASE_NAME, "etsq"). | |||||
-define(LIB_NIF_VSN, 1). | |||||
-define(LIB_APP_NAME, etsq). | |||||
-spec new(atom()) -> ok | {error, already_exists}. | |||||
new(_Name) -> | |||||
erlang:nif_error({nif_not_loaded,module,?MODULE,line,?LINE}). | |||||
-spec info(atom()) -> ok. | |||||
info(_Name) -> | |||||
erlang:nif_error({nif_not_loaded,module,?MODULE,line,?LINE}). | |||||
-spec push(atom(), term()) -> ok. | |||||
push(Name, Term) -> | |||||
push_back(Name, term_to_binary(Term)). | |||||
-spec pop(atom()) -> ok | {error, empty}. | |||||
pop(Name) -> | |||||
get_val(pop_front(Name)). | |||||
-spec front(atom()) -> ok | {error, empty}. | |||||
front(Name) -> | |||||
get_val(get_front(Name)). | |||||
get_val(Value) when is_binary(Value) -> | |||||
binary_to_term(Value); | |||||
get_val(Value) -> | |||||
Value. | |||||
push_back(_Name, _Bin) -> | |||||
erlang:nif_error({nif_not_loaded,module,?MODULE,line,?LINE}). | |||||
pop_front(_Name) -> | |||||
erlang:nif_error({nif_not_loaded,module,?MODULE,line,?LINE}). | |||||
get_front(_Name) -> | |||||
erlang:nif_error({nif_not_loaded,module,?MODULE,line,?LINE}). | |||||
-spec load_nif() -> ok | {error, term()}. | |||||
load_nif() -> | |||||
LibBaseName = ?LIB_BASE_NAME, | |||||
PrivDir = code:priv_dir(etsq), | |||||
LibName = case erlang:system_info(build_type) of | |||||
opt -> | |||||
LibBaseName; | |||||
Type -> | |||||
LibTypeName = LibBaseName ++ "." ++ atom_to_list(Type), | |||||
case (filelib:wildcard( | |||||
filename:join( | |||||
[PrivDir, | |||||
"lib", | |||||
LibTypeName ++ "*"])) /= []) orelse | |||||
(filelib:wildcard( | |||||
filename:join( | |||||
[PrivDir, | |||||
"lib", | |||||
erlang:system_info(system_architecture), | |||||
LibTypeName ++ "*"])) /= []) of | |||||
true -> LibTypeName; | |||||
false -> LibBaseName | |||||
end | |||||
end, | |||||
Lib = filename:join([PrivDir, "lib", LibName]), | |||||
Status = case erlang:load_nif(Lib, ?LIB_NIF_VSN) of | |||||
ok -> ok; | |||||
{error, {load_failed, _}}=Error1 -> | |||||
ArchLibDir = | |||||
filename:join([PrivDir, "lib", | |||||
erlang:system_info(system_architecture)]), | |||||
Candidate = | |||||
filelib:wildcard(filename:join([ArchLibDir,LibName ++ "*" ])), | |||||
case Candidate of | |||||
[] -> Error1; | |||||
_ -> | |||||
ArchLib = filename:join([ArchLibDir, LibName]), | |||||
erlang:load_nif(ArchLib, ?LIB_NIF_VSN) | |||||
end; | |||||
Error1 -> Error1 | |||||
end, | |||||
case Status of | |||||
ok -> ok; | |||||
{error, {E, Str}} -> | |||||
error_logger:error_msg("Unable to load ~p nif library. " | |||||
"Failed with error:~n\"~p, ~s\"~n", [?LIB_APP_NAME, E, Str]), | |||||
Status | |||||
end. |
@ -0,0 +1,65 @@ | |||||
%% @author vinod | |||||
%% @doc @todo Add description to etsq_tests. | |||||
-module(etsq_tests). | |||||
-compile(export_all). | |||||
-export([init/0, | |||||
time/3, | |||||
stats/3]). | |||||
-type microseconds() :: pos_integer(). | |||||
-type milliseconds() :: pos_integer(). | |||||
%% ==================================================================== | |||||
%% API functions | |||||
%% ==================================================================== | |||||
init() -> | |||||
etsq:new(queue), | |||||
ets:new(tab, [named_table, public]). | |||||
-spec time(run_ets | run_queue, pos_integer()) -> microseconds(). | |||||
time(Op, NumOp) -> | |||||
{Time, _} = timer:tc(?MODULE, Op, [NumOp]), | |||||
Time. | |||||
-spec time(pos_integer(), run_ets | run_queue, pos_integer()) -> microseconds(). | |||||
time(NumProc, Op, NumOp) -> | |||||
{Time, _} = timer:tc(?MODULE, spawn, [NumProc, Op, NumOp]), | |||||
Time. | |||||
-spec stats(run_ets | run_queue, pos_integer()) -> milliseconds(). | |||||
stats(Op, NumOp) -> | |||||
erlang:statistics(runtime), | |||||
?MODULE:Op(NumOp), | |||||
{_, Time} = erlang:statistics(runtime), | |||||
Time. | |||||
-spec stats(pos_integer(), run_ets | run_queue, pos_integer()) -> milliseconds(). | |||||
stats(NumProc, Op, NumOp) -> | |||||
erlang:statistics(runtime), | |||||
?MODULE:spawn(NumProc, Op, NumOp), | |||||
{_, Time} = erlang:statistics(runtime), | |||||
Time. | |||||
run_ets(Num) -> | |||||
Self = self(), | |||||
Data = lists:seq(1, 100), | |||||
L = lists:seq(1, Num), | |||||
[ets:insert(tab, {{Self, K}, Data}) || K <- L], | |||||
[ets:take(tab, {Self, K}) || K <- L]. | |||||
run_queue(Num) -> | |||||
Self = self(), | |||||
Data = lists:seq(1, 100), | |||||
L = lists:seq(1, Num), | |||||
[etsq:push(queue, {{Self, K}, Data}) || K <- L], | |||||
[etsq:pop(queue) || _ <- L]. | |||||
spawn(NumProc, Op, NumOp) -> | |||||
Pid = self(), | |||||
L = lists:seq(1, NumProc), | |||||
[spawn_link(fun() -> ?MODULE:Op(NumOp), Pid ! done end) || _ <- L], | |||||
[receive done -> ok end || _ <- L]. |
@ -0,0 +1,102 @@ | |||||
-module(btree_lru). | |||||
-export([create/1, | |||||
close/1, | |||||
register_pid/2, | |||||
unregister_pid/1, | |||||
get_registered_pid/1, | |||||
set_max_size/2, | |||||
get_max_size/1, | |||||
get_size/1, | |||||
write/2, | |||||
write/3, | |||||
read/2, | |||||
next/2, | |||||
prev/2, | |||||
remove/2, | |||||
seek/2, | |||||
iterate_next/2, | |||||
oldest/1, | |||||
latest/1, | |||||
last/1, | |||||
first/1]). | |||||
-on_load(init/0). | |||||
init() -> | |||||
Dir = "../priv", | |||||
PrivDir = | |||||
case code:priv_dir(?MODULE) of | |||||
{error, _} -> | |||||
case code:which(?MODULE) of | |||||
Filename when is_list(Filename) -> | |||||
filename:join([filename:dirname(Filename), Dir]); | |||||
_ -> | |||||
Dir | |||||
end; | |||||
Path -> Path | |||||
end, | |||||
Lib = filename:join(PrivDir, "btreelru_nif"), | |||||
erlang:load_nif(Lib, 0). | |||||
write(Tab, {Key, Value}) -> | |||||
write(Tab, Key, Value). | |||||
create(_Maxsize) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
register_pid(_Tab, _Pid) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
unregister_pid(_Tab) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
get_registered_pid(_Tab) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
set_max_size(_Tab, _MaxSize) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
get_max_size(_Tab) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
get_size(_Tab) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
write(_Tab, _Key, _Value) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
read(_Tab, _Key) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
next(_Tab, _Key) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
prev(_Tab, _Key) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
remove(_Tab, _Key) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
seek(_Tab, _Key) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
iterate_next(_Tab, _It) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
oldest(_Tab) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
latest(_Tab) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
close(_Tab) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
last(_Tab) -> | |||||
erlang:nif_error(nif_library_not_loaded). | |||||
first(_Tab) -> | |||||
erlang:nif_error(nif_library_not_loaded). |
@ -0,0 +1,59 @@ | |||||
-module(btree_lru_test). | |||||
-compile(export_all). | |||||
-export([create/0, | |||||
create/1]). | |||||
create() -> | |||||
create(1024*1024*1024*1000). | |||||
create(Size) -> | |||||
{ok, _Tab} = btree_lru:create(Size). | |||||
write(Tab) -> | |||||
Objs = [{X,X} || X <- lists:seq(1,10000000)], | |||||
write(Tab, Objs). | |||||
write(Tab, [Obj | Objs]) -> | |||||
ok = btree_lru:write(Tab, Obj), | |||||
write(Tab, Objs); | |||||
write(_Tab, []) -> | |||||
ok. | |||||
read(Tab, [{K,D} | Objs]) -> | |||||
{K,D} = btree_lru:read(Tab, K), | |||||
read(Tab, Objs); | |||||
read(_Tab, []) -> | |||||
ok. | |||||
timing_write(Tab) -> | |||||
Objs = [{X,X} || X <- lists:seq(1,10000000)], | |||||
timer:tc(?MODULE, write, [Tab, Objs]). | |||||
timing_read(Tab) -> | |||||
Objs = [{X,X} || X <- lists:seq(1,10000000)], | |||||
timer:tc(?MODULE, read, [Tab, Objs]). | |||||
timing_ets_write(Tab) -> | |||||
Objs = [{X,X} || X <- lists:seq(1,10000000)], | |||||
timer:tc(?MODULE, ets_write, [Tab, Objs]). | |||||
timing_ets_read(Tab) -> | |||||
Objs = [{X,X} || X <- lists:seq(1,10000000)], | |||||
timer:tc(?MODULE, ets_read, [Tab, Objs]). | |||||
ets_write(Tab, [Obj | Objs]) -> | |||||
true = ets:insert(Tab, Obj), | |||||
ets_write(Tab, Objs); | |||||
ets_write(_Tab, []) -> | |||||
ok. | |||||
ets_read(Tab, [{K,D} | Objs]) -> | |||||
[{K,D}] = ets:lookup(Tab, K), | |||||
ets_read(Tab, Objs); | |||||
ets_read(_Tab, []) -> | |||||
ok. | |||||
@ -0,0 +1,6 @@ | |||||
{application, gb_lru, | |||||
[{description, "gb_lru"}, | |||||
{vsn, "0.1"}, | |||||
{registered, []}, | |||||
{applications, []} | |||||
]}. |
@ -0,0 +1,19 @@ | |||||
-module(native_array). | |||||
-export([new/2, get/2, put/3, delete/1]). | |||||
-on_load(init/0). | |||||
init() -> | |||||
ok = erlang:load_nif("./native_array_nif", 0). | |||||
new(_Idx, _Length) -> | |||||
exit(nif_library_not_loaded). | |||||
get(_Idx, _Offset) -> | |||||
exit(nif_library_not_loaded). | |||||
put(_Idx, _Offset, _NewVal) -> | |||||
exit(nif_library_not_loaded). | |||||
delete(_Idx) -> | |||||
exit(nif_library_not_loaded). | |||||