1 คะแนน โดย GN⁺ 2024-12-19 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • gem json มาตรฐานของ Ruby ถูกปรับปรุงโดยกำจัดคอขวดผ่านการทำ profiling เพื่อลด แรงกดดันในการใช้งานจริง ที่ต้องเปลี่ยนไปใช้ oj เพราะเรื่องความเร็ว
  • เป้าหมายไม่ใช่การเอาชนะ oj แบบเด็ดขาด แต่คือการมอบการประมวลผล JSON ที่เร็วพอและคาดเดาได้ โดยไม่ต้องพึ่ง การ monkey patch อย่าง Oj.mimic_JSON และ Oj.optimize_rails
  • oj เร็วกว่าในบาง benchmark แต่ก็สร้างภาระด้านเสถียรภาพในการใช้งานจริงและความเข้ากันได้ของ API จากปัญหาอย่างการเพิกเฉยต่อออปชัน script_safe, ความต่างของการ serialize ใน Rails และการทำให้ Ruby crash
  • การปรับแต่งหลักประกอบด้วยการลบการตรวจ UTF-8 ซ้ำซ้อน, ตรวจเงื่อนไขที่พบบ่อยก่อน, ลดต้นทุนการตั้งค่า generator, หลีกเลี่ยงการไล่ตาม pointer ของ encoding และการตรวจ escape โดยอิง lookup table
  • ใน benchmark การ generate twitter.json ขนาด 467KiB การเปลี่ยนแต่ละจุดให้ผลดีขึ้น 3%, 8%, 15%, 30% และการ generate Hash ขนาดเล็กเร็วขึ้น 1.51 เท่า เพียงจากการลดต้นทุนการตั้งค่า

เบื้องหลังที่ทำให้ json gem เร็วขึ้น

  • หลังจากได้เป็น maintainer ของ json gem เมื่อไม่นานนี้ ผู้เขียนได้ทั้งแก้บั๊กเก่าและโฟกัสกับการเพิ่มประสิทธิภาพ จนผลลัพธ์คือมันกลายเป็น parser และ generator JSON ที่เร็วที่สุด สำหรับ Ruby ใน benchmark ส่วนใหญ่
  • patch ด้านประสิทธิภาพส่วนใหญ่ไม่ได้มาจากเคล็ดลับพิเศษ แต่ใกล้เคียงกับการใช้ profiling หา bottleneck แล้วลดความสูญเปล่าแบบตรงไปตรงมา
  • แรงจูงใจหลักคือทำให้ ruby/json เร็วพอจนผู้ใช้ไม่จำเป็นต้องเลือก gem อื่นเพียงเพราะเรื่องความเร็ว

