Làm thế nào để máy ảo Hip hop (HHVM) về mặt lý thuyết cải thiện hiệu năng thời gian chạy PHP?


9

Từ cấp độ cao, Facebook, et. al sử dụng để cải thiện hiệu suất PHP với Máy ảo Hip Hop?

Nó khác với việc thực thi mã bằng cách sử dụng công cụ zend truyền thống như thế nào? Có phải vì các loại được xác định tùy chọn với hack cho phép các kỹ thuật tối ưu hóa trước?

Sự tò mò của tôi trỗi dậy sau khi đọc bài viết này, áp dụng HHVM .

Câu trả lời:


7

Họ đã thay thế bộ ba của TranslatorX64 bằng Đại diện trung gian HipHop mới (hhir) và một lớp cảm ứng mới trong đó nằm trong logic để tạo ra hhir, mà thực sự được gọi bằng cùng tên, hhir.

Từ cấp độ cao, nó đang sử dụng 6 hướng dẫn để thực hiện 9 hướng dẫn cần thiết trước đó, như đã lưu ý ở đây: "Nó bắt đầu với cùng một lỗi đánh máy nhưng cơ thể của bản dịch là 6 hướng dẫn, tốt hơn đáng kể so với 9 từ TranslatorX64"

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

Đó chủ yếu là một tạo tác về cách hệ thống được thiết kế và là thứ chúng tôi dự định cuối cùng sẽ dọn sạch. Tất cả các mã còn lại trong TranslatorX64 là máy móc cần thiết để phát mã và liên kết các bản dịch với nhau; mã đã hiểu làm thế nào để dịch mã byte riêng lẻ đi từ TranslatorX64.

Khi hhir thay thế TranslatorX64, nó đã tạo ra mã nhanh hơn khoảng 5% và trông tốt hơn đáng kể khi kiểm tra thủ công. Chúng tôi đã tiếp tục ra mắt sản phẩm của mình với một khóa nhỏ khác và có thêm 10% tăng hiệu suất trên đó. Để xem một số cải tiến này đang hoạt động, chúng ta hãy xem một hàm addPositive và một phần trong bản dịch của nó.

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;
    }

Hàm này trông giống như rất nhiều mã PHP: nó lặp trên một mảng và làm một cái gì đó với mỗi phần tử. Bây giờ chúng ta hãy tập trung vào các dòng 5 và 6, cùng với mã byte của chúng:

    $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)

Hai dòng này tải một phần tử từ một mảng, lưu trữ nó trong một biến cục bộ, sau đó so sánh giá trị của cục bộ đó với 0 và có điều kiện nhảy ở đâu đó dựa trên kết quả. Nếu bạn quan tâm đến chi tiết hơn về những gì đang diễn ra trong mã byte, bạn có thể lướt qua bytecode.specification. JIT, cả bây giờ và trở lại trong TranslatorX64 ngày, chia mã này thành hai phần ba: một chỉ với CGetM, sau đó một phần khác với các hướng dẫn còn lại (một lời giải thích đầy đủ về lý do tại sao điều này xảy ra không liên quan ở đây, nhưng nó chủ yếu là vì chúng ta không biết tại thời điểm biên dịch loại phần tử mảng sẽ là gì). Bản dịch của CGetM thực hiện một cuộc gọi đến chức năng của trình trợ giúp C ++ và không thú vị lắm, vì vậy chúng ta sẽ xem xét các mục thứ hai. Cam kết này là nghỉ hưu chính thức của 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

Bốn dòng đầu tiên là các lỗi đánh máy xác minh rằng giá trị trong $ elem và giá trị trên đỉnh của ngăn xếp là loại chúng tôi mong đợi. Nếu một trong số chúng không thành công, chúng tôi sẽ chuyển sang mã kích hoạt việc truyền lại bộ ba, sử dụng các loại mới để tạo ra một đoạn mã máy chuyên dụng khác nhau. Phần cốt lõi của bản dịch theo sau, và mã có nhiều chỗ để cải thiện. Có một tải chết trên dòng 8, một thanh ghi có thể tránh được dễ dàng để đăng ký di chuyển trên dòng 12 và cơ hội lan truyền liên tục giữa các dòng 10 và 16. Đây là tất cả các hậu quả của cách tiếp cận bytecode được sử dụng bởi TranslatorX64. Không có trình biên dịch đáng kính nào sẽ phát ra mã như thế này, nhưng các tối ưu hóa đơn giản cần có để tránh nó chỉ không phù hợp với mô hình TranslatorX64.

