← Назад к вопросам

Почему отказались от тиков процессора в Python?

2.0 Middle🔥 121 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему отказались от тиков процессора в 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-расширения должны проверять сигналы явно, но это правильная архитектура в любом случае.

Почему отказались от тиков процессора в Python? | PrepBro