ภาระที่เกิดจากการใช้ oj แทน

  • ความต่างระหว่าง json 2.7.2 กับ oj ใน benchmark บางแบบที่ใกล้เคียงการใช้งานจริงไม่ได้มากนัก
    • การ parse เอกสาร JSON ขนาด 467KiB ที่มีทวีต 100 รายการ json 2.7.2 ใช้ 1.9ms ส่วน oj ใช้ 1.6ms
    • การ generate เอกสารเดียวกัน json 2.7.2 ใช้ 0.8ms ส่วน oj ใช้ 0.4ms
  • ในหลายกรณี ส่วนที่ช้าไม่ใช่การ serialize JSON เอง แต่เป็น ชั้นที่อยู่เหนือกว่า ที่แปลงโมเดล Active Record ให้เป็น Ruby Hash และ Array
  • oj ถูกใช้ในหลายโปรเจกต์รวมถึง codebase ของ Shopify และความนิยมนี้น่าจะมาจากเรื่องความเร็วเป็นหลัก
  • ความไม่ตรงกันของ API จากการ monkey patch

    • Oj.mimic_JSON มักถูกใช้เพื่อ monkey patch json gem และ Oj.optimize_rails เพื่อ monkey patch ActiveSupport::JSON
    • JSON.dump(data, script_safe: true) สามารถ escape </script> เป็น <\/script> เพื่อให้ใส่ JSON ภายในแท็ก <script> ได้อย่างปลอดภัย
    • oj ไม่รู้จักออปชัน script_safe และจึงเพิกเฉยมัน ทำให้ gem ที่เดิมปลอดภัยเมื่อใช้ลำพัง อาจเปิดช่องให้เกิด การโจมตีแบบ XSS ได้เมื่ออยู่ในแอปพลิเคชันที่เรียก Oj.mimic_JSON
    • Oj.optimize_rails ก็อาจทำให้เกิดความต่างเล็กน้อยในการ serialize object ได้เช่นกัน
    • เมื่อ ActiveSupport::JSON::Encoding.time_precision = 0 ค่า ActiveSupport::JSON.encode(t) สามารถสร้างสตริงระดับวินาทีได้
    • แต่หลังจาก Oj.optimize_rails และ Oj.mimic_JSON อาจมีกรณีที่ได้สตริงที่รวมมิลลิวินาทีออกมาด้วย
    • กรณีนี้เป็น corner case ที่เกิดจากลำดับการโหลด แต่ในอดีตเคยมีพฤติกรรมที่เปลี่ยนไปมากกว่านี้อีก
  • ปัญหาด้านเสถียรภาพในสภาพแวดล้อม production

    • ในสภาพแวดล้อมขนาดใหญ่ oj เป็นหนึ่งในสาเหตุเด่นของการ crash ของ Ruby และมีปัญหารองจาก grpc
    • การเขียน native gem ต้องเข้าใจ Ruby VM และโดยเฉพาะ GC ไม่เช่นนั้นอาจเกิด crash หรือ memory corruption ได้
    • ใน codebase ของ oj มี hack ที่ทำให้เชื่อถือได้ยาก และครั้งหนึ่งเคยปิด GC ในบางสถานการณ์เพื่อหลบบั๊ก
    • เมื่อเปิด GC กลับมาอีกครั้ง อาจกระตุ้นให้เกิด major GC cycle ได้
    • โค้ดลักษณะนี้อาจดีต่อ microbenchmark แต่กลับทำให้ประสิทธิภาพจริงใน production แย่ลง
    • จากประสบการณ์นี้ Shopify จึงถอด Oj ออกจาก monolith ของตน และในกระบวนการนั้นก็พบความต่างเล็กน้อยระหว่าง Oj.mimic_JSON กับ json จริง

หา bottleneck ด้วย benchmark และ profiling

  • เป้าหมายคือทำให้ ruby/json ทำงานใกล้เคียง oj ทั้งในการใช้งานจริงและ microbenchmark เพื่อลดแรงจูงใจในการใช้ Oj.mimic_JSON เพราะเรื่องความเร็ว
  • ขั้นแรกคือการจัดทำ benchmark suite
  • สำหรับ C profiler ใช้ samply
    • ข้อดีคือสามารถส่งออกรายงานที่เข้ากันได้กับ Firefox Profiler จึงแชร์ได้ง่าย

