2 คะแนน โดย GN⁺ 2023-11-30 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • จุดเริ่มต้นมาจากรายงานว่า การอ่านไฟล์ใน Python binding ของ Apache OpenDAL ช้ากว่า open().read() ในตัวของ Python แต่คอขวดไม่ได้อยู่ที่ OpenDAL หรือ PyO3 เอง
  • ใน benchmark การอ่านไฟล์ 64MiB พบว่า python-fs-read ใช้เวลาประมาณ 15~19ms ขณะที่ Rust std::fs และ implementation ภาษา C ใช้เวลาประมาณ 23ms ทำให้ดูเหมือนว่า Rust/C ช้ากว่า Python
  • เมื่อตามรอยด้วย strace, eBPF, perf ความแตกต่างเชื่อมโยงกับ offset ภายใน page ของ buffer ปลายทางของ system call read และเกิดอาการช้าซ้ำได้บริเวณใกล้ 0x10
  • พบปรากฏการณ์คล้ายกันในตระกูล AMD Ryzen 9 5900X, Ryzen 7 5700X, Ryzen 9 5900HX โดยเบาะแสสำคัญคือประสิทธิภาพการทำงานของ rep movsb ภายใน _copy_to_iter ของ kernel
  • ไม่ใช่ว่า Python เร็วกว่าโดยธรรมชาติ แต่เป็นผลจากบั๊กของ CPU AMD Zen 3 ที่เกี่ยวข้องกับ FSRM/rep movsb และความบังเอิญของ memory offset ส่วนการที่ jemalloc ช่วยให้ดีขึ้นก็ไม่ได้มาจาก allocator เอง แต่เพราะได้ offset ที่ต่างออกไป

Benchmark แปลก ๆ ที่เริ่มจาก OpenDAL Python binding

  • Apache OpenDAL เป็น data access layer สำหรับอ่านและเขียนข้อมูลจากหลาย storage service ด้วยวิธีแบบรวมศูนย์ ส่วน Python binding ให้บริการผ่าน PyO3
  • ผู้ใช้รายงานว่าโค้ดที่อ่านไฟล์ 150MB ด้วย OpenDAL Python binding ช้ากว่าการอ่านไฟล์ในตัวของ Python
    • open(...).read() ในตัวของ Python 100 ครั้ง: 4.470868484000675
    • OpenDAL Python binding 100 ครั้ง: 8.993250704006641
  • แม้ในการอ่านไฟล์ 64MiB ที่ทำให้เรียบง่ายขึ้น OpenDAL binding ก็ยังช้ากว่า
    • python-fs-read: เฉลี่ย 15.9ms
    • python-opendal-read: เฉลี่ย 32.9ms
    • การอ่านในตัวของ Python ถูกวัดว่าเร็วกว่า OpenDAL binding 2.07 เท่า

การไล่ตามลงไปถึง Rust OpenDAL และ std::fs

  • แม้ implement logic เดียวกันด้วยบริการ OpenDAL fs ของ Rust ก็ยังช้ากว่าการอ่านในตัวของ Python
    • rust-opendal-fs-read: เฉลี่ย 23.8ms
    • python-fs-read: เฉลี่ย 15.6ms
    • การอ่านในตัวของ Python ถูกวัดว่าเร็วกว่า implementation Rust OpenDAL 1.52 เท่า
  • เนื่องจากบริการ fs ของ OpenDAL ใช้ std::fs ของ Rust จึงเขียน implementation ที่ใช้ std::fs แยกต่างหากเพื่อตรวจสอบ overhead ของ OpenDAL เอง
  • ใน implementation ที่ใช้ Rust std::fs โดยตรงก็ยังได้แนวโน้มเดิม
    • rust-std-fs-read: เฉลี่ย 23.1ms
    • python-fs-read: เฉลี่ย 15.2ms
    • การอ่านในตัวของ Python ถูกวัดว่าเร็วกว่า Rust std::fs 1.52 เท่า

