Эээ.
Повторяю. Специально для танкистов.
Каждые 100 инструкций байткода (внимание, не ассемблера, питон исполняет свой байткод интерпретатором!) происходит следующее:
- отпустили GIL
- захватили GIL
и это делается даже в однопоточном приложении.
#ifdef WITH_THREAD
if (interpreter_lock) {
/* Give another thread a chance */
if (PyThreadState_Swap(NULL) != tstate)
Py_FatalError("ceval: tstate mix-up");
PyThread_release_lock(interpreter_lock);
/* Other threads may run now */
PyThread_acquire_lock(interpreter_lock, 1);
if (PyThreadState_Swap(tstate) != NULL)
Py_FatalError("ceval: orphan tstate");
/* Check for thread interrupts */
if (tstate->async_exc != NULL) {
x = tstate->async_exc;
tstate->async_exc = NULL;
PyErr_SetNone(x);
Py_DECREF(x);
why = WHY_EXCEPTION;
goto on_error;
}
}
#endif
PyThreadState_Swap - простая запись в глобальную переменную, а PyThread_release_lock/PyThread_acquire_lock - хуже.
Реализация зависит от платформы.
На Windows это interlocked variable+Event (“тяжелый” объект ядра).
Для Posix - все еще хитрее. Это либо семафор - либо pthread_mutex_t + pthread_cond_t (все они - тоже “ядерные” объекты).
В любом случае получается, что PyThread_release_lock/PyThread_acquire_lock - довольно затратные операции.
И тут можно влететь на затык.
Antoine Pitrou сейчас крутит вариант с более легковесным GIL для py3k. В какой релиз это попадет - непонятно. Скорее всего, только в 3.2 (2.7, вероятно, пролетает).
Из переписки видно, что со старой (она же текущая) реализацией все более или менее нормально на linux, несколько хуже на windows и, как я понял, совсем плохо на mac - там иногда можно попасть на довольно большие задержки.
P.S.
- Питон без GIL - это компиляция без флага WITH_THREAD, который ставится по умолчанию.
- время выполнения одного байткода сильно от этого самого байткода зависит. Диапазон от долей микросекунды до секунд в наиболее злых случаях. Но в любом разе это много-много ассемблерных команд.