468 lines
15 KiB
C++
468 lines
15 KiB
C++
// Numexpr - Fast numerical array expression evaluator for NumPy.
|
|
//
|
|
// License: BSD
|
|
// Author: See AUTHORS.txt
|
|
//
|
|
// See LICENSE.txt for details about copyright and rights to use.
|
|
//
|
|
// module.cpp contains the CPython-specific module exposure.
|
|
|
|
#define DO_NUMPY_IMPORT_ARRAY
|
|
|
|
#define CHECK_END_COND if(gs.barrier_passed == BARRIER_EXIT) return(0);
|
|
|
|
#include "module.hpp"
|
|
#include "interp_header_GENERATED.hpp"
|
|
#include "numexpr_object.hpp"
|
|
#include "benchmark.hpp"
|
|
|
|
using namespace std;
|
|
|
|
// Global state definition. Also includes what used to be in th_params.
|
|
global_state gs;
|
|
// Benchmarking data containers
|
|
#if defined(_WIN32) && defined(BENCHMARKING)
|
|
LARGE_INTEGER TIMES[512];
|
|
LARGE_INTEGER T_NOW;
|
|
double FREQ;
|
|
#elif defined(BENCHMARKING) // Linux
|
|
timespec TIMES[512];
|
|
timespec T_NOW;
|
|
#endif
|
|
|
|
|
|
// Do the worker job for a certain thread
|
|
void* th_worker(void *tidptr) {
|
|
int tid = *(int *)tidptr;
|
|
// Parameters for threads
|
|
npy_intp start;
|
|
npy_intp vlen;
|
|
npy_intp task_size;
|
|
NpyIter* iter;
|
|
NumExprObject* neObj;
|
|
|
|
int* pc_error;
|
|
int ret;
|
|
npy_intp istart, iend;
|
|
char** errorMessage;
|
|
|
|
while(1) {
|
|
|
|
if( tid == 0 ) {
|
|
gs.init_sentinels_done = 0; // sentinels have to be initialised yet
|
|
}
|
|
|
|
// Meeting point for all threads (wait for initialization)
|
|
CHECK_END_COND;
|
|
pthread_mutex_lock(&gs.count_threads_mutex);
|
|
if( gs.count_threads < gs.n_thread ) {
|
|
gs.count_threads++;
|
|
// printf( "Count threads start %d\n", gs.count_threads );
|
|
do {
|
|
pthread_cond_wait(&gs.count_threads_cv, &gs.count_threads_mutex);
|
|
} while (gs.barrier_passed == BARRIER_HALT);
|
|
}
|
|
else { // Every thread is ready, go...
|
|
gs.barrier_passed = BARRIER_PASS;
|
|
pthread_cond_broadcast(&gs.count_threads_cv);
|
|
}
|
|
pthread_mutex_unlock(&gs.count_threads_mutex);
|
|
// Get parameters for this thread before entering the main loop
|
|
start = gs.start;
|
|
vlen = gs.vlen;
|
|
task_size = gs.task_size;
|
|
neObj = &gs.params[tid];
|
|
pc_error = gs.pc_error;
|
|
|
|
errorMessage = gs.errorMessage;
|
|
|
|
// Loop over blocks
|
|
CHECK_END_COND;
|
|
pthread_mutex_lock(&gs.count_mutex);
|
|
if( !gs.init_sentinels_done ) {
|
|
// Set sentinels and other global variables
|
|
gs.gindex = start;
|
|
istart = gs.gindex;
|
|
iend = istart + task_size;
|
|
if (iend > vlen) {
|
|
iend = vlen;
|
|
}
|
|
gs.init_sentinels_done = 1; // sentinels have been initialised
|
|
gs.giveup = 0; // no giveup initially
|
|
} else {
|
|
gs.gindex += task_size;
|
|
istart = gs.gindex;
|
|
iend = istart + task_size;
|
|
if (iend > vlen) {
|
|
iend = vlen;
|
|
}
|
|
}
|
|
// Grab one of the iterators
|
|
iter = gs.iter[tid];
|
|
if( iter == NULL ) {
|
|
gs.ret_code = -1;
|
|
gs.giveup = 1;
|
|
}
|
|
pthread_mutex_unlock(&gs.count_mutex);
|
|
DIFF_TIME(150+tid);
|
|
|
|
BENCH_TIME(200+tid);
|
|
while( istart < vlen && !gs.giveup ) {
|
|
// Reset the iterator to the range for this task
|
|
ret = NpyIter_ResetToIterIndexRange(iter, istart, iend,
|
|
errorMessage);
|
|
// Execute the task
|
|
if( ret >= 0 ) {
|
|
|
|
ret = vm_engine_iter_task(iter, neObj, tid, pc_error, errorMessage);
|
|
|
|
}
|
|
|
|
if( ret < 0 ) {
|
|
CHECK_END_COND;
|
|
pthread_mutex_lock(&gs.count_mutex);
|
|
gs.giveup = 1;
|
|
// Propagate error to main thread
|
|
gs.ret_code = ret;
|
|
pthread_mutex_unlock(&gs.count_mutex);
|
|
break;
|
|
}
|
|
CHECK_END_COND;
|
|
pthread_mutex_lock(&gs.count_mutex);
|
|
gs.gindex += task_size;
|
|
istart = gs.gindex;
|
|
iend = istart + task_size;
|
|
if (iend > vlen) {
|
|
iend = vlen;
|
|
}
|
|
pthread_mutex_unlock(&gs.count_mutex);
|
|
}
|
|
DIFF_TIME(200+tid);
|
|
BENCH_TIME(250+tid);
|
|
// Meeting point for all threads (wait for finalization)
|
|
CHECK_END_COND;
|
|
pthread_mutex_lock(&gs.count_threads_mutex);
|
|
if (gs.count_threads > 0) { // Not the last thread to join
|
|
gs.count_threads--;
|
|
// printf( "Count threads finalize %d\n", gs.count_threads );
|
|
do {
|
|
pthread_cond_wait(&gs.count_threads_cv, &gs.count_threads_mutex);
|
|
} while (gs.barrier_passed == BARRIER_PASS);
|
|
} else { // We're the last thread, get out of here.
|
|
gs.barrier_passed = BARRIER_HALT;
|
|
pthread_cond_broadcast(&gs.count_threads_cv);
|
|
}
|
|
pthread_mutex_unlock(&gs.count_threads_mutex);
|
|
DIFF_TIME(250+tid);
|
|
} // closes while(1)
|
|
|
|
// This should never be reached
|
|
return(0);
|
|
}
|
|
|
|
// Initialize threads and allocate space for arrays in global_state
|
|
int reinit_threads(int n_thread_old) {
|
|
int tid, rc;
|
|
|
|
gs.count_threads = 0; // Reset threads counter
|
|
gs.barrier_passed = BARRIER_HALT;
|
|
|
|
// Allocate memory
|
|
gs.threads = (pthread_t*)realloc( gs.threads, gs.n_thread * sizeof(pthread_t) );
|
|
gs.tids = (int*)realloc( gs.tids, gs.n_thread * sizeof(int*) );
|
|
|
|
gs.params = (NumExprObject*)realloc( gs.params, gs.n_thread * sizeof(NumExprObject) );
|
|
// params[R].program is shared.
|
|
|
|
// Allocate an arena for the registers
|
|
// Since NPY_MAXARGS=32, we can just allocate a maximum space and only
|
|
// consume a kB or so of RAM.
|
|
gs.registerArena = (char *)realloc( gs.registerArena, gs.n_thread * NPY_MAXARGS * sizeof(NumExprReg) );
|
|
for( int I=0; I < gs.n_thread; I++ ) {
|
|
gs.params[I].registers = (NumExprReg*)(gs.registerArena + I*NPY_MAXARGS*sizeof(NumExprReg) );
|
|
}
|
|
|
|
gs.stridesArray = (npy_intp*)realloc( gs.stridesArray, gs.n_thread * sizeof(npy_intp) );
|
|
gs.iter = (NpyIter**)realloc( gs.iter, gs.n_thread * sizeof(NpyIter*) );
|
|
gs.reduce_iter = (NpyIter**)realloc( gs.reduce_iter, gs.n_thread * sizeof(NpyIter*) );
|
|
|
|
// Finally, create the threads
|
|
for (tid = 0; tid < gs.n_thread; tid++) {
|
|
gs.tids[tid] = tid;
|
|
rc = pthread_create(&gs.threads[tid], NULL, th_worker, (void *)&gs.tids[tid]);
|
|
|
|
if (rc) {
|
|
fprintf(stderr,
|
|
"ERROR; return code from pthread_create() is %d\n", rc);
|
|
fprintf(stderr, "\tError detail: %s\n", strerror(rc));
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
gs.init_threads_done = 1; // Initialization done!
|
|
gs.pid = (int)getpid(); // save the PID for this process
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
int numexpr_set_nthreads(int n_thread_new) {
|
|
// Set the number of threads in numexpr's VM
|
|
int n_thread_old = gs.n_thread;
|
|
int T, rc;
|
|
void *status;
|
|
|
|
// printf("numexpr_set_nthreads #1, old =%d, new =%d\n", n_thread_old, n_thread_new );
|
|
if( n_thread_new <= 0 ) {
|
|
fprintf(stderr, "Error. `nthreads` must be a positive integer");
|
|
return -1;
|
|
}
|
|
if( n_thread_new == n_thread_old ) {
|
|
return n_thread_old;
|
|
}
|
|
|
|
// Only join threads if they are not initialized or if our PID is
|
|
// different from that in pid var (probably means that we are a
|
|
// subprocess, and thus threads are non-existent).
|
|
if( gs.init_threads_done && gs.pid == getpid() ) {
|
|
// Tell all existing threads to finish
|
|
gs.barrier_passed = BARRIER_EXIT;
|
|
pthread_mutex_lock(&gs.count_threads_mutex);
|
|
// Ensure that workers never wait on the condition variable
|
|
gs.count_threads = n_thread_old;
|
|
// Wake all workers
|
|
pthread_cond_broadcast(&gs.count_threads_cv);
|
|
pthread_mutex_unlock(&gs.count_threads_mutex);
|
|
|
|
for( T=0; T < n_thread_old; T++ ) {
|
|
// Join exiting threads
|
|
rc = pthread_join(gs.threads[T], &status);
|
|
if (rc) {
|
|
printf( "JOIN ERROR %d\n", rc );
|
|
fprintf(stderr, "ERROR; return code from pthread_join() is %d\n", rc);
|
|
fprintf(stderr, " Error detail: %s\n", strerror(rc));
|
|
exit(-1);
|
|
}
|
|
|
|
}
|
|
pthread_mutex_unlock(&gs.count_threads_mutex);
|
|
pthread_mutex_unlock(&gs.count_mutex);
|
|
gs.count_threads = 0;
|
|
gs.n_thread = n_thread_new;
|
|
gs.init_threads_done = 0;
|
|
gs.barrier_passed = BARRIER_HALT;
|
|
}
|
|
else {
|
|
gs.n_thread = n_thread_new;
|
|
}
|
|
|
|
// Launch a new pool of threads
|
|
reinit_threads(n_thread_old);
|
|
|
|
return n_thread_old;
|
|
}
|
|
PyDoc_STRVAR(SetNumThreads__doc__,
|
|
"Sets a maximum number of threads to be used in operations. Returns old value.\n");
|
|
static PyObject*
|
|
PySet_num_threads(PyObject *self, PyObject *args) {
|
|
int n_threads_new, n_threads_old;
|
|
if (!PyArg_ParseTuple(args, "i", &n_threads_new))
|
|
return NULL;
|
|
|
|
pthread_mutex_lock(&gs.global_mutex);
|
|
n_threads_old = numexpr_set_nthreads(n_threads_new);
|
|
pthread_mutex_unlock(&gs.global_mutex);
|
|
return Py_BuildValue("i", n_threads_old);
|
|
}
|
|
|
|
PyDoc_STRVAR(GetNumThreads__doc__,
|
|
"Gets a maximum number of threads to be used in operations.\n");
|
|
static PyObject*
|
|
PyGet_num_threads(PyObject *self, PyObject *args) {
|
|
int n_thread;
|
|
|
|
pthread_mutex_lock(&gs.global_mutex);
|
|
n_thread = gs.n_thread;
|
|
pthread_mutex_unlock(&gs.global_mutex);
|
|
return Py_BuildValue("i", n_thread);
|
|
}
|
|
|
|
|
|
Py_ssize_t numexpr_set_tempsize(Py_ssize_t newSize) {
|
|
|
|
// printf( "DEBUG: setting temporary space to %d bytes.\n", newSize );
|
|
// The bytes of pre-allocated space for temporaries PER THREAD.
|
|
Py_ssize_t oldSize = gs.tempSize;
|
|
if( newSize <= 0) {
|
|
// Free space for hibernation
|
|
gs.tempSize = 0;
|
|
free( gs.tempArena );
|
|
|
|
} else if( oldSize <= 0 ) {
|
|
// Space was deallocated or never initialized.
|
|
gs.tempSize = newSize;
|
|
gs.tempArena = (char *)malloc(gs.tempSize);
|
|
|
|
} else {
|
|
// We can use realloc here.
|
|
gs.tempSize = newSize;
|
|
gs.tempArena = (char *)realloc(gs.tempArena, gs.tempSize);
|
|
}
|
|
return oldSize;
|
|
}
|
|
|
|
PyDoc_STRVAR(SetTempSize__doc__,
|
|
"set_tempsize(int size) -- Sets the size of the temporary array arena. If you \
|
|
expect to continuously increase the number of temporaries this can avoid \
|
|
realloc's of the arena. Set to zero to free all temporary array resources.\n");
|
|
static PyObject*
|
|
PySet_tempsize(PyObject *self, PyObject *args) {
|
|
Py_ssize_t newSize, oldSize;
|
|
if (!PyArg_ParseTuple(args, "n", &newSize))
|
|
return NULL;
|
|
|
|
pthread_mutex_lock(&gs.global_mutex);
|
|
oldSize = numexpr_set_tempsize(newSize);
|
|
pthread_mutex_unlock(&gs.global_mutex);
|
|
|
|
return Py_BuildValue("n", oldSize );
|
|
}
|
|
|
|
#ifdef USE_VML
|
|
PyDoc_STRVAR(GetVMLVersion__doc__,
|
|
"Get the VML/MKL library version.\n");
|
|
static PyObject*
|
|
PyGet_vml_version(PyObject *self, PyObject *args) {
|
|
int len=198;
|
|
char buf[198];
|
|
mkl_get_version_string(buf, len);
|
|
return Py_BuildValue("s", buf);
|
|
}
|
|
|
|
PyDoc_STRVAR(SetVMLAccuracy__doc__,
|
|
"Set accuracy mode for VML functions.\n");
|
|
static PyObject*
|
|
PySet_vml_accuracy_mode(PyObject *self, PyObject *args) {
|
|
int mode_in, mode_old;
|
|
if (!PyArg_ParseTuple(args, "i", &mode_in))
|
|
return NULL;
|
|
mode_old = vmlGetMode() & VML_ACCURACY_MASK;
|
|
vmlSetMode((mode_in & VML_ACCURACY_MASK) | VML_ERRMODE_IGNORE );
|
|
return Py_BuildValue("i", mode_old);
|
|
}
|
|
|
|
PyDoc_STRVAR(SetVMLNumThreads__doc__,
|
|
"Suggests a maximum number of threads to be used in VML operations.\n");
|
|
static PyObject*
|
|
PySet_vml_num_threads(PyObject *self, PyObject *args) {
|
|
int max_num_threads;
|
|
if (!PyArg_ParseTuple(args, "i", &max_num_threads))
|
|
return NULL;
|
|
mkl_domain_set_num_threads(max_num_threads, MKL_DOMAIN_VML);
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
#endif
|
|
|
|
static PyMethodDef module_methods[] = {
|
|
#ifdef USE_VML
|
|
{"_get_vml_version", PyGet_vml_version, METH_VARARGS, GetVMLVersion__doc__ },
|
|
{"_set_vml_accuracy_mode", PySet_vml_accuracy_mode, METH_VARARGS, SetVMLAccuracy__doc__ },
|
|
{"_set_vml_num_threads", PySet_vml_num_threads, METH_VARARGS, SetVMLNumThreads__doc__},
|
|
#endif
|
|
{"set_num_threads", PySet_num_threads, METH_VARARGS, SetNumThreads__doc__ },
|
|
{"set_tempsize", PySet_tempsize, METH_VARARGS, SetTempSize__doc__ },
|
|
{"get_num_threads", PyGet_num_threads, METH_VARARGS, GetNumThreads__doc__ },
|
|
{NULL}
|
|
};
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// #if PY_MAJOR_VERSION >= 3
|
|
|
|
// Handle the "global_state" state via moduedef
|
|
static struct PyModuleDef moduledef = {
|
|
PyModuleDef_HEAD_INIT,
|
|
"interpreter",
|
|
NULL,
|
|
-1, // sizeof(struct global_state),
|
|
module_methods,
|
|
NULL,
|
|
NULL, // module_traverse,
|
|
NULL, // module_clear,
|
|
NULL
|
|
};
|
|
|
|
#define INITERROR return NULL
|
|
|
|
PyObject*
|
|
PyInit_interpreter(void)
|
|
// #else // Python 2.7
|
|
// #define INITERROR return
|
|
|
|
// PyMODINIT_FUNC
|
|
// initinterpreter()
|
|
// #endif
|
|
{
|
|
// PyObject *m, *d;
|
|
PyObject *m;
|
|
|
|
// WARNING: PyType_Ready MUST be called to finalize new Python types before
|
|
// a module is created. Official documentation is weak on this point.
|
|
if (PyType_Ready(&NumExprType) < 0)
|
|
INITERROR;
|
|
|
|
// #if PY_MAJOR_VERSION >= 3
|
|
m = PyModule_Create(&moduledef);
|
|
// #else
|
|
// m = Py_InitModule3("interpreter", module_methods, NULL);
|
|
// #endif
|
|
|
|
if (m == NULL)
|
|
INITERROR;
|
|
|
|
Py_INCREF(&NumExprType);
|
|
PyModule_AddObject(m, "CompiledExec", (PyObject *)&NumExprType);
|
|
|
|
import_array();
|
|
|
|
// Let's export the block sizes to Python side for benchmarking comparisons
|
|
PyModule_AddIntConstant(m, "__BLOCK_SIZE1__", BLOCK_SIZE1 );
|
|
PyModule_AddIntConstant(m, "__BLOCK_SIZE2__", BLOCK_SIZE2 );
|
|
|
|
PyModule_AddIntConstant(m, "MAX_ARGS", NPY_MAXARGS );
|
|
PyModule_AddIntConstant(m, "MAX_DIMS", NPY_MAXDIMS );
|
|
|
|
|
|
#ifdef BENCHMARKING
|
|
PyModule_AddIntConstant(m, "BENCHMARKING", 1 );
|
|
#if defined(_WIN32) // On Windows x64: LARGE_INTEGER is a union and LARGE_INTEGER.QuadPart is int64.
|
|
npy_intp dims = BCOUNT;
|
|
QueryPerformanceFrequency( &T_NOW );
|
|
PyModule_AddIntConstant(m, "cpu_freq", T_NOW.QuadPart );
|
|
PyObject* bench_times = PyArray_SimpleNewFromData( 1, &dims, NPY_INT64, TIMES );
|
|
#else // On Linux: struct timespec { long ts_sec; long ts_nsec };
|
|
// So the array is 2*BCOUNT long and will need to be sliced on the Python end
|
|
npy_intp dims = 2*BCOUNT;
|
|
PyObject* bench_times = PyArray_SimpleNewFromData( 1, &dims, NPY_INT64, TIMES );
|
|
#endif
|
|
PyModule_AddObject(m, "bench_times", bench_times ); // Make NumPy arrays from the timing arrays.
|
|
#else // No BENCHMARKING
|
|
PyModule_AddIntConstant(m, "BENCHMARKING", 0 );
|
|
#endif
|
|
|
|
// The OpTable is loaded via pickle now.
|
|
// d = PyDict_New();
|
|
// if (!d) INITERROR;
|
|
|
|
// #if PY_MAJOR_VERSION >= 3
|
|
return m;
|
|
// #endif
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
} // extern "C"
|
|
#endif
|