+− THE DAILY DIFFdev & AI news
SHIP IT

Производительность Python: нативные циклы NumPy, колбэки и стоимость конвертации

Контролируемый числовой тест только на CPU отслеживает идентичные значения через циклы CPython, арифметику NumPy, обертку колбэка Python и скомпилированный цикл Numba.

Want to see our stories first on Google? Add us to your preferred sources — it helps us grow.

Контролируемый числовой тест только на CPU отслеживает идентичные значения через циклы CPython, арифметику NumPy, обертку колбэка Python и скомпилированный цикл Numba. NumPy с готовыми входными данными имел большое преимущество, в то время как конвертация из списков Python и обратно уменьшила это преимущество до почти равных результатов. Эпизод объясняет границу выполнения и как измерить результат, необходимый приложению.

Читать письменное издание (на английском) ↗

Что освещается в этом видео

  • CPython компилирует исходный код в байт-код. Повторяющаяся работа интерпретатора/объектов и типизированный нативный числовой цикл — это разные схемы выполнения.
  • Числовая арифметика NumPy может передавать типизированные пакеты скомпилированным операциям; арифметический цикл все еще существует, и представления могут иметь несмежное расположение.
  • Измеряйте стоимость конвертации и требуемое представление вывода. В этом запуске преобразование списков Python в массивы и обратно устранило большое преимущество готового массива.
  • Небольшие рабочие нагрузки могут отдавать предпочтение обычному Python, потому что важны фиксированные затраты на вызов массива. Генератор из восьми значений выиграл в этом измеренном сравнении.
  • np.vectorize — это удобная обертка, которая вызывает Python для последовательных элементов; сам по себе пакетный интерфейс не обеспечивает нативное числовое выполнение.
  • Скомпилированные циклы с синтаксисом Python также могут быть быстрыми. Включайте затраты на "холодную" компиляцию/настройку, когда они важны для вызывающего кода.
  • Нативное выполнение, SIMD процессора, многопоточность и бэкенды GPU — это отдельные идеи. Доступные функции CPU не отслеживают инструкции, используемые в конкретном запуске.

Переведенная стенограмма

Переведено с оригинального английского повествования. Доступные аудио и субтитры контролируются YouTube.

Почему один и тот же ЦП может завершить работу раньше?

0:00 Вы думаете, что Python нужен новый язык, чтобы стать быстрым. Тот же ЦП выполнил нашу партию быстрее после перемещения цикла. Куда делась работа? Когда ускорение исчезает? Какой ярлык возвращает Python? Это The Daily Diff, взгляд изнутри. Следите за этим ярлыком. CPython — это стандартная реализация Python.

0:17 Он компилирует ваш исходный код в байт-код, затем выполняет эти инструкции в интерпретаторе. NumPy — это численная библиотека, массивы которой содержат значения с объявленным типом данных. Его встроенная арифметика может передать целую партию скомпилированным операциям. Возьмите миллион значений с плавающей точкой, возведите каждое в квадрат, затем добавьте исходное значение. Каждая версия получает идентичные значения и выдает совпадающие ответы. Значения достаточно малы, чтобы исключить различия в округлении из гонки.

0:40 На этой машине генератор занял около тридцати трех миллисекунд. NumPy занял менее одной. Оба работали на ЦП. Это полезное доказательство для этой рабочей нагрузки и ужасный некролог для языка программирования. Начнем с обычного цикла.

Какую работу повторяет цикл Python?

0:53 Python получает следующее значение, выполняет арифметическую операцию и добавляет ответ. Затем он делает это снова. Умножение дешевое. Организация следующего умножения и обработка объектов Python вокруг него добавляют работу каждый раз. Это фактическая дизассемблированная функция теста. Итерация, умножение и сложение отображаются как инструкции байт-кода. Версии и интерпретаторы Python делают разный выбор.

1:14 Эти измерения времени относятся к версии на экране, это Python 3.10. Список Python хранит ссылки на объекты, поэтому разные виды вещей могут совместно использовать список. Наша арифметическая задача использует числа повсюду. Она платит за общую организацию, запрашивая повторяющуюся услугу. Представьте, что вы заказываете один и тот же кофе миллион раз и заполняете новую форму для каждой чашки. Кофемашина в порядке.

1:36 Документация развила свою собственную карьеру.

Куда NumPy перемещает цикл?

1:38 NumPy меняет контракт. Этот массив содержит значения с плавающей точкой фиксированного размера с одним объявленным типом. Наш массив смежный, поэтому его значения располагаются в памяти рядом. Представления могут иметь другие макеты. Арифметическая операция знает, какой тип данных она получает. Вызов передает партию нативной операции. Цикл все еще обходит значения внутри скомпилированного кода. Он может повторно использовать знание типа для всей этой работы.

2:00 Python остается тем, кто передает задание. Итамар Тёрнер Трауринг объясняет это различие в Python Speed. Пакетные интерфейсы, нативные реализации и векторные инструкции процессора — это отдельные вещи. Имя функции дает очень мало доказательств.

Означает ли "быстрый" GPU или много ядер ЦП?

2:13 Этот результат получен с использованием NumPy на ЦП. Бэкенд GPU, такой как CuPy, выбирает другое место для массивов и операций. Покупка GPU оставляет этот обычный цикл Python работать там, где он уже работал. Векторные инструкции ЦП могут обрабатывать несколько значений одновременно. NumPy имеет механизм для выбора поддерживаемых ядер. Наше время выполнения сообщает о доступных функциях, но не отслеживает инструкции этого выражения. Приписывать весь измеренный прирост векторным инструкциям было бы преувеличением доказательств.

