First usable IFS implementation

Implements borrowed if and other needed stuff + tests & benchamrk
This commit is contained in:
Yann Weber 2023-10-04 12:17:45 +02:00
commit 7c335fcff0
13 changed files with 723 additions and 131 deletions

View file

@ -9,7 +9,7 @@ ifeq ($(DEBUG), 1)
#PYTHON=python3dm
PYTHON=python3-dbg
else
CFLAGS=-fPIC -Wall -Werror
CFLAGS=-fPIC -Wall -Werror -DNDEBUG
LDFLAGS=-s
NASMCFLAGS=-f elf64
PYTHON=python3
@ -75,8 +75,12 @@ doc/.doxygen.stamp: $(wildcard *.c) $(wildcard *.h) Doxyfile
checks: runtest unittest
benchmark: $(LIB)
PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py 0x500 0x2000; \
PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py 0x500 0x4000;
PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x2000 -s 0x50; \
PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x2000 -s 0xa0; \
PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x2000 -s 0x100; \
PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x5000 -s 0x50; \
PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x5000 -s 0xa0; \
PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x5000 -s 0x100; \
unittest: $(LIB_COV)

32
benchplot.sh Executable file
View file

@ -0,0 +1,32 @@
#!/bin/sh
gpdat=/tmp/bench.dat
e=500
min_i=10000
max_i=100000
it_i=1
min_s=10
max_s=10000
it_s=30
if [ $it_i -eq 1 ]
then
inc_i=$max_i
else
inc_i=$(expr $(expr $max_i - $min_i) / $(expr $it_i - 1))
fi
inc_s=$(expr $(expr $max_s - $min_s) / $(expr $it_s - 1))
for i in $(seq $min_i $inc_i $max_i)
do
for s in $(seq $min_s $inc_s $max_s)
do
echo "GO $e $i $s"
PYTHONPATH=./ python3 tests/benchmark.py -c $e -i $i -s $s -G $gpdat
done
echo "" >> $gpdat
done

View file

