เครื่องฮิปฮอปเสมือนจริง (HHVM) จะปรับปรุงประสิทธิภาพรันไทม์ของ PHP ในทางทฤษฎีได้อย่างไร


9

ในระดับสูง Facebook และอื่น ๆ ใช้เพื่อปรับปรุงประสิทธิภาพ PHP ด้วย Hip Hop Virtual Machine หรือไม่?

มันแตกต่างจากการรันโค้ดโดยใช้เอ็นจิ้น zend อย่างไร เป็นเพราะประเภทมีการกำหนดทางเลือกกับแฮ็คที่อนุญาตให้ใช้เทคนิคการเพิ่มประสิทธิภาพล่วงหน้า?

curiousity ของฉันเกิดขึ้นหลังจากที่ได้อ่านบทความนี้ยอมรับ HHVM

คำตอบ:


7

พวกเขาแทนที่ผู้ติดตามของ TranslatorX64 ด้วยการเป็นตัวแทน HipHop Intermediate (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% และดูดีขึ้นอย่างมากเมื่อตรวจสอบด้วยตนเอง เราติดตามการเปิดตัวการผลิตด้วย mini-lockdown อีกครั้งและรับเพิ่ม 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 คุณสามารถอ่านผ่าน 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 และค่าที่ด้านบนของสแต็กเป็นประเภทที่เราคาดหวัง หากสิ่งใดสิ่งหนึ่งล้มเหลวเราจะข้ามไปยังรหัสที่ทำให้เกิดการส่งสัญญาณการสืบค้นกลับโดยใช้ประเภทใหม่เพื่อสร้างรหัสเฉพาะของเครื่องที่แตกต่างกัน เนื้อของการแปลตามมาและรหัสมีพื้นที่มากมายสำหรับการปรับปรุง มีการโหลดที่ตายแล้วในบรรทัดที่ 8 การลงทะเบียนที่หลีกเลี่ยงได้อย่างง่ายดายเพื่อลงทะเบียนการย้ายที่บรรทัดที่ 12 และโอกาสสำหรับการแพร่กระจายอย่างต่อเนื่องระหว่างบรรทัดที่ 10 และ 16 สิ่งเหล่านี้เป็นผลสืบเนื่องมาจากวิธี bytecode ไม่มีคอมไพเลอร์ที่มีหน้ามีตาจะปล่อยโค้ดเช่นนี้ แต่การเพิ่มประสิทธิภาพอย่างง่ายที่จำเป็นในการหลีกเลี่ยงมันก็ไม่เหมาะกับรุ่น TranslatorX64

ตอนนี้เราจะเห็น tracelet เดียวกันที่แปลโดยใช้ 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 ทันทีจากไบต์ 0 Inttecode ถูกแพร่กระจายลงไปที่ cmp ในบรรทัดที่ 12 นี่คือ hhir ที่ถูกสร้างขึ้นระหว่าง tracelet และการแปลที่:

  (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

คำสั่ง bytecode ถูกแบ่งย่อยออกเป็นการดำเนินงานที่เล็กและง่ายขึ้น การดำเนินการหลายอย่างที่ซ่อนอยู่ในพฤติกรรมของบางไบต์จะแสดงอย่างชัดเจนใน hhir เช่น LdStack ในบรรทัดที่ 6 ซึ่งเป็นส่วนหนึ่งของ SetL โดยการใช้ชื่อชั่วคราว (t1, t2, ฯลฯ ... ) แทนการลงทะเบียนทางกายภาพเพื่อเป็นตัวแทนของการไหลของค่าเราสามารถติดตามความหมายและการใช้งานของแต่ละค่า สิ่งนี้ทำให้ดูเล็กน้อยว่าปลายทางของการโหลดนั้นถูกใช้จริงหรือถ้าหนึ่งในอินพุตของคำสั่งนั้นเป็นค่าคงที่จาก 3 ไบต์ที่ผ่านมา สำหรับคำอธิบายที่ละเอียดยิ่งขึ้นว่า hhir คืออะไรและทำงานอย่างไรให้ดูที่ ir.specification

ตัวอย่างนี้แสดงการปรับปรุงเพียงเล็กน้อยที่ทำผ่าน TranslatorX64 การเริ่มใช้งาน hhir กับการผลิตและการเลิกจ้างนักแปล TranslateX64 ในเดือนพฤษภาคม 2556 ถือเป็นความสำเร็จครั้งยิ่งใหญ่ที่เกิดขึ้น แต่มันเป็นเพียงการเริ่มต้น ตั้งแต่นั้นมาเราได้ดำเนินการเพิ่มประสิทธิภาพอื่น ๆ อีกมากมายซึ่งเกือบเป็นไปไม่ได้ใน TranslatorX64 ทำให้ hhvm มีประสิทธิภาพเกือบสองเท่าในกระบวนการ สิ่งสำคัญในความพยายามของเราในการทำให้ hhvm ทำงานบนโปรเซสเซอร์ ARM โดยการแยกและลดจำนวนรหัสเฉพาะสถาปัตยกรรมที่เราจำเป็นต้องนำมาใช้ใหม่ ดูการโพสต์ที่กำลังจะมาถึงที่ทุ่มเทเพื่อพอร์ต ARM ของเราสำหรับรายละเอียดเพิ่มเติม!


1

กล่าวโดยย่อ: พวกเขาพยายามลดการเข้าถึงหน่วยความจำแบบสุ่มและข้ามไปมาระหว่างส่วนต่าง ๆ ของรหัสในหน่วยความจำเพื่อเล่นกับแคชแคช

ตามสถานะประสิทธิภาพ HHVMพวกเขาปรับประเภทข้อมูลที่ใช้บ่อยที่สุดซึ่งเป็นสตริงและอาร์เรย์เพื่อลดการเข้าถึงหน่วยความจำแบบสุ่ม แนวคิดคือการเก็บชิ้นส่วนข้อมูลไว้ด้วยกัน (เช่นไอเท็มในอาร์เรย์) ใกล้กันมากที่สุดเท่าที่จะทำได้ในหน่วยความจำ ด้วยวิธีนี้ถ้าข้อมูลพอดีกับ CPU L2 / L3 แคชมันสามารถประมวลผลคำสั่งขนาดเร็วกว่าถ้ามันอยู่ใน RAM

อีกเทคนิคที่กล่าวถึงคือการคอมไพล์พา ธ ที่ใช้บ่อยที่สุดในโค้ดในลักษณะที่คอมไพล์เป็นแบบเชิงเส้น (ei มี "กระโดด" น้อยที่สุด) เท่าที่จะทำได้และโหลดข้อมูลเข้า / ออกจากหน่วยความจำน้อยที่สุด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.