company_banner

PHP GR8: повысит ли JIT производительность PHP 8

Автор оригинала: Joe Watkins
  • Перевод


PHP — один из основных языков разработки в Badoo. В наших дата-центрах тысячи процессорных ядер заняты выполнением миллионов строк кода на PHP. Мы внимательно следим за новинками и активно ищем пути улучшения производительности, так как на наших объёмах даже небольшая оптимизация приводит к существенной экономии ресурсов. Одна из главных новостей в области производительности PHP — появление JIT в восьмой версии языка. Это, безусловно, не могло остаться без нашего внимания, и мы перевели статью о том, что есть JIT, как он будет реализован в PHP, зачем его решили делать и что от него ждать.

Если вы не вышли из пещеры или не прибыли из прошлого (в этом случае добро пожаловать), то уже знаете, что в PHP 8 будет JIT: на днях тихо-мирно завершилось голосование, и подавляющее большинство участников высказались за внедрение, так что всё решено.
 
Можно в порыве радости даже изобразить несколько безумных движений как на фото (это, к слову, называется «детройтский JIT»:
 


А теперь присядьте и прочтите эту развенчивающую мифы статью. Я хочу прояснить недопонимание, связанное с тем, что собой представляет JIT и чем он полезен, и рассказать о том, как он работает (но не слишком подробно, чтобы вы не заскучали).

Поскольку я не знаю, кто будет читать статью, пойду от простых вопросов к сложным. Если вы уже знаете ответ на вопрос в заголовке, можете смело пропускать соответствующую главу.
 

Что такое JIT?


PHP реализован на базе виртуальной машины (мы называем её Zend VM). Язык компилирует исходный PHP-код в инструкции, которые понимает виртуальная машина (это называется стадией компиляции). Полученные на стадии компиляции инструкции виртуальной машины мы называем опкодами (opcodes). На стадии исполнения (runtime) Zend VM исполняет опкоды, выполняя тем самым требуемую работу.
 
Эта схема прекрасно работает. Кроме того, инструменты вроде APC (раньше) и OpCachе (сегодня) кешируют результаты выполнения стадии компиляции, так что эта стадия выполняется лишь в случае необходимости.
 
Если коротко, то JIT — это стратегия компиляции just in time (в нужный момент), при которой код сначала переводится в промежуточное представление, которое затем в ходе исполнения превращается в машинный код, зависящий от архитектуры.
 
В PHP это означает, что JIT будет рассматривать полученные на стадии компиляции инструкции для виртуальной машины как промежуточное представление и выдавать машинный код, который будет выполняться уже не Zend VM, а непосредственно процессором.
 

Для чего PHP нужен JIT?


Незадолго до появления PHP 7.0 основным направлением работы команды PHP стала производительность языка. Большинство основных изменений в PHP 7.0 содержались в патче PHPNG, который значительно улучшил то, как PHP использует память и процессор. С тех пор каждому из нас приходится поглядывать за производительностью языка.
 
После выхода PHP 7.0 улучшения производительности продолжились: оптимизирована хеш-таблица (основная структура данных в PHP), внедрены специализация определённых опкодов в Zend VM и специализация определённых последовательностей в компиляторе, постоянно улучшается Optimizer (компонент OpCache) и реализовано ещё множество других изменений.
 
Суровая правда заключается в том, что в результате всех этих оптимизаций мы быстро приближаемся к пределу возможностей улучшения производительности.
 
Обратите внимание: под «пределом возможностей улучшения» я имею в виду тот факт, что компромиссы, на которые придётся пойти ради дальнейших улучшений, больше не выглядят привлекательными. Когда речь идёт об оптимизации производительности, мы всегда говорим о компромиссах. Нередко ради производительности нам приходится жертвовать простотой. Каждому хотелось бы думать, что самый простой код является и самым быстрым, но в современном мире программирования на С это не так. Самым быстрым чаще всего оказывается код, который подготовлен к использованию преимуществ внутреннего устройства архитектуры или встроенных в платформу/компилятор конструкций. Простота сама по себе не гарантирует лучшей производительности.
 
Поэтому на данном этапе оптимальным способом выжать из PHP ещё больше производительности выглядит внедрение JIT.
 

JIT ускорит работу моего сайта?


По всей вероятности, незначительно.
 
Возможно, это не тот ответ, который вы ожидали. Дело в том, что в общем случае PHP-приложения ограничены по вводу-выводу (I/O bound), а JIT лучше всего работает с кодом, который ограничен по процессору (CPU bound).
 

Что означает «ограничен по вводу-выводу и по процессору»?


Для описания характеристик общей производительности какого-то кода или приложения мы используем термины «ограничен по вводу-выводу» и «ограничен по процессору».
 
Самое простое определение:

  • ограниченный по вводу-выводу код будет работать значительно быстрее, если мы найдём способ улучшить (уменьшить, оптимизировать) выполняемые операции ввода-вывода;
  • ограниченный по процессору код будет работать значительно быстрее, если мы найдём способ улучшить (уменьшить, оптимизировать) выполняемые процессором инструкции или волшебным образом увеличим тактовую частоту процессора.

Код и приложение могут быть ограничены по вводу-выводу, по процессору или по тому и другому.
 
В целом PHP-приложения склонны быть ограничены по вводу-выводу: основным их узким местом зачастую оказываются операции ввода-вывода — подключение, чтение и запись в базу данных, кеши, файлы, сокеты и т. д.
 

Как выглядит ограниченный по процессору PHP-код?


Возможно, некоторые PHP-программисты плохо знакомы с ограниченным по процессору кодом из-за самой природы большинства PHP-приложений: обычно они выполняют роль связующего звена с базой данных или с кешем, поднимают и выдают небольшие количества HTML/JSON/XML-ответов.
 
Вы можете посмотреть на свою кодовую базу и найти много кода, который не имеет ничего общего с вводом-выводом, кода, который вызывает функции, никак не связанные с вводом-выводом. И вас может смутить, что это не делает ваше приложение ограниченным по процессору, хотя в его коде больше строк, не работающих с вводом-выводом, чем работающих.
 
Дело в том, что PHP — один из самых быстрых интерпретируемых языков. Не существует заметной разницы между вызовом функции, не задействующей ввод-вывод, в Zend VM и в машинном коде. Конечно, какая-то разница есть, но и машинный код, и Zend VM используют соглашение о вызовах (calling convention), поэтому не имеет значения, вызываете вы какую-то_функцию_уровня_С() в опкодах или в машинном коде, — это не окажет заметного влияния на производительность всего приложения, которое совершает вызов.
 
Примечание: если говорить упрощённо, то соглашение о вызовах — это последовательность инструкций, исполняемых до входа в другую функцию. В обоих случаях соглашение о вызовах передаёт аргументы в стек.
 
Вы спросите: «А что насчёт циклов, хвостовых вызовов (tail calls) и прочего»? PHP достаточно сообразителен — и при включённом компоненте Optimizer из OpCache ваш код будет волшебным образом преобразован в более эффективную версию написанного вами.
 
Здесь нужно отметить, что JIT не изменит соглашения о вызовах Zend VM. Сделано так, потому что PHP должен уметь в любой момент переключаться между режимами JIT и VM (поэтому решили сохранить текущие соглашения). В результате любые вызовы, которые вы видите повсюду, с использованием JIT будут работать ненамного быстрее.
 
Если хотите увидеть, как выглядит ограниченный по процессору PHP-код, загляните сюда: https://github.com/php/php-src/blob/master/Zend/bench.php. Это крайний пример, но он показывает, что всё великолепие JIT раскрывается в математике.
 

Пришлось пойти на такой экстремальный компромисс, чтобы ускорить математические вычисления в PHP?


Нет. Мы пошли на это ради расширения спектра применения языка (и расширения значительного).
 
Не хотим хвастаться, но PHP доминирует в вебе. Если вы занимаетесь веб-разработкой и не рассматриваете использование PHP в своём следующем проекте, то вы что-то делаете неправильно (по мнению очень предвзятого разработчика PHP).
 
На первый взгляд может показаться, что ускорение математических вычислений в PHP имеет очень узкое применение. Однако это открывает нам дорогу, например, к машинному обучению, 3D-рендерингу, 2D-рендерингу (GUI) и анализу данных.
 

Почему это нельзя реализовать в PHP 7.4?


Выше я назвал JIT экстремальным компромиссом, и я действительно так считаю: это одна из самых сложных стратегий компилирования среди всех существующих, если не самая сложная. Внедрение JIT — это значительное повышение сложности.
 
Если вы спросите Дмитрия, автора JIT, сделал ли он PHP сложным, он ответит: «Нет, я ненавижу сложность» (это цитата).
 
По сути, «сложное» означает «то, что мы не понимаем». И сегодня мало кто из разработчиков языка действительно понимает имеющуюся реализацию JIT.
 
Работа над PHP 7.4 идёт быстрыми темпами, и внедрение JIT в эту версию приведёт к тому, что лишь единицы смогут отлаживать, исправлять и улучшать язык. Это неприемлемо для тех, кто голосовал против JIT в PHP 7.4.
 
До релиза PHP 8 многие из нас будут разбираться в реализации JIT. Есть фичи, которые мы хотим реализовать, и инструменты, которые хотим переписать для восьмой версии, поэтому вникнуть в JIT нам необходимо в первую очередь. Нам нужно это время, и мы очень благодарны, что большинство проголосовали за то, чтобы дать нам его.
 
Сложное не синоним ужасного. Сложное может быть прекрасным как звёздная туманность, и это как раз про JIT. Иными словами, даже когда у нас в команде человек 20 станут разбираться в JIT не хуже Дмитрия, это не изменит сложности самой природы JIT.
 

Разработка PHP замедлится?


Нет причин так думать. У нас достаточно времени, поэтому можно утверждать, что к моменту готовности PHP 8 среди нас будет достаточно тех, кто освоился с JIT настолько, чтобы работать не менее эффективно, чем сегодня, когда речь пойдёт об исправлении ошибок и развитии PHP.
 
Когда будете пытаться соотнести это с представлением об изначальной сложности JIT, помните, что большая часть времени, которое мы тратим на внедрение новых фич, уходит на их обсуждение. Чаще всего при работе над фичами и исправлении ошибок написание кода занимает минуты или часы, а обсуждения — недели или месяцы. В редких случаях код приходится писать часами или днями, но и тогда обсуждения всегда длятся дольше.
 
Это всё, что я хотел сказать.

И раз уж мы заговорили о производительности, приглашаю на доклад моего коллеги Павла Мурзакова 17 мая на конференции PHP Russia. Паша знает, как выжать последнюю CPU секунду из PHP-кода!
Badoo
240,00
Big Dating
Поделиться публикацией

Комментарии 76

    +3
    Вот если бы еще кашу из функций как-то в порядок привели в 8-й версии. Разные стили именования, разная очередность параметров и прочие соглашения…
      +23
      То получили бы полную и катастрофическую поломку обратной совместимости, ещё хуже чем в Python 2 -> Python 3

      P.S. Да и статья как бы не об этом
        +5
        Это достаточно просто сделать без поломки. Способ предлагался, почему-то по нему не пошли. Надо считать примитивные типы некими псевдообъектами с возможность вызова у них методов. Тогда ф-и можно оставить как есть, а новые методы проименовать нормально.
          0

          О каком способе идёт речь? Поделитесь ссылкой на rfc

            +2

            Это не RFC, это пакет Никиты: https://github.com/nikic/scalar_objects

              0
              RFC там не было, вроде, был какой-то модуль и идея за ним в виде какой-то статьи или сообщения в internal, не помню точно.
              +4

              Ну это такое себе решение. Запихнуть в строку весь stdlib языка — сомнительный профит. Мне больше нравится с распихиванием функций по неймспейсам и прокидывание фоллбеков. Если я не путаю, то что-то в стиле Groovy, когда вызов println фактически ссылается на System.out.println. В PHP возможно реализовать такое же, где map([...]) будет ссылаться на какой-нибудь use function Core\Array\map

              0

              Так а почему бы не заалиасить? И тянуть какое-то время по 2+ наименований функций.

            –11
            и не рассматриваете использование PHP в своём следующем проекте, то вы что-то делаете неправильно

            Я понимаю использование в существующих проектах, которые уже написаны на PHP и работают. Но на кой мне PHP в следующем проекте, когда есть современные, намного лучше спроектированные языки, не обременённые обратной совместимостью с древним легаси.
              +14
              У современных языков нет готовых фреймворков (даже если есть, то молодые) и решение тривиальных задач может требовать некоторых усилий. Вместе с легаси у старых языков (не мертвых) есть и сообщество, есть куча решений которые прошли не один бой и даже выиграли войну, когда у новых языков всего этого нет и там опять все это нужно строить. Я не совсем уверен, но думаю бизнес чаще будет предпочитать стабильность вместо современности, а мы разработчики чаще всего работаем на бизнес. Хотя сам пхп меня не очень радует.
                –9

                Чем пхп лучше для веба по сравнению с питоном или руби?

                  +1
                  Не знаю, разве в этом была тема? Тема была в том, чтоб уйти со старого языка на современные.
                    0

                    Окей, что вы называете "современными языками"?

                      0
                      Раст?
                        +1

                        Для типовых веб-проектов не подходит совершенно. При всей моей любви к Rust, тут лучше подойдёт golang. Тем более, что он изначально под это делался.

                    +18
                    Чем пхп лучше для веба по сравнению с питоном или руби?


                    Я извиняюсь за излишнюю резкость в суждениях, но: В PHP популярные фреймворки следуют хотя бы SOLID и GRASP (ну или пытаются), а в приведённых вами — я не видел ни одного решения, где бы был адекватный код. Лично у меня глаза кровоточат от засирания глобального пространства процедурками в Django или харкод с завязыванием на контроллеры внутри объекта реквеста в каком-нибудь RoR. Да и разработчики даже не слышали про инверсию зависимостей, учитывая то, что в этих языках даже интерфейсов нет.

                    Короче, как бы не смешно это звучало, но решения на PHP намного качественнее сабжей.
                      0
                      Интерфейсы — это концепция в голове у разработчика а вовсе не ключевое слово «interface» в языке. На python и ruby эта концепция прекрасно реализуется.
                        +3

                        Допускаю. Можете привести пример инверсии контроля не на основе ключевого слова "interface"?


                        Ну, например, примитивный пример из мира PHP с ISP + DI:


                        Код
                        // Репа, которая предоставляет несколько возможностей
                        interface UsersRepository extends 
                            FindableById, 
                            ProvidesAuthentication, 
                            ... { ... }
                        
                        // Одна из возможностей - получение Authentcatable объекта по почте+паролю
                        interface ProvidesAuthentication 
                        {
                            public function findUsingCredentials(string $email, string $password): ?Authentcatable;
                        }

                        +


                        class AuthController
                        {
                            // Внедрение зависимостей объекта реквеста и репы 
                            // для получения Authentcatable сущности
                            public function login(RequestInterface $request, ProvidesAuthentication $repository)
                            {
                                $user = $repository->findUsingCredentials(
                                    ...$request->only('email', 'password')
                                );
                            }
                        }

                        Прошу заметить, что из подобной реализации мы можем двумя пинками подменить реализации и даже перейти с классических репозиториев на какой-нибудь CQRS, т.к. реализация нам не важна, а интерфейс с одним методом (за счёт ISP) вполне себе команда.


                        P.S. Я на всякий случай хочу напомнить, что ни в питоне, ни в руби нельзя реализовать методы без их имплементации. Только суперкостылями в виде выкидывания исключений (ну или пилить декораторы, если мы про питон).

                          –4
                          from abc import ABC, abstractmethod
                          from typing import Mapping, Optional
                          import hashlib
                          
                          
                          def hashfunc(string: str) -> str:
                              return hashlib.md5(string.encode("utf-8")).hexdigest()
                          
                          
                          class Request(ABC):
                              @property
                              @abstractmethod
                              def form(self) -> Mapping[str, str]:
                                  pass
                          
                          
                          class MockRequest(Request):
                              def __init__(self, dummy_form: Mapping[str, str]) -> None:
                                  self.dummy_form = dummy_form
                          
                              @property
                              def form(self) -> Mapping[str, str]:
                                  return self.dummy_form
                          
                          
                          class Identifiable(ABC):
                              @abstractmethod
                              def get_id(self) -> int:
                                  pass
                          
                          
                          class Authenticable(ABC):
                              pass
                          
                          
                          class User(Identifiable, Authenticable):
                              def __init__(self, id: int, email: str, password_hash: str
                                           ) -> None:
                                  self.id = id
                                  self.email = email
                                  self.password_hash = password_hash
                          
                              def get_id(self) -> int:
                                  return self.id
                          
                              def get_email(self) -> str:
                                  return self.email
                          
                              def get_password_hash(self) -> str:
                                  return self.password_hash
                          
                              def __repr__(self) -> str:
                                  return f"id=={self.id}, email=={self.email}, password_hash=={self.password_hash}"
                          
                          
                          class FindableById(ABC):
                              @abstractmethod
                              def find_by_id(self, id: int) -> Optional[Identifiable]:
                                  pass
                          
                          
                          class ProvidesAuthentication(ABC):
                              @abstractmethod
                              def find_using_credentials(self, email: str, password: str
                                                         ) -> Optional[Authenticable]:
                                  pass
                          
                          
                          class MockUsersRepository(FindableById, ProvidesAuthentication):
                              def __init__(self):
                                  self.dummy_users = (
                                      User(1, "example@company.ru", hashfunc("123")),
                                      User(2, "test@ya.ru", hashfunc("456")),
                                      User(3, "foobar@gmail.com", hashfunc("789")),
                                  )
                          
                              def find_by_id(self, id: int) -> Optional[Identifiable]:
                                  return next((
                                      user for user in self.dummy_users if user.get_id() == id),
                                      None
                                  )
                          
                              def find_using_credentials(self, email: str, password: str
                                                         ) -> Optional[Authenticable]:
                                  for user in self.dummy_users:
                                      if user.get_email() == email \
                                              and user.get_password_hash() == hashfunc(password):
                                          return user
                                  return None
                          
                          
                          class AuthController:
                              def login(self, request: Request,
                                        repository: ProvidesAuthentication):
                                  user = repository.find_using_credentials(
                                      request.form["email"], request.form["password"])
                                  # debug print:
                                  print(f"user: {user}")
                                  if user:
                                      ...  # login and redirect to '/index'
                                  else:
                                      ...  # render template login_form.html
                          
                          
                          if __name__ == "__main__":
                              authController = AuthController()
                              request = MockRequest({
                                  "email": "test@ya.ru",
                                  "password": "456",
                              })
                              users_repository = MockUsersRepository()
                          
                              authController.login(request, users_repository)
                          
                          
                            +6

                            И вы считает, что это ноимальный код?

                              0
                              Вполне.
                                +1

                                Давайте посмотрим:
                                1) Множественное наследование.
                                2) Реализация абстрактных методов через декоратор + костыль с pass.


                                Т.е. никто не ударит по рукам, если реализация потеряется, только в рантайме во время вызова. Задача интерфейсов — гарантировать реализацию, а тут игра на внимательность. Ну такое, вроде и повторяет поведение, но костылями и половина плюшек срезается.


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

                                  –2
                                  В какой-нибудь джаве было бы
                                  class MockUsersRepository implements FindableByIdInterface, ProvidesAuthenticationInterface
                                  что ровно то же самое множественное наследование, которое обозвали словом implements. Собственно, как написали в комменте ниже, наличие интерфейсов на уровне синтаксиса — результат того, что разработчики компилятора не шмогли или поленились реализовать полноценное множественное наследование и предоставили вместо него легковесный суррогат — интерфейсы.

                                  Питон динамический язык. За строгими проверками во время компиляции — это к товарищам C#, Java…

                                  pass — это просто элемент синтаксиса. Впрочем, в сколько-нибудь нетривиальных случаях вместо pass пишут док-строку к методу.
                                  """Method description."""

                                    +5
                                    что ровно то же самое множественное наследование, которое обозвали словом implements.

                                    Проблемы множественное наследования:
                                    1) Не позволяет понимать какая реализация будет использована
                                    2) Не позволяет ссылаться на родителя или потомка (через позднее статическое связывание)


                                    Пересечение:

                                    Интерфейсы и решают эту проблему, позволяя запросто пересекаться методам:


                                    interface ProvidesUuid
                                    {
                                        public function getId(): UuidInterface;
                                    }
                                    
                                    interface Identifiable
                                    {
                                        public function getId();
                                    }
                                    
                                    class User implements Identifiable, ProvidesUuid
                                    {
                                        ...
                                    }

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


                                    Иерархия

                                    Допустим, у нас есть потомок с переопределением метода:


                                    class Storage {
                                        public function save(...) { ... }
                                    }
                                    
                                    class SomeAnotherStorage {
                                        public function save(...) { ... }
                                    }
                                    
                                    // Наследуемся от этих реализаций (в PHP так нельзя)
                                    class FileStorage extends Storage, SomeAnotherStorage 
                                    {
                                        final public function save(File $file)
                                        {
                                            $file->saveTo('some/directory');
                                    
                                            return parent::save($file); // И к какому parent должно быть обращение?
                                        }
                                    }

                                    Можно и обратно, из родителя к потомку через late-binding. Главное в том, что сохраняется чёткая иерархия в том случае, если "extends" допускает только одного родителя.


                                    Питон динамический язык. За строгими проверками во время компиляции — это к товарищам C#, Java…

                                    И PHP. Он тоже проверяет что всё корректно перегружено по LSP и все нужные методы имеют реализацию во время компиляции.

                            –2
                              +3
                              Вы видимо не захотели прочитать книгу «Паттерны проектирования» от банды четырех.
                              Там говорилось об одном важном пункте композиция вместо наследования.
                              Рекомендую почитать аргументацию, она развенчивает все мифы, указанные по вашей ссылке и дает понимание, когда использовать то или другое.

                              К тому же я хочу обратить ваше внимание на наличие частичного наследования в PHP, реализованного в виде трейтов.

                              P.S. Строка выше относится к тому, что оригинальная статья опирается на синтаксис языка Java, которая не позволяет использовать множественное наследование. Кроме того, рекомендую прочитать википедию по поводу множественного наследования и убедиться, что оно далеко не так «красиво», как его изображают в вашем материале.
                                0
                                Композиция, как и само понятие «интерфейс», прекрасно существует и без ключевого слова «interface».
                                Статья написана человеком, сформулировавшим принципы SOLID. Он лжец?
                                  +3
                                  Да, как бы странно это не было, я не считаю в данном случае Мартина правым в данной ситуации. Вернее, не согласен с тем, как этот материал повернут вами в контексте комментария, на который вы его отправили.

                                  Смотрите. Кирилл говорит о том, что реализация на языке Python приводит к проблемам, которые будут найдены непосредственно в рантайме, что недопустимо. Что можно избежать в PHP использовав принципы того же Мартина — ISP + DI с использованием встроенных средств языка, а не внимательности разработчика. Кроме того, он апелирует к тому, что мы перестаем хардкодить и начинаем работать с абстракциями.

                                  Суть комментария в том, что не важно, является ли ключевое слово вредным или нет: такое решение в языках есть и с ним мы живем. Если в Python реализовали множественное наследование через пень-колоду (к слову, сам автор в комментариях согласен с тем, что интерфейсы нужны), не дали нормального способа создавать абстрактные классы и методы, а в PHP это сделать реально, пусть и с использованием «вредного» interface (к слову, я не вижу дублирования кода в данном контексте, хотя если судить по Мартину оно здесь должно быть) — я выберу более безопасный, а как следствие, подходящий инструмент, который меня защитит от этого.

                                  Кроме того. Мартин аппелирует к Eiffel, который решил проблему множественного наследования. Каким образом он это сделал? Мы можем указать, какие методы родительского класса мы наследуем. То есть потенциально доступна ситуация, когда родительский класс имеет какой-то метод, а дочерний — не имеет. Для меня такая ситуация является недопустимой и именно для этого принципиально и нужны интерфейсы. Это контракт, который гарантирует мне работу и ошибки компиляции.

                                  P.S. Я не спорю с самой статьей и идеей. Если бы решилась проблема множественного наследования, принципиально у нас бы было всего два инструмента: класс и интерфейс, а абстрактный класс был бы не нужен, так как его функции перенял бы интерфейс. Ну или наоборот.
                                    0
                                    Спасибо за развернутый ответ.
                                    Но PHP ведь не компилируется, и проверки тайпхинтов все равно происходят в рантайме. Или нет? Конечно, еще в редакторе будут ошибки подсвечиваться, это плюс, не спорю.
                                      0
                                      Вот тут вы правы. PHP — интерпретируемый язык программирования, несмотря на opcache и ввод JIT в 8 ветке языка. Но если у PHP есть что-то вроде php -f index.php где он ругнется на неверный синтаксис и бросит (сразу) error, то для python мы это увидим когда непосредственно столкнемся, что может произойти далеко не сразу (знатоки питона, если это не правда — поправляйте). Однако, и правда, все это происходит в рантайме.

                                      Это дело решается тестами, но, поверьте, тесты не всегда хорошие и не всегда имеют 100% покрытия.
                                        +3
                                        И да и нет. Вывод сигнатур языка происходит во время раннего связывания, так что язык знает о них ещё ДО того как что-либо стартануть.

                                        Это можно запросто проверить написав:
                                        return 42;
                                        
                                        class Example {}
                                        

                                        Класс Example будет доступен всегда, т.к. это инструкция раннего этапа, а return — позднего (рантайма).
                                        Заголовок спойлера
                                        Это почти всегда так за редкими исключениями, декларации в сочетании с if:
                                        return 42;
                                        
                                        if (true) {
                                            class Example {}
                                        }
                                        



                                        Построение иерархии зависимостей и перегрузки (переопределения) методов происходят точно так же на раннем этапе. Т.е. язык уже перед тем как запустить какой-либо код внутри файла (файл в PHP это единица компиляции) заранее знает правильно ли сделано наследование/имплементация.

                                        Ну а затем уже происходит рантайм с проверками типов. И вот на этом этапе могут возникнуть другие ошибки типизации.

                                        Т.е. другими словами в PHP есть несколько этапов работы и один из них очень похож на поведение компиляторов. На этом этапе собирается из исходников опокод, оптимизируется (включая DCE), выводятся типы и прочее. А затем уже через VM запускаются опкоды, которые уже непосредственно выполняют императивные инструкции.

                                        Заголовок спойлера
                                        Ну и вот, например, вдогонку чуть-чуть. Ещё примеры того, как работает php внутри.
                                        const SOME = 'hello' . 42 . 'world';
                                        


                                        Эта конструкция выполняется после парсинга, но до рантайма, собираясь в одну единственную инструкцию виртуальной машины. Любая ошибка в ней приведёт к её возникновению ещё до того как код запустился. Если мы добавим что-то спереди — оно выполнено не будет.

                                        $var = 'hello' . 42 . 'world';
                                        


                                        А вот это уже инструкция рантайма (т.е. набор инструкций для VM) и поломать её до запуска самой VM могут лишь синтаксические ошибки.
                                0
                                Ну, если совсем грубо — то никто не мешает сделать ровно то же самое на любом друом языке (включая python/ruby), только без сахара в виде вынесения единственного публичного метода класса в интерфейс и проверки типов. Можно точно так же определить соглашение, точно так же реализовать это соглашение в классах, точно так же подменять конкретную реализацию, и т.п. Язык в этом никак не поможет, да, но сделать это всё равно можно, и кода будет написано плюс-минус столько же.
                              0
                              засирания глобального пространства процедурками в Django

                              Что? В Питоне нет глобального пространства имен, все имена в модулях.

                                0
                                Да, это правда. Я некорректно выразился, имел ввиду вроде таких вот файликов: github.com/django/django/blob/master/django/middleware/csrf.py Где вперемешку кони и люди. Это всё действительно скрыто и наружу отправится только при явном импорте, но выглядит всё равно довольно стрёмно и с повышенной связанностью (cohesion который).
                                  0
                                  В этом файлике две публичные функции get_token, rotate_token и один публичный класс CsrfViewMiddleware. Прочие функции приватные в соответствии с соглашением об именовании.
                                    0
                                    Тут просто нужно смириться, что файлики в пайтоне — это не файлики в пхп, а модули и тогда станет легче это воспринимать)
                                      +1
                                      Так я же не кричу во всю глотку что это говнокод (хотя он действительно попахивает магическими константами и хардкодом логики с нарушением open/close). Я просто написал выше, что тем, кто привык к строгости Java/C#/PHP, где один файл — один класс подобные простыни в стиле Python/JS доставляют моральные страдания.

                                      P.S. А не, действительно в самом первом моём комментарии я излишне эмоционально всё это расписал со словами «засирание глобального пространства», что вполне интерпретируется как «говнокод». Только кто-то (не будут тыкать в себя пальцем) забыл про то, что этот код инкапсулируется с помощью магии модулей и PEP во что меня можно смело тыкать носом. В любом случае он лучше того, что обычно творится в JS с теми самыми же модулями. Но всё равно плохой. =)))
                                        0

                                        Так вроде кричите

                                          0
                                          Угу, я уже дописал в «P.S.» это. С кем не бывает?
                                            0

                                            «Нет, с ним всё в порядке, проблем никаких, делает то, что нужно, неудобств не доставляет. Но всё равно плохой.»


                                            Так и скажите, что не нравится, но ещё не придумали, почему.

                                              +1
                                              Ну я же придумал, в самом начале коммента выше отписал:
                                              1) Магические константы
                                              2) Нарушение open/close с харкодом логики

                                              В пыхе для таких штук вначале создаётся интерфейс, а на него уже навешиваются реализации. Отдельно для сессий, отдельно для флеш-токенов, отдельно для…

                                              Вон, у зенда даже зависимости на request нет: github.com/zendframework/zend-expressive-csrf/blob/master/src/SessionCsrfGuard.php Можно прикрутить к консольке. Не знаю зачем, но главное что можно.

                                              P.S. Знаю даже зачем. Можно запилить интеграционное тестирование без селениума и даже без запуска HTTP слоя.
                                          0

                                          Что такое «попахивает магическими константами»? Там либо есть магические константы, либо нет. В данном случае я ничего такого не вижу. Если вам «попахивает магическими константами», возможно это где-то рядом с вами чем-то пахнет.

                                            +2
                                            github.com/django/django/blob/master/django/middleware/csrf.py#L85-L92 Т.е. это нормально три раза дублировать одну и ту же строчку? А как её подменить на другую?
                                              +1

                                              Рискну спросить: а как бы вы это написали на PHP?

                                                +2

                                                Конкретно этот кейс? Тогда за предоставление csrf токена должен отвечать соответствующий интерфейс:


                                                interface CsrfProviderInterface
                                                {
                                                    public function getToken(): string;
                                                }

                                                Заголовок спойлера
                                                // Для HTTP реквеста
                                                class RequestCsrfProvider implements CsrfProviderInterface
                                                {
                                                    // Можно в константу с переопределением через наследование.
                                                    // А можно и в переменную.
                                                    protected const CSRF_REQUEST_ATTRIBUTE = 'CSRF_COOKIE';
                                                
                                                    public function __construct(ServerRequestInterface $request) { ... }
                                                    public function getToken(): string { ... };
                                                }

                                                // Блокирующий (для тестов, например)
                                                class FlushCsrfProvider implement CsrfProviderInterface
                                                {
                                                    public function getToken(): string
                                                    {
                                                        return \random_bytes(32);
                                                    }
                                                }

                                                class ConstantCsrfProvider implements CsrfProviderInterface {}

                                                Конечно, это адовый оверинжинеринг в рамках обычных проектов, где можно просто TODO оставить и переписать код в любой момент. Но именно так и обязаны писать внутри фреймворков и ядре, где предполагается то, что всё можно заменить. А "META", если не путаю (поправьте) — это HTTP заголовки, которые обязаны быть заменяемыми.

                                            –3
                                            привык к строгости Java/C#/PHP, где один файл — один класс

                                            Не могу перестать удивляться с вашего комментария. В приведенном вами в пример файле ровно один класс.


                                            Но вы не задумывались отчего в PHP (про остальные не скажу) такая строгость (один файл — один класс)? Она там далеко не от излишнего удобства. Просто в PHP есть такое понятие, как автолоад. Если класс не будет лежать в строго определенном месте, он просто не будет найден.


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

                                              +2
                                              Но вы не задумывались отчего в PHP (про остальные не скажу) такая строгость (один файл — один класс)? Она там далеко не от излишнего удобства. Просто в PHP есть такое понятие, как автолоад. Если класс не будет лежать в строго определенном месте, он просто не будет найден.


                                              Класс будет вполне найден. Как раз из-за удобства так и сделано. Автолоад не влияет на ограничения по количеству кода в файлах, их внутренности, никак не влияет на именование и вообще ни на что. Автолоад — это императивная функция языка, которой достаточно подсунуть нужный файл во время триггера события «Class not found»: www.php.net/manual/ru/function.spl-autoload-register.php

                                              Вы путаете инструкцию автолоадинга со стандартами PSR-0 и PSR-4. Ну или PHP перепутали с Java. Т.е. это примерно как сказать то, что все приватные методы должны быть с нижним подчёркиванием. Спека (это в PEP кажется?) говорит одно, а на деле никто не ограничивает.

                                              Не могу перестать удивляться с вашего комментария. В приведенном вами в пример файле ровно один класс.


                                              И две публичные функции с захардкоженными внутри строчками.
                                      0
                                      для руби есть hanamirb.org
                                    –6

                                    Судя по всему, современный веб развивается настолько бурно, что «готовые решения» существуют только для legacy проектов.


                                    Приведём в пример graphql. С чего бы гипотетический «другой язык», появившийся в 2013 году может иметь менее зрелое решение, чем PHP, если graphql появился в 2015?


                                    Если говорить о «хорошо спроектированных языках», то Laravel появляется в 2011 году, в то время как Rails — в 2005. И Django в 2005. Symphony правда тоже появляется в 2005, но даже если считать что это технологии одного поколения — о каком отсутствии фреймворков вы говорите? А ведь в этих Питонах и Рубях всегда были ООП, и история «объектно ориентированного проектирования» у них побольше чем у PHP.


                                    Кроме того, сегодня среды череды бесконечных одинаковых MVC фреймворков каждый пытается предложить свой изюм. Кто-то говорит «интегрируй АИ легко и нативно» как фреймворка на Python. Кто-то говорит «разрабатывай фронт и бэк одинаково» как нода решения. Кто-то гонится за бешеной скоростью работы на Го. Или бешеной скоростью разработки на рельсах. Или горизонтальным масштабированием из коробки у Erlang/Elixir… Где изюмчик у PHP — для меня загадка. И поэтому слышать «PHP всегда фигурирует при выборе стека разработки» для меня тоже загадка.

                                  +1

                                  Действительно непонятно что это даст в итоге. Надеюсь машинный код тоже кешируется? А то каждый запрос компилить в машинный код наверное не хорошо.

                                    0
                                    Конечно, иначе какой в нём смысл?
                                    –21
                                    Не хотим хвастаться, но PHP доминирует в вебе. Если вы занимаетесь веб-разработкой и не рассматриваете использование PHP в своём следующем проекте, то вы что-то делаете неправильно.


                                    Простите, при всей моей ностальгической любви к PHP, вы тег «ирония» потеряли. Осторожнее, люди же читают и верят.
                                      +6
                                        –15
                                        Есть ложь, есть наглая ложь, а есть статистика.

                                        Убираем из этой статистики друпалы, вордпрессы, битриксы и так далее — получим совершенно другую картину.
                                        Ну или можно пойти от обратного — как много вы знаете крупных порталов, продуктов и систем, которые написаны на PHP?

                                        Если кто-то правда сидит и думает, на чём ему написать очередной CRUD — да, PHP отлично под это подходит. Но разбрасываться фразами про «доминирование» и «делаете неправильно» — это очень странно. Причём вне зависимости от контекста.
                                          +2
                                          Тем не менее тезисы «PHP доминирует в вебе» и «PHP занимает 80% рынка» и имеют схожий смысл.
                                            –7
                                            Я к тому что эти «80% рынка» не имеют отношения к разработке, и являются готовыми настроенными решениями. Что никак не должно влиять на решение выбора языка при разработке своего проекта.

                                            Если мы хотим узнать, что именно «на рынке» — можно, посмотреть последний опрос на stackoverflow по используемым языкам, например. Или зайти на hh и сравнить количество вакансий по разным языкам. И увидеть, что там и в помине нет никаких 80% PHP.
                                            +25
                                            Убираем из этой статистики друпалы, вордпрессы, битриксы и так далее — получим совершенно другую картину.


                                            А заодно RoR, Django, .NET… да давайте вообще всё уберём. Ну просто почему бы и нет? За компанию. И вообще интернет — это миф, его не существует!

                                            Ну или можно пойти от обратного — как много вы знаете крупных порталов, продуктов и систем, которые написаны на PHP?


                                            Я так понимаю, что какие-то левые ресурсы mail, yandex и rambler не в счёт? Так же как и госуслуги, ну или кусок vk, кусок facebook, кусок youtube, продукты 1c (тьфу-тьфу), badoo, альфа-банк, delicious, wikipedia, dailymotion, w3 counter, photobucket, avito, lamoda, yahoo, ted, roave, onliner, skyeng, blablacar, pornhub… Кстати, а habr считается крупным или toster? Так-с, дайте подумать, даже не знаю, вроде Вы действительно правы, таких сайтов не существует в природе!
                                              0

                                              Добавьте в списки Ali и Lazada, ozone...

                                        –3
                                        Если честно, чем дальше идет развитие PHP — тем сильнее он становится похожим на Java/Net. Т.е. если мы условно отходим от парадигмы того, что PHP — это для небольших сайтов, а PHP — это интерпрайз, ПХП просто забирает проверенные фичи из Java/.Net

                                        Например, если мы сравниваем Spring /.Net / Symfony — они как браться близнцы уже=) Особенно если учитывать магию конфигов и аннотаций.

                                        Почему это плохо! Чем сильнее php будет становиться интерпрайз языком, тем выше станут требования к уровню разработчика. А если граница между ПХП и Java станет очень близкой, смысл учить ПХП про пропадет, ведь за тот же уровень усилий за Java платят лучше.

                                        Например, если сейчас попытаться найти разработчика на Symfony, который могет в ООП, Паттерны, Стратегию развития интерпрайз проектов и прочее — вы поймете, что на рынке единицы таких разработчиков, а их уровень оплаты 120 + (что уже не сильно отличается от Java).

                                        т.е. смотрите, почему умер Ruby c ROR (не умер, а потерял популярность) — он оказался сложнее, чем ПХП для тех же задач, и он не мог быть универсльным, как Python.

                                        Если ПХП потеряет свою простоту для веба, у него ничего не будет против Java/.Net
                                          –4
                                          +1, как бы не получили две официальные версии PHP: энтерпрайз решение для нагруженных проектов (платную?), а вторую лайтовую для простых страничек и шард-хостингов (айфон 8? :) ). Как несколько лет назад простой PHP vs PHP-phalcon, HHVM (бесплатные решения, но точка выхода выше)
                                            +2

                                            Вас никто не заставляет использовать симфонии, берите вп, будет вам простота

                                            +18

                                            Какая же статья о пхп будет без срача о том что руби или Пайтон лучше?

                                              +4

                                              Надо сделать jit, и написать на пхп интерпретатор питона. Во будет срача..

                                                +4

                                                Мне честно кажется, что python уже опоздал. Пока народ холиварил о "красоте" python, PHP выкатило много крутых обновлений и системы типов, и объектов и производительности. Тот же python ничего сравнимого за эти годы, скажем с PHP 5.5, не сделал.

                                                  0

                                                  Ну в питоне тоже в 3ке подвезли типизацию: https://docs.python.org/3/library/typing.html Почему сразу ничего? =)

                                                    0
                                                    Это только для тайпхинтов. Проверок нет никаких
                                                      0
                                                      В смысле в рантайме нету, но возможна статическая проверка
                                                    +1
                                                    Python просто забил на это дело и ушёл в ML, где уверенно занял лидирующую позицию.
                                                      +3

                                                      Тогда его пора убирать из списков языков общего назначения.

                                                      0
                                                      Вы, похоже, сильно далеки от python. JIT, «прекомпиляция», асинхронность, ffi, и прочие мастхев фичи, которые в php появляются/планируются только сейчас и подаются как революшн, в python существуют и развиваются уже добрый десяток дет
                                                        +1

                                                        Программировал на обоих. Раньше да, python кое в чем положительно отличался, но сейчас он мало развивается и куда-то непонятно куда.

                                                    +1
                                                    Нельзя не упомянуть, что уже существует альтернатива JIT для PHP www.peachpie.io
                                                      0
                                                      Как-то их сайт не очень быстро работает, для компилируемого языка. Или дело в Microsoft Azure на котором их сайт лежит?
                                                      Если говорить про такие решения, то есть ещё KPHP. По крайней мере был.
                                                      +1
                                                        0
                                                        Для своих задач PHP отличный язык, который стремиться всё больше и больше расширить свою сферу применения, не смотря на огромную армию хейтеров. (предлагаю о вкусах не спорить)

                                                        А вот чего хочется видеть, так это некого аналога go-рутин и каналов(нативно, без смс, костылей и регистрации)

                                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.