ch
Feedback
P(hD)ython

P(hD)ython

前往频道在 Telegram

О Python, PhD, распределённых системах и не только Автор - Михаил Масягин (@masyagin1998): - Python Lead в NDA HFT; - преподаватель в Бауманке; - эксперт по СУБД System Design World; - любитель PhD и авторегрессии.

显示更多
未指定国家未指定类别
214
订阅者
无数据24 小时
无数据7
无数据30
帖子存档
Приветствую Вас на канале P(hD)ython 👋 Меня зовут Михаил Масягин. Я тимлид, разработчик, аспирант и преподаватель МГТУ им. Н
Приветствую Вас на канале P(hD)ython 👋 Меня зовут Михаил Масягин. Я тимлид, разработчик, аспирант и преподаватель МГТУ им. Н.Э. Баумана. Сейчас я руковожу backend- и frontend-разработкой в HFT-компании. До этого были Lawful Interception и Bare Metal-проекты, работа с AWS и даже погружение в ML и NLP. С опытом я понял, что самые ценные знания обычно не попадают в учебники. Они появляются при решении реальных задач - через ошибки, багфиксы и дебаг, и, увы, часто теряются. Именно поэтому появился этот канал. Здесь я буду делиться тем, что считаю реально полезным: ⚡️ Python и современные практики разработки ⚡️ оптимизация кода и performance engineering ⚡️ C, Linux и немного Bare Metal ⚡️ распределённые системы и архитектура ⚡️ алгоритмы и структуры данных ⚡️ HFT и инженерные решения из индустрии ⚡️ опыт из преподавания, аспирантуры и написания диссертации Если Вам интересно не просто писать код, а понимать, почему он работает именно так, - добро пожаловать 🤝 С уважением, Михаил Масягин

test

«Финишная прямая 🎓» Научный руководитель наконец одобрил текст диссертации, и сегодня я отнёс «кирпич» в 2-х экземплярах на
«Финишная прямая 🎓» Научный руководитель наконец одобрил текст диссертации, и сегодня я отнёс «кирпич» в 2-х экземплярах на финальную проверку на кафедру 😎! Думаю, есть шанс, что первая предзащита будет в текущем учебном году (в июне). С уважением, Михаил Масягин P.S. А ещё со следующего учебного года ассистент становится старшим преподавателем 😎