system call และ mmap ที่เห็นจาก strace

  • จากการวิเคราะห์ด้วย strace พบว่าทั้ง Rust และ Python ใช้ mmap สำหรับการ allocate buffer ขนาดใหญ่
  • การรัน Rust std::fs มีลำดับคือเปิด /tmp/file, อ่าน 64MiB หนึ่งครั้ง, เรียก read เพื่อตรวจ EOF แล้วปิดไฟล์
  • การอ่านในตัวของ Python เรียก system call มากกว่า เช่น newfstatat, ioctl, lseek แต่เวลารวมกลับสั้นกว่า
  • การเรียก mmap(NULL, 67112960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) ไม่ได้ใช้ map ไฟล์ แต่ใช้สำหรับ allocate anonymous memory
    • 67112960 คือขนาด 64MiB บวกอีก 4KiB
    • MAP_ANONYMOUS หมายถึงการ allocate memory ที่ไม่เกี่ยวกับไฟล์
  • build ค่าเริ่มต้นของ Rust บน x86_64-unknown-linux-gnu ใช้ malloc ของ glibc และ glibc สามารถใช้ mmap สำหรับ allocation ขนาดใหญ่ได้

Rust ที่เร็วขึ้นด้วย jemalloc และข้อสรุประหว่างทางที่พลิกกลับ

  • เมื่อเปลี่ยน global allocator ของ Rust เป็น jemallocator::Jemalloc ก็เร็วกว่า Python
    • rust-std-fs-read-with-jemalloc: เฉลี่ย 9.7ms
    • python-fs-read: เฉลี่ย 15.8ms
    • implementation Rust ที่ใช้ jemalloc ถูกวัดว่าเร็วกว่า Python 1.64 เท่า
  • ณ จุดนี้ดูเหมือนว่า mmap หรือ default memory allocator จะเป็นสาเหตุ แต่การตีความถูกแก้ไขในอัปเดตภายหลัง
  • ตามอัปเดตวันที่ 2023-12-01 ความต่างไม่ได้เกิดจาก jemalloc, pymalloc, mimalloc เร็วกว่า glibc malloc โดยพื้นฐาน
  • ความต่างจริง ๆ มาจาก offset ภายใน page ของ buffer ที่ allocator สร้างขึ้น
    • rust-std-fs-read: อ่านที่ offset 0x10 จาก address เริ่มต้นของ mmap
    • rust-std-fs-read-with-jemalloc: อ่านที่ offset 0x740 จาก address เริ่มต้นของ mmap
  • ช่วงที่มีปัญหาถูกสรุปว่าอยู่ในช่วง 0x00..0x10 ภายใน page และสามารถ reproduce ปัญหาเดียวกันได้แม้ใช้ jemalloc

ปัญหาที่ reproduce ตามเครื่องได้มากกว่าการตั้งค่าซอฟต์แวร์

  • เมื่อการอภิปรายดำเนินต่อไป มีการยืนยันว่าอาการ Rust ช้ากว่า Python เด่นชัดเป็นพิเศษบนเครื่องของผู้เขียน
  • CPU ของผู้เขียนคือ AMD Ryzen 9 5950X 16-Core Processor และหน่วยความจำเป็น DDR4 3200 MT/s 16GB DIMM
  • แม้เปลี่ยนการตั้งค่าหลายอย่าง ความต่างด้านประสิทธิภาพแบบสัมพัทธ์ก็ไม่หายไป
    • เปิด mitigations=off ของ Linux kernel กลับคืน ก็ไม่ทำให้ผลลัพธ์เปลี่ยน
    • เมื่อเปลี่ยน Transparent Hugepage เป็น always, madvise, never ค่าสัมบูรณ์เปลี่ยน แต่สัดส่วนสัมพัทธ์ยังคงอยู่
    • แม้ผูกกับ CPU core เฉพาะด้วย core_affinity ผลลัพธ์ก็เหมือนเดิม
  • การวัด latency ของ system call read ด้วย eBPF ก็พบว่าฝั่ง Rust ช้ากว่า
    • Python read file: 8,134,049ns
    • Rust std::fs read file: 24,636,975ns
  • จากการสังเกต เป็นเรื่องยากที่จะอธิบายความต่างด้วย OpenDAL, PyO3 หรือ Rust standard library เพียงอย่างเดียว และเวลาต่างกันตั้งแต่ระดับ system call แล้ว

