Python стремительно набирает популярность. Он используется в DevOps, науке о данных, веб-разработке и безопасности.Он, однако, не выигрывает никаких медалей за скорость.
Как Java сравнивается с точки зрения скорости с C или C++ или C# или Python? Ответ во многом зависит от типа запущенного приложения. Ни один тест не является идеальным, но компьютерный язык бенчмарки игра является хорошая отправная точка.
Я имел в виду компьютерный язык бенчмарков игры на протяжении более десяти лет; по сравнению с другими языками, такими как Java, C#, Go, JavaScript, C++, Python является один из самых медленных. Это включает в себя: JIT (C#, Java) и АОТ (C, C++) компиляторы, а также интерпретируемые языки, такие как JavaScript.
NB: когда я говорю “Python”, я говорю о ссылочной реализации языка, CPython. Я буду ссылаться на другие среды выполнения в этой статье.
Когда Python завершает сопоставимое приложение в 2-10 раз медленнее, чем другой язык, почему это так медленно? и разве мы не можем ... сделать его быстрее?
Основные теории::
- “Это GIL (Global Interpreter Lock)”
- “Это потому, что его интерпретируют и не компилируют”
- "Это потому, что его динамически типизированный язык”
Какая из этих причин оказывает наибольшее влияние на производительность?
“Это все Гил.”
Современные компьютеры поставляются с процессорами, которые имеют несколько ядер, а иногда и несколько процессоров. Чтобы использовать все эти дополнительные вычислительные мощности, операционная система определяет низкоуровневую структуру, называемую потоком, где процесс (например, браузер Chrome) может порождать несколько потоков и иметь инструкции для системы внутри. Таким образом, если один процесс особенно интенсивен для процессора, эта нагрузка может быть разделена между ядрами, и это эффективно делает большинство приложений быстрее выполнять задачи.
Мой браузер Chrome, как я пишу эту статью, имеет 44 нити открыты. Имейте в виду, что структура и API потоковой передачи отличаются между POSIX-based (например, Mac OS и Linux) и Windows OS. Операционная система также обрабатывает планирование потоков.
Если вы раньше не занимались многопоточным программированием, вам нужно будет быстро ознакомиться с замками. В отличие от однопоточного процесса, вам необходимо убедиться, что при изменении переменных в памяти несколько потоков не пытаются получить доступ и изменить один и тот же адрес памяти одновременно.
Когда CPython создает переменные, он выделяет память и затем подсчитывает, сколько ссылок на эту переменную существует, это понятие, известное как подсчет ссылок. Если число ссылок равно 0, то он освобождает этот кусок памяти из системы. Вот почему создание "временной" переменной в пределах, скажем, области цикла for, не увеличивает потребление памяти вашим приложением.
Затем возникает проблема, когда переменные совместно используются в нескольких потоках, как CPython блокирует счетчик ссылок. Существует "глобальная блокировка интерпретатора", которая тщательно контролирует выполнение потока. Интерпретатор может выполнять только одну операцию одновременно, независимо от того, сколько потоков он имеет.
Что это означает для производительности приложения Python?
Если у вас есть однопоточное, одно приложение интерпретатора. Это не будет иметь никакого значения для скорости. Удаление GIL не окажет никакого влияния на производительность вашего кода.
Если вы хотите реализовать параллелизм в рамках одного интерпретатора (процесс Python) с помощью threading, и ваши потоки были интенсивными IO (например, Network IO или Disk IO), вы увидите последствия конкуренции GIL.
Если у вас есть веб-приложение (например, Django) и вы используете WSGI, то каждый запрос к вашему веб-приложению является a разделять Интерпретатор Python, поэтому есть только 1 блокировка по запрос. Поскольку интерпретатор Python запускается медленно, некоторые реализации WSGI имеют " демонический режим” которые держат процесс(ы) Python на ходу для вас.
А как насчет других сред выполнения Python?
PyPy имеет GIL и это, как правило, >3x быстрее, чем CPython.
У джайтона нет Гила поскольку поток Python в Jython представлен потоком Java и извлекает выгоду из системы управления памятью JVM.
Как JavaScript делает это?
Ну, во-первых все движки Javascript используйте сборку мусора mark-and-sweep. Как уже говорилось, основная потребность в GIL-это алгоритм управления памятью CPython.
JavaScript не имеет GIL, но это также однопоточный, поэтому он не требует одного. Цикл событий JavaScript и шаблон Promise/Callback - это то, как асинхронное программирование достигается вместо параллелизма. Python имеет аналогичную вещь с циклом событий asyncio.
“Это потому, что его интерпретируемый язык”
Я слышу это много, и я считаю, что это грубое упрощение того, как CPython на самом деле работает. Если на терминале вы писали python myscript.py затем CPython начнет длинную последовательность чтения, лексики, синтаксического анализа, компиляции, интерпретации и выполнения этого кода.
Если вам интересно, как работает этот процесс, я уже писал об этом раньше:
Важным моментом в этом процессе является создание .pyc файл, на этапе компилятора, последовательность байт-кода записывается в файл внутри __pycache__/ на Python 3 или в том же каталоге в Python 2. Это относится не только к вашему сценарию, но и ко всему импортируемому вами коду, включая модули сторонних разработчиков.
Так что большую часть времени (если вы не пишете код, который вы только один раз запускаете?), Python интерпретирует байт-код и выполняет его локально. Сравните это с Java и C#.NET:
Java компилируется в " промежуточный язык”, и виртуальная машина Java считывает байт-код и точно в срок компилирует его в машинный код. .NET CIL - это то же самое, .NET Common-Language-Runtime, CLR, использует компиляцию just-in-time для машинного кода.
Итак, почему Python настолько медленнее, чем Java и C# в тестах, если все они используют виртуальную машину и какой-то байт-код? Во-первых, .NET и Java скомпилированы JIT.
JIT или Just-In-time компиляция требует промежуточного языка, чтобы позволить коду быть разделенным на куски (или фреймы). Компиляторы Ahead of time (AOT) предназначены для обеспечения того, чтобы процессор мог понимать каждую строку в коде до начала любого взаимодействия.
JIT сам по себе не делает выполнение быстрее, потому что он все еще выполняет те же последовательности байт-кода. Однако JIT позволяет выполнять оптимизацию во время выполнения. Хороший JIT-оптимизатор увидит, какие части приложения выполняются много, назовите эти “горячие точки”. Затем он будет выполнять оптимизацию для этих битов кода, заменяя их более эффективными версиями.
Это означает, что когда ваше приложение снова и снова делает одно и то же, оно может быть значительно быстрее. Кроме того, имейте в виду, что Java и C# являются строго типизированными языками, поэтому оптимизатор может сделать еще много предположений о коде.
PyPy имеет Джит и как уже упоминалось в предыдущем разделе, значительно быстрее, чем CPython. В этой статье Тест производительности рассматривается более подробно —
Так почему же CPython не использует Джит?
У JITs есть свои минусы: один из них-время запуска. Время запуска CPython уже сравнительно медленно, PyPy в 2–3 раза медленнее для запуска, чем CPython. Виртуальная машина Java, как известно, медленно загружается. Среда CLR .NET обходит это, начиная с запуска системы, но разработчики среды CLR также разрабатывают операционную систему, на которой работает среда CLR.
Если у вас есть один процесс Python, работающий в течение длительного времени, с кодом, который может быть оптимизирован, потому что он содержит “горячие точки”, то JIT имеет большой смысл.
Однако CPython - это реализация общего назначения. Поэтому, если бы вы разрабатывали приложения командной строки с использованием Python, необходимость ждать запуска JIT каждый раз, когда вызывается CLI, была бы ужасно медленной.
CPython должен стараться обслуживать как можно больше вариантов использования. Была возможность подключить JIT к CPython но этот проект в значительной степени застопорился.
Если вы хотите воспользоваться преимуществами JIT и у вас есть рабочая нагрузка, которая подходит для него, используйте PyPy.
"Это потому, что его динамически типизированный язык”
В” статически типизированном " языке необходимо указать тип переменной при ее объявлении. Они будут включать в себя C, C++, Java, C#, Go.
В динамически типизированном языке все еще существует понятие типов, но тип переменной является динамическим.
а = 1
а = " фу"
В этом игрушечном примере Python создает вторую переменную с таким же именем и типом str и освобождает память, созданную для первого экземпляра a
Статически типизированные языки не предназначены как таковые, чтобы сделать вашу жизнь трудной, они разработаны таким образом из-за того, как работает процессор. Если все в конечном итоге должно быть приравнено к простой двоичной операции, вы должны преобразовать объекты и типы вниз к низкоуровневой структуре данных.
Python делает это для вас, вы просто никогда не видите его, и вам не нужно заботиться.
Не нужно объявлять тип-это не то, что делает Python медленным, дизайн языка Python позволяет вам сделать почти все динамичным. Вы можете заменить методы на объекты во время выполнения, вы можете обезьяна-патч низкоуровневые системные вызовы для значения, объявленного во время выполнения. Почти все возможно.
Это тот дизайн, который делает его невероятно тяжело чтобы оптимизировать Python.