Merge pull request #3 from johannesh/hyper_carray
hyper_carray, a hyper_register implemented by a C-array
diff --git a/.gitignore b/.gitignore
index 6d8c311..be93a07 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,6 @@
*~
hyper.png
estimates.csv
-erl_crash.dump
\ No newline at end of file
+erl_crash.dump
+*.so
+*.o
diff --git a/c_src/hyper_carray.c b/c_src/hyper_carray.c
new file mode 100644
index 0000000..554badd
--- /dev/null
+++ b/c_src/hyper_carray.c
@@ -0,0 +1,334 @@
+// Copyright (c) 2014 Johannes Huning <mail@johanneshuning.com>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <stdio.h>
+#include <string.h>
+#include <tgmath.h>
+
+#include "erl_nif.h"
+
+/*
+ * Erlang NIF resource type used to 'tag' hyper_carrays.
+ */
+static ErlNifResourceType *carray_resource;
+
+/*
+ * Single resource produced and consumed by these NIFs.
+ * Header placed directly in front of the items it points to.
+ */
+struct hyper_carray {
+ /*
+ * Precision = log_2(size). Handy to have it.
+ */
+ unsigned int precision;
+ /*
+ * Number of items.
+ */
+ unsigned int size;
+ /*
+ * Array of items each one byte in size.
+ */
+ char *items;
+};
+
+#define HYPER_CARRAY_SIZE sizeof(struct hyper_carray)
+
+/*
+ * Attempts to read a hyper_carray from _term into _varname.
+ * Returns badarg on failure to do so.
+ */
+#define HYPER_CARRAY_OR_BADARG(_term, _varname) \
+ void* _varname_res = NULL; \
+ if (!enif_get_resource(env, _term, carray_resource, &_varname_res)) \
+ return enif_make_badarg(env); \
+ _varname = _varname_res;
+
+/*
+ * Allocate a new hyper_carray for use as an Erlang NIF resource.
+ */
+static void carray_alloc(unsigned int precision,
+ struct hyper_carray *restrict * arr)
+{
+ unsigned int nitems = pow(2, precision);
+ size_t header_size = HYPER_CARRAY_SIZE;
+ size_t res_size = header_size + nitems;
+
+ void *res = enif_alloc_resource(carray_resource, res_size);
+ *arr = res;
+
+ memset(*arr, 0, header_size);
+ (*arr)->precision = precision;
+ (*arr)->size = nitems;
+ (*arr)->items = res + header_size;
+}
+
+/*
+ * Given an hyper_carray and a valid index, set the value at that index to
+ * max(current value, given value).
+ */
+static inline void carray_merge_item(struct hyper_carray *restrict arr,
+ unsigned int index,
+ unsigned int value)
+{
+ char *item = arr->items + index;
+ if (value > *item)
+ *item = value;
+}
+
+/*
+ * Create a new hyper_carray resource with all items set to 0.
+ */
+static ERL_NIF_TERM new_hyper_carray(ErlNifEnv * env, int argc,
+ const ERL_NIF_TERM argv[])
+{
+ unsigned int precision = 0;
+ if (!enif_get_uint(env, argv[0], &precision))
+ return enif_make_badarg(env);
+
+ struct hyper_carray *restrict arr = NULL;
+ carray_alloc(precision, &arr);
+ memset(arr->items, 0, arr->size);
+
+ ERL_NIF_TERM erl_res = enif_make_resource(env, arr);
+ enif_release_resource(arr);
+ return erl_res;
+}
+
+/*
+ * NIF variant of carray_merge_item (see above).
+ */
+static ERL_NIF_TERM set(ErlNifEnv * env, int argc,
+ const ERL_NIF_TERM argv[])
+{
+ struct hyper_carray *restrict arr = NULL;
+ HYPER_CARRAY_OR_BADARG(argv[2], arr);
+
+ unsigned int index = 0;
+ unsigned int new_value = 0;
+ if (!enif_get_uint(env, argv[0], &index)
+ || !enif_get_uint(env, argv[1], &new_value))
+ return enif_make_badarg(env);
+
+ // Validate bounds
+ if (index > arr->size - 1)
+ return enif_make_badarg(env);
+
+ carray_merge_item(arr, index, new_value);
+
+ return enif_make_resource(env, arr);
+}
+
+void dtor(ErlNifEnv * env, void *obj);
+
+/*
+ * Given a list of at least 2 hyper_carrays [A,B,...], merge into a single new
+ * hyper_carray N. Where the i-ths item N[i] is max(A[i], B[i], ...).
+ * A, B, and so on are assumed to be _different_ hyper_carrays.
+ */
+static ERL_NIF_TERM max_merge(ErlNifEnv * env, int argc,
+ const ERL_NIF_TERM argv[])
+{
+ unsigned int narrays = 0;
+ ERL_NIF_TERM head;
+ ERL_NIF_TERM tail;
+
+ if (!enif_get_list_length(env, argv[0], &narrays)
+ || !enif_get_list_cell(env, argv[0], &head, &tail))
+ return enif_make_badarg(env);
+
+ if (narrays < 2)
+ return enif_make_badarg(env);
+
+ void *tmp = NULL;
+ struct hyper_carray *restrict first = NULL;
+ struct hyper_carray *restrict acc = NULL;
+ struct hyper_carray *restrict curr = NULL;
+
+ HYPER_CARRAY_OR_BADARG(head, first);
+
+ carray_alloc(first->precision, &acc);
+ memcpy(acc->items, first->items, acc->size);
+
+ unsigned int nitems = 0;
+ for (int i = 1; i < narrays; ++i) {
+ if (!enif_get_list_cell(env, tail, &head, &tail)
+ || !enif_get_resource(env, head, carray_resource,
+ &tmp))
+ goto dealloc_and_badarg;
+ curr = tmp;
+
+ // Require uniform precision.
+ if (curr->precision != acc->precision)
+ goto dealloc_and_badarg;
+
+ nitems = curr->size;
+ for (int j = 0; j < nitems; ++j)
+ carray_merge_item(acc, j, curr->items[j]);
+
+ continue;
+
+ dealloc_and_badarg:
+ dtor(env, acc);
+ return enif_make_badarg(env);
+ }
+
+ return enif_make_resource(env, acc);
+}
+
+/*
+ * Return the total number of bytes allocated for the given hyper_carray.
+ * Includes the header's size.
+ */
+static ERL_NIF_TERM bytes(ErlNifEnv * env, int argc,
+ const ERL_NIF_TERM argv[])
+{
+ struct hyper_carray *restrict arr = NULL;
+ HYPER_CARRAY_OR_BADARG(argv[0], arr);
+ return enif_make_int(env, HYPER_CARRAY_SIZE + arr->size);
+}
+
+/*
+ * Sum over 2^-X where X is the value of each item in the given hyper_carray.
+ */
+static ERL_NIF_TERM register_sum(ErlNifEnv * env, int argc,
+ const ERL_NIF_TERM argv[])
+{
+ struct hyper_carray *restrict arr = NULL;
+ HYPER_CARRAY_OR_BADARG(argv[0], arr);
+
+ int currval = 0;
+ double sum = 0.0;
+ unsigned int size = arr->size;
+
+ for (int i = 0; i < size; ++i) {
+ currval = arr->items[i];
+ sum += pow(2, -currval);
+ }
+
+ return enif_make_double(env, sum);
+}
+
+/*
+ * Number of items with a 0 as value;
+ */
+static ERL_NIF_TERM zero_count(ErlNifEnv * env, int argc,
+ const ERL_NIF_TERM argv[])
+{
+ struct hyper_carray *restrict arr = NULL;
+ HYPER_CARRAY_OR_BADARG(argv[0], arr);
+
+ unsigned int nzeros = 0;
+ unsigned int size = arr->size;
+
+ for (int i = 0; i < size; ++i) {
+ if (arr->items[i] == 0)
+ ++nzeros;
+ }
+
+ return enif_make_int(env, nzeros);
+}
+
+/*
+ * Encode the given hyper_carray as an Erlang binary.
+ */
+static ERL_NIF_TERM encode_registers(ErlNifEnv * env, int argc,
+ const ERL_NIF_TERM argv[])
+{
+ struct hyper_carray *restrict arr = NULL;
+ HYPER_CARRAY_OR_BADARG(argv[0], arr);
+
+ size_t nbytes = HYPER_CARRAY_SIZE + arr->size;
+
+ ERL_NIF_TERM bin;
+ unsigned char *buf = enif_make_new_binary(env, nbytes, &bin);
+ memcpy(buf, arr, nbytes);
+
+ return bin;
+}
+
+/*
+ * Decode the given serialized hyper_carray into a new resource.
+ */
+static ERL_NIF_TERM decode_registers(ErlNifEnv * env, int argc,
+ const ERL_NIF_TERM argv[])
+{
+ unsigned int precision = 0;
+ ErlNifBinary bin;
+
+ if (!enif_get_uint(env, argv[1], &precision)
+ || !enif_inspect_binary(env, argv[0], &bin))
+ return enif_make_badarg(env);
+
+ struct hyper_carray *restrict arr = NULL;
+ carray_alloc(precision, &arr);
+ memcpy(arr, bin.data, HYPER_CARRAY_SIZE + arr->size);
+
+ ERL_NIF_TERM erl_res = enif_make_resource(env, arr);
+ enif_release_resource(arr);
+
+ return erl_res;
+}
+
+/*
+ * Map of funs to NIFs.
+ */
+static ErlNifFunc niftable[] = {
+ {"new", 1, new_hyper_carray},
+ {"set", 3, set},
+ {"max_merge", 1, max_merge},
+ {"bytes", 1, bytes},
+ {"register_sum", 1, register_sum},
+ {"zero_count", 1, zero_count},
+ {"encode_registers", 1, encode_registers},
+ {"decode_registers", 2, decode_registers}
+};
+
+/*
+ * Destructor for hyper_carray resources.
+ */
+void dtor(ErlNifEnv * env, void *obj)
+{
+ enif_release_resource(obj);
+}
+
+/*
+ * Creates or opens the hyper_carray resource _type_.
+ * Registers dtor to be called on garbage collection of hyper_carrays.
+ * Please see http://www.erlang.org/doc/man/erl_nif.html.
+ */
+static int load(ErlNifEnv * env, void **priv_data, ERL_NIF_TERM load_info)
+{
+ carray_resource =
+ enif_open_resource_type(env, NULL, "hyper_carray", &dtor,
+ ERL_NIF_RT_CREATE |
+ ERL_NIF_RT_TAKEOVER, 0);
+ return 0;
+}
+
+/*
+ * Called when the NIF library is loaded and there is old code of this module
+ * with a loaded NIF library.
+ */
+static int upgrade(ErlNifEnv * env, void **priv, void **old_priv,
+ ERL_NIF_TERM load_info)
+{
+ *priv = *old_priv;
+ return 0;
+}
+
+/*
+ * Initialize the NIF library.
+ */
+ERL_NIF_INIT(hyper_carray, niftable, &load, NULL, &upgrade, NULL);
diff --git a/priv/.keep b/priv/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/priv/.keep
diff --git a/rebar.config b/rebar.config
index 47ba7fe..20a0778 100644
--- a/rebar.config
+++ b/rebar.config
@@ -3,3 +3,13 @@
{bisect, "",
{git, "https://github.com/knutin/bisect.git", {branch, "master"}}}
]}.
+
+
+{port_specs, [
+ {"priv/hyper_carray.so", ["c_src/hyper_carray.c"]}
+ ]}.
+
+{port_env, [
+ {"CC", "c99"},
+ {"CFLAGS", "$CFLAGS -Wall -Werror -O3"}
+ ]}.
diff --git a/src/hyper_carray.erl b/src/hyper_carray.erl
new file mode 100644
index 0000000..524a041
--- /dev/null
+++ b/src/hyper_carray.erl
@@ -0,0 +1,90 @@
+%% @doc
+%% Dummy module for the NIF implementation in {@link /c_src/hyper_carray.c}.
+-module (hyper_carray).
+
+-on_load(load_shared_obj/0).
+
+-behaviour(hyper_register).
+-export([new/1,
+ set/3,
+ max_merge/1,
+ max_merge/2,
+ bytes/1,
+ register_sum/1,
+ zero_count/1,
+ encode_registers/1,
+ decode_registers/2,
+ compact/1]).
+
+
+% Pull in the NIF.
+load_shared_obj() ->
+ EbinDir = filename:dirname(code:which(?MODULE)),
+ AppPath = filename:dirname(EbinDir),
+ Path = filename:join([AppPath, "priv", "hyper_carray"]),
+ ok = erlang:load_nif(Path, 0).
+
+
+% Nothing to do.
+compact(Handle) ->
+ Handle.
+
+
+% Delegate to generalization.
+max_merge(HandleA, HandleB) ->
+ max_merge([HandleA, HandleB]).
+
+
+% Returns badarg on:
+% - Precision is not a positive integer
+new(_Precision) ->
+ erlang:nif_error(nif_not_loaded).
+
+% Returns badarg on:
+% - Index is not a positive integer
+% - Value not a positive integer
+% - Handle is not a hyper_carray
+% Value is assumed to fit into one byte, that is within [0, 255]
+set(_Index, _Value, _Handle) ->
+ erlang:nif_error(nif_not_loaded).
+
+
+% Returns badarg on:
+% - Argument is not a list
+% - Argument is a list of less than 2 elements
+% - An element is not a hyper_carray
+% - The elements lack uniform precision
+% - Failure to obtain a list element (enif_get_list_cell)
+max_merge(_Handles) ->
+ erlang:nif_error(nif_not_loaded).
+
+
+% Returns badarg on:
+% - Handle is not a hyper_carray
+register_sum(_Handle) ->
+ erlang:nif_error(nif_not_loaded).
+
+
+% Returns badarg on:
+% - Handle is not a hyper_carray
+zero_count(_Handle) ->
+ erlang:nif_error(nif_not_loaded).
+
+
+% Returns badarg on:
+% - Handle is not a hyper_carray
+encode_registers(_Handle) ->
+ erlang:nif_error(nif_not_loaded).
+
+
+% Returns badarg on:
+% - First argument is not a binary
+% - Precision is not a positive integer
+decode_registers(_Binary, _Precision) ->
+ erlang:nif_error(nif_not_loaded).
+
+
+% Returns badarg on:
+% - Handle is not a hyper_carray
+bytes(_Handle) ->
+ erlang:nif_error(nif_not_loaded).