เบาะแส memory offset ที่ปรากฏใน implementation ภาษา C

  • แม้ implement การอ่านไฟล์ 64MiB แบบเดียวกันด้วย C fopen/malloc/fread ก็ยังช้ากว่า Python
    • c-fs-read: เฉลี่ย 23.8ms
    • python-fs-read: เฉลี่ย 19.1ms
    • การอ่านในตัวของ Python ถูกวัดว่าเร็วกว่า implementation ภาษา C 1.25 เท่า
  • เมื่อตรวจ address ของ pointer ด้วย strace -e raw=read,mmap พบว่า offset เริ่มต้นของ buffer ใน C และ Python ต่างกัน
    • C: read ที่ offset 0x10 จาก address ที่ mmap return
    • Python: read ที่ offset 0x30 จาก address ที่ mmap return
  • เมื่อปรับ offset ใน implementation ภาษา C ด้วยวิธีเดียวกัน ประสิทธิภาพดีขึ้นอย่างมาก
    • c-fs-read-with-offset: เฉลี่ย 8.9ms
    • เร็วกว่า Python 2.15 เท่า และเร็วกว่า implementation C เดิม 2.68 เท่า
  • ปัญหานี้ reproduce ได้บน AMD Ryzen 9 5900X และ AMD Ryzen 7 5700X ด้วย
  • ใน Std::fs::read slow? ของชุมชน Rust ก็มีรายงานปรากฏการณ์คล้ายกัน และมีการชี้ถึงความเกี่ยวข้องระหว่าง offset ของ memory region กับประสิทธิภาพของ system call

rep movsb ที่ perf analysis ชี้ไป

  • kernel developer รายหนึ่ง reproduce c-fs-read และเวอร์ชันที่ใส่ offset บน AMD Ryzen 9 5900HX แล้ววิเคราะห์ด้วย perf
  • ค่า L1-dcache-prefetches และ L1-dcache-loads ต่างกันมากตามการมีหรือไม่มี offset
    • ไม่มี offset: L1-dcache-loads ประมาณ 127,845,213, L1-dcache-prefetches ประมาณ 1,843,493
    • มี offset: L1-dcache-loads ประมาณ 13,965,813, L1-dcache-prefetches ประมาณ 395,578
  • hotspot อยู่ใน path ของ read ใน kernel ต่อเนื่องจาก shmem_file_read_itercopy_page_to_iter_copy_to_iter
  • assembly หลักภายใน _copy_to_iter คือ rep movsb และ sample ส่วนใหญ่กระจุกอยู่ที่ instruction นี้
  • การวิเคราะห์ภายหลังสรุปว่า เบาะแสที่สำคัญกว่าไม่ใช่ L1 prefetch เอง แต่เป็นปรากฏการณ์ที่ rep movsb ทำงานได้แย่กับข้อมูลที่ page-aligned และดีขึ้นเมื่อ page alignment ถูกทำให้คลาดออกไป

FSRM และปัญหาของ AMD Zen 3

  • รายงานบั๊ก Ubuntu glibc ที่ถูกแชร์คือ Terrible memcpy performance on Zen 3 when using rep movsb ก็กล่าวถึงปัญหาประสิทธิภาพของ rep movsb เช่นกัน
  • ตัวอย่างในรายงานดังกล่าวอธิบายว่า ในการ copy 2113 bytes เส้นทาง rep movsb ทำได้ประมาณ 3.2GB/s แต่เมื่อเปลี่ยนขนาดเป็น 2111 bytes จะเพิ่มขึ้นเป็นมากกว่า 100GB/s
  • FSRM ย่อมาจาก Fast Short REP MOV เป็นฟีเจอร์ที่ออกแบบมาเพื่อทำให้ rep movsb และ rep movsd เร็วขึ้น
  • FSRM เป็นฟีเจอร์ที่เริ่มจาก Intel และถูกนำมาใช้ใน AMD ด้วย โดยบน CPU ที่ประกาศรองรับ glibc จะใช้ FSRM เป็นค่าเริ่มต้น
  • ดังนั้น ไม่ใช่ว่า Python เร็วกว่า C/Rust โดยพื้นฐาน แต่เป็นเพราะบั๊กของ AMD CPU ที่ทำให้ read path ของ C/Rust ช้าลงที่ memory offset บางตำแหน่ง

