Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Нативные расширения
native

Нативные расширения

Профилирование C-кода, numpy, Cython, флаг --native

Профилирование нативных расширений с флагом --native

Python — это только верхушка айсберга. Под ним скрывается океан C-кода.

Многие Python-библиотеки используют C-код для ускорения:

  • numpy, scipy — научные вычисления
  • pandas — обработка данных
  • lxml, xml.etree — парсинг XML
  • cryptography, hashlib — криптография
  • Cython-модули — скомпилированный Python

Без флага --native вы видите только Python-функции. С --native — полный стек включая C.

#Базовое использование

# Без нативного стека (по умолчанию) py-spy record -o profile.svg --pid 12345 # С нативным стеком py-spy record -o profile.svg --native --pid 12345

#Что меняется с --native

#Без --native

import numpy as np def compute(): arr = np.random.rand(1000, 1000) result = np.linalg.inv(arr) # Вызов C-кода return result

Профиль:

┌─────────────────────────────────────────┐
│            compute (95%)                │
├─────────────────────────────────────────┤
│         np.linalg.inv (90%)             │ ← «Чёрный ящик»
│         np.random.rand (5%)             │
└─────────────────────────────────────────┘

Вы видите np.linalg.inv, но не знаете, что внутри.

#С --native

Профиль:

┌─────────────────────────────────────────┐
│            compute (95%)                │
├─────────────────────────────────────────┤
│         np.linalg.inv (90%)             │
│  ┌──────────────────────────────────┐   │
│  │    dgetrf_ (LAPACK, 60%)         │   │ ← C-функция!
│  │    dgetri_ (LAPACK, 30%)         │   │
│  └──────────────────────────────────┘   │
└─────────────────────────────────────────┘

Теперь видно, что время тратится в LAPACK функциях dgetrf_ и dgetri_.

#Практический пример: numpy

#Код

import numpy as np import time def matrix_operations(): """Тяжёлые вычисления с матрицами""" results = [] for _ in range(100): # Создание матрицы arr = np.random.rand(500, 500) # Инверсия — дорогая операция inv = np.linalg.inv(arr) # Умножение product = np.dot(arr, inv) # Сумма results.append(np.sum(product)) return results if __name__ == "__main__": start = time.time() matrix_operations() print(f"Время: {time.time() - start:.2f}с")

#Запуск профилирования

# Без нативного стека py-spy record -o numpy_basic.svg -- python numpy_script.py # С нативным стеком py-spy record -o numpy_native.svg --native -- python numpy_script.py

#Анализ

Без --native:

┌─────────────────────────────────────────┐
│       matrix_operations (98%)           │
├─────────────────────────────────────────┤
│    np.linalg.inv (70%)                  │
│    np.dot (15%)                         │
│    np.random.rand (10%)                 │
│    np.sum (3%)                          │
└─────────────────────────────────────────┘

С --native:

┌─────────────────────────────────────────┐
│       matrix_operations (98%)           │
├─────────────────────────────────────────┤
│    np.linalg.inv (70%)                  │
│  ┌──────────────────────────────────┐   │
│  │  dgetrf_ (45%) - LU разложение   │   │
│  │  dgetri_ (25%) - инверсия        │   │
│  └──────────────────────────────────┘   │
│    np.dot (15%)                         │
│  ┌──────────────────────────────────┐   │
│  │  dgemm_ (12%) - умножение        │   │
│  └──────────────────────────────────┘   │
└─────────────────────────────────────────┘

Вывод: Основная нагрузка — LAPACK функции. Это нормально для линейной алгебры.

#Профилирование Cython

Cython компилирует Python в C. Без --native вы видите только Python-обёртки.

#Cython-код (example.pyx)

# cython: boundscheck=False, wraparound=False import numpy as np def cython_sum(int n): cdef int i cdef double total = 0.0 for i in range(n): total += i * i return total

#Профиль без --native

┌─────────────────────────────────────────┐
│         cython_sum (80%)                │ ← Видите только Python-обёртку
└─────────────────────────────────────────┘

#Профиль с --native

┌─────────────────────────────────────────┐
│         cython_sum (80%)                │
│  ┌──────────────────────────────────┐   │
│  │  __pyx_pf_7example_cython_sum    │   │ ← Скомпилированная функция
│  │  (75%)                           │   │
│  └──────────────────────────────────┘   │
└─────────────────────────────────────────┘

#Профилирование hashlib / cryptography

#Код

import hashlib def hash_many(items): """Хэширование множества элементов""" results = [] for item in items: h = hashlib.sha256(item.encode()).hexdigest() results.append(h) return results items = [f"data_{i}" for i in range(100_000)] hash_many(items)

#Профиль с --native

