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-кода!

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


  1. vdem
    18.04.2019 19:25
    -1

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


    1. SerafimArts
      18.04.2019 20:41
      +4

      То получили бы полную и катастрофическую поломку обратной совместимости, ещё хуже чем в Python 2 -> Python 3

      P.S. Да и статья как бы не об этом


      1. bolk
        19.04.2019 07:56
        +1

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


        1. Compolomus
          19.04.2019 11:50

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


          1. SerafimArts
            19.04.2019 13:16

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


          1. bolk
            19.04.2019 14:37

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


        1. SerafimArts
          19.04.2019 13:15

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


      1. Gemorroj
        20.04.2019 09:09

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


  1. Amikko
    19.04.2019 22:13

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

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


    1. Dimensi
      18.04.2019 23:38

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


      1. slonopotamus
        18.04.2019 23:57

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


        1. Dimensi
          18.04.2019 23:58

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


          1. slonopotamus
            19.04.2019 00:04

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


            1. Dimensi
              19.04.2019 00:08

              Раст?


              1. lain8dono
                19.04.2019 04:06

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


        1. SerafimArts
          19.04.2019 01:10
          +3

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


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

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


          1. Amikko
            19.04.2019 03:41

            Интерфейсы — это концепция в голове у разработчика а вовсе не ключевое слово «interface» в языке. На python и ruby эта концепция прекрасно реализуется.


            1. SerafimArts
              19.04.2019 03:53

              Допускаю. Можете привести пример инверсии контроля не на основе ключевого слова "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')
                      );
                  }
              }


              1. Amikko
                19.04.2019 05:47

                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)
                
                


                1. codemafia
                  19.04.2019 08:41

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


                  1. Amikko
                    19.04.2019 13:17

                    Вполне.


                    1. SerafimArts
                      19.04.2019 13:48

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


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


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


                      1. Amikko
                        19.04.2019 14:12

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

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

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

                        """Method description."""


                        1. SerafimArts
                          19.04.2019 16:05
                          +2

                          что ровно то же самое множественное наследование, которое обозвали словом 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 и все нужные методы имеют реализацию во время компиляции.


              1. RENESiS
                19.04.2019 07:41

                1. be_a_dancer
                  19.04.2019 17:44
                  +3

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

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

                  P.S. Строка выше относится к тому, что оригинальная статья опирается на синтаксис языка Java, которая не позволяет использовать множественное наследование. Кроме того, рекомендую прочитать википедию по поводу множественного наследования и убедиться, что оно далеко не так «красиво», как его изображают в вашем материале.


                  1. RENESiS
                    19.04.2019 19:20

                    Композиция, как и само понятие «интерфейс», прекрасно существует и без ключевого слова «interface».
                    Статья написана человеком, сформулировавшим принципы SOLID. Он лжец?


                    1. be_a_dancer
                      19.04.2019 20:14
                      +3

                      Да, как бы странно это не было, я не считаю в данном случае Мартина правым в данной ситуации. Вернее, не согласен с тем, как этот материал повернут вами в контексте комментария, на который вы его отправили.

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

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

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

                      P.S. Я не спорю с самой статьей и идеей. Если бы решилась проблема множественного наследования, принципиально у нас бы было всего два инструмента: класс и интерфейс, а абстрактный класс был бы не нужен, так как его функции перенял бы интерфейс. Ну или наоборот.


                      1. RENESiS
                        19.04.2019 20:56

                        Спасибо за развернутый ответ.
                        Но PHP ведь не компилируется, и проверки тайпхинтов все равно происходят в рантайме. Или нет? Конечно, еще в редакторе будут ошибки подсвечиваться, это плюс, не спорю.


                        1. be_a_dancer
                          19.04.2019 21:11

                          Вот тут вы правы. PHP — интерпретируемый язык программирования, несмотря на opcache и ввод JIT в 8 ветке языка. Но если у PHP есть что-то вроде php -f index.php где он ругнется на неверный синтаксис и бросит (сразу) error, то для python мы это увидим когда непосредственно столкнемся, что может произойти далеко не сразу (знатоки питона, если это не правда — поправляйте). Однако, и правда, все это происходит в рантайме.

                          Это дело решается тестами, но, поверьте, тесты не всегда хорошие и не всегда имеют 100% покрытия.


                        1. SerafimArts
                          19.04.2019 22:01
                          +3

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

                          Это можно запросто проверить написав:

                          return 42;
                          
                          class Example {}
                          

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


              1. barbeer
                21.04.2019 12:17

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


          1. homm
            19.04.2019 12:24

            засирания глобального пространства процедурками в Django

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


            1. SerafimArts
              19.04.2019 13:08

              Да, это правда. Я некорректно выразился, имел ввиду вроде таких вот файликов: github.com/django/django/blob/master/django/middleware/csrf.py Где вперемешку кони и люди. Это всё действительно скрыто и наружу отправится только при явном импорте, но выглядит всё равно довольно стрёмно и с повышенной связанностью (cohesion который).


              1. Amikko
                19.04.2019 13:45

                В этом файлике две публичные функции get_token, rotate_token и один публичный класс CsrfViewMiddleware. Прочие функции приватные в соответствии с соглашением об именовании.


              1. quantum
                19.04.2019 14:30

                Тут просто нужно смириться, что файлики в пайтоне — это не файлики в пхп, а модули и тогда станет легче это воспринимать)


                1. SerafimArts
                  19.04.2019 16:24

                  Так я же не кричу во всю глотку что это говнокод (хотя он действительно попахивает магическими константами и хардкодом логики с нарушением open/close). Я просто написал выше, что тем, кто привык к строгости Java/C#/PHP, где один файл — один класс подобные простыни в стиле Python/JS доставляют моральные страдания.

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


                  1. homm
                    19.04.2019 16:25

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


                    1. SerafimArts
                      19.04.2019 16:53

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


                      1. homm
                        19.04.2019 16:59

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


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


                        1. SerafimArts
                          19.04.2019 17:31
                          +1

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

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

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

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


                  1. homm
                    19.04.2019 17:06

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


                    1. SerafimArts
                      19.04.2019 17:33
                      +2

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


                      1. Sannis
                        19.04.2019 18:00

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


                        1. SerafimArts
                          19.04.2019 18:16
                          +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 {}


                  1. homm
                    19.04.2019 17:13
                    -3

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

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


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


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


                    1. SerafimArts
                      19.04.2019 17:36
                      +2

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


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

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

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


                      И две публичные функции с захардкоженными внутри строчками.


          1. amerov
            20.04.2019 11:07

            для руби есть hanamirb.org


      1. Virviil
        20.04.2019 18:32
        -2

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


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


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


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


  1. zim32
    18.04.2019 23:00

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


    1. batyrmastyr
      22.04.2019 13:07

      Конечно, иначе какой в нём смысл?


  1. jehy
    18.04.2019 23:37

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


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


    1. SerafimArts
      19.04.2019 01:13
      +1

      1. jehy
        19.04.2019 02:03

        Есть ложь, есть наглая ложь, а есть статистика.

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

        Если кто-то правда сидит и думает, на чём ему написать очередной CRUD — да, PHP отлично под это подходит. Но разбрасываться фразами про «доминирование» и «делаете неправильно» — это очень странно. Причём вне зависимости от контекста.


        1. roman901
          19.04.2019 02:22

          Тем не менее тезисы «PHP доминирует в вебе» и «PHP занимает 80% рынка» и имеют схожий смысл.


          1. jehy
            19.04.2019 08:15

            Я к тому что эти «80% рынка» не имеют отношения к разработке, и являются готовыми настроенными решениями. Что никак не должно влиять на решение выбора языка при разработке своего проекта.

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


        1. SerafimArts
          19.04.2019 03:22

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


          А заодно 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? Так-с, дайте подумать, даже не знаю, вроде Вы действительно правы, таких сайтов не существует в природе!


          1. JekaMas
            19.04.2019 12:53

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


  1. Terras
    19.04.2019 07:31

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

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

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

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

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

    Если ПХП потеряет свою простоту для веба, у него ничего не будет против Java/.Net


    1. Arik
      19.04.2019 07:46

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


    1. psFitz
      19.04.2019 11:33

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


  1. psFitz
    19.04.2019 10:04
    +1

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


    1. zim32
      19.04.2019 11:41

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


    1. JekaMas
      19.04.2019 12:56

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


      1. SerafimArts
        19.04.2019 13:25

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


        1. quantum
          19.04.2019 14:25

          Это только для тайпхинтов. Проверок нет никаких


          1. patricksafarov
            21.04.2019 12:16

            В смысле в рантайме нету, но возможна статическая проверка


      1. dbelka
        19.04.2019 13:30

        Python просто забил на это дело и ушёл в ML, где уверенно занял лидирующую позицию.


        1. JekaMas
          19.04.2019 13:31

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


      1. Pydeg
        20.04.2019 11:39

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


        1. JekaMas
          20.04.2019 12:04

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


  1. AirLight
    19.04.2019 18:25

    Нельзя не упомянуть, что уже существует альтернатива JIT для PHP www.peachpie.io


    1. Tatikoma
      20.04.2019 18:46

      Как-то их сайт не очень быстро работает, для компилируемого языка. Или дело в Microsoft Azure на котором их сайт лежит?
      Если говорить про такие решения, то есть ещё KPHP. По крайней мере был.


  1. serf
    19.04.2019 18:35

  1. Bazis007
    21.04.2019 12:16

    Для своих задач PHP отличный язык, который стремиться всё больше и больше расширить свою сферу применения, не смотря на огромную армию хейтеров. (предлагаю о вкусах не спорить)

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


    1. bat
      22.04.2019 07:49

      посмотрите на это — github.com/krakjoe/parallel