ลบการตรวจ UTF-8 ซ้ำซ้อน

  • เมื่อทำ profiling JSON.dump ด้วย payload twitter.json พบว่าในตัว JSON เองใช้เวลา 9% ไปกับ isLegalUTF8 และ 1.9% ไปกับ rb_enc_str_asciionly_p
  • Ruby String มีคุณสมบัติภายในชื่อ coderange ซึ่งจะ cache สถานะ encoding ของสตริงหรือความเป็น ASCII-only หลังจากสแกนหนึ่งครั้ง
    • ENC_CODERANGE_UNKNOWN: ยังไม่เคยสแกน
    • ENC_CODERANGE_VALID: encoding ถูกต้อง
    • ENC_CODERANGE_7BIT: encoding ถูกต้องและมีเฉพาะอักขระ ASCII
    • ENC_CODERANGE_INVALID: encoding ไม่ถูกต้อง
  • เดิม convert_UTF8_to_JSON_ASCII จะเรียก rb_enc_str_asciionly_p ก่อน แล้วค่อยสแกนสตริงด้วยมืออีกครั้งเพื่อตรวจความถูกต้องของ UTF-8 ซึ่งเป็น งานซ้ำซ้อน
  • หลังการเปลี่ยนแปลง ใช้การเปรียบเทียบ coderange ที่คำนวณไว้แล้วเพื่อตัดสินความถูกต้องของ UTF-8
    • ในรูปแบบ Ruby จะมีโครงสร้างประมาณว่า ถ้า string.ascii_only? ไม่เป็นจริง และ string.encoding != Encoding::UTF_8 หรือ !string.valid_encoding? ก็ให้ raise JSON::GeneratorError
    • ทั้ง #ascii_only? และ #valid_encoding? ใช้ coderange ที่ cache ไว้ จึงเกิดการสแกนสตริงได้มากสุดเพียงครั้งเดียว
  • แม้จะคาดหวัง 9% แต่ผลจริงดีขึ้นเพียงประมาณ 3%
    • เวลาที่เดิมอยู่ใน isLegalUTF8 ถูกย้ายไปอยู่ใน convert_UTF8_to_JSON จำนวนมาก
    • ยังไม่ทราบเหตุผลแน่ชัด แต่อาจเป็นไปได้ว่าส่วนใหญ่ของ 9% นั้นเป็นต้นทุนในการดึงไบต์ของสตริงจาก RAM เข้าสู่ CPU cache
    • benchmark การ generate twitter.json เร็วขึ้นจาก 1077.3 i/s เป็น 1113.3 i/s หรือ 1.03x

ตรวจเงื่อนไขที่ถูกกว่าและมีโอกาสเกิดสูงกว่าก่อน

  • fbuffer_inc_capa กินเวลารันไทม์ทั้งหมด 5.7% และเวลาส่วนใหญ่หมดไปกับการตรวจว่ามีการ allocate buffer แล้วหรือยัง
  • ฟังก์ชันนี้ถูกเรียกทุกครั้งที่จะเขียนอะไรลง buffer แต่หลังจากเรียกครั้งแรก buffer จะถูก allocate แล้วเสมอ
  • โครงสร้างเดิมตรวจเงื่อนไขที่แทบไม่เกิดก่อน จึงสิ้นเปลืองโดยไม่จำเป็น และถ้า buffer ยังไม่ถูก allocate ค่า fb->capa ก็จะเป็น 0 อยู่แล้ว จึงซ้ำบางส่วนกับการตรวจ required > fb->capa
  • หลังปรับแล้ว จะตรวจกรณีที่พบบ่อยที่สุดก่อนคือ “ความจุของ buffer เพียงพอ” และใช้ RB_LIKELY กับ RB_UNLIKELY เพื่อบอกใบ้การทำนาย branch ของ CPU
  • ฟังก์ชันถูกทำเครื่องหมายเป็น inline เพื่อลดต้นทุนการเรียก และในกรณีส่วนใหญ่ งานที่ต้องทำเหลือเพียง การลบและการเปรียบเทียบ
  • การเปลี่ยนจุดนี้ทำให้ benchmark การ generate twitter.json ขยับจาก 1068.6 i/s เป็น 1224.7 i/s หรือเร็วขึ้น 1.15 เท่า
  • หลักการเดียวกันนี้ใช้กับโค้ด Ruby ได้เช่นกัน คือให้ตรวจเงื่อนไขที่ถูกที่สุดและมีโอกาสเกิดมากที่สุดก่อน

