Я давний пользователь Python. Несколько лет назад я начал изучать C ++, чтобы посмотреть, что он может предложить с точки зрения скорости. В течение этого времени я продолжал использовать Python как инструмент для создания прототипов. Казалось, это была хорошая система: гибкая разработка на Python, быстрое выполнение на C ++.
В последнее время я все больше и больше использую Python и узнаю, как избежать всех ловушек и анти-паттернов, которые я быстро использовал в ранние годы работы с языком. Насколько я понимаю, использование определенных функций (списки, перечисления и т. Д.) Может повысить производительность.
Но существуют ли технические ограничения или языковые функции, которые мешают моему скрипту Python работать так же быстро, как эквивалентная программа на C ++?
источник
Ответы:
Я как бы сам попал в эту стену, когда пару лет назад начал работать на Python. Я люблю Python, правда, но когда я начал заниматься настройкой производительности, у меня были некоторые грубые шоки.
Строгие Pythonistas могут исправить меня, но вот вещи, которые я нашел, нарисованные очень широкими мазками.
Это влияет на производительность, потому что это означает, что во время выполнения существуют дополнительные уровни косвенности, в дополнение к огромному объему памяти по сравнению с другими языками.
Другие могут общаться с моделью исполнения, но Python - это время компиляции, а затем интерпретируемое, что означает, что он не доходит до машинного кода. Это также влияет на производительность. Вы можете легко связать модули C или C ++ или найти их, но если вы просто запустите Python, это приведет к снижению производительности.
Теперь в тестах веб-сервисов Python выгодно отличается от других языков компиляции во время выполнения, таких как Ruby или PHP. Но это довольно далеко позади большинства компилируемых языков. Даже языки, которые компилируются на промежуточный язык и работают на виртуальной машине (например, Java или C #), делают намного лучше.
Вот действительно интересный набор тестов, на которые я иногда ссылаюсь:
http://www.techempower.com/benchmarks/
(Несмотря на это, я до сих пор очень люблю Python, и если у меня есть возможность выбрать язык, на котором я работаю, это мой первый выбор. В большинстве случаев я не ограничен сумасшедшими требованиями к пропускной способности в любом случае.)
источник
__slots__
. PyPy должен быть намного лучше в этом отношении, но я не знаю достаточно, чтобы судить.Ссылочная реализация Python является интерпретатором «CPython». Он пытается быть достаточно быстрым, но в настоящее время он не использует передовые оптимизации. И для многих сценариев использования это хорошая вещь: компиляция некоторого промежуточного кода происходит непосредственно перед временем выполнения, и каждый раз, когда программа выполняется, код компилируется заново. Таким образом, время, необходимое для оптимизации, должно быть сопоставлено со временем, полученным оптимизацией - если нет чистого выигрыша, оптимизация не имеет смысла. Для очень долго работающей программы или программы с очень узкими циклами было бы полезно использовать расширенную оптимизацию. Однако CPython используется для некоторых заданий, которые исключают агрессивную оптимизацию:
Краткосрочные сценарии, используемые, например, для задач sysadmin. Многие операционные системы, такие как Ubuntu, строят значительную часть своей инфраструктуры поверх Python: CPython достаточно быстр для работы, но практически не имеет времени запуска. Пока это быстрее, чем bash, это хорошо.
CPython должен иметь четкую семантику, поскольку он является эталонной реализацией. Это позволяет выполнять простые оптимизации, такие как «оптимизировать реализацию оператора foo» или «компилировать списки для более быстрого байт-кода», но, как правило, исключает оптимизацию, которая уничтожает информацию, такую как встроенные функции.
Конечно, существует больше реализаций Python, чем просто CPython:
Jython построен поверх JVM. JVM может интерпретировать или JIT-компилировать предоставленный байт-код и имеет оптимизированные профили. Он страдает от высокого времени запуска и занимает некоторое время, пока не вступит в силу JIT.
PyPy - это современное состояние JITting Python VM. PyPy написан на RPython, ограниченном подмножестве Python. Это подмножество удаляет некоторую выразительность из Python, но позволяет статически выводить тип любой переменной. Виртуальная машина, написанная на RPython, может быть перенесена на C, что дает производительность, подобную RPython C. Однако RPython по-прежнему более выразителен, чем C, что позволяет быстрее разрабатывать новые оптимизации. PyPy является примером начальной загрузки компилятора. PyPy (не RPython!) В основном совместим с эталонной реализацией CPython.
Cython - это (как RPython) несовместимый диалект Python со статической типизацией. Он также переносится в C-код и может легко генерировать C-расширения для интерпретатора CPython.
Если вы готовы перевести свой код Python на Cython или RPython, то вы получите производительность, подобную C. Однако их следует понимать не как «подмножество Python», а как «C с Python-синтаксисом». Если вы переключитесь на PyPy, ваш обычный код Python получит значительное повышение скорости, но также не сможет взаимодействовать с расширениями, написанными на C или C ++.
Но какие свойства или функции препятствуют достижению ванильным Python уровня производительности, подобного C, помимо продолжительного времени запуска?
Авторы и финансирование. В отличие от Java или C #, за языком нет ни одной движущей компании, заинтересованной в том, чтобы сделать этот язык лучшим в своем классе. Это ограничивает развитие в основном добровольцами и случайными грантами.
Позднее связывание и отсутствие какой-либо статической типизации. Python позволяет нам писать дерьмо так:
В Python любая переменная может быть переназначена в любое время. Это предотвращает кэширование или вставку; любой доступ должен проходить через переменную. Эта косвенность снижает производительность. Конечно, если ваш код не делает таких безумных вещей, так что каждой переменной перед компиляцией может быть присвоен определенный тип, и каждая переменная присваивается только один раз, то - теоретически - можно выбрать более эффективную модель выполнения. Язык, помнящий об этом, обеспечит некоторый способ помечать идентификаторы как константы и, по крайней мере, разрешать необязательные аннотации типов («постепенная типизация»).
Сомнительная объектная модель. Если слоты не используются, сложно определить, какие поля у объекта (объект Python - это, по сути, хеш-таблица полей). И даже когда мы там, мы все еще не знаем, какие типы имеют эти поля. Это предотвращает представление объектов в виде плотно упакованных структур, как в случае с C ++. (Конечно, представление объектов в C ++ также не идеально: из-за структуроподобной природы даже частные поля принадлежат общедоступному интерфейсу объекта.)
Вывоз мусора. Во многих случаях GC можно полностью избежать. C ++ позволяет статически выделить объекты , которые уничтожаются автоматически , когда текущая область остается:
Type instance(args);
. До тех пор объект является живым и может быть передан другим функциям. Обычно это делается с помощью «передачи по ссылке». Такие языки, как Rust, позволяют компилятору статически проверять, чтобы ни один указатель на такой объект не превышал время жизни объекта. Эта схема управления памятью полностью предсказуема, высокоэффективна и подходит для большинства случаев без сложных графов объектов. К сожалению, Python не был разработан с учетом управления памятью. Теоретически, анализ побега может быть использован, чтобы найти случаи, когда GC можно избежать. На практике простые цепочки методов, такие какfoo().bar().baz()
придется выделить большое количество недолговечных объектов в куче (генерация GC - один из способов сохранить эту проблему небольшой).В других случаях программист может уже знать окончательный размер некоторого объекта, такого как список. К сожалению, Python не предлагает способ сообщить об этом при создании нового списка. Вместо этого новые элементы будут вставлены в конец, что может потребовать многократного перераспределения. Несколько заметок:
Списки определенного размера могут быть созданы как
fixed_size = [None] * size
. Однако память для объектов внутри этого списка должна быть выделена отдельно. Контраст C ++, где мы можем сделатьstd::array<Type, size> fixed_size
.Упакованные массивы определенного нативного типа могут быть созданы в Python через
array
встроенный модуль. Кроме того,numpy
предлагает эффективные представления буферов данных с определенными формами для собственных числовых типов.Резюме
Python был разработан для простоты использования, а не для производительности. Его дизайн затрудняет создание высокоэффективной реализации. Если программист воздерживается от проблемных функций, то компилятор, понимая оставшиеся идиомы, сможет создавать эффективный код, который может конкурировать с Си по производительности.
источник
Да. Основная проблема заключается в том, что язык определен как динамический, то есть вы никогда не знаете, что делаете, пока не собираетесь это сделать. Это делает его очень трудно производить эффективный машинный код, потому что вы не знаете , что производит машинный код для . JIT-компиляторы могут выполнять некоторую работу в этой области, но это никогда не сравнимо с C ++, потому что JIT-компилятор просто не может тратить время и память на запуск, так как это время и память, которую вы не тратите на выполнение своей программы, и существуют жесткие ограничения на то, что они могут достичь без нарушения динамической семантики языка.
Я не собираюсь утверждать, что это неприемлемый компромисс. Но для природы Python фундаментально, что реальные реализации никогда не будут такими быстрыми, как реализации C ++.
источник
Есть три основных фактора, которые влияют на производительность всех динамических языков, некоторые больше, чем другие.
Для C / C ++ относительные затраты этих трех факторов практически равны нулю. Инструкции выполняются непосредственно процессором, диспетчеризация занимает не более одной или двух косвенных операций, куча памяти никогда не выделяется, если вы так не говорите. Хорошо написанный код может подойти на ассемблере.
Для C # / Java с JIT-компиляцией первые два являются низкими, но сборка мусора имеет свою стоимость. Хорошо написанный код может приближаться к 2x C / C ++.
Для Python / Ruby / Perl стоимость всех этих трех факторов относительно высока. Подумайте 5 раз по сравнению с C / C ++ или хуже. (*)
Помните, что код библиотеки времени выполнения вполне может быть написан на том же языке, что и ваши программы, и иметь те же ограничения производительности.
(*) Поскольку компиляция Just-In_Time (JIT) распространяется на эти языки, они также приблизятся (обычно в 2 раза) к скорости хорошо написанного кода C / C ++.
Следует также отметить, что, как только разрыв между конкурирующими языками будет узким, различия будут зависеть от алгоритмов и деталей реализации. JIT-код может превзойти C / C ++, а C / C ++ может опередить ассемблер, потому что написать хороший код проще.
источник
Hash
класс Rubinius (одна из основных структур данных в Ruby) написан на Ruby, и он работает сравнимо, иногда даже быстрее, чемHash
класс YARV, который написан на C. И одна из причин заключается в том, что большие части времени выполнения Rubinius Система написана на Ruby, так что они могут ...Нет. Это просто вопрос денег и ресурсов, направленных на то, чтобы заставить C ++ работать быстро, по сравнению с деньгами и ресурсами, которые заставляют Python работать быстро.
Например, когда вышла Self VM, это был не только самый быстрый динамический язык OO, это был самый быстрый период языка OO. Несмотря на то, что он был невероятно динамичным языком (например, гораздо лучше, чем Python, Ruby, PHP или JavaScript), он был быстрее, чем большинство доступных реализаций C ++.
Но затем Sun отменила проект Self (зрелый OO-язык общего назначения для разработки больших систем), чтобы сосредоточиться на небольшом языке сценариев для анимированных меню в телевизионных приставках (вы, возможно, слышали об этом, он называется Java), не было больше финансирования. В то же время Intel, IBM, Microsoft, Sun, Metrowerks, HP и соавт. потратили огромные деньги и ресурсы на быстрое создание C ++. Производители процессоров добавили функции в свои чипы, чтобы сделать C ++ быстрым. Операционные системы были написаны или изменены, чтобы сделать C ++ быстрым. Итак, С ++ работает быстро.
Я не очень хорошо знаком с Python, я скорее специалист по Ruby, поэтому приведу пример из Ruby:
Hash
класс (эквивалентный по функции и важностиdict
в Python) в реализации Rubinius Ruby написан на 100% чистом Ruby; тем не менее, он конкурирует выгодно, а иногда даже превосходитHash
класс в YARV, который написан на оптимизированном вручную языке C. И по сравнению с некоторыми из коммерческих систем Lisp или Smalltalk (или вышеупомянутой Self VM), компилятор Rubinius даже не настолько умен ,В Python нет ничего, что могло бы замедлить работу. В современных процессорах и операционных системах есть функции, которые наносят вред Python (например, известно, что виртуальная память ужасна для производительности сборки мусора). Существуют функции, которые помогают C ++, но не помогают Python (современные процессоры стараются избегать промахов кэша, потому что они очень дороги. К сожалению, избежать промахов кэша сложно, если у вас OO и полиморфизм. Скорее, вы должны снизить стоимость кэша Процессор Azul Vega, который был разработан для Java, делает это.)
Если вы тратите столько же денег, исследований и ресурсов на создание Python быстро, как это было сделано для C ++, и вы тратите столько же денег, исследований и ресурсов на создание операционных систем, которые делают программы Python быстрыми, как это было сделано для C ++, и вы тратите много денег, исследований и ресурсов на создание процессоров, которые делают программы на Python быстрыми, как это было сделано для C ++, тогда я не сомневаюсь, что Python сможет достичь производительности, сопоставимой с C ++.
Мы видели с ECMAScript, что может произойти, если только один игрок серьезно относится к производительности. В течение года мы продемонстрировали 10-кратное увеличение производительности по всем направлениям для всех основных поставщиков.
источник