Ответ 1
При создании функции Numba вы на самом деле создать Numba Dispatcher
объект. Этот объект "перенаправляет" "вызов" на boring_numba
на правильную (насколько это касается типов) внутреннюю функцию "jitted". Поэтому, даже если вы создали функцию boring_numba
- эта функция не вызывается, то, что называется, является скомпилированной функцией, основанной на вашей функции.
Просто вы видите, что boring_numba
функция boring_numba
(даже если это не так, что называется CPUDispatcher.__call__
) во время профилирования объекта Dispatcher
необходимо подключиться к текущему состоянию потока и проверить, работает ли профайлер/трассировщик и если "да", это заставляет его выглядеть как boring_numba
Этот последний шаг - это то, что навлекает накладные расходы, потому что он должен подделать "стек стека Python" для boring_numba
.
Немного более технический:
Когда вы вызываете функцию boring_numba
она фактически вызывает Dispatcher_Call
которая является оберткой вокруг call_cfunc
и вот основное различие: когда у вас есть профайлер, выполняющий код, связанный с профилировщиком, составляет большую часть вызова функции (просто сравните if (tstate->use_tracing && tstate->c_profilefunc)
веткой else
которая запущена, если нет профилировщика/трассировщика):
static PyObject *
call_cfunc(DispatcherObject *self, PyObject *cfunc, PyObject *args, PyObject *kws, PyObject *locals)
{
PyCFunctionWithKeywords fn;
PyThreadState *tstate;
assert(PyCFunction_Check(cfunc));
assert(PyCFunction_GET_FLAGS(cfunc) == METH_VARARGS | METH_KEYWORDS);
fn = (PyCFunctionWithKeywords) PyCFunction_GET_FUNCTION(cfunc);
tstate = PyThreadState_GET();
if (tstate->use_tracing && tstate->c_profilefunc)
{
/*
* The following code requires some explaining:
*
* We want the jit-compiled function to be visible to the profiler, so we
* need to synthesize a frame for it.
* The PyFrame_New() constructor doesn't do anything with the 'locals' value if the 'code's
* 'CO_NEWLOCALS' flag is set (which is always the case nowadays).
* So, to get local variables into the frame, we have to manually set the 'f_locals'
* member, then call 'PyFrame_LocalsToFast', where a subsequent call to the 'frame.f_locals'
* property (by virtue of the 'frame_getlocals' function in frameobject.c) will find them.
*/
PyCodeObject *code = (PyCodeObject*)PyObject_GetAttrString((PyObject*)self, "__code__");
PyObject *globals = PyDict_New();
PyObject *builtins = PyEval_GetBuiltins();
PyFrameObject *frame = NULL;
PyObject *result = NULL;
if (!code) {
PyErr_Format(PyExc_RuntimeError, "No __code__ attribute found.");
goto error;
}
/* Populate builtins, which is required by some JITted functions */
if (PyDict_SetItemString(globals, "__builtins__", builtins)) {
goto error;
}
frame = PyFrame_New(tstate, code, globals, NULL);
if (frame == NULL) {
goto error;
}
/* Populate the 'fast locals' in 'frame' */
Py_XDECREF(frame->f_locals);
frame->f_locals = locals;
Py_XINCREF(frame->f_locals);
PyFrame_LocalsToFast(frame, 0);
tstate->frame = frame;
C_TRACE(result, fn(PyCFunction_GET_SELF(cfunc), args, kws));
tstate->frame = frame->f_back;
error:
Py_XDECREF(frame);
Py_XDECREF(globals);
Py_XDECREF(code);
return result;
}
else
return fn(PyCFunction_GET_SELF(cfunc), args, kws);
}
Я предполагаю, что этот дополнительный код (в случае профайлера) замедляет функцию, когда вы выполняете cProfile-ing.
Немного неудачно, что функция numba добавляет столько накладных расходов при запуске профилировщика, но замедление фактически будет практически незначительным, если вы сделаете что-то существенное в функции numba. Если бы вы также переместили цикл for
в функцию numba, тем более.
Если вы заметили, что функция numba (с запуском профайлера или без него) занимает слишком много времени, вы, вероятно, слишком часто вызываете ее. Затем вы должны проверить, действительно ли вы можете переместить цикл внутри функции numba или обернуть код, содержащий цикл, в другую функцию numba.
Примечание. Все это (небольшая) спекуляция, я на самом деле не строю numba с символами отладки и не профилировал C-Code в случае, если работает профайлер. Однако количество операций в случае, если работает профайлер, делает это очень правдоподобным. И все это предполагает numba 0.39, не уверен, что это относится и к прошлым версиям.