ลดต้นทุนการตั้งค่าของ JSON generator

  • Yusuke Endoh aka Mame ซึ่งเป็น Ruby committer ก็มีส่วนร่วมในการปรับแต่ง ruby/json และมี PR เก่า ที่รวมการปรับแต่งหลายรายการ
  • การเปลี่ยนหลายอย่างมุ่งไปที่การลด ต้นทุนการตั้งค่า ที่ต้องจ่ายก่อน generate JSON
    • การ parse argument
    • การ allocate generator และโครงสร้างที่เกี่ยวข้อง
    • งานเตรียมการก่อนเข้าสู่การ generate จริง
  • ต้นทุนการตั้งค่านี้ใน ruby/json สูงกว่าการ implement แบบอื่น จึงทำให้ดูแย่ใน microbenchmark
  • JSON.generate รับออปชันอย่าง array_nl, object_nl, indent, space เพื่อใช้สร้าง pretty JSON ได้
  • เดิมมีการคำนวณ separator buffer ล่วงหน้าจากสตริงที่ส่งมา
    • ตัวอย่าง: ",#{opts[:array_nl]}", ",#{opts[:object_nl]}", ":#{opts[:space]}"
    • เจตนาคือจะ append ชิ้นยาวในครั้งเดียว แต่ในทางปฏิบัติงานที่ประหยัดได้มีไม่มาก
    • ส่วนใหญ่แล้วออปชันเหล่านี้ไม่ถูกใช้ ทำให้ต้นทุนการคำนวณล่วงหน้ากลับเด่นกว่า
  • Mame จึงย้อน optimization นี้กลับไปแทบทั้งหมด ทำให้ต้นทุนการตั้งค่าลดลงมาก
    • ใน benchmark ขนาดใหญ่ความต่างไม่มาก
    • แต่ benchmark การ generate Hash ขนาดเล็ก 65 bytes เร็วขึ้นจาก 2,112,189.3 i/s เป็น 3,199,311.0 i/s หรือ 1.51 เท่า

หลีกเลี่ยงการไล่ตาม pointer และเปรียบเทียบ index ของ encoding แทน

  • การปรับแต่งอีกข้อของ Mame คือการตัดการเรียก rb_enc_get ออกหนึ่งครั้ง
  • JSON จำเป็นต้องตรวจบ่อยว่าสตริงเข้ากันได้กับ UTF-8 หรือไม่ และเดิมจะใช้ rb_enc_get(obj) เพื่อเอา rb_encoding * แล้วค่อยเทียบว่าเป็น US-ASCII หรือ UTF-8 หรือไม่
  • rb_enc_get เป็น API ระดับสูงที่ออกแบบมาอย่างเผื่อกรณีต่าง ๆ จึงมีการตรวจชนิดหลายแบบ
    • รองรับ object หลายชนิด เช่น String, Symbol, Regexp, File, Data เป็นต้น
    • มี conditional จำนวนมาก และถ้า CPU branch prediction พลาด ต้นทุนก็อาจสูง
  • ในเชิงแนวคิด Ruby String มี reference ไปยัง encoding แต่ในทางปฏิบัติจะเก็บเป็น encoding index ขนาด 7 บิต ที่เล็กกว่าไว้ใน bitmap ภายในของ String แทน pointer 64 บิต
  • ถ้าต้องการ pointer ของ object encoding เต็ม ๆ จะต้องค้นหา encoding จริงผ่าน global array ภายในของ VM ซึ่งในโค้ดระดับล่างถือเป็นการ ไล่ตาม pointer
    • ถ้าข้อมูลอยู่ใน CPU cache แล้วก็เร็ว แต่ถ้าต้องดึงจาก RAM CPU ก็ต้องรอ
  • json รู้อยู่แล้วว่าเป้าหมายเป็น String และข้อมูลที่ต้องการมีเพียงว่ามันเป็น ASCII หรือ UTF-8 หรือไม่ จึงสามารถใช้ RB_ENCODING_GET เพื่อเทียบ encoding index ได้โดยตรง
  • การเปลี่ยนนี้ทำให้ benchmark การ generate twitter.json สูงขึ้นจาก 1159.6 i/s เป็น 1253.3 i/s หรือ 1.08 เท่า

