Каким образом виртуальная машина Hip Hop (HHVM) теоретически повышает производительность среды выполнения PHP?

9

С высокого уровня, как работает Facebook, et. Вы используете для повышения производительности PHP с виртуальной машиной хип-хоп?

Чем он отличается от выполнения кода с использованием традиционного движка Zend? Это потому, что типы опционально определяются с помощью хака, который учитывает методы предварительной оптимизации?

Мое любопытство возникло после прочтения этой статьи, принятие HHVM .

chrisjlee
источник

Ответы:

7

Они заменили трейлеты TranslatorX64 новым промежуточным представлением HipHop (hhir) и новым косвенным слоем, в котором находится логика для генерации hhir, которая фактически называется тем же именем hhir.

С высокого уровня, он использует 6 инструкций для выполнения того, что требовалось 9 инструкций ранее, как отмечалось здесь: «Он начинается с тех же проверок типов, но текст перевода состоит из 6 инструкций, что значительно лучше, чем 9 из TranslatorX64»

http://hhvm.com/blog/2027/faster-and-cheaper-the-evolution-of-the-hhvm-jit

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

Когда hhir заменил TranslatorX64, он генерировал код, который был примерно на 5% быстрее и выглядел значительно лучше при ручной проверке. Мы последовали за его производственным дебютом еще одной мини-блокировкой и дополнительно увеличили производительность на 10%. Чтобы увидеть некоторые из этих улучшений в действии, давайте рассмотрим функцию addPositive и часть ее перевода.

function addPositive($arr) {
      $n = count($arr);
      $sum = 0;
      for ($i = 0; $i < $n; $i++) {
        $elem = $arr[$i];
        if ($elem > 0) {
          $sum = $sum + $elem;
        }
      }
      return $sum;
    }