Bây giờ chúng ta hãy xem cùng một bản dịch được sử dụng hhir, tại cùng bản sửa đổi 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

Nó bắt đầu với cùng một kiểu đánh máy nhưng phần thân của bản dịch là 6 hướng dẫn, tốt hơn đáng kể so với 9 từ TranslatorX64. Lưu ý rằng không có tải chết hoặc đăng ký để đăng ký di chuyển và 0 ngay lập tức từ mã byte Int 0 đã được truyền xuống cmp trên dòng 12. Đây là hhir được tạo ra giữa tr trid và bản dịch đó:

  (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

Các hướng dẫn mã byte đã được chia thành các hoạt động nhỏ hơn, đơn giản hơn. Nhiều hoạt động ẩn trong hành vi của một số mã byte nhất định được thể hiện rõ ràng trong hhir, chẳng hạn như LdStack trên dòng 6, một phần của SetL. Bằng cách sử dụng các thời gian không tên (t1, t2, v.v.) thay vì các thanh ghi vật lý để thể hiện luồng giá trị, chúng ta có thể dễ dàng theo dõi định nghĩa và cách sử dụng của từng giá trị. Điều này làm cho nó trở nên tầm thường để xem liệu đích đến của tải có thực sự được sử dụng hay không, nếu một trong những đầu vào của một lệnh thực sự là một giá trị không đổi từ 3 byte trước đây. Để được giải thích kỹ lưỡng hơn nhiều về hhir là gì và cách thức hoạt động của nó, hãy xem ir.specifying.

Ví dụ này chỉ ra một vài trong số những cải tiến mà hhir đã thực hiện trên TranslatorX64. Bắt hhir triển khai để sản xuất và nghỉ hưu TranslatorX64 vào tháng 5 năm 2013 là một cột mốc lớn để đạt được, nhưng đó mới chỉ là khởi đầu. Kể từ đó, chúng tôi đã triển khai nhiều tối ưu hóa gần như không thể có trong TranslatorX64, giúp hhvm hiệu quả gần gấp đôi trong quy trình. Chúng tôi cũng rất nỗ lực để có được hhvm chạy trên bộ xử lý ARM bằng cách cô lập và giảm số lượng mã cụ thể theo kiến ​​trúc mà chúng tôi cần thực hiện lại. Theo dõi bài viết sắp tới dành cho cổng ARM của chúng tôi để biết thêm chi tiết! "


1

Nói tóm lại: họ cố gắng giảm thiểu truy cập bộ nhớ ngẫu nhiên và nhảy giữa các đoạn mã trong bộ nhớ để chơi độc đáo với bộ đệm CPU.

Theo Trạng thái hiệu suất HHVM, họ đã tối ưu hóa các loại dữ liệu được sử dụng thường xuyên nhất, đó là các chuỗi và mảng, để giảm thiểu truy cập bộ nhớ ngẫu nhiên. Ý tưởng là giữ cho các phần dữ liệu được sử dụng cùng nhau (như các mục trong một mảng) càng gần nhau càng tốt trong bộ nhớ, lý tưởng nhất là theo kiểu tuyến tính. Theo cách đó, nếu dữ liệu phù hợp với bộ đệm CPU L2 / L3, nó có thể được xử lý các đơn đặt hàng có cường độ nhanh hơn so với khi nó nằm trong RAM.

Một kỹ thuật khác được đề cập là biên dịch các đường dẫn được sử dụng thường xuyên nhất trong mã theo cách mà phiên bản được biên dịch là tuyến tính (ei có số lần "nhảy" ít nhất có thể) và tải dữ liệu vào / ra khỏi bộ nhớ càng ít càng tốt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.