ใช้ lookup table เพื่อจัดการการ escape สตริงให้เร็วขึ้น

  • การ dump สตริง JSON มีต้นทุนสูง เพราะต้องตรวจทุกอักขระว่าสามารถคัดลอกตรง ๆ ได้หรือจำเป็นต้อง escape
  • วิธีตรงไปตรงมาคือเช็กหลายเงื่อนไขต่อหนึ่งอักขระ
    • ตรวจว่าเป็นอักขระควบคุมแบบ ASCII หรือไม่
    • ตรวจว่าเป็น \n, \r, \t, \f, \b หรือไม่
    • ตรวจว่าเป็น " หรือ \ หรือไม่
  • วิธีแบบ lookup table คือคำนวณการตัดสินนี้ไว้ล่วงหน้าใน static array แล้วสำหรับแต่ละอักขระก็อ่านค่า boolean จาก offset แบบ dynamic แทนการเปรียบเทียบหลายครั้ง
  • แลกกับการใช้หน่วยความจำแบบ static เพิ่มขึ้นเล็กน้อย แต่ลูปจะเร็วขึ้นมาก
  • ภายใต้สมมติฐานว่าสตริงส่วนใหญ่ไม่มีอักขระที่ต้อง escape, Mame ได้เพิ่มเงื่อนไขล่วงหน้าว่าให้ตรวจแบบถูก ๆ ก่อนว่าพอจะเข้า fast path ได้หรือไม่ และถ้าได้ก็ copy ทั้งสตริงลง buffer ในครั้งเดียว
  • patch ของ Mame เป็นโค้ด C จึงซับซ้อนกว่า แต่ใช้รูปแบบเดียวกัน
  • เพียงการเปลี่ยนนี้อย่างเดียวก็ทำให้ benchmark การ generate twitter.json เพิ่มจาก 1258.1 i/s เป็น 1630.2 i/s หรือ 1.30 เท่า

การปรับแต่งที่ตามมา

  • ยังมีการปรับแต่งอีกหลายจุดที่จะพูดถึงในบทความถัดไป
  • ต่อมามีการเผยแพร่ part two แล้ว

