การปรับแต่ง JSON ของ Ruby, ตอนที่ 1
(byroot.github.io)- 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 ของ
jsongem เมื่อไม่นานนี้ ผู้เขียนได้ทั้งแก้บั๊กเก่าและโฟกัสกับการเพิ่มประสิทธิภาพ จนผลลัพธ์คือมันกลายเป็น 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
- การ parse เอกสาร JSON ขนาด 467KiB ที่มีทวีต 100 รายการ
- ในหลายกรณี ส่วนที่ช้าไม่ใช่การ serialize JSON เอง แต่เป็น ชั้นที่อยู่เหนือกว่า ที่แปลงโมเดล Active Record ให้เป็น Ruby Hash และ Array
ojถูกใช้ในหลายโปรเจกต์รวมถึง codebase ของ Shopify และความนิยมนี้น่าจะมาจากเรื่องความเร็วเป็นหลัก-
ความไม่ตรงกันของ API จากการ monkey patch
Oj.mimic_JSONมักถูกใช้เพื่อ monkey patchjsongem และOj.optimize_railsเพื่อ monkey patchActiveSupport::JSONJSON.dump(data, script_safe: true)สามารถ escape</script>เป็น<\/script>เพื่อให้ใส่ JSON ภายในแท็ก<script>ได้อย่างปลอดภัยojไม่รู้จักออปชันscript_safeและจึงเพิกเฉยมัน ทำให้ gem ที่เดิมปลอดภัยเมื่อใช้ลำพัง อาจเปิดช่องให้เกิด การโจมตีแบบ XSS ได้เมื่ออยู่ในแอปพลิเคชันที่เรียกOj.mimic_JSONOj.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
- มีทั้ง microbenchmark และ benchmark ที่ใกล้เคียงการใช้งานจริงมากขึ้น
- ใช้ benchmark suite ของ rapidjson-ruby gem โดย John Hawthorn เป็นฐาน แล้วเพิ่มบางส่วนเข้าไป
- สำหรับ C profiler ใช้ samply
- ข้อดีคือสามารถส่งออกรายงานที่เข้ากันได้กับ Firefox Profiler จึงแชร์ได้ง่าย
ลบการตรวจ UTF-8 ซ้ำซ้อน
- เมื่อทำ profiling
JSON.dumpด้วย payloadtwitter.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 ถูกต้องและมีเฉพาะอักขระ ASCIIENC_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?ก็ให้ raiseJSON::GeneratorError - ทั้ง
#ascii_only?และ#valid_encoding?ใช้coderangeที่ cache ไว้ จึงเกิดการสแกนสตริงได้มากสุดเพียงครั้งเดียว
- ในรูปแบบ Ruby จะมีโครงสร้างประมาณว่า ถ้า
- แม้จะคาดหวัง 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 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ชอบงานของ byroot มากจริง ๆ ไม่ใช่แค่ประเภทของสิ่งที่เขามีส่วนร่วมเท่านั้น แต่ ระดับผลิตภาพ ก็น่าทึ่งเสมอ
เคยพยายามเข้าไปช่วยงานฝั่ง Ruby core อยู่หลายครั้ง แต่หางานที่เหมาะกับระดับทักษะของตัวเองและน่าจะมีส่วนร่วมในทางบวกได้ไม่เจอ และพอผ่านไปหลายสัปดาห์โดยไม่มีผลงาน แรงจูงใจก็หายไป เพราะการมีบริบทแบบที่เขาแชร์ไว้ในบทความนั้นยากมากจริง ๆ
ถ้าคนฝั่ง Ruby C เขียนบทความบ่อยขึ้น น่าจะมีคนที่มีความสามารถที่จำเป็นต่อการปรับปรุง Ruby เพิ่มขึ้นอีก คำแนะนำเรื่อง C profiler ก็ดี และทำให้คิดว่าอาจเริ่มจากการหยิบ Ruby gem ที่มีโค้ด C อยู่มาลองปรับ optimization ใหม่ก็ได้
แม้จะเป็น C extension แต่ก็ช่วยให้เข้าใจแนวคิดบางอย่างได้
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?
https://github.com/ruby/json/releases/tag/v2.7.3
https://github.com/ruby/json/releases/tag/v2.8.0
บทความยอดเยี่ยมและงานก็ดี ต่อไปยังมีเหตุผลให้ใช้ 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 ซึ่งทำเรื่องแบบนั้นอยู่แล้ว
สั้น ๆ คือ
ruby/jsonถูกแจกมาพร้อมกับ Ruby จึงต้องเข้ากับข้อจำกัดของ Ruby และตอนนี้หมายถึง C99 ล้วนและไม่มี C++ ส่วนไลเซนส์ Apache 2 ของ simdjson ก็อาจเป็นปัญหาได้ แต่ไม่แน่ใจโดยรวมแล้วอยากใช้ C++ library ดี ๆ อย่าง dragonbox แต่ทำไม่ได้
อีกอย่าง ตอนตรวจล่าสุด simdjson มีแค่ parser เท่านั้น ส่วน gem ruby/json ทำทั้ง parsing และ encoding จึงช่วยได้แค่ครึ่งหนึ่งของพื้นที่ปัญหา
งานแบบนี้เกิดขึ้นไม่พอเลยในโปรเจกต์สมัยใหม่ ถ้าทำเรื่องแบบนี้กันเป็นประจำมากขึ้น ก็สงสัยว่าแต่แรกเราจะยังต้องมี library อย่าง simdjson หรือ oj ไหม พื้นที่ปัญหานี้ไม่ได้ยากถึงขนาดนั้น
Ruby JSON ใช้ intrinsics ไหม? ใช้ได้ไหม?
แล้วมันทำงานร่วมกับ JIT ต่าง ๆ อย่างไร?
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 สมัยใหม่
“ตั้งแต่ 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”