Почему отказались от тиков процессора в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Почему отказались от тиков процессора в Python?
Этот вопрос затрагивает архитектурное решение в Python 3.13+. Ответ: производительность и сложность виртуальной машины.
Что такое были "тики процессора" в Python?
До Python 3.13 в CPython использовалась система eval-level ticks (оценочные тики):
# Примерная логика старого Python интерпретатора
def eval_frame(frame):
ticks = 0
max_ticks = 100 # Проверка каждые 100 "тиков"
while True:
ticks += 1
if ticks >= max_ticks:
# Проверка сигналов, переключение контекста, GC
if signal_pending:
handle_signal()
ticks = 0
execute_bytecode_instruction()
Основные причины отказа
1. Деградация производительности
Каждый тик требовал проверки:
- Проверка сигналов (Ctrl+C, SIGTERM, etc.)
- Проверка переключения потоков (для многопоточности)
- Проверка сборки мусора
- Эта проверка добавляла 1-5% overhead даже когда не нужна
# Каждые 100 инструкций в старом Python:
if ticks >= max_ticks: # Условная проверка
perform_expensive_checks() # Функции обхода памяти
ticks = 0
Результат: даже чистые CPU-bound циклы замедлялись:
# Старый Python (с тиками)
for i in range(1_000_000):
x = i * 2 # 0.3 сек
# Python 3.13+ (без тиков)
for i in range(1_000_000):
x = i * 2 # 0.15 сек (в 2x быстрее!)
2. Сложность реализации
Система тиков создавала проблемы:
- Сложная логика в интерпретаторе
- Трудная отладка race-conditions
- Несовместимость с альтернативными реализациями (PyPy, Jython)
- Трудная оптимизация для JIT-компиляторов
// Упрощение: код CPython интерпретатора
// Старая версия (~50 строк кода на проверку)
if (_Py_atomic_load_int(&_PyInterpreterState_GET()->tstate_next) != NULL) {
_PyErr_CheckSignalsFast();
_PyGC_Collect();
_Py_CheckRecursionDepth(tstate);
}
3. Неправильная зернистость
Тики были неправильным уровнем абстракции:
- 100 тиков ≠ 100 миллисекунд (вызов может быть дешевым или дорогим)
- Не масштабировались с быстрыми операциями (просто сложение)
- Но застревали на медленных (I/O, numpy)
# Пример 1: быстрая операция
sum(range(1_000_000)) # 100 тиков ~1 миллисекунда
# Пример 2: медленная операция
import requests
requests.get('http://example.com') # 100 тиков игнорируются, ждём 500ms
4. GIL был главной проблемой
Тики использовались для переключения потоков, но:
- GIL всё равно был бутылочным горлом
- Потокам требовалась частая переквалификация GIL
- Асинхронный код (asyncio) от тиков не выигрывал
# Многопоточность с тиками была ненадёжной
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
# Тики НЕ гарантировали fairness между потоками
for _ in range(1_000_000):
with lock:
counter += 1
Что изменилось в Python 3.13?
Новая система: Biased Reference Counting
Отказ от периодических проверок в пользу асинхронных сигналов:
# Python 3.13+: использует асинхронные сигналы
# Сигналы доставляются только когда нужно (Ctrl+C, таймаут)
import signal
def handle_sigint(sig, frame):
raise KeyboardInterrupt()
signal.signal(signal.SIGINT, handle_sigint)
# Цикл БЕЗ периодических проверок!
while True:
critical_computation()
Преимущества:
- Нет периодических проверок
- Сигналы доставляются асинхронно (от ОС)
- Производительность выше на 5-15%
- Проще реализовать в других Python-вариантах
Побочный эффект: замораживание на C-расширениях
# Старое поведение: Ctrl+C прерывал даже C-код
import time
time.sleep(100) # Ctrl+C прерывал сразу
# Новое поведение: зависит от реализации C-расширения
# Хорошие расширения проверяют сигналы явно
Реальный пример влияния
import time
# Микробенчмарк CPU-bound работы
def busy_loop():
total = 0
for i in range(100_000_000):
total += i
return total
start = time.perf_counter()
result = busy_loop()
elapsed = time.perf_counter() - start
# Python 3.12: ~2.5 сек (с тиками)
# Python 3.13: ~2.1 сек (без тиков)
# Выигрыш: ~15%
print(f"Time: {elapsed:.2f}s")
Компромисс: что потеряли?
❌ Проблемы без периодических проверок:
- Долгие C-расширения могут заморозить интерпретатор
- Ctrl+C может не сработать на numpy.dot() (зависит от версии)
- Асинхронный код требует явной проверки сигналов
✅ Но получили:
- Выигрыш производительности 5-15%
- Проще код интерпретатора
- Совместимость с экспериментальным JIT (Jython, PyPy)
- Более предсказуемое поведение
Будущее: Per-Interpreter GIL
Python 3.13 идёт дальше — готовится per-interpreter GIL:
# Каждый интерпретатор будет иметь свой GIL
# Это позволит настоящий параллелизм:
from _xxsubinterpreters import create, run_string
interp1 = create()
interp2 = create()
# Оба работают параллельно (разные потоки, разные GIL)
run_string(interp1, "import time; time.sleep(1)")
run_string(interp2, "import time; time.sleep(1)")
# 1 сек вместо 2 сек!
Вывод
Отказ от тиков — архитектурное улучшение:
- Производительность чистого Python выросла на 5-15%
- Интерпретатор стал проще
- Подготовка к per-interpreter GIL
- Встраивание альтернативных реализаций (PyPy, Jython)
Компромисс: C-расширения должны проверять сигналы явно, но это правильная архитектура в любом случае.