힙합 가상 머신 (HHVM)은 이론적으로 PHP 런타임 성능을 어떻게 개선합니까?


9

높은 수준에서 Facebook 등은 어떻습니까? 힙합 가상 머신으로 PHP 성능을 향상시키는 데 사용합니까?

기존 zend 엔진을 사용하여 코드를 실행하는 것과 어떻게 다릅니 까? 사전 최적화 기술을 허용하는 해킹으로 유형이 선택적으로 정의 되었기 때문입니까?

이 기사를 읽은 후 HHVM 채택에 대한 호기심이 생겼습니다 .

답변:


7

그들은 TranslatorX64의 tracelet을 새로운 HipHop Intermediate Representation (hhir)과 hhir을 생성하기위한 로직이 상주하는 새로운 간접 레이어로 대체했습니다.

높은 수준에서, 여기에 언급 된 것처럼 6 개의 명령어를 사용하여 이전에 필요한 9 개의 명령어를 수행하고 있습니다.

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을 통해 훑어 볼 수 있습니다. JL은 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의 값과 스택의 맨 위에있는 값이 예상되는 유형인지 확인합니다. 둘 중 하나라도 실패하면 새로운 유형을 사용하여 다르게 특수화 된 기계어 코드 덩어리를 생성하는 트레이스 릿의 재번역을 트리거하는 코드로 넘어갑니다. 번역의 핵심은 다음과 같으며 코드에는 개선의 여지가 충분합니다. 8 행에는 불로드가 발생하고 12 행에서는 이동을 등록하기 위해 쉽게 피할 수있는 레지스터와 10 행과 16 행 사이의 지속적인 전파 기회가 있습니다.이 모든 것은 TranslatorX64에서 사용하는 한 번에 바이트 코드 접근 방식의 결과입니다. 어떤 컴파일러도 이와 같은 코드를 생성하지는 않지만이를 피하기 위해 필요한 간단한 최적화는 TranslatorX64 모델에 맞지 않습니다.

이제 동일한 hhvm 개정판에서 hhir을 사용하여 번역 된 동일한 추적을 보도록하겠습니다.

  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 개의 명령으로, TranslatorX64의 9보다 훨씬 뛰어납니다. 움직임을 등록하기위한 데드로드 나 레지스터가 없으며 Int 0 바이트 코드에서 즉시 0이 12 행의 cmp로 전파되었습니다. 다음은 tracelet과 해당 변환 사이에 생성 된 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

바이트 코드 명령어는 더 작고 간단한 작업으로 분류되었습니다. SetL의 일부인 6 행의 LdStack과 같이 특정 바이트 코드의 동작에 숨겨진 많은 작업이 hhir로 명시 적으로 표시됩니다. 값의 흐름을 나타 내기 위해 물리적 레지스터 대신 이름이없는 임시 (t1, t2 등)를 사용하면 각 값의 정의 및 사용을 쉽게 추적 할 수 있습니다. 따라서로드 대상이 실제로 사용되는지 또는 명령어 입력 중 하나가 실제로 3 바이트 코드의 상수 값인지 확인하는 것이 쉽지 않습니다. hhir의 정의와 작동 방식에 대한 자세한 내용은 ir. 사양을 살펴보십시오.

이 예제는 TranslatorX64에 비해 몇 가지 개선 사항을 보여줍니다. 2013 년 5 월에 Hhir을 배포하고 TranslatorX64를 폐기하는 것은 큰 이정표이지만 시작에 불과했습니다. 그 이후로 TranslatorX64에서는 거의 불가능한 최적화를 구현하여 hhvm을 프로세스에서 거의 두 배나 효율적으로 만들었습니다. 또한 구현해야하는 아키텍처 별 코드의 양을 격리하고 줄임으로써 hhvm이 ARM 프로세서에서 실행되도록하는 노력에 매우 중요했습니다. "자세한 내용은 ARM 포트 전용 게시물을 확인하십시오!"


1

한마디로, 그들은 임의의 메모리 액세스를 최소화하려고 시도하고 CPU 캐시를 훌륭하게 재생하기 위해 메모리의 코드 조각 사이를 뛰어 넘습니다.

HHVM 성능 상태 에 따르면 이들은 가장 자주 사용되는 데이터 유형 (문자열 및 배열)을 최적화하여 임의 메모리 액세스를 최소화했습니다. 아이디어는 데이터 조각 (예 : 배열의 항목)을 가능한 한 선형 적으로 메모리에서 서로 가깝게 유지하는 것입니다. 이렇게하면 데이터가 CPU L2 / L3 캐시에 맞으면 RAM에있는 것보다 훨씬 빠르게 처리 할 수 ​​있습니다.

언급 된 다른 기술은 컴파일 된 버전이 가능한 한 선형 적 (최소한 "점프"를 가짐)하고 가능한 한 드물게 메모리에 메모리 내외부로 데이터를로드하는 방식으로 코드에서 가장 자주 사용되는 경로를 컴파일하는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.