«Data Lake: от перестановки мест слагаемых сумма... меняется? 👷» Недавно проводил лекцию по DWH на курсе System Design от ne
«Data Lake: от перестановки мест слагаемых сумма... меняется? 👷» Недавно проводил лекцию по DWH на курсе System Design от nevzorov.courses. На лекции разбирали довольно частый практический кейс: - есть ряд поддерживаемых источников данных (Sources); - есть множество клиентов (Customers); - для каждого клиента необходимо сохранять и обрабатывать данные из его источников (Customer Sources); - вопрос: как лучше спроектировать Data Lake под эту задачу? Вариант 1: customers/<customer_name>/source=<source_name> Вариант 2: sources/<source_name>/customer=<customer_name> Интуитивно рука тянется к 1 варианту... Однако для Data Lake и дальнейшей DWH-инфраструктуры часто лучше именно 2 вариант:
raw/sources/<source_name>/customer=<customer_name>/...
cleaned/sources/<source_name>/customer=<customer_name>/...
...
Например:
...
raw/sources/google_play/customer=rammstein/dt=2026-05-24/*.parquet
raw/sources/google_play/customer=sabaton/dt=2026-05-24/*.parquet
raw/sources/google_play/customer=megadeth/dt=2026-05-24/*.parquet
...
raw/sources/trustpilot/customer=rammstein/dt=2026-05-24/*.parquet
raw/sources/trustpilot/customer=led_zeppelin/dt=2026-05-24/*.parquet
raw/sources/trustpilot/customer=lordi/dt=2026-05-24/*.parquet
...
Почему? 1. Source естественным образом превращается в таблицу. Для AWS Athena, Apache Trino или Apache Spark - google-play, trustpilot и т.д. - это отдельные логические таблицы, разложенные по Parquet-файлам и партициям в виде Customer'ов:
SELECT
    *
FROM
    "raw"."google_play"
WHERE
    ("customer" = 'rammstein') AND ("dt" >= DATE '2026-05-01');
У google-play даже в сыром виде (и уж тем более в очищенном) есть какая-то своя схема данных, ключи, timestamp'ы, правила дедупликации, SLA, логика инкрементальной загрузки и т.д. У trustpilot и любого другого Source'а - свои. Если же сделать наоборот:
...
raw/customers/rammstein/source=google_play/dt=2026-05-24/*.parquet
raw/customers/rammstein/source=trustpilot/dt=2026-05-24/*.parquet
...
raw/customers/sabaton/source=google_play/dt=2026-05-24/*.parquet
...
то один логический источник google-play размазывается по разным корням. А дальше начинается адъ 👹: - отдельные таблицы на каждый Source каждого Customer'а; - UNION ALL запросы и VIEW-шки; - бардак с Data Governance. В общем, Data Lake, а следом за ним и DWH медленно, но неотвратимо превращаются в DataSwamp 😄 2. Data Mesh проще делать именно по Source'ам. Естественная единица владения - это не «папка клиента» (Customer), а доменный источник (Source). У каждого такого Source'а есть отдельная команда-владелец, контракты, документация, SLA, data quality checks и правила эволюции схемы. Команда, отвечающая за google_play, должна владеть одной папкой sources/google_play/customer=<customer_name>/*, а не тысячами подпапок customers/*/source=google_play/*. - Добавили нового клиента? Добавили новую партицию. - Поменяли контракт источника? Обновили один data product. - Поймали баг в ingestion? Чиним одний единственный ETL-pipeline. 3. Pipeline'ы обычно тоже мыслят именно Source'ами:
...
google_play
trustpilot
...
А не:
...
ingest_rammstein_everything
ingest_sabaton_everything
ingest_led_zeppelin_everything
...
Иначе очень быстро появляются «особые клиенты»: - у этого legacy CSV; - у этого timezone в строке; - у этого timestamp иногда null; - у этого producer шлёт дубликаты; - у этого «ну вы там руками поправьте, пожалуйста». Поздравляю, у вас не DWH, а зоопарк с Airflow DAG-ами 🦓 4. Наконец, Source-First Layout упрощает сложную аналитику:
SELECT
    "customer", count(*)
FROM
    "raw"."google_play"
WHERE
    "dt" = DATE '2026-05-24'
GROUP BY
    "customer";
Можно с лёгкостью строить Usage-Based Billing по конкретным Source'ам, позволять даже менеджменту без труда копаться в данных и т.д. Таким образом, проектируя DWH-систему лучше думать не о том, какие у вас будут клиенты, а о том, какие источники данных вы будете для них поддерживать. С уважением, Михаил Масягин

«Cursor на миллиард 🤑» В нашей команде мы активно используем множество ИИ-инструментов, в том числе Cursor. Сидим на Teams P
«Cursor на миллиард 🤑» В нашей команде мы активно используем множество ИИ-инструментов, в том числе Cursor. Сидим на Teams Plan. И сегодня я нашёл в этом «плане» неприятный сюрприз. В Teams Plan команда представляет собой одного админа (Admin) и множество обычных пользователей (Member). При этом имеется возможность ограничить расход средств, выставив максимально допустимую месячную сумму, которую команда тратит на токены: превысил лимит - жди следующего месяца. Но оказалось, что по умолчанию функция выставления лимитов доступна не только админу, но и любому члену команды! Да-да, не админу, не владельцу карты, а обычному Member-у! Имхо, это крайне неочевидное и небезопасное поведение, о котором документация упоминает лишь всколзь. Заходишь в Settings → Spend Limit → Team Spend Limit, ставишь лимит в миллиард долларов 💵 и уходишь на ночь, запустив 1000 и 1 агента 😎. Auto-моделька Cursor уверенно говорит, что это не баг, а фича, дабы «упростить онбординг команды» 😁 Чтобы запретить это веселье, нужно отдельно включить тумблер: Settings → Usage-Based Pricing Settings → Admin-only modifications. После этого вкладка Spend Limit исчезает у обычных пользователей. Интересная, конечно, помощь в онбординге команды... С уважением, Михаил Масягин P.S. Может, имелся в виду онбординг команды топ-менеджеров Cursor на очередную яхту 🧐?

«Python 3.15 beta: что нового 🐍» 7 мая зафризили фичи Python 3.15, и сейчас, в длинные выходные, самое время обсудить ключев
«Python 3.15 beta: что нового 🐍» 7 мая зафризили фичи Python 3.15, и сейчас, в длинные выходные, самое время обсудить ключевые изменения. Сразу уточню, что полный стабильный релиз будет 1 октября, поэтому пока что катаемся на test- и debug- ENV-ах 🤓. 1. Lazy imports (PEP 810) 🥱 В язык завезли новое ключевое слово lazy. Ленивый модуль загружается только при непосредственном обращении к его коду, что ускоряет старт Python-процесса:
lazy import numpy as np
lazy from pandas import DataFrame

df = DataFrame()  # только здесь pandas реально загрузится
Можно включить глобально через флаг -X lazy_imports=all или переменную PYTHON_LAZY_IMPORTS. 2. Распаковка в comprehensions (PEP 798) 📦 Самое долгожданное расширение синтаксиса за годы. Теперь * и ** работают внутри list/set/dict-comprehensions и генераторов:
lists = [[1, 2], [3, 4], [5]]
flat  = [*L for L in lists]                # [1, 2, 3, 4, 5]
merged = {**d for d in [{'a': 1}, {'b': 2}]}  # {'a': 1, 'b': 2}
То, что раньше писалось через itertools.chain.from_iterable или вложенные циклы, теперь - одна строка. Работает и в async for. Наконец вопрос на собесах «как разжать список списков» получил однозначный и окончательный ответ. 3. frozendict как builtin (PEP 814) 😎 «Замороженный» словарь - теперь встроенный тип. Можно класть в set, использовать ключом другого dict, да ещё и хэш не зависит от порядка вставки!
config = frozendict(host="localhost", port=5432)
cache  = {config: "primary"}
hash(frozendict(a=1, b=2)) == hash(frozendict(b=2, a=1))  # True
Также его подружили с copy, json, pickle, pprint. 4. sentinel builtin (реализация PEP 661) 🛡 Все мы писали этот хак: _MISSING = object(), чтобы отличать «не передал» от «передал None». Теперь это часть языка:
MISSING = sentinel("MISSING")

def get(d, key, default=MISSING):
    if default is MISSING:
        raise KeyError(key)
    return d.get(key, default)
Мелочь, а приятно. 5. Tachyon - сэмплирующий профайлер (PEP 799) 🔎 Появился пакет profiling с двумя бэкендами: profiling.tracing (бывший cProfile) и profiling.sampling - статистический профайлер с почти нулевым оверхедом. Самое крутое - сэмплирующий профайлер умеет подключаться к уже работающему процессу по его `PID`у:
python -m profiling.sampling --pid 12345 --format flamegraph -o out.svg
Кто хоть раз профилировал прод - понимает цену вопроса. 6. Очередное ускорение 🚀 Ускорили JIT (да, в CPython есть JIT, хоть и по умолчанию он недоступен!) на 8-9% на x86-64 Linux и на 12-13% на AArch64 macOS. Таким образом, 3.15 - это пусть и не «революционный», но важный релиз, значительно повышающий качество жизни разработчиков. Стандартная библиотека продолжает вбирать в себя то, что годами жило в формате рецептов на Stack Overflow. Это ли не говорит о зрелости языка? С уважением, Михаил Масягин

sticker.webp0.19 KB

«Мама, я в телевизоре 😎» Ну, может и не в телевизоре, но с первым опытом студийной записи меня 😅 С уважением, Михаил Масяги
+1
«Мама, я в телевизоре 😎» Ну, может и не в телевизоре, но с первым опытом студийной записи меня 😅 С уважением, Михаил Масягин

«Айтишники 💻 и металлурги 🛠» Последние пару месяцев активно провожу собесы Python-разработчиков: отвечаю за алгоритмическую
«Айтишники 💻 и металлурги 🛠» Последние пару месяцев активно провожу собесы Python-разработчиков: отвечаю за алгоритмическую секцию, где кандидатам предлагается решить несколько задач уровня LeetCode Easy/Medium и пообщаться о внутрянке Python. К сожалению, списывание и использование GPT на интервью лишь набирает обороты. Обычно это заметно довольно быстро: - либо человек не может объяснить «своё же» решение; - либо сыпется на каверзных вопросах про асимптотику, дополнительные ограничения и прочие нюансы. Недавно узнал, что в бигтехах 🏙 во время интервью кандидату могут задать пару случайных дурацких вопросов: - если человек честно говорит, что не знает - всё ок ✅; - а вот если отвечает, то, как модно сегодня говорить, это редфлаг ❌. И буквально час назад у меня случилась идеальная иллюстрация этого подхода. Кандидат ⭐️: - шикарный опыт; - почти 1 в 1 попадает в наш стэк; - решает задачи раза в полтора быстрее всех прошлых кандидатов; - знает абсолютно всё об asyncio; - strong hire! Но в какой-то момент в голове рождается мысль: а чем я хуже интервьюеров из бигтеха 😎? И звучит вопрос: - А расскажи мне, пожалуйста, про эвтектику в СУБД. (эвтектику, если что, мне подсказал GPT - как что-то максимально умное, солидное и при этом абсолютно не к месту) Кандидат без запинки отвечает, что проходил это ещё в вузе, и выдаёт какой-то поток несвязного бреда. Чувствую, что на подходе материал для поста (всё ради вас, подписчики ❤️), и решаю дожать: - Супер. А откуда это вообще пошло? Что такое эвтектика в исходном смысле? И тут человек снова без малейшей паузы выдаёт: - Эвтектика - это смесь двух или более веществ, которая плавится или затвердевает при фиксированной, самой низкой температуре для данной системы, действуя как чистое вещество. За пару минут собеседование Python-разработчика превратилось в устный экзамен по металлургии! Похоже, дурацкие вопросы работают! Иногда даже слишком хорошо 🤓 P.S. Эвтектика - это вполне реальный термин из металлургии и неорганической химии. P.P.S. До сих пор не исключаю, что у человека первое образование было металлургическое 👀 С уважением, Михаил Масягин

«Мам, сфоткай типо я кант-трейдор 🥴» С уважением, Михаил Масягин
«Мам, сфоткай типо я кант-трейдор 🥴» С уважением, Михаил Масягин

«Отель для настоящих HFT-разрабов и квантов 😎!» С уважением, Михаил Масягин P.S. Кто угадает страну... тот молодец
«Отель для настоящих HFT-разрабов и квантов 😎!» С уважением, Михаил Масягин P.S. Кто угадает страну... тот молодец

«CQRS: нормально делай - нормально будет!» Разбавим Python-посты архитектурой! На днях со студентами System Design World обсу
«CQRS: нормально делай - нормально будет!» Разбавим Python-посты архитектурой! На днях со студентами System Design World обсуждали паттерны, и закономерно всплыл CQRS. Его просто обожают на System Design Interview, и... регулярно путают с CQS! Micro vs Macro CQS (Command-Query Separation) - это принцип создания классов и API. - Command-методы меняют состояние и либо НЕ отдают данные (void), либо возвращают служебные значения (id, ok, error и т.д.); - Query-методы возвращают данные и никогда НЕ меняют состояние. На простых классах от CQS мало пользы, зато при написании фабрик, репозиториев и прочих паттернов он реально выручает: - меньше неявного поведения; - проще кэшировать и оптимизировать; - проще поддерживать и дебажить код. Пример:
from dataclasses import dataclass

@dataclass(frozen=True)
class Car:
    num: str

class Base:
    def __init__(self):
        self._cs = {}

class FactoryBad(Base):
    # get with unexpected side effect
    def get(self, num: str) -> Car:
        if num not in self._cs:
            self._cs[num] = Car(num=num)
        return self._cs[num]

class FactoryCQS(Base):
    def find(self, num: str) -> Car | None:
        return self._cs.get(num)

    def get(self, num: str) -> Car:
        return self._cs[num]

    def register(self, num: str) -> None:
        if num in self._cs:
            raise ValueError(f"Car with number '{num}' already exists!")
        self._cs[num] = Car(num=num)
CQRS (Command Query Responsibility Segregation) - это архитектурный паттерн: - разные пути и модели данных для записи и чтения - write-side и read-side; - часто разные Handler'ы, контракты и схемы - но всё же это детали реализации. По сути CQRS - это CQS «на стероидах». CQRS - Кафка, Стриминг, 2 Сурса Частая ошибка - воспринимать CQRS как обязательную связку из условных read- и write-СУБД, очереди и Eventual Consistency. Действительно, так часто бывает, но в первую очередь CQRS - про разделение путей и моделей данных, а не про инфраструктуру. Начать внедрение CQRS можно и с 1 СУБД: - write - нормализованные таблицы под базовые сущности; - read - денормализованные таблицы/view под чтение; - если обновлять read-проекции синхронно с write-проекциями, можно получить и Strong Consistency. Зачем всё это CRUD-сервис «на всё» (create, update, get, find, ...) быстро «пухнет»: - чтение и запись смешиваются, API становится неочевидным; - хотим масштабировать чтение, но read-only инстансы вынужденно тащат write-зависимости и флаги/роутинг; - репозиторий превращается в комбайн с бесконечными зависимостями; - страдает производительность; - сложнее растить команду. CQRS предлагает решение этой проблемы: - изолированные Handler'ы для команд и запросов (часто реально «по 1 файлу на операцию»); - лишь нужные зависимости в каждом Handler'е - ускоряет разработку и реально отделяет write-side от read-side. CQRS - не серебряная пуля: - если проект компактный и несложный - лучше CRUD + нормальный репозиторий; - CQRS добавляет бойлерплейт. Даже если код «генерится Claude'ом», растет объём и контекст. Идемпотентность команд - must have Команды могут повторяться из-за retry, timeout и at-least-once доставки. Handler должен быть идемпотентным и не допускать создания дубликатов. Блеск CQRS CQRS раскрывается, когда система становится read-heavy/нуждается в разных формах данных. Тогда вы: - масштабируете read- и write-side независимо; - держите read-модели под конкретные задачи: поиск, отчёты и т.д. Отдельный плюс - несколько read-моделей одновременно: Postgres для запросов, Elastic для FTS и т.д. При этом они могут строить свои проекции из единого потока событий (event bus, outbox, CDC и т.д.). Отсюда и дружба с Event Sourcing: при хранении изменений как Event Log, проекции можно пересобирать с нуля. Идеи CQS и CQRS во многом звучат как «нормально делай - нормально будет», они очень интуитивны. Тем не менее выработка общих терминологии и понимания - это всегда большой плюс. В следующий раз разберём Event Sourcing! С уважением, Михаил Масягин P.S. Рекомендую к просмотру выступление Андрея Цветциха.

«Сказано - сделано 😎🚀!» @ashm_tech получил свой приз 📕! С уважением, Михаил Масягин
«Сказано - сделано 😎🚀!» @ashm_tech получил свой приз 📕! С уважением, Михаил Масягин

🎉 Розыгрыш завершен! 🏆 Победители: 1. @ashm_tech 🔍 Проверить результаты

«Итоги 2025: Python 3.14 🐍 и немного моей жизни 🎄» Ну что, под Новый Год самое время подвести итоги. 7 октября 2025 вышла ф
«Итоги 2025: Python 3.14 🐍 и немного моей жизни 🎄» Ну что, под Новый Год самое время подвести итоги. 7 октября 2025 вышла финальная версия Python 3.14.0, меняющая как внутреннее устройство языка, так и добавляющая в него новые полезные функции. Число и значимость изменений сравнимы с 3.4.0, подарившей нам asyncio в 2014 году! 1. Kill GIL! Как известно, у змей - раздвоенный язык, и Python теперь - не исключение. Начиная с 3.14.0 существуют 2 сборки CPython - стандартная 3.14 и... 3.14t без GIL! Теперь, пусть и с рядом новых трудностей, мы можем достигать в Python истинного параллелизма потоков исполнения прямо как в C\C++, Java и т.д.! 2. concurrent.interpreters - до тех пор, пока 3.14t не станет стандартом запуск нескольких независимых интерпретаторов всё ещё актуален. Что же, теперь мы можем делать это в рамках одного процесса, минимизируя траты на IPC! 3. Отладка современного Python-кода стала ещё проще и приятнее - к целевому процессу можно подключиться на лету без необходимости его перезапуска! 4. Инкрементальный GC - осталось лишь 2 поколения объектов: молодое и старое, и на каждом цикле GC сканируется всё молодое и эвристически лишь часть старого. За счёт этого работа сборщика мусора стала предсказуемее и стабильнее. 5. Ряд оптимизаций байткода Python, включая Tail Calling. 6. t-strings - всё те же f-strings, но теперь завёрнутые в отдельный класс! 7. Аннотации больше не тормозят import'ы, так как вычисляются отложенно, а ещё их можно анализировать через annotationlib. 8. Все мы знаем, что zstd - это база, а теперь это знает и Python! zstandard теперь входит в стандартную поставку интерпретатора! 9. И много-много чего ещё! Все фичи подробно разберём в постах 2026 года! Ну и пару слов о моих итогах 😅: 1. Начал разбирать Кабанчика 🐷 на System Design World с @vova_dev! 2. Запустили с @vova_dev наш курс по System Design! Уже прошло 3 потока и на подходе 4! 3. + 1 новая статья ВАК/WoS и Conference Paper в IEEE - за последнее отдельное спасибо @Kvassir! 4. Выступил с лекциями по HFT на ICPC, FaangTalk (@volyx ❤️) и в Бауманке! 5. Пережил ремонт и даже несмотря на него побывал в новой стране - Вьетнаме! 6. Познакомился со столькими замечательными людьми: @vova_dev, @LooksOfTheMoon, @terapsyda - вы лучшие ❤️! 7. Начал активно собесить не только питонистов, но и растовчан на работе 💪! 8. Завёл канал, где вы сейчас и читаете этот пост! Нас уже 160+ 👫! Были и факапы, главный - диссер с конца 2025 года переехал на весну 2026 года 😢. К чему я это всё? Думаю, к тому, что надо работать, фигачить, выкладываться на 110%! Достичь всего - нереально, многого - очень сложно, но возможно 💪! С Новым Годом, друзья 🎄! Соблюдаем Work-Job Balance и идём седлать огненного коня 2026 года 🎠! С уважением, Михаил Масягин

«Розыгрыш 🎰, дедлайны ❌ и Рождество 🎄» Ну что, классика жанра: к дедлайну я, как всегда, не успел - поэтому розыгрыш будет
«Розыгрыш 🎰, дедлайны ❌ и Рождество 🎄» Ну что, классика жанра: к дедлайну я, как всегда, не успел - поэтому розыгрыш будет не к Новому году, а… к Рождеству 😅 В честь праздников разыгрываю среди подписчиков бумажную книгу Майкла Льюиса «Flash Boys: A Wall Street Revolt» - самое то, чтобы начать плавное погружение в HFT, попивая какао ☕️ у рождественской ёлки 🎄 Если вы в Москве - с радостью вручу лично и угощу кофе/пивом/какао 🍾 Как участвовать: - подписаться на канал (нас уже 150+ - спасибо вам ❤️); - нажать кнопку «Участвую!» под постом; - сделать репост в своё сообщество, добавив друзей по ссылке и т.д. - шанс на победу x2 💰; - ждать итогов 7 января 🎁. С уважением, Михаил Масягин

«Python для собесов - 2026 🚀» Продолжаем серию постов для Python-собесов! В прошлых постах мы научились ускорять Python (FFI
«Python для собесов - 2026 🚀» Продолжаем серию постов для Python-собесов! В прошлых постах мы научились ускорять Python (FFI & Python Compilers). Теперь поговорим о том, что именно нужно ускорять, потому что оптимизировать абсолютно всё - сложно, долго и дорого (да зачастую и не нужно) 😵. Базовый алгоритм выглядит следующим образом: 1. Замерили время выполнения кода. 2. Нашли узкое место. 3. Оптимизировали. 4. Проверили, что стало лучше. Если нет или недостаточно, то возвращаемся к пункту 1 😄. Начнём с простейших измерений. На собесах часто спрашивают как в коде замерить время выполнения отдельной функции. К сожалению, ответ time() встречается неприлично часто. Однако time() это так называемые «wall clock» 🕚 - они не монотонны (могут перепрыгивать из-за NTP, смены поясов, гибернации), а их точность и разрешение невысоки и зависят от настроек ОС. Вместо них лучше использовать монотонные счётчики: - perf_counter_ns() - точный и монотонный аналог time() (для общих случаяев); - process_time_ns() - только время на CPU (удобен для числодробилок). Пример кода:
from time import perf_counter_ns as pc_ns

def work(n):
    s = 0
    for i in range(n):
        s += i*i
    return s

if __name__ == "__main__":
    t = pc_ns()
    work(1000000)
    dt = pc_ns() - t
    print(f"{dt/1e6:.3f} ms")
и его запуск:
python3 pc_ns_demo.py 
23.767 ms
Один прогон не слишком показателен - виной тому «шум» планировщика, кэши, вызовы GC и т.д. diff счётчиков можно сохранять в массив в цикле, но лучше использовать готовый инструмент - timeit. Он позволяет запускать много раз функции или даже программы, задавая как число прогонов в замере (number), так и число самих замеров (repeat), тем самым получая устойчивое распределение времени. Пример кода:
import timeit
from pc_ns_demo import work

rs = timeit.repeat(lambda: work(1000000), number=10, repeat=10)

print("runs:")
for i, t in enumerate(rs, 1):
    print(f"  {i:02d}: {t/10*1e3:.3f} ms") 

bs = min(rs)
print(f"min: {bs/10*1e3:.3f} ms")
и его вызов:
python3 timeit_demo.py 
runs:
  01: 23.849 ms
  ...
  10: 23.650 ms
min: 23.650 ms
Ещё короче:
python3 -m timeit -n 10 -r 10 -u msec -s "from pc_ns_demo import work" "work(1000000)"
10 loops, best of 10: 23.5 msec per loop
Поговорим о профилировании - определении конкретных медленных участков кода с cProfile. Он позволяет подсчитать число вызовов каждой функции (ncalls), её целевое и суммарное время исполнения (без/с учётом вызовов функций внутри) (tottime, percall, cumtime, percall) с привязкой к определению (filename:...). Запуск профайлера для программы:
python3 -m cProfile -o prof.bin pc_ns_demo.py
24.467 ms

python -c "import pstats;p=pstats.Stats('prof.bin');p.sort_stats('tottime').print_stats(20)"
Fri Dec 19 06:00:17 2025    prof.bin

         7 function calls in 0.024 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.024    0.024    0.024    0.024 pc_ns_demo.py:3(work)
   ...
Файл prof.bin можно визуализировать пакетом snakeviz - скрин в комментах. И внутри python-кода:
import cProfile
import pstats
from io import StringIO
from pc_ns_demo import work

def kek(a, b, c):
    return work(a) + work(b) + work(c)

pr = cProfile.Profile()
pr.enable()
print(kek(1000000, 2000000, 3000000))
pr.disable()

s = StringIO()
pstats.Stats(pr, stream=s).sort_stats("cumtime").print_stats(5)
print(s.getvalue())
и запуск:
python3 cprofile_demo.py
11999993000001000000
         6 function calls in 0.146 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.146    0.146 /home/mikhail/cprofile_demo.py:6(kek)
        3    0.146    0.049    0.146    0.049 /home/mikhail/pc_ns_demo.py:3(work)
...
Существуют и другие инструменты вроде py-spy и scalene. Они позволяют периодически дампить callstack процесса, а scalene показывает ещё и аллокации 🤓. Вот мы и познакомились с джентльменским набором тайминга и профайлинга в Python! ⚙️🚀 С уважением, Масягин Михаил

«А почему бы и... да 😂😎!» https://youtube.com/live/F8A6Nq8c13U?feature=share https://t.me/faangtalk_news/329 И уже в эту среду в 9 вечера по Москве самая полная, подробная и хардокрная версия HFT-доклада! Да ещё и под соусом System Design! Да ещё и у самих t.me/faangtalk 😎! С уважением, Михаил Масягин

«Эффект попугая у публичных спикеров 🦜» Периодически посматриваю лекции и выступления известных учёных и программистов - на YouTube, в подкастах, на конференциях и т.д. С лёгким ужасом для себя обнаружил, что большинство профессиональных спикеров на разных площадках рассказывают одно и то же: те же истории, те же тезисы, иногда даже те же слайды. Но чем больше я на это смотрю, тем сильнее понимаю: этого не избежать. Со временем у тебя формируется стабильный набор экспертных тем, десяток любимых слайдов и пара-тройка шуток - они становятся таким же твоим неизменным атрибутом, как потрёпанный портфельчик у Жванецкого 😅 https://t.me/studsovet_iu/1319 Уже в следующую среду читаю расширенный доклад о современной HFT-инфраструктуре в Бауманке. Постепенно превращаюсь в профессионального спикера. Обещаю, что добавлю хотя бы один новый слайд и пару новых шуток, но это не точно 😄 P.S. Помню о рубрике «Python для собесов - 2026 🚀». На выходных выложу новый пост 🤝! С уважением, Михаил Масягин

«Горячий HFT-доклад 🔥» Выложил запись своего выступления про HFT-инфраструктуру с Moscow ICPC 2025 на YouTube! https://youtu.be/us53niWItTg?si=UwSvvEsaJMDFJ_RM Смотрите, комментируйте, поддерживайте лайками и репостами! В докладе про HFT, FPGA, Kernel-Bypass и много-много денег 🤑! P.S. Сразу после выступления очень сильно заболел, поэтому видео немного задержалось. Но лучше поздно, чем никогда 💪! С уважением, Михаил Масягин