อัปเดต: AMD รับรู้หรือไม่ และการรับมือของ glibc

  • ตามอัปเดตวันที่ 2023-12-01 เข้าใจว่า AMD ทราบบั๊กนี้มาตั้งแต่ปี 2021
  • หลังบทความเผยแพร่ ผู้อ่านหลายคนส่งลิงก์ให้ AMD ดังนั้นจึงถือว่า AMD รับรู้ปัญหานี้แล้ว
  • ผู้เขียนเห็นว่า AMD ควรรับผิดชอบแก้บั๊กนี้ใน amd-ucode แต่จากข้อมูลที่ยังไม่ยืนยัน อาจแก้ผ่าน amd-ucode บน Zen 3 ได้ยาก
  • ความหวังที่เป็นไปได้ในทางปฏิบัติคือให้ glibc ปิดใช้ FSRM เมื่อจำเป็น
  • ฝั่ง glibc กำลังมีงาน x86: Improve ERMS usage on Zen3 อยู่

โค้ดสำหรับ reproduce และข้อมูลที่เกี่ยวข้อง

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

 
GN⁺ 2023-11-30
ความคิดเห็นจาก Hacker News
  • มี แฟล็กคุณสมบัติของ CPU เฉพาะถึงสองตัวที่บ่งชี้ว่า REP STOS/MOV เร็ว และสามารถใช้เป็นลำดับคำสั่งสั้น ๆ ของ memset/memcpy ได้
    ความเจ็บปวดที่ต้องเขียนรูทีนปรับแต่งด้วยมือใหม่ทุกครั้งที่ CPU ออกรุ่นใหม่ลากยาวมาหลายสิบปีแล้ว แต่ยังเป็นแบบนี้อยู่ ก็อดคิดไม่ได้ว่านี่ควรอยู่ในชุดทดสอบไทมิงของผู้ผลิต CPU หรือเปล่า

    • เดาล้วน ๆ แต่มันอาจเป็นผลจากการแก้บั๊กที่ใส่เข้ามาใน อัปเดตไมโครโค้ด ช่วงนาทีสุดท้ายหรือหลังวางจำหน่ายก็ได้
      อาจมีปัญหากับ rep movs แบบเร็วที่จัดแนวตามเพจ หรือมีช่องโหว่ต่อการโจมตีบางอย่างจนต้องปิดใช้งาน
    • ถ้าผมเข้าใจถูก นี่หมายความว่าต้องสร้างไฟล์ปฏิบัติการสองตัวสำหรับแต่ละบิลด์ ณ เวลาคอมไพล์หรือเปล่า หรือหมายความว่าต้องคอมไพล์บนฮาร์ดแวร์เฉพาะกันแน่
      ไม่รู้ว่าการแก้ไขควรเป็นแบบไหน ต้องมีอะไรอย่าง การตรวจสอบขณะรันไทม์ หรือไม่
    • มันง่ายที่จะคิดว่าผู้ผลิต CPU น่าจะรู้จัก CPU ของตัวเองดีที่สุด
      ถ้ามีการทำงานแบบ “ซอฟต์แวร์” ที่เร็วกว่า ก็สงสัยว่าทำไมถึงไม่ทำให้ REP MOVS ทำงานแบบเดียวกันอย่างน้อยก็ในระดับ ไมโครโค้ด
  • บั๊ก glibc ที่เกี่ยวข้องอยู่ที่นี่ แต่เคสนี้เป็น Zen 4: https://sourceware.org/bugzilla/show_bug.cgi?id=30994

  • ตอนแรกอ่านแล้วเตรียมจะหัวเราะเยาะว่าผู้เขียนใช้ std::fs ผิด แต่จริง ๆ แล้วกลายเป็นบทความสนุกที่พาลง โพรงกระต่ายของการดีบัก และปริศนาต่อเนื่อง
    เขียนได้ดีและน่าสนใจมาก

    • เป็นบทความที่ดีจริง ๆ วิธี ดีบัก ด้วยการสร้างโปรแกรมทดสอบแล้วค่อย ๆ ลอกแต่ละชั้นออกนั้นฉลาดมาก บทสรุปก็น่าสนใจและคาดไม่ถึง อีกทั้งเขียนชัดเจนจนตามได้ง่าย
  • สมมติฐานตั้งต้นชวนสับสนนิดหน่อย นี่ไม่ได้เทียบโค้ด Python ล้วนกับโค้ด C/Rust แบบเนทีฟ แต่เป็นการเทียบเมธอดอ่านไฟล์ของ Python ซึ่งเป็นแรปเปอร์ Python บนโค้ดเนทีฟ กับ OpenDAL ซึ่งก็เป็นแรปเปอร์อีกตัวบนโค้ดเนทีฟเช่นกัน
    การที่มีความต่างด้านประสิทธิภาพก็ยังน่าสนใจอยู่ แต่การเรียกว่า “ช้ากว่า Python” นั้นค่อนข้างแปลก เหมือนคาดหวังว่าไลบรารีมาตรฐานของ Python ทั้งหมดเขียนด้วย Python ล้วนอย่างนั้นหรือ ตรงกันข้าม ผมคาดว่าการ implement ฟังก์ชันในไลบรารีมาตรฐานของ Python น่าจะเป็นเนทีฟและถูกปรับแต่งอย่างมากเป็นรายฟังก์ชัน
    ไม่แปลกใจที่ข้อสรุปเกี่ยวข้องกับวิธีทำงานของโค้ดเนทีฟ แต่คำตอบเฉพาะนั้นเกินคาดอยู่ดี แค่จุดเริ่มต้นทำให้สับสนเท่านั้น ตัวบทความเองน่าสนใจมาก
    อีกอย่าง ชื่อ “C is slower than Python with specified offset” สำหรับเจ้าของภาษาจะอ่านได้ว่า “C ช้ากว่า Python แม้ในกรณีที่ระบุออฟเซ็ตแล้ว” แต่จริง ๆ หมายถึงตรงกันข้าม คือพอระบุออฟเซ็ตที่ใช้ใน Python ให้กับ C ด้วยแล้ว C ก็เร็วขึ้น

    • กลับไม่ค่อยเข้าใจว่าทำไมถึงสับสน
      งานง่าย ๆ อย่างการอ่านไฟล์ที่ไลบรารีมาตรฐานของ Rust ช้ากว่าไลบรารีมาตรฐานของ Python นั้นเป็นเรื่องน่าประหลาดใจ ต่อให้รู้ว่าคอลล์ของไลบรารีมาตรฐาน Python แบบนี้เขียนด้วย C ก็ยังคาดหวังได้ว่าคอลล์ของไลบรารีมาตรฐาน Rust น่าจะเร็วใกล้เคียงกัน
      ดังนั้นโดยทั่วไปคงคาดว่าใช้ผิดวิธี หรือมีพฤติกรรมแปลก ๆ ในไลบรารีมาตรฐาน Rust แต่ครั้งนี้ไม่ใช่ทั้งสองอย่าง กลับเป็น หน้าผาประสิทธิภาพ ที่เกิดจากการจัดแนวของการจัดสรรหน่วยความจำบนฮาร์ดแวร์เฉพาะ
      เราคาดได้ว่าการอ่านไฟล์ใน Python น่าจะถูกปรับแต่งมาดี แต่ก็คิดว่า Rust ก็ควรเป็นเช่นเดียวกัน ดังนั้นการที่ฝั่ง Rust ช้ากว่ามากจึงน่าประหลาดใจ และยิ่งน่าประหลาดใจขึ้นเพราะมันขึ้นกับฮาร์ดแวร์และ allocator
    • ไม่เข้าใจว่าทำไม Python ถึงโดนด่าว่าเป็นภาษาช้าเวลามันช้า แต่พอมันเร็วกลับไม่ได้รับการยอมรับด้วยเหตุผลว่า “นั่นไม่ใช่ Python จริง ๆ”
      ถ้าโค้ดที่เขียนด้วย Python ทำงานได้เร็ว สำหรับผมก็คือ Python เร็ว นั่นแหละ ไม่ค่อยสำคัญว่าที่เป็นแบบนั้นเพราะ implementation อยู่ในภาษาอื่นหรือเพราะเหตุผลอื่น
    • ไม่รู้ว่ามีเหตุผลอะไรให้คาดหวังว่า “ถูกปรับแต่งอย่างมากเป็นรายฟังก์ชัน”
      สิ่งที่เกิดขึ้นในบทความต้นทางเป็นเรื่องใกล้เคียงกับความบังเอิญล้วน ๆ โค้ด C ของ CPython ไม่ได้ใส่ใจแม้แต่ความสม่ำเสมอของ const และมีการจัดสรรหน่วยความจำแบบไดนามิกกับคอลล์ช่วยเหลือ/อำนวยความสะดวกจำนวนมาก แม้แต่เรื่องอย่างการคำนวณเลขก็ยังมีการจัดสรรหน่วยความจำแบบไดนามิก
      ถ้าเคยทำงานกับ CPython โดยปกติจะไม่คาดหวังว่าประสิทธิภาพจะดี เวลาต้องการปรับปรุงประสิทธิภาพก็มักจะพยายามเลี่ยงฟีเจอร์ที่มันให้มา
      นอกจากนี้ Python ไม่มีมาตรฐาน ดังนั้นหากพูดอย่างเคร่งครัดก็ไม่มีไลบรารีมาตรฐาน และไลบรารีที่แจกมาด้วยส่วนใหญ่เขียนด้วย Python บางส่วนเขียนด้วย C แต่แม้ในโค้ด C เหล่านั้น ก็มีสัดส่วนไม่น้อยที่โดยสาระแล้วเป็นการย้ายโค้ด Python ไปเป็น C แบบค่อนข้างกลไก ตัวอย่างเช่น implementation ของ binary search ใน Python เดิมเขียนด้วย Python แล้วภายหลังถูกแปลเป็น C โดยใช้ Python C API
      สิ่งที่พอคาดหวังได้คือ ฟีเจอร์ที่แมปกับความสามารถของระบบปฏิบัติการโดยตรงจะมีแรปเปอร์ที่ค่อนข้างบาง กล่าวคือการอ่านไฟล์โดยพื้นฐานแล้วเข้า อินเทอร์เฟซระบบ โดยตรง จึงไม่น่าต้องมีโค้ด binding มากนัก
    • ขอบคุณที่ชี้ให้เห็น แก้ชื่อเรื่องแล้ว
    • สมมติฐานตั้งต้นคือ ถ้าใช้ข้อความประมาณว่า “Python เร็วกว่า Rust” ต่อให้ไม่จริงก็จะได้ เพจวิว
      หลังจากมีบทความทำนองนี้ขึ้นมาหลายสิบครั้ง ทุกคนก็เริ่มรู้ทันกันแล้ว
  • ตัวบทความเองยอดเยี่ยม และมีข้อมูลที่น่าสนใจมากมายเกี่ยวกับประเด็นนี้
    แต่ส่วนที่ผมสนใจและกังวลมากกว่าคือ ประเด็นนี้ถูกรายงาน·บันทึกอย่างไร และการสื่อสารถูกจัดการอย่างไร
    การรายงานเกิดขึ้นบน Discord ซึ่งเป็นสภาพแวดล้อมแบบปิด ไม่ถูกทำดัชนี ค้นหายาก และไม่ได้ถูกเก็บรักษาไว้ การอภิปรายเกิดขึ้นบน Discord และ Telegram ซึ่งในบริบทนี้ Telegram อาจจะแย่ยิ่งกว่า
    บล็อกโพสต์นี้กับ GitHub repository คือร่องรอยทั้งหมดที่เหลืออยู่ ถ้า Xuanwo ไม่ได้เขียนลงบล็อก มันก็คงหายไปในไทม์ไลน์แล้ว เป็นสถานการณ์ที่น่าสนใจทีเดียว

    • เป็นแพลตฟอร์มปิดจริง และไม่ดีจริง แต่ข้อกล่าวหาว่าไม่ถูกทำดัชนีหรือค้นหาไม่ได้ ผมเห็นด้วยได้ยาก
      แทบไม่มีเมสเซนเจอร์ไหนที่ทำดัชนี·ค้นหาล็อกที่เข้าถึงสาธารณะได้ตามค่าเริ่มต้นให้ ทุก IRC server ก็ไม่ได้มีล็อกสาธารณะให้ทั้งหมด และกลุ่ม Matrix ก็เช่นกัน ผมไม่เข้าใจว่าทำไมถึงมองว่าการอภิปรายที่นั่นจะไม่หายไปในไทม์ไลน์
      เหตุผลที่สามารถมีล็อกสาธารณะได้ ไม่ใช่เพราะมันไม่ใช่ระบบปิด แต่เพราะมี API ที่อนุญาตให้ทำ logging ได้ Telegram ก็มี API แบบนั้น และกลุ่มอภิปรายของเราก็มีล็อกที่ค้นหาได้ดูได้ที่นี่: https://luoxu-web.vercel.app/#g=1264662201
      การที่ไม่มีการทำดัชนีสาธารณะ ส่วนใหญ่เป็นเพราะ ความเป็นส่วนตัว ไม่ใช่เพราะแพลตฟอร์มเป็นระบบปิด
    • นี่แหละคือเหตุผลที่ผมไม่ยอมรับคำตอบว่า “ตอนนี้ก็มี Discord แล้วไง” ทุกครั้งที่เสียดายการล่มสลายของ USENET
      เมื่อก่อนสามารถค้นหาข้อความทั้งหมดได้อย่างเป็นระเบียบผ่าน DejaNews และต่อมาก็ Google
      การสื่อสารสำคัญของโปรเจกต์โอเพนซอร์สสำคัญ ๆ อย่างสแตกอินเทอร์เน็ต/WWW และเครื่องมือ·ไลบรารีหลักด้านโปรแกรมมิ่ง ควรกลับไปใช้ มาตรฐานเปิด
  • เป็นบทความที่น่าสนใจที่สุดที่อ่านในสัปดาห์นี้ สรุปได้ยอดเยี่ยม

  • สิ่งที่ควรทำอย่างชัดเจนดูเหมือนจะเป็นการส่งแพตช์ไปยังเมธอดเคอร์เนล copy_user_generic
    ถ้าตรวจพบ CPU ที่มีปัญหาและก่อให้เกิดบั๊กที่ทำให้การจัดแนวหน่วยความจำช้าลง ก็ให้ใช้ implementation สำหรับคัดลอกหน่วยความจำ แบบอื่นแทน

    • ไม่ได้ชัดเจนขนาดนั้น ถ้าแก้ด้วย ไมโครโค้ด ได้ การให้ผู้คนใช้ไมโครโค้ดที่อัปเดตแล้วน่าจะดีกว่าการกระจายโค้ดแก้ปัญหาที่โดยเนื้อแท้แล้วแพตช์ได้ด้วยซอฟต์แวร์ไปทั่วเคอร์เนล
      การแก้ไขที่คนไม่มีประสบการณ์กับเคอร์เนลจะยอมรับได้นั้นไม่น่าจะเล็กน้อย ที่สำคัญกว่านั้น วิธีเปิดใช้ workaround ก็ดูไม่ชัดเจนเช่นกัน อาจจะดีที่สุดถ้าวัดตอนบูต ไม่อย่างนั้นก็ไม่แน่ว่าจะรู้ได้อย่างไรว่าโมเดลและ stepping ไหนได้รับผลกระทบ
    • ไม่ใช่การแก้ไขเล็กน้อย AMD ต้องหาสาเหตุให้ได้ว่าทำไม aliasing ถึงพังที่แอดเดรสใกล้กับการจัดแนวหน้าเพจ ดังนั้นการแก้น่าจะอยู่ฝั่งไมโครโค้ด
      มาตรการบรรเทาทางซอฟต์แวร์ก็น่าจะซับซ้อนเช่นกัน เพราะเคอร์เนลไม่สามารถใช้คำสั่งเวกเตอร์ที่ปกติใช้ในเส้นทางสำรองเมื่อใช้ ERMS ไม่ได้จริง ๆ
  • jemalloc เคยเป็น allocator ค่าเริ่มต้นของ Rust จนถึงปี 2018
    https://internals.rust-lang.org/t/jemalloc-was-just-removed-...

  • ส่วนที่บอกว่า “นักพัฒนา Rust อาจพิจารณาเปลี่ยนไปใช้ jemallocator เพื่อเพิ่มประสิทธิภาพ” นั้นน่าสนใจ
    ไม่แน่ใจว่าใคร ๆ ก็จะได้ประสิทธิภาพเพิ่มขึ้นแทบฟรีหรือไม่ หรือมีข้อควรระวังอะไรบ้าง อยากรู้ว่า codebase ภาษา C ก็จะได้ประโยชน์ด้วยไหม และนี่เป็นประสิทธิภาพที่ตอนนี้เรากำลังพลาดไปเฉย ๆ หรือเปล่า

    • ต้องรู้ไว้ว่าการใช้ jemalloc จะทำให้เกิดปัญหาเรื่อง observability เพราะ MADV_FREE
      htop จะไม่แสดงหน่วยความจำที่ใช้งานจริงได้อย่างแม่นยำอีกต่อไป
      https://github.com/jemalloc/jemalloc/issues/387#issuecomment...
      https://gitlab.haskell.org/ghc/ghc/-/issues/17411
      ตอนนี้ดูเหมือนว่า jemalloc จะเรียก MADV_DONTNEED หลังจาก MADV_FREE ไปแล้ว 10 วินาที: https://github.com/JuliaLang/julia/issues/51086#issuecomment...
      ดังนั้นมันจึง “แก้” ปัญหานี้ก็จริง แต่จะมีดีเลย์ที่ทำให้สับสนระหว่างเวลาที่คืนหน่วยความจำกับเวลาที่เห็นข้อเท็จจริงนั้นใน htop
      อย่างไรก็ตาม ตาม https://jemalloc.net/jemalloc.3.html สามารถตั้ง opt.muzzy_decay_ms = 0 เพื่อลบดีเลย์ได้
      ถึงอย่างนั้นผู้เขียน musl ก็ยังระมัดระวังต่อการใช้ jemalloc เป็นค่าเริ่มต้น: https://www.openwall.com/lists/musl/2018/04/23/2
      ใจความคือมีปัญหาเรื่องการพองตัวอย่างรุนแรง, ASLR อ่อนลง และการปรับแต่งที่เอนเอียงไปทางทำให้เร็วที่สุดโดยไม่สนใจการใช้หน่วยความจำ แม้ค่าปรับแต่งข้างต้นอาจช่วยบรรเทาได้บ้าง แต่แนวโน้มโดยรวมว่าจะเน้นประสิทธิภาพหรือการใช้หน่วยความจำก็น่าจะยังคงเป็น trade-off อยู่ดี
    • ผมมองว่าเป็นประสิทธิภาพที่เราพลาดไปแทบฟรี มีต้นทุนคือขนาดไบนารีเพิ่มขึ้นเล็กน้อย
      อาจไม่ได้เร็วกว่าเสมอในทุกสถานการณ์ แต่ในเกือบทั้งหมดน่าจะเร็วกว่า Rust เองก็เคยใช้ jemalloc เป็นค่าเริ่มต้น แต่ถูกเปลี่ยนไปเพราะมีคนมองว่ามันเป็นค่าเริ่มต้นที่น่าประหลาดใจ
    • การเปลี่ยนไปใช้ allocator ที่ไม่ใช่ค่าเริ่มต้นไม่ได้ทำให้ประสิทธิภาพสูงขึ้นเสมอไป
      ขึ้นกับ workload มาก จึงต้องมี profiling และ benchmarking ถึงอย่างนั้นภาษา low-level อย่าง C/C++/Rust ก็ควรเลือก allocator เหล่านี้ได้
      ข้อควรระวังอย่างหนึ่งคือขนาดไบนารี allocator แบบกำหนดเองจะเพิ่มจำนวนไบต์เข้าไปในไฟล์ปฏิบัติการ
    • Rust เคยใช้ jemalloc เป็นค่าเริ่มต้น แต่ราวปี 2018 ก็กลับไปใช้ malloc ของระบบอีกครั้ง[0]
      ตอนนี้ Rust มี trait GlobalAlloc และ attribute #[global_allocator] แล้ว ดังนั้นถ้าแอปต้องการก็สามารถใช้ jemalloc เป็น allocator ได้ ไม่แน่ใจว่าผู้ใช้จะ override ด้วยวิธีอย่าง LD_PRELOAD ได้หรือไม่
      jemalloc ไม่ได้เป็นตัวเลือกที่ดีที่สุดเสมอสำหรับทุก workload และ use case allocator ของระบบมักห่างไกลจากความสมบูรณ์แบบ แต่ อย่างน้อยก็ผ่านการทดสอบอย่างกว้างขวางในฐานะ allocator อเนกประสงค์
      [0] https://github.com/rust-lang/rust/issues/36963
    • ประสิทธิภาพไม่ใช่ มาตรวัดมิติเดียว ที่ทำให้โปรแกรมเคลื่อนจาก “ช้า” ไป “เร็ว” ได้อย่างเดียว มีปัจจัยอื่นทำงานร่วมกันเสมอ
      jemalloc อาจเป็นตัวเลือกที่เหมาะกับบางแอปพลิเคชัน แต่ในกรณีอื่น allocator ตัวอื่นอาจเร็วกว่า หรือแม้จะช้ากว่า แต่ก็อาจสอดคล้องกับเป้าหมายอย่าง dirty memory ที่น้อยลง, observability ที่ดีกว่า, หรือการรับประกันด้านความปลอดภัยบางอย่างได้ดีกว่า
  • ส่งเรื่องนี้ไปให้คนที่เหมาะสมแล้ว

    • หมายความว่าส่งให้ฝั่ง AMD เหรอ?