2:40 Корзина покупок GPU может оставаться закрытой. В исправлении производительности есть что-то глубоко удовлетворяющее, что также отменяет покупку. К сожалению, бенчмарк продолжается.

Когда впечатляющее ускорение исчезает?

2:49 При восьми значениях генератор выиграл. Вызов механизмов массива стоил больше, чем крошечная задача. Более крупные готовые массивы вырвались вперед. Мы использовали повторные испытания и "разогретый" процесс. Новые сборки Python могут изменить точку пересечения. Затем измените то, что требуется приложению. Начните со списка Python и закончите новым списком Python. NumPy должен преобразовать входные данные в массив и снова превратить ответ в объекты

3:11 Python. Эти шаги должны быть включены в счет, если вашему приложению они нужны. При миллионе значений этот полный путь занял около двадцати восьми миллисекунд. Генератор занял тоже около двадцати восьми. Наше драматическое преимущество стало почти ничьей. Быстрая часть была реальной. Преимущество зависело от того, чтобы данные оставались в своей числовой форме.

Какие границы должны быть в бенчмарке?

3:28 Следуйте за данными. Если следующая операция принимает массив, сохраняйте массив. Если вы многократно преобразуете списки в массивы и обратно, измерьте эти преобразования. Остановка до дорогостоящей передачи данных дает точные числа для неправильного вопроса. Умножение этого массива создает временный результат. Добавление исходных значений создает вывод. Нативное выполнение все еще требует памяти и перемещает данные.

3:47 Некоторые операции принимают выходной буфер, уменьшая количество выделений, когда ваш код может безопасно его повторно использовать. Одна красивая строка может заказать несколько больших буферов. Python Speed также указывает на эту проблему. Распределитель памяти удивительно безразличен к вашей эстетике.

Может ли скомпилированный цикл Python быть быстрым?

4:00 Вы также можете сохранить синтаксис цикла и изменить его выполнение. Numba может компилировать поддерживаемые численные функции Python в нативный код. Вот цикл из нашего теста с его декоратором компилятора. Он все еще содержит "for". Дорогой путь интерпретатора изменился. После компиляции этот цикл занял менее полумиллисекунды с готовыми массивами. Сама компиляция заняла около секунды в этом запуске. Эти затраты на настройку были исключены из повторных измерений времени,

4:22 поэтому крошечная одноразовая работа заслуживает другого сравнения. Собственное руководство Numba с удовольствием показывает явные циклы. Начинать миграцию языка, не найдя "горячего" пути, — это сложный способ избежать открытия профайлера. Сначала найдите, что потребляет время. Ожидания сети, разбор объектов и массовая арифметика требуют разных исправлений. Операции с нативными массивами помогают, когда расчет и данные подходят.

4:43 Измерьте результат, который на самом деле нужен вызывающей стороне. Теперь о том ярлыке. У вас есть функция Python, и вы хотите, чтобы она принимала массив. Обертка с названием vectorize звучит как недостающая кнопка производительности. В ее документации есть важное предложение.

Что на самом деле выполняет np.vectorize?

4:56 NumPy говорит, что его обертка vectorize предназначена в первую очередь для удобства. Ее реализация по сути является циклом for. Обертка принимает пакеты, но продолжает вызывать вашу функцию Python для последовательных элементов. Интерфейс стал короче. Граница выполнения оставалась занятой. Посмотрите, как те же значения возвращаются через колбэк Python. С готовыми массивами эта версия заняла около восьмидесяти семи миллисекунд. Она была медленнее нашего генератора.

5:18 Полезная обертка для "широковещательной" рассылки может сохранить точную поэлементную работу, которую вы пытались удалить. Проверьте реализацию за пакетным интерфейсом, сохраняйте совместимые данные в их полезной форме и включайте преобразование в сравнение. Нативные, колбэковые и скомпилированные циклы могут выглядеть похожими с точки зрения вызывающей стороны. Имя vectorize дало обещание, которое ваш секундомер никогда не подписывал. Оставьте его, когда удобство — это то, что вам нужно. Для производительности проверьте выполняющийся цикл.

Что вы должны изменить в своем коде?

5:41 Мой вердикт — SHIP IT для пакетной обработки измеренного "горячего" пути. Сохраняйте обычные циклы, когда они уже хорошо работают. Проверьте, где происходит работа и что нужно следующему вызывающему объекту. Подпишитесь, нажмите колокольчик и скажите мне в комментариях, если бы вы оценили это по-другому. И это "разница" на сегодня. Я Нико из Axrisi. Объединяйте ответственно.

Источники

  1. Universal functions (ufunc) basics — NumPy 2.2NumPy
  2. The N-dimensional array — NumPy 2.2NumPy
  3. numpy.vectorize — convenience and performance notesNumPy
  4. CPU/SIMD optimizations — NumPy 2.2NumPy
  5. dis — Disassembler for Python bytecodePython Software Foundation
  6. CPython 3.10.12 list representationCPython contributors
  7. The limits of Python vectorization as a performance techniqueItamar Turner-Trauring / Python⇒Speed
  8. Performance TipsNumba
  9. Basics of CuPyCuPy

Похожие видео

under-the-hood · ru · 12 окт. 2026 г.

Файлы моделей Hugging Face могут запускать скрытый код

Вредоносные пакеты моделей могут выполнять код во время небезопасной загрузки. Три документированных примера объясняют обратные оболочки Pickle, поведение сканера поврежденных файлов и продемонстриров

3:33 ↗