1 ความคิดเห็น

 
GN⁺ 2024-12-19
ความคิดเห็นจาก Hacker News
  • ชอบงานของ byroot มากจริง ๆ ไม่ใช่แค่ประเภทของสิ่งที่เขามีส่วนร่วมเท่านั้น แต่ ระดับผลิตภาพ ก็น่าทึ่งเสมอ
    เคยพยายามเข้าไปช่วยงานฝั่ง Ruby core อยู่หลายครั้ง แต่หางานที่เหมาะกับระดับทักษะของตัวเองและน่าจะมีส่วนร่วมในทางบวกได้ไม่เจอ และพอผ่านไปหลายสัปดาห์โดยไม่มีผลงาน แรงจูงใจก็หายไป เพราะการมีบริบทแบบที่เขาแชร์ไว้ในบทความนั้นยากมากจริง ๆ
    ถ้าคนฝั่ง Ruby C เขียนบทความบ่อยขึ้น น่าจะมีคนที่มีความสามารถที่จำเป็นต่อการปรับปรุง Ruby เพิ่มขึ้นอีก คำแนะนำเรื่อง C profiler ก็ดี และทำให้คิดว่าอาจเริ่มจากการหยิบ Ruby gem ที่มีโค้ด C อยู่มาลองปรับ optimization ใหม่ก็ได้

    • ยังมีซีรีส์ยอดเยี่ยมของ Peter Zhu ด้วย: https://blog.peterzhu.ca/ruby-c-ext/
      แม้จะเป็น C extension แต่ก็ช่วยให้เข้าใจแนวคิดบางอย่างได้
    • “ผลิตภาพมหาศาล” ก็จริง แต่เขายังเป็น คนที่ฉลาดอย่างเหลือเชื่อ ด้วย เคยทำงานออฟฟิศเดียวกันที่ Shopify และเป็นคนในระดับที่ดูเหมือนเอื้อมไม่ถึง
  • Part 2 ก็ลงแล้ว: https://byroot.github.io/ruby/json/2024/12/18/optimizing-rub...

  • สิ่งหนึ่งที่ควรพูดถึงคือ jbuilder ซึ่งเป็นวิธีใช้งานพื้นฐานของ Rails ถึงตัว jbuilder เองจะไม่ใช่ส่วน serialization ของ JSON แต่ถ้าพูดถึงสิ่งที่ทำให้การ render JSON ใน Ruby/Rails ช้า มันจะอยู่บนสุดในลิสต์ของผม
    ถ้า render partial จำนวนมากด้วย jbuilder ความเร็วจะช้าจริง ๆ

  • บทความในหัวข้อนี้อ่านตามง่าย และทำให้อยากลอง benchmark และ optimize โค้ด Ruby ของตัวเอง ทั้งบทความและงานที่ทำออกมาดีมาก

  • อาจจะพลาดไปเอง แต่มีที่ไหนบอกไหมว่าเวอร์ชันใหม่ที่ใส่ optimization ทั้งหมดแล้ว ใช้เวลานานเท่าไรในการ parse/encode Twitter JSON dump?

  • บทความยอดเยี่ยมและงานก็ดี ต่อไปยังมีเหตุผลให้ใช้ Oj อยู่ไหม?

    • ผู้เขียนเอง
      Oj มี API ขนาดใหญ่มากที่ gem json พื้นฐานไม่ได้ตั้งใจจะเลียนแบบ เช่น “SAJ” (การ parse สไตล์ SAX), วิธี escape หลายแบบ เป็นต้น
      เป้าหมายของผมมีแค่ทำให้ใน use case ประมาณ 95% ไม่จำเป็นต้องใช้ Oj ดังนั้นสำหรับหลายการใช้งาน Oj ก็ยังมีประโยชน์อยู่
  • หลังบทความนี้ออกมา อยากรู้ว่าเร็วกว่า implementation ตัวนี้ที่ตอนนี้ไม่ได้ maintain แล้วมากแค่ไหน
    https://netflixtechblog.com/fast-json-api-serialization-with...
    ผมเคยคิดว่า implementation แบบ Ruby ล้วน ตัวนี้ค่อนข้างเรียบร้อยดี แต่ไม่เคยใช้ใน production จริง มันถูกทิ้งไว้เฉยมานานแล้ว
    โดยรวมแล้วก็อยากรู้สถานะของ implementation แบบ Ruby ล้วนต่าง ๆ ด้วย ดูเหมือนว่า json_pure ถูกเอาออกไปแล้ว ถ้าอย่างนั้นก็น่าเสียดาย มีใครรู้รายละเอียดไหม? ส่วนที่น่าสนใจที่สุดในบทความสำหรับผมคือฝั่ง optimization ของ Ruby มากกว่า C

  • อ่านสนุกดี แต่สำหรับ optimization ที่ไม่ได้เฉพาะเจาะจงกับ Ruby เช่น lookup table สำหรับอักขระ escape ก็สงสัยว่าทำไมไม่ใช้ library ที่มีอยู่แล้วอย่าง simdjson ซึ่งทำเรื่องแบบนั้นอยู่แล้ว

    • ตอบไปบ้างแล้วที่ https://news.ycombinator.com/item?id=42450085
      สั้น ๆ คือ ruby/json ถูกแจกมาพร้อมกับ Ruby จึงต้องเข้ากับข้อจำกัดของ Ruby และตอนนี้หมายถึง C99 ล้วนและไม่มี C++ ส่วนไลเซนส์ Apache 2 ของ simdjson ก็อาจเป็นปัญหาได้ แต่ไม่แน่ใจ
      โดยรวมแล้วอยากใช้ C++ library ดี ๆ อย่าง dragonbox แต่ทำไม่ได้
      อีกอย่าง ตอนตรวจล่าสุด simdjson มีแค่ parser เท่านั้น ส่วน gem ruby/json ทำทั้ง parsing และ encoding จึงช่วยได้แค่ครึ่งหนึ่งของพื้นที่ปัญหา
    • สิ่งที่ดีในบทความนี้คือมันเป็น งานวิศวกรรมจริง กับ codebase ที่มีอยู่ ไม่ได้พยายามแทนที่อะไรทั้งก้อนหรือเปลี่ยน library เพียงเพื่อให้เร็วขึ้นอีกนิด แต่ลงไปขุดโค้ดจริงและพยายามปรับปรุงจริง ๆ ไม่ใช่แค่ความเร็ว แต่รวมถึงประสิทธิภาพด้วย
      งานแบบนี้เกิดขึ้นไม่พอเลยในโปรเจกต์สมัยใหม่ ถ้าทำเรื่องแบบนี้กันเป็นประจำมากขึ้น ก็สงสัยว่าแต่แรกเราจะยังต้องมี library อย่าง simdjson หรือ oj ไหม พื้นที่ปัญหานี้ไม่ได้ยากถึงขนาดนั้น
  • Ruby JSON ใช้ intrinsics ไหม? ใช้ได้ไหม?
    แล้วมันทำงานร่วมกับ JIT ต่าง ๆ อย่างไร?

    • ไม่แน่ใจว่า intrinsics หมายถึงอะไรแน่
      gem json เขียนด้วย C ดังนั้นสำหรับ YJIT หรือ JIT ของ reference implementation มันเป็น กล่องดำ
      TruffleRuby JIT เคยสามารถตีความ C extension ด้วย sulong แล้ว JIT ข้ามขอบเขตภาษาได้ แต่เท่าที่รู้ ช่วงหลังเลิกใช้วิธีนั้นเพราะปัญหาความเข้ากันได้หลายอย่าง
      นอกจากนี้ใน TruffleRuby ตัว JSON parser เขียนด้วย C แต่ encoder เป็น Ruby ล้วน: https://github.com/ruby/json/blob/e1f6456499d497f33f69ae4c1a...
  • ถ้าจำไม่ผิด branch prediction hint ไม่มีประโยชน์บน CPU สมัยใหม่

    • เคยไม่มีประโยชน์บน CPU สมัยใหม่ แต่กับ CPU บางรุ่นมันกลับมามีประโยชน์อยู่บ้าง https://www.phoronix.com/news/GCC-Clang-Intel-x86-Branch-Hin...
      “ตั้งแต่ microarchitecture Redwood Cove เป็นต้นมา ถ้า predictor ไม่มีข้อมูลที่เก็บไว้สำหรับ branch หนึ่ง และ branch นั้นมี Intel SSE2 branch taken hint หรือก็คือ instruction prefix 3EH เมื่อ codec decode branch นั้น มันจะพลิกการทำนาย branch จาก not-taken เป็น taken จากนั้นจะ flush front-end pipeline และชี้นำ pipeline ให้ดึงเส้นทาง taken
      ...
      hint นี้จะถูกใช้ก็ต่อเมื่อ predictor ไม่มีข้อมูลที่เก็บไว้สำหรับ branch นั้นเท่านั้น เพื่อหลีกเลี่ยง code bloat และการลดลงของ bandwidth ในการ fetch instruction ไม่ควรเพิ่ม hint ให้กับ branch ใน hot code เช่น branch ภายใน loop ที่มีจำนวนรอบสูง เพราะ predictor มีแนวโน้มสูงว่าจะเก็บข้อมูลของ branch นั้นไว้แล้ว ตามอุดมคติควรเพิ่ม hint เฉพาะกับ branch ที่ถูก execute ไม่บ่อยแต่ส่วนใหญ่เป็น taken อย่างไรก็ตาม การระบุ branch แบบนั้นอาจทำได้ยาก แนะนำให้ compiler เพิ่ม hint เป็นส่วนหนึ่งของ profile-guided optimization ในกรณีที่ไม่สามารถจัดวาง execution path ฝั่งหนึ่งให้เป็น fall-through ได้ microarchitecture Redwood Cove เพิ่ม performance monitoring event ใหม่เพื่อใช้เป็นแนวทางในการวาง hint”