┌─────────────────────────────────────────┐
│         hash_many (95%)                 │
├─────────────────────────────────────────┤
│    hashlib.sha256 (90%)                 │
│  ┌──────────────────────────────────┐   │
│  │  SHA256_Update (OpenSSL, 70%)    │   │
│  │  SHA256_Final (OpenSSL, 20%)     │   │
│  └──────────────────────────────────┘   │
└─────────────────────────────────────────┘

Видно, что время тратится в OpenSSL функциях.

#Ограничения --native

#1. Требует отладочных символов

Для чтения C-стека нужны отладочные символы. Они есть в:

  • ✅ Стандартных сборках Python (Linux, macOS)
  • ✅ Большинство pip-пакетов с C-кодом
  • ❌ Сжатых/оптимизированных сборках

Проверка:

# Если py-spy выдаёт предупреждение о missing symbols: # Установите отладочные символы # Ubuntu/Debian sudo apt-get install python3-dbg # macOS (через Homebrew) brew install python --with-debug

#2. Работает только на уровне процесса

--native показывает C-стек внутри процесса Python. Отдельные процессы (subprocess) не показываются.

#3. Может быть медленнее

Чтение C-стека добавляет небольшой оверхед. Для продакшена используйте с осторожностью.

#4. Не показывает ассемблер

Вы видите имена C-функций, но не ассемблерный код. Для ассемблера используйте perf (Linux) или Instruments (macOS).

#Сравнение: с --native и без

Без --nativeС --native
Только Python-фреймыPython + C-фреймы
Быстрее (<5% оверхед)Медленнее (~10% оверхед)
Достаточно для чистого PythonНужно для numpy, Cython, cryptography
Меньше файлБольше файл

Рекомендация:

  • Чистый Python → без --native
  • numpy, pandas, scipy → с --native
  • Cython → с --native
  • Не уверены → попробуйте оба варианта

#Практический пример: Поиск узкого места в pandas

#Сценарий

Обработка DataFrame занимает 30 секунд. Где узкое место?

#Код

import pandas as pd import numpy as np def process_data(): df = pd.DataFrame({ 'A': np.random.rand(1_000_000), 'B': np.random.rand(1_000_000), 'C': np.random.rand(1_000_000) }) # Группировка и агрегация result = df.groupby(df['A'] > 0.5).agg({ 'B': ['sum', 'mean'], 'C': ['sum', 'mean'] }) return result process_data()

#Профилирование

py-spy record -o pandas.svg --native -- python pandas_script.py

#Анализ

┌─────────────────────────────────────────┐
│         process_data (95%)              │
├─────────────────────────────────────────┤
│    DataFrame.groupby (60%)              │
│  ┌──────────────────────────────────┐   │
│  │  groupby_apply (40%)             │   │
│  │  take_2d_multi (20%)             │   │
│  └──────────────────────────────────┘   │
│    DataFrame.agg (30%)                  │
└─────────────────────────────────────────┘

Вывод: groupby занимает 60%. Возможно, стоит использовать более эффективный метод группировки или индексацию.

#Практический пример: Оптимизация lxml парсинга

#Сценарий

Парсинг большого XML файла занимает 2 минуты.

#Код

from lxml import etree def parse_large_xml(filename): tree = etree.parse(filename) root = tree.getroot() # XPath запросы results = [] for elem in root.xpath('//item[@active="true"]'): results.append(elem.text) return results parse_large_xml('large.xml')

#Профилирование

py-spy record -o lxml.svg --native -- python parse_xml.py

#Анализ

┌─────────────────────────────────────────┐
│         parse_large_xml (90%)           │
├─────────────────────────────────────────┤
│    etree.parse (50%)                    │
│  ┌──────────────────────────────────┐   │
│  │  xmlReadFile (libxml2, 45%)      │   │
│  └──────────────────────────────────┘   │
│    root.xpath (40%)                     │
│  ┌──────────────────────────────────┐   │
│  │  xmlXPathEval (libxml2, 35%)     │   │
│  └──────────────────────────────────┘   │
└─────────────────────────────────────────┘

Вывод: Время тратится в libxml2. Возможные оптимизации:

  • Использовать итеративный парсинг (iterparse)
  • Кэшировать результаты XPath
  • Использовать lxml.etree.XMLParser с опциями

#Частые C-функции в профиле

ФункцияБиблиотекаЧто делает
dgemm_, dgetrf_LAPACKЛинейная алгебра
SHA256_UpdateOpenSSLХэширование
xmlReadFile, xmlXPathEvallibxml2Парсинг XML
PyEval_EvalFrameExCPythonВыполнение байт-кода
malloc, freelibcВыделение памяти

#Практическое задание

  1. Создайте скрипт с numpy (пример выше)
  2. Запустите профилирование без --native
  3. Запустите профилирование с --native
  4. Сравните результаты — какие C-функции вы видите?

Ключевая идея: --native открывает «чёрный ящик» C-расширений. Вы видите не только Python-обёртки, но и реальные C-функции, которые выполняют работу.

Далее: Продакшен-профилирование