Эта функция выглядит как большой PHP-код: она перебирает массив и что-то делает с каждым элементом. Давайте сосредоточимся на строках 5 и 6, а также на их байт-коде:

    $elem = $arr[$i];
    if ($elem > 0) {
  // line 5
   85: CGetM <L:0 EL:3>
   98: SetL 4
  100: PopC
  // line 6
  101: Int 0
  110: CGetL2 4
  112: Gt
  113: JmpZ 13 (126)

Эти две строки загружают элемент из массива, сохраняют его в локальной переменной, затем сравнивают значение этого локального с 0 и условно переходят куда-то на основе результата. Если вас интересует более подробная информация о том, что происходит в байт-коде, вы можете просмотреть bytecode.specification. JIT, как сейчас, так и в дни TranslatorX64, разбивает этот код на две трассировки: одну только с CGetM, затем другую с остальными инструкциями (полное объяснение того, почему это происходит, здесь не актуально, но главным образом потому, что мы не знаем во время компиляции, какой будет тип элемента массива). Перевод CGetM сводится к вызову вспомогательной функции C ++ и не очень интересен, поэтому мы рассмотрим второй трейлер. Этот коммит был официальной отставкой TranslatorX64,

  cmpl  $0xa, 0xc(%rbx)
  jnz 0x276004b2
  cmpl  $0xc, -0x44(%rbp)
  jnle 0x276004b2
101: SetL 4
103: PopC
  movq  (%rbx), %rax
  movq  -0x50(%rbp), %r13
104: Int 0
  xor %ecx, %ecx
113: CGetL2 4
  mov %rax, %rdx
  movl  $0xa, -0x44(%rbp)
  movq  %rax, -0x50(%rbp)
  add $0x10, %rbx    
  cmp %rcx, %rdx    
115: Gt
116: JmpZ 13 (129)
  jle 0x7608200

Первые четыре строки - это проверки типов, подтверждающие, что значение в $ elem и значение в верхней части стека соответствуют ожидаемым типам. Если какой-либо из них завершится неудачно, мы перейдем к коду, который запускает ретрансляцию tracelet, используя новые типы для генерации по-разному специализированного фрагмента машинного кода. Суть перевода следует, и в коде есть много возможностей для улучшения. В строке 8 есть мертвая нагрузка, легко регистрируемый регистр для перемещения в строке 12 и возможность постоянного распространения между строками 10 и 16. Все это является следствием подхода байт-кода за раз, используемого TranslatorX64. Ни один уважаемый компилятор никогда не будет генерировать такой код, но простые оптимизации, необходимые для его избежания, просто не вписываются в модель TranslatorX64.

Теперь давайте посмотрим на тот же самый тралет, переведенный с помощью hhir, с той же ревизией hhvm:

  cmpl  $0xa, 0xc(%rbx)
  jnz 0x276004bf
  cmpl  $0xc, -0x44(%rbp)
  jnle 0x276004bf
101: SetL 4
  movq  (%rbx), %rcx
  movl  $0xa, -0x44(%rbp)
  movq  %rcx, -0x50(%rbp)
115: Gt    
116: JmpZ 13 (129)
  add $0x10, %rbx
  cmp $0x0, %rcx    
  jle 0x76081c0

Он начинается с тех же проверок типов, но текст перевода состоит из 6 инструкций, что значительно лучше, чем 9 из TranslatorX64. Обратите внимание, что нет мертвых нагрузок или регистров для регистрации ходов, и немедленный 0 из байтового кода Int 0 был передан до cmp в строке 12. Вот hhir, который был сгенерирован между тралетом и этим переводом:

  (00) DefLabel    
  (02) t1:FramePtr = DefFP
  (03) t2:StkPtr = DefSP<6> t1:FramePtr
  (05) t3:StkPtr = GuardStk<Int,0> t2:StkPtr
  (06) GuardLoc<Uncounted,4> t1:FramePtr
  (11) t4:Int = LdStack<Int,0> t3:StkPtr
  (13) StLoc<4> t1:FramePtr, t4:Int
  (27) t10:StkPtr = SpillStack t3:StkPtr, 1
  (35) SyncABIRegs t1:FramePtr, t10:StkPtr
  (36) ReqBindJmpLte<129,121> t4:Int, 0

Инструкции байт-кода были разбиты на более мелкие и простые операции. Многие операции, скрытые в поведении определенных байт-кодов, явно представлены в hhir, например, LdStack в строке 6, которая является частью SetL. Используя неназванные временные данные (t1, t2 и т. Д.) Вместо физических регистров для представления потока значений, мы можем легко отслеживать определение и использование каждого значения. Это делает тривиальным, чтобы увидеть, действительно ли используется назначение нагрузки, или действительно ли один из входов инструкции является постоянным значением от 3 байт-кодов назад. Для более подробного объяснения, что такое hhir и как оно работает, взгляните на ir.specification.

Этот пример продемонстрировал лишь некоторые улучшения, внесенные в TranslatorX64. Внедрение hhir в производство и выход на пенсию TranslatorX64 в мае 2013 года было большой вехой, но это было только начало. С тех пор мы внедрили намного больше оптимизаций, которые были бы почти невозможны в TranslatorX64, что сделало hhvm почти вдвое более эффективным в этом процессе. В наших усилиях также важно, чтобы hhvm работал на процессорах ARM, изолируя и уменьшая объем специфичного для архитектуры кода, который нам нужно переопределить. Следите за предстоящим постом, посвященным нашему порту ARM, чтобы узнать больше! "

Пол У
источник
1

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

В соответствии с HHVM Performance Status они оптимизировали наиболее часто используемые типы данных - строки и массивы, чтобы минимизировать произвольный доступ к памяти. Идея состоит в том, чтобы части данных, используемые вместе (например, элементы в массиве), были как можно ближе друг к другу в памяти, в идеале линейным образом. Таким образом, если данные помещаются в кэш-память ЦП L2 / L3, они могут обрабатываться на порядки быстрее, чем если бы они были в ОЗУ.

Другая упомянутая методика - это компиляция наиболее часто используемых путей в коде таким образом, чтобы скомпилированная версия была настолько линейной (например, имеет наименьшее количество «скачков»), насколько это возможно, и загружала данные в / из памяти как можно реже.

scriptin
источник