@ -190,6 +190,8 @@ PyObject* rpnif_new(PyTypeObject *subtype, PyObject *args, PyObject* kwds)
}
expr = (PyRPNIterExpr_t*)ret;
expr->rif = NULL;
expr->rif_params = NULL;
expr->borrowed_if = 0;
return ret;
}
@ -219,6 +221,7 @@ int rpnif_init(PyObject *self, PyObject *args, PyObject *kwds)
rpn_if_param_t *rif_params = _rpnif_get_params(pos_flag, res_flag,
lim_obj, const_values_obj, stack_size);
expr_self->rif_params = rif_params;
if(!rif_params)
{
@ -228,6 +231,11 @@ int rpnif_init(PyObject *self, PyObject *args, PyObject *kwds)
expr_self->ndim = ((rpn_if_default_data_t*)(rif_params->data))->ndim;
const Py_ssize_t expt_sz = rif_params->mem_sz * rif_params->value_sz;
if(mmap_obj == Py_None)
{
Py_DECREF(mmap_obj);
mmap_obj = NULL;
}
if(!mmap_obj)
{
PyObject *fileno = PyLong_FromLong(-1);
@ -282,6 +290,102 @@ of mmap.mmap");
return _rpnif_init_expr_tuple(expr_self);
}
PyObject* rpnif_init_borrowed(rpn_if_t *borrowed_if, PyObject *mmap_obj)
{
PyObject *sz_lim, *const_val;
PyObject *args, *ret;
rpn_if_default_data_t *params;
params = (rpn_if_default_data_t*)(borrowed_if->params->data);
const size_t nsz = params->ndim + (params->pos_flag == RPN_IF_POSITION_XDIM?1:0);
sz_lim = PyTuple_New(nsz);
for(size_t i=0; i<nsz; i++)
{
PyObject *v = PyLong_FromLong(params->size_lim[i]);
PyTuple_SET_ITEM(sz_lim, i, v);
}
const size_t const_val_sz = params->res_flag == RPN_IF_RES_CONST ? 1 : \
(params->res_flag == RPN_IF_RES_CONST_RGBA ? 4 : 0);
if(const_val_sz)
{
const_val = PyTuple_New(const_val_sz);
for(size_t i=0; i<const_val_sz; i++)
{
PyObject *v = PyLong_FromLong(params->const_val[i]);
PyTuple_SET_ITEM(const_val, i, v);
}
}
else
{
const_val = Py_None;
Py_INCREF(const_val);
}
// no mmap :/
// It seems there is no elegant solutions for the moment
// if we give None a new mmap is created
// if we give a dummy mmap it has to have the expected size...
// using None for the moment...
/**@todo find a solution */
PyObject *mmap_arg = Py_None;
if(mmap_obj)
{
mmap_arg = mmap_obj;
}
args = Py_BuildValue("hhOOBO", params->pos_flag, params->res_flag,
sz_lim, const_val,
borrowed_if->params->rpn_stack_sz,
mmap_arg);
Py_DECREF(sz_lim);
Py_DECREF(const_val);
if(!args || PyErr_Occurred())
{
return NULL;
}
ret = PyObject_CallObject((PyObject*)&RPNIterExprType, args);
Py_DECREF(args);
if(!ret || PyErr_Occurred())
{
return NULL;
}
PyRPNIterExpr_t* instance = (PyRPNIterExpr_t*)ret;
// changing if instance
rpn_if_free(instance->rif);
instance->rif = borrowed_if;
// reseting expr tuple
Py_DECREF(instance->expr);
instance->expr = NULL;
// mmap update
PyBuffer_Release(&instance->mm_buff);
if(!mmap_obj)
{
Py_DECREF(instance->mmap);
instance->mmap = NULL;
instance->mm_buff.buf = NULL;
instance->_mmap = borrowed_if->mem;
}
instance->borrowed_if = 1;
// someone else will take care to free the rif params
if(instance->rif_params) { free(instance->rif_params); }
instance->rif_params = NULL;
if(_rpnif_init_expr_tuple(instance) < 0)
{
return NULL;
}
return ret;
}
/**@brief Returns the parameters in a named tuple */
PyObject *rpnif_get_params(PyObject *self)
{
@ -414,47 +518,7 @@ PyObject *rpnif_to_pos(PyObject *self, PyObject** argv, Py_ssize_t argc)
{
PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self;
rpn_if_default_data_t *rif_data = (rpn_if_default_data_t*)expr_self->rif->params->data;
const short idx_off = rif_data->pos_flag == RPN_IF_POSITION_XDIM?1:0;
long long value;
if((size_t)argc != rif_data->ndim)
{
PyErr_Format(PyExc_IndexError,
"Expression expect %lu dimentions coordinates,"
" but %ld arguments given.",
rif_data->ndim, argc);
return NULL;
}
rpn_value_t result=0;
size_t cur_dim_sz = 1;
for(size_t i=0; i<rif_data->ndim; i++)
{
if(!PyLong_Check(argv[i]))
{
PyErr_SetString(PyExc_TypeError,
"coordinates must be integers");
return NULL;
}
value = PyLong_AsLongLong(argv[i]);
if(value < 0)
{
value = (-(rpn_value_t)-value)%rif_data->size_lim[i+idx_off];
}
if((size_t)value >= rif_data->size_lim[i+idx_off])
{
PyErr_Format(PyExc_IndexError,
"Coordinate %ld overflows : %R/%lu",
i, argv[i],
rif_data->size_lim[i+idx_off]);
return NULL;
}
result += value * cur_dim_sz;
cur_dim_sz *= rif_data->size_lim[i+idx_off];
}
return PyLong_FromUnsignedLongLong(result);
return _rpnif_to_pos(rif_data, argv, argc);
}
@ -462,48 +526,8 @@ PyObject *rpnif_from_pos(PyObject *self, PyObject* _pos)
{
PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self;
rpn_if_default_data_t *rif_data = (rpn_if_default_data_t*)expr_self->rif->params->data;
const short idx_off = rif_data->pos_flag == RPN_IF_POSITION_XDIM?1:0;
if(!PyLong_Check(_pos))
{
PyErr_SetString(PyExc_TypeError,
"Expected position to be an integer");
return NULL;
}
size_t pos = PyLong_AsUnsignedLong(_pos);
if(PyErr_Occurred())
{
return NULL;
}
PyObject *res = PyTuple_New(rif_data->ndim);
if(PyErr_Occurred())
{
return NULL;
}
for(size_t i=0; i<rif_data->ndim; i++)
{
size_t val;
size_t lim = rif_data->size_lim[i+idx_off];
val = pos % lim;
pos /= lim;
PyObject *elt = PyLong_FromUnsignedLong(val);
if(PyErr_Occurred())
{
goto err;
}
PyTuple_SET_ITEM(res, i, elt);
}
return res;
err:
Py_DECREF(res);
return NULL;
return _rpnif_from_pos(rif_data, _pos);
}
@ -637,7 +661,7 @@ err_loop_newtuple:
void rpnif_del(PyObject *self)
{
PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self;
if(expr_self->rif)
if((!expr_self->borrowed_if) && expr_self->rif)
{
rpn_if_free(expr_self->rif);
expr_self->rif = NULL;
@ -657,6 +681,7 @@ void rpnif_del(PyObject *self)
Py_DECREF(expr_self->expr);
expr_self->expr = NULL;
}
if(expr_self->rif_params) { free(expr_self->rif_params); }
}
@ -836,7 +861,7 @@ PyObject* rpnif_subscript(PyObject *self, PyObject *key)
if(idx < 0)
{
PyErr_Format(PyExc_IndexError,
"No expression '%s' with given parameters",
"No expression '%R' with given parameters",
key);
return NULL;
}
@ -851,7 +876,7 @@ int rpnif_ass_subscript(PyObject *self, PyObject *key, PyObject *elt)
if(idx < 0)
{
PyErr_Format(PyExc_IndexError,
"Cannot set expression '%U' that do not exists with this parameters",
"Cannot set expression '%R' that do not exists with this parameters",
key);
return -1;
}
@ -1319,11 +1344,13 @@ PyObject* rpnif_setstate(PyObject *self, PyObject *state_bytes)
"Unable to initialize rif expression");
return NULL;
}
expr_self->rif_params = params;
expr_self->ndim = ser_data->ndim;
if(_rpnif_init_expr_tuple(expr_self) < 0)
{
return NULL;
}
expr_self->borrowed_if = 0; // explicitly not borrowed
Py_RETURN_NONE;
}
@ -1556,6 +1583,99 @@ PyObject *_rpnif_params_to_tuple(const rpn_if_param_t *_params)
}
PyObject *_rpnif_to_pos(rpn_if_default_data_t *rif_data, PyObject** argv, Py_ssize_t argc)
{
const short idx_off = rif_data->pos_flag == RPN_IF_POSITION_XDIM?1:0;
long long value;
if((size_t)argc != rif_data->ndim)
{
PyErr_Format(PyExc_IndexError,
"Expression expect %lu dimentions coordinates,"
" but %ld arguments given.",
rif_data->ndim, argc);
return NULL;
}
rpn_value_t result=0;
size_t cur_dim_sz = 1;
for(size_t i=0; i<rif_data->ndim; i++)
{
if(!PyLong_Check(argv[i]))
{
PyErr_SetString(PyExc_TypeError,
"coordinates must be integers");
return NULL;
}
value = PyLong_AsLongLong(argv[i]);
if(value < 0)
{
value = (-(rpn_value_t)-value)%rif_data->size_lim[i+idx_off];
}
if((size_t)value >= rif_data->size_lim[i+idx_off])
{
PyErr_Format(PyExc_IndexError,
"Coordinate %ld overflows : %R/%lu",
i, argv[i],
rif_data->size_lim[i+idx_off]);
return NULL;
}
result += value * cur_dim_sz;
cur_dim_sz *= rif_data->size_lim[i+idx_off];
}
return PyLong_FromUnsignedLongLong(result);
}
PyObject *_rpnif_from_pos(rpn_if_default_data_t *rif_data, PyObject* _pos)
{
const short idx_off = rif_data->pos_flag == RPN_IF_POSITION_XDIM?1:0;
if(!PyLong_Check(_pos))
{
PyErr_SetString(PyExc_TypeError,
"Expected position to be an integer");
return NULL;
}
size_t pos = PyLong_AsUnsignedLong(_pos);
if(PyErr_Occurred())
{
return NULL;
}
PyObject *res = PyTuple_New(rif_data->ndim);
if(PyErr_Occurred())
{
return NULL;
}
for(size_t i=0; i<rif_data->ndim; i++)
{
size_t val;
size_t lim = rif_data->size_lim[i+idx_off];
val = pos % lim;
pos /= lim;
PyObject *elt = PyLong_FromUnsignedLong(val);
if(PyErr_Occurred())
{
goto err;
}
PyTuple_SET_ITEM(res, i, elt);
}
return res;
err:
Py_DECREF(res);
return NULL;
}
int _rpnif_init_expr_tuple(PyRPNIterExpr_t *expr_self)
{
expr_self->expr = PyTuple_New(expr_self->rif->params->rpn_sz);

View file

@ -101,6 +101,13 @@ typedef struct
/**@brief Memory map buffer pointer */
void *_mmap;
/**@brief Pointer on params to free at del */
rpn_if_param_t *rif_params;
/**@brief If true, someone else (an ifs ?) will take care of freeing
* the if at deletion */
short borrowed_if;
} PyRPNIterExpr_t;
/**@brief RPNIterExpr.params static method
@ -128,6 +135,13 @@ PyObject* rpnif_new(PyTypeObject *subtype, PyObject* args, PyObject* kwds);
*/
int rpnif_init(PyObject *self, PyObject *args, PyObject *kwds);
/**@brief RPNIterExpr constructor with a borrowed expression
* @param borrowed_if Pointer on borrowed instance
* @param mmap Pointer on borrowed python mmap object (NULL if deserialized instance)
* @return 0 if no error else -1
*/
PyObject* rpnif_init_borrowed(rpn_if_t *borrowed_if, PyObject *mmap);
/**@brief RPNIterExpr __del__ method
* @param self RPNExpr instance
* @ingroup pymod_pyrpn_RPNExprIter
@ -311,6 +325,13 @@ rpn_if_param_t *_rpnif_get_params(unsigned short pos_flag, unsigned short res_fl
*/
PyObject *_rpnif_params_to_tuple(const rpn_if_param_t *params);
/**@brief Implementation of both RPNIterExpr.to_pos and RPNIFS.to_pos */
PyObject *_rpnif_to_pos(rpn_if_default_data_t *rif_data, PyObject** argv, Py_ssize_t argc);
/**@brief Implementation of bot RPNIterExpr.to_pos and RPNIFS.from_pos */
PyObject *_rpnif_from_pos(rpn_if_default_data_t *rif_data, PyObject* _pos);
int _rpnif_init_expr_tuple(PyRPNIterExpr_t *expr_self);
#endif

