답변:
그들은 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 포트 전용 게시물을 확인하십시오!"
한마디로, 그들은 임의의 메모리 액세스를 최소화하려고 시도하고 CPU 캐시를 훌륭하게 재생하기 위해 메모리의 코드 조각 사이를 뛰어 넘습니다.
HHVM 성능 상태 에 따르면 이들은 가장 자주 사용되는 데이터 유형 (문자열 및 배열)을 최적화하여 임의 메모리 액세스를 최소화했습니다. 아이디어는 데이터 조각 (예 : 배열의 항목)을 가능한 한 선형 적으로 메모리에서 서로 가깝게 유지하는 것입니다. 이렇게하면 데이터가 CPU L2 / L3 캐시에 맞으면 RAM에있는 것보다 훨씬 빠르게 처리 할 수 있습니다.
언급 된 다른 기술은 컴파일 된 버전이 가능한 한 선형 적 (최소한 "점프"를 가짐)하고 가능한 한 드물게 메모리에 메모리 내외부로 데이터를로드하는 방식으로 코드에서 가장 자주 사용되는 경로를 컴파일하는 것입니다.