View file

@ -31,11 +31,28 @@ in the system."),
METH_FASTCALL,
"self, idx, weight=None",
"Get or set a single weight"),
PYRPN_method("position", rpnifs_position,
METH_FASTCALL,
"self, position=None",
"Get or set the current position"),
PYRPN_method("run", rpnifs_run,
METH_FASTCALL,
"self, steps, rand_bytes=None, /",
"Run ifs steps times"),
PYRPN_method("to_pos", rpnifs_to_pos,
METH_FASTCALL,
"self, *args, /",
"Return a position (int) from a coordinates given as"
"argument."),
PYRPN_method("from_pos", rpnifs_from_pos,
METH_O,
"self, position, /",
"Return a coordinates tuple from given position."),
{NULL} // Sentinel
};
static PyMemberDef RPNIFS_members[] = {
{"expressions", T_OBJECT, offsetof(PyRPNIFS_t, rifs), READONLY,
{"expressions", T_OBJECT, offsetof(PyRPNIFS_t, rpn_if), READONLY,
"The tuple with RPNIterExpr instances"},
{"mmap", T_OBJECT, offsetof(PyRPNIFS_t, mmap), READONLY,
"The mmap storing data"},
@ -172,7 +189,12 @@ length %ld provided",
"Error initializing ifs: %s", strerror(errno));
return -1;
}
ifs_self->rpn_if = NULL;
if(_rpnifs_update_if_tuple(ifs_self) < 0)
{
return -1;
}
return 0;
}
@ -182,6 +204,7 @@ void rpnifs_del(PyObject *self)
PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self;
rpn_ifs_free(ifs_self->ifs);
free(ifs_self->ifs);
if(ifs_self->mmap)
{
if(ifs_self->mm_buff.buf)
@ -195,6 +218,23 @@ void rpnifs_del(PyObject *self)
}
PyObject *rpnifs_to_pos(PyObject *self, PyObject** argv, Py_ssize_t argc)
{
PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self;
rpn_if_default_data_t *rif_data = (rpn_if_default_data_t*)ifs_self->ifs->params.data;
return _rpnif_to_pos(rif_data, argv, argc);
}
PyObject *rpnifs_from_pos(PyObject *self, PyObject* _pos)
{
PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self;
rpn_if_default_data_t *rif_data = (rpn_if_default_data_t*)ifs_self->ifs->params.data;
return _rpnif_from_pos(rif_data, _pos);
}
PyObject *rpnifs_weights(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self;
@ -264,6 +304,11 @@ PyObject *rpnifs_weights(PyObject *self, PyObject *const *args, Py_ssize_t nargs
if(PyErr_Occurred()) { return NULL; }
PyTuple_SET_ITEM(ret, i, item);
}
if(_rpnifs_update_if_tuple(ifs_self) < 0)
{
return NULL;
}
return ret;
err_weights:
@ -280,6 +325,85 @@ PyObject *rpnifs_weight(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
}
PyObject *rpnifs_position(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self;
if(nargs == 1 && args[0] != Py_None)
{
size_t pos = PyLong_AsSize_t(args[0]);
if(PyErr_Occurred()) { return NULL; }
if(pos > ifs_self->ifs->params.mem_sz)
{
pos %= ifs_self->ifs->params.mem_sz;
#ifndef NDEBUG
PyErr_WarnFormat(PyExc_RuntimeWarning, 1,
"Given position %R overflows and stored as %lu",
args[0], pos);
#endif
}
ifs_self->ifs->pos = pos;
}
return PyLong_FromSize_t(ifs_self->ifs->pos);
}
PyObject *rpnifs_run(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self;
if(nargs == 0 || nargs > 2)
{
PyErr_Format(PyExc_ValueError,
"Expected 1 or 2 arguments but %ld given",
nargs);
return NULL;
}
size_t steps = PyLong_AsSize_t(args[0]);
if(PyErr_Occurred())
{
return NULL;
}
else if(steps == 0)
{
PyErr_Format(PyExc_ValueError,
"Steps arguments must be > 0");
return NULL;
}
unsigned char *rnd_buf = NULL;
if(nargs == 2)
{
// Python bytes given as random source
const Py_ssize_t sz = PyBytes_Size(args[1]);
if(PyErr_Occurred()) { goto err; }
if((size_t)sz != sizeof(char)*steps)
{
PyErr_Format(PyExc_ValueError,
"Given random bytes are too small."
"Expected %ld bytes but got %ld",
sizeof(char)*steps,
sz);
goto err;
}
rnd_buf = (unsigned char*)PyBytes_AsString(args[1]);
}
const int ret = rpn_ifs_run(ifs_self->ifs, steps, rnd_buf);
if(ret < 0)
{
PyErr_Format(PyExc_RuntimeError,
"Unable to run IFS : %s",
strerror(errno));
return NULL;
}
Py_RETURN_NONE;
err:
return NULL;
}
PyObject *rpnifs_str(PyObject *self)
{
PyErr_SetString(PyExc_NotImplementedError, "TODO");
@ -303,8 +427,23 @@ Py_ssize_t rpnifs_len(PyObject *self)
PyObject *rpnifs_expr_item(PyObject *self, Py_ssize_t idx)
{
PyErr_SetString(PyExc_NotImplementedError, "TODO");
return NULL;
PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self;
Py_ssize_t _idx = idx;
if(idx < 0)
{
_idx = ifs_self->ifs->if_sz - 1 + idx;
}
if(_idx < 0 || (size_t) _idx >= ifs_self->ifs->params.rpn_sz)
{
PyErr_Format(PyExc_IndexError,
"There is %ld expressions in the system but index %ld asked",
ifs_self->ifs->params.rpn_sz, idx);
return NULL;
}
PyObject *ret = PyTuple_GET_ITEM(ifs_self->rpn_if, _idx);
Py_INCREF(ret);
return ret;
}
@ -326,4 +465,23 @@ void rpnifs_releasebuffer(PyObject *self, Py_buffer *view)
PyErr_SetString(PyExc_NotImplementedError, "TODO");
}
int _rpnifs_update_if_tuple(PyRPNIFS_t *ifs_self)
{
if(ifs_self->rpn_if)
{
Py_DECREF(ifs_self->rpn_if);
}
ifs_self->rpn_if = PyTuple_New(ifs_self->ifs->if_sz);
for(size_t i=0; i<ifs_self->ifs->if_sz; i++)
{
PyObject *py_if = rpnif_init_borrowed(ifs_self->ifs->rpn_if[i],
ifs_self->mmap);
if((!py_if) || PyErr_Occurred())
{
return -1;
}
PyTuple_SET_ITEM(ifs_self->rpn_if, i, py_if);
}
return 0;
}

View file

@ -62,6 +62,7 @@ typedef struct
/** @brief Pointer on IF expressions in the system */
rpn_if_t **rifs;
/**@brief Python mmap.mmap instance representing rif memory map
* @note NULL if unpickled instance */
PyObject *mmap;
@ -80,8 +81,14 @@ PyObject *rpnifs_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds);
int rpnifs_init(PyObject *self, PyObject *args, PyObject *kwds);
void rpnifs_del(PyObject *self);
PyObject *rpnifs_to_pos(PyObject *self, PyObject** argv, Py_ssize_t argc);
PyObject *rpnifs_from_pos(PyObject *self, PyObject* _pos);
PyObject *rpnifs_weights(PyObject *self, PyObject *const *args, Py_ssize_t nargs);
PyObject *rpnifs_weight(PyObject *self, PyObject *const *args, Py_ssize_t nargs);
PyObject *rpnifs_position(PyObject *self, PyObject *const *args, Py_ssize_t nargs);
PyObject *rpnifs_run(PyObject *self, PyObject *const *args, Py_ssize_t nargs);
PyObject *rpnifs_str(PyObject *self);
PyObject *rpnifs_repr(PyObject *self);
@ -93,5 +100,7 @@ int rpnifs_expr_ass_item(PyObject *self, Py_ssize_t idx, PyObject *value);
int rpnifs_getbuffer(PyObject *self, Py_buffer *view, int flags);
void rpnifs_releasebuffer(PyObject *self, Py_buffer *view);
int _rpnifs_update_if_tuple(PyRPNIFS_t *ifs_self);
#endif

View file

@ -176,6 +176,11 @@ void rpn_if_free(rpn_if_t* rif)
size_t rpn_if_step(rpn_if_t *rif, size_t pos)
{
size_t newpos;
assert(rif != NULL);
assert(rif->params != NULL);
assert(rif->params->getarg_f != NULL);
rif->params->getarg_f(rif, pos);
for(size_t i=0; i<rif->params->rpn_sz; i++)
{

View file

@ -21,6 +21,8 @@
#include "config.h"
#include <assert.h>
#include "rpn_jit.h"
/**@file rpn_if.h

View file

@ -209,6 +209,9 @@ int rpn_ifs_run(rpn_ifs_t *rifs, size_t n, unsigned char *rnd)
}
if(getrandom(_rnd, n, GRND_NONBLOCK) < 0)
{
int err = errno;
free(_rnd);
errno = err;
return -1;
}
}
@ -220,6 +223,7 @@ int rpn_ifs_run(rpn_ifs_t *rifs, size_t n, unsigned char *rnd)
for(size_t i=0; i<n; i++)
{
rpn_if_t *cur_if = rifs->if_proba[_rnd[i]];
assert(cur_if != NULL);
rifs->pos = rpn_if_step(cur_if, rifs->pos);
}
return 0;
@ -251,9 +255,13 @@ int rpn_ifs_weight_update(rpn_ifs_t *rifs)
j++;
}
}
if(j==255)
for(j=j; j<256; j++)
{
rifs->if_proba[255] = rifs->if_proba[254];
// dirty & quick fill with last value
/* @todo enhance the random choice resolution to match
* more closely asked weights (uint16 vs actualt uint8 random
* choice ? */
rifs->if_proba[j] = rifs->if_proba[j-1];
}
return 0;
}

View file

@ -21,6 +21,7 @@
#include "config.h"
#include <assert.h>
#include <string.h>
#include "rpn_jit.h"
@ -126,7 +127,7 @@ int rpn_ifs_del_if(rpn_ifs_t *rifs, size_t if_idx);
* @param rifs The iterated function system
* @param n consecutive IFS calls
* @param seed prepopulated array of random bytes (if NULL one is generated)
* @return 1 if error else 0
* @return -1 if error else 0
*/
int rpn_ifs_run(rpn_ifs_t *rifs, size_t n, unsigned char *rnd);

View file

@ -1,5 +1,7 @@
#!/usr/bin/python3
import argparse
import os
import sys
import random
import time
@ -16,31 +18,24 @@ except (ImportError, NameError) as e:
file=sys.stderr)
raise e
if len(sys.argv) > 5:
usage()
exit(1)
allint = lambda val: int(val, 0)
#expr_count = 0x200
#max_iter = 0x3000
#argc = 2
#sz = 0x20
expr_count = 0x200
max_iter = 0x5000
#argc = 2
#sz = 0x30
argc = 5
sz = 0x50
parser = argparse.ArgumentParser(description="Benchmark RPNExpr vs IFS")
parser.add_argument("-c", "--expr-count", type=allint, default=0x200)
parser.add_argument("-i", "--iterations", type=allint, default=0x5000)
parser.add_argument("-s", "--expr-size", type=allint, default=0x50)
parser.add_argument("-G", "--gnuplot-output", type=str, default=None)
try:
expr_count = int(sys.argv[1], base=0)
max_iter = int(sys.argv[2], base=0)
argc = int(sys.argv[3], base=0)
sz = int(sys.argv[4], base=0)
except IndexError:
pass
except Exception:
usage()
exit(2)
args = parser.parse_args()
expr_count = args.expr_count
max_iter = args.iterations
sz = args.expr_size
argc = 1
gpout=None
if args.gnuplot_output is not None:
gpout = open(args.gnuplot_output, "a")
try:
from tqdm import tqdm
@ -53,14 +48,6 @@ except (ImportError, NameError) as e:
tqr = range
write=True
if len(sys.argv) > 1:
expr_count = int(sys.argv[1], 0)
if len(sys.argv) > 2:
max_iter = int(sys.argv[2], 0)
if len(sys.argv) > 3:
sz = int(sys.argv[4], 0)
if len(sys.argv) > 4:
argc = int(sys.argv[3], 0)
tot = 0
time_op = 0
@ -68,12 +55,15 @@ start = time.time()
IMAX = (1<<63)-1
samples = 8
print("========\nIF :", end=" ")
print("Running %dK iter on %d expressions with %d op and %d args" % (max_iter//1000,
expr_count,
sz, argc))
res = [0 for _ in range(512)]
rnd_samples = [[[random.randint(0, IMAX) for _ in range(argc)] for _ in range(max_iter)]
for _ in range(samples)]
for i in tqr(expr_count):
rnd_expr = pyrpn.random_expr(argc, sz)
all_start = time.time()
@ -82,7 +72,10 @@ for i in tqr(expr_count):
start_op = time.time()
rnds = random.choice(rnd_samples)
for rnd in rnds:
expr.eval(*rnd)
r = expr.eval(*rnd)
# emulate memory access for simple if to make comparison
# consistant with IFS
res[r%512] += 1
time_op += time.time() - start_op
tot += time.time() - all_start
if write:
@ -91,8 +84,7 @@ for i in tqr(expr_count):
if write:
sys.stderr.write("\n")
total = time.time() - start
print("Runned %dK iter on %d expressions in %.2fs" % (max_iter//1000,
expr_count, total))
ops = max_iter*expr_count*sz/time_op/(1000*1000)
msg = "%5.1fM op/s %6.1fK iter/s %.1fms per expr (%dK iter, %d op per expr)"
msg %= (ops,
@ -100,3 +92,51 @@ msg %= (ops,
tot/expr_count*1000,
max_iter//1000, sz)
print(msg)
print("++++++++")
if_ops = ops
if_sz = 5
ifs_count = expr_count // if_sz
ifs_sz = 3
mem_sz = (512,512)
expr_count = ifs_count * if_sz
print("IFS :", end=" ")
print("Running %dK iter on %d ifs (sz=%d with %d if choosen randomly and %d op) RGB of size %r" % \
(max_iter/1000, ifs_count , if_sz, ifs_sz, sz, mem_sz))
ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, mem_sz)
runtime = 0
all_start = time.time()
for i in tqr(ifs_count):
weights = [random.randint(1,50) for _ in range(ifs_sz)]
ifs.weights(weights)
for i in range(ifs_sz):
for expr_id in 'RGBXY':
ifs[i][expr_id] = pyrpn.random_expr(if_sz, sz)
rnd = os.getrandom(max_iter)
start = time.time()
ifs.run(max_iter, rnd)
runtime += time.time() - start
if write:
sys.stderr.write(".")
sys.stderr.flush()
if write:
sys.stderr.write("\n")
total = time.time() - all_start
ops = max_iter*ifs_count*if_sz*sz/(runtime*1000*1000)
msg = "%5.1fM op/s %6.1fK expr_iter/s %.2fms per ifs"
msg %= (ops,
max_iter*ifs_count*if_sz/runtime/1000,
total/ifs_count)
ifs_ops = ops
print(msg)
print("========")
if gpout:
line = "%d %d %f %f\n" % (max_iter, sz, if_ops, ifs_ops)
gpout.write(line)
gpout.close()

View file

@ -262,8 +262,8 @@ int test_rpn_if_default()
return -1;
}
free(rif_param);
rpn_if_free(rif);
free(rif_param);
return 0;
}
@ -289,8 +289,8 @@ int test_rpn_if_default2()
return -1;
}
free(rif_param);
rpn_if_free(rif);
free(rif_param);
return 0;
}

View file

@ -25,6 +25,7 @@ import sys
import unittest
try:
import pyrpn
except (ImportError, NameError) as e:
@ -63,3 +64,194 @@ class TestRPNIFS(unittest.TestCase):
nw = ifs.weights()
self.assertEqual(tuple(w), nw)
def test_position(self):
ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY,
pyrpn.const.RESULT_COUNT,
(640,480))
self.assertEqual(len(ifs), 0)
self.assertEqual(ifs.position(), 0)
self.assertEqual(ifs.position(10), 10)
self.assertEqual(ifs.position(), 10)
for i in range(640*480):
ifs.position(i)
self.assertEqual(ifs.position(), i)
def test_run(self):
""" Testing ifs run """
ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY,
pyrpn.const.RESULT_COUNT,
(256,256))
ifs.weights([1])
ifs[0]['X'] = 'A0 13 +'
ifs[0]['Y'] = 'A1 2 +'
xpos = lambda steps: (steps*13)%256
ypos = lambda steps: (steps*2)%256
ifs.run(1)
self.assertEqual(ifs.position(), ifs.to_pos(xpos(1),ypos(1)))
steps = 1
for _ in range(128):
rnd = random.randint(1,128)
steps += rnd
ifs.run(rnd)
self.assertEqual(ifs.position(),
ifs.to_pos(xpos(steps), ypos(steps)))
def test_run_rnd(self):
""" Testing ifs run with random if choice, same weights """
sz = [10000,10000]
ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY,
pyrpn.const.RESULT_COUNT,
sz)
ifs.weights([1,1])
ifs[0]['X'] = 'A0 1 +'
ifs[0]['Y'] = 'A1 1 -'
ifs[1]['X'] = 'A0 1 -'
ifs[1]['Y'] = 'A0 1 +'
for _ in Progress(256):
ifs.position(ifs.to_pos(*[s//2 for s in sz]))
steps = 0
steps_per_loop = 0x1000
for _ in range(4):
steps += steps_per_loop
ifs.run(steps_per_loop)
coord = ifs.from_pos(ifs.position())
distance = [min(c, sz[i]-c) for i, c in enumerate(coord)]
dist = sum(distance)/2
percent = steps / (dist * 100)
self.assertLess(percent, 1)
class TestRPNIFSCoordinates(unittest.TestCase):
""" Testing methods for coordinates <-> position convertions """
def test_position_linear(self):
""" Testing linear coordinate convertion methods """
rif = pyrpn.RPNIFS(pyrpn.const.POS_LINEAR,
pyrpn.const.RESULT_CONST,
(256,), (42,))
for pos in range(256):
with self.subTest(rif=rif, position=pos, expt=(pos,)):
res = rif.from_pos(pos)
self.assertEqual(res, (pos,))
with self.subTest(rif=rif, expt=pos, coord=(pos,)):
res = rif.to_pos(pos)
self.assertEqual(res, pos)
def test_position_xy(self):
""" Testing XY coordinate convertion methods """
for _ in Progress(5):
sz = (random.randint(32,128), random.randint(32,128))
rif = pyrpn.RPNIFS(pyrpn.const.POS_XY,
pyrpn.const.RESULT_CONST,
sz, (42,))
for pos in range(sz[0]*sz[1]):
coord = rif.from_pos(pos)
expt = (pos % sz[0], pos // sz[0])
with self.subTest(sz=sz, pos=pos, expt=expt, result=coord):
self.assertEqual(expt, coord)
result = rif.to_pos(*coord)
with self.subTest(sz=sz, coord=coord, expt=pos, result=result):
self.assertEqual(result, pos)
def test_position_xy_neg(self):
""" Testing XY coordinates with negative values """
sz = (100,200)
rif = pyrpn.RPNIFS(pyrpn.const.POS_XY,
pyrpn.const.RESULT_CONST,
sz, (42,))
for _ in range(1000):
ny = random.randint(-0x1000000, sz[1])
nx = random.randint(0,sz[0]-1)
pos = rif.to_pos(nx, ny)
coord = rif.from_pos(pos)
self.assertEqual(coord, (nx, (ny%(1<<64))%sz[1]))
def test_position_xy_random(self):
""" Testing XY coordinate convertion methods (random samples)"""
for _ in Progress(256):
sz = (random.randint(32,4096), random.randint(32,4096))
rif = pyrpn.RPNIFS(pyrpn.const.POS_XY,
pyrpn.const.RESULT_CONST,
sz, (42,))
for _ in range(500):
pos = random.randint(0, (sz[0]*sz[1])-1)
coord = rif.from_pos(pos)
expt = (pos % sz[0], pos // sz[0])
with self.subTest(sz=sz, pos=pos, expt=expt, result=coord):
self.assertEqual(expt, coord)
result = rif.to_pos(*coord)
with self.subTest(sz=sz, coord=coord, expt=pos, result=result):
self.assertEqual(result, pos)
def test_position_xdim(self):
""" Testing XDIM coordinate convertion methods """
ndim = 5
resol = [8 for _ in range(ndim)]
sz = [ndim]+resol
linear_size = 1
for e in resol:
linear_size *= e
rif = pyrpn.RPNIFS(pyrpn.const.POS_XDIM,
pyrpn.const.RESULT_CONST,
sz, (42,))
for pos in range(linear_size):
coord = rif.from_pos(pos)
expt = []
cur = pos
for dim in resol:
elt = cur % dim
cur //= dim
expt.append(elt)
expt = tuple(expt)
with self.subTest(sz=sz, pos=pos, expt=expt, result=coord):
self.assertEqual(expt, coord)
result = rif.to_pos(*coord)
with self.subTest(sz=sz, coord=coord, expt=pos, result=result):
self.assertEqual(result, pos)
def test_position_xdim_neg(self):
""" Testing XDIM negative position convertion """
# TODO test negativ for other coordinate systems
ndim = 4
szlim = (ndim, 10,20,30,40)
rif = pyrpn.RPNIFS(pyrpn.const.POS_XDIM,
pyrpn.const.RESULT_CONST,
szlim, (42,))
pos = rif.to_pos(0,0,-5,0)
self.assertEqual(rif.from_pos(pos), (0,0,(-5%(1<<64))%30, 0))
def test_position_xdim(self):
""" Testing XDIM coordinate convertion methods (random sampling)"""
for _ in Progress(16):
ndim = random.randint(3,8)
resol = [random.randint(2, 16) for _ in range(ndim)]
sz = [ndim]+resol
linear_size = 1
for e in resol:
linear_size *= e
rif = pyrpn.RPNIFS(pyrpn.const.POS_XDIM,
pyrpn.const.RESULT_CONST,
sz, (42,))
for _ in range(2000):
pos = random.randint(0, linear_size-1)
coord = rif.from_pos(pos)
expt = []
cur = pos
for dim in resol:
elt = cur % dim
cur //= dim
expt.append(elt)
expt = tuple(expt)
with self.subTest(sz=sz, pos=pos, expt=expt, result=coord):
self.assertEqual(expt, coord)
result = rif.to_pos(*coord)
with self.subTest(sz=sz, coord=coord, expt=pos, result=result):
self.assertEqual(result, pos)