5 คะแนน โดย GN⁺ 2023-07-04 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • High throughput Fizz Buzz ของ Code Golf Stack Exchange เป็นการทดลองด้านประสิทธิภาพที่แข่งกันว่าใครจะสร้างผลลัพธ์ FizzBuzz แล้วส่งผ่าน pipe ได้เร็วแค่ไหน โดยให้คะแนนจาก throughput ของ <program> | pv > /dev/null
  • เอาต์พุตที่ใช้ได้ต้องเป็น ASCII ล้วน, ขึ้นบรรทัดใหม่ด้วย \n, ใช้ Fizz/Buzz/FizzBuzz อย่างถูกต้อง, ต้องพิมพ์ต่อเนื่องอย่างน้อยถึง 2^58 หรือแทบจะไม่สิ้นสุด และห้ามใช้ กลเม็ด null byte ที่ทำให้ pv ตรวจจับได้เพียงอย่างเดียว
  • โค้ดระดับบน ๆ เลิกใช้ % 3, % 5, และลูป printf แล้วหันไปใช้การคลี่แพตเทิร์น 15 บรรทัด, การจัดการตัวเลขความกว้างคงที่, บัฟเฟอร์ขนาดใหญ่, การทำงานขนาน, และ เอาต์พุตแบบ zero-copy เพื่อลดคอขวด
  • คำตอบ assembly ที่แสดงสำหรับ x86-64+AVX2 ตั้งอยู่บนสมมติฐานว่าใช้ Linux และ AVX2 โดยผสาน vmsplice, L2 cache, huge page, และอินเทอร์พรีเตอร์ FizzBuzz bytecode เข้าด้วยกัน จนทำได้ราว 61GiB/s
  • ผลลัพธ์แกว่งมากตาม CPU, pipe buffer, เวอร์ชันของ pv, CPU affinity, และการตั้งค่า mitigation ต่าง ๆ โดยบนเครื่องของผู้ตั้งคำถาม คะแนนสูงสุดที่สรุปไว้คือ C++ implementation ของ David Frank ที่ราว 1.7Tb/s

ขีดจำกัด throughput ที่ถูกไล่ด้วย FizzBuzz

  • แก่นสำคัญไม่ใช่โจทย์ FizzBuzz เอง แต่คือการดูว่าในการสร้างข้อความที่ง่ายมาก ๆ นั้น ระหว่าง การคำนวณของ CPU, การคัดลอกหน่วยความจำ, pipe I/O, และขอบเขตเคอร์เนล อะไรจะกลายเป็นคอขวดก่อน
  • ตัวอย่าง C แบบตรงไปตรงมาที่ใช้เป็นฐานอ้างอิงใช้ % 3, % 5, และ printf ซึ่งบนเครื่องทั่วไปทำได้ราว 170MiB/s
  • ผู้ตั้งคำถามเคยเห็น implementation ที่ทำได้เกิน 3GiB/s บนเครื่องเดียวกัน และต้องการให้ชุมชนลองสำรวจเพดาน throughput ที่สูงกว่านั้น
  • คะแนนวัดบนเดสก์ท็อปของผู้ตั้งคำถาม
    • AMD 5950x, 16C/32T
    • RAM 64GB 3200MHz
    • ปิดใช้งาน CPU mitigations
  • ตารางแยกตามภาษามี asm 60.8GiB/s, C 20.9GiB/s, Julia 15.5GiB/s, Go 6.8GiB/s, Java 5.8GiB/s, Rust 3.4GiB/s, Ruby 1.7GiB/s, Python 0.5GiB/s เป็นต้น
  • คำตอบ x86-64+AVX2 assembly ของ ais523 มุ่งเป้าประสิทธิภาพสูงสุดแบบเธรดเดียว โดยทำได้ราว 31GiB/s บนเครื่องของผู้เขียน และราว 61GiB/s ในการสรุปผลของผู้ตั้งคำถาม
  • C++ implementation ของ David Frank เป็นคะแนนสูงสุดปัจจุบันตามเนื้อหาของคำถามที่ราว 1.7 Terrabit/s และยังระบุว่า implementation C++20 อีกตัวหนึ่งทำได้ 283GB/s บน AMD Ryzen 9 7700X

กติกาเอาต์พุตและเงื่อนไขของเบนช์มาร์ก

  • เงื่อนไขของเอาต์พุตที่ใช้ได้ค่อนข้างเข้มงวด
    • เอาต์พุตต้องเป็น FizzBuzz ที่ถูกต้องเป๊ะ
    • ASCII ต้องใช้ 1 ไบต์ต่ออักขระ
    • อนุญาตให้ขึ้นบรรทัดใหม่ด้วย \n เท่านั้น
    • ไม่อนุญาต \r\n
    • เอาต์พุตต้องต่อเนื่องไปถึงค่าที่ใหญ่มากอย่างน้อย 2^58
  • วิธีวัดเบนช์มาร์กเองก็ส่งผลต่อ throughput เช่นกัน
    • มีการพูดถึงว่า pv และ Linux pipe buffer ใช้บัฟเฟอร์ 64K เป็นค่าปริยาย
    • การจัดวาง sibling core ของ CPU อาจทำให้เส้นทาง L2 cache ระหว่างโปรแกรมที่สร้างข้อมูลกับโปรแกรมที่บริโภคข้อมูลต่างกัน
    • สามารถใช้ taskset บังคับการจัดวาง CPU เพื่อเปรียบเทียบได้

แนวทาง optimization ของ implementation แบบ assembly

  • หัวใจของ implementation แบบ assembly คือการลด ต้นทุนการคัดลอกเอาต์พุตมากกว่าต้นทุนการคำนวณ
    • หากใช้ write จะมีต้นทุนการคัดลอกจาก user space ไป kernel space สูง
    • ผู้เขียนระบุว่าถ้าเปลี่ยนไปใช้ write ประสิทธิภาพจะตกเหลือหนึ่งในห้า
    • vmsplice ทำให้ pipe อ้างอิงบัฟเฟอร์ของโปรแกรมได้โดยตรง จึงลดการคัดลอก
  • การคำนวณ FizzBuzz แบ่งเป็น 3 ช่วง
    • ช่วงแรก hardcode สตริงเริ่มต้นไว้
    • ช่วงที่สองจัดการตัวเลข 2–5 หลักด้วยรูทีน AVX2 ที่ค่อนข้างตรงไปตรงมา
    • ช่วงที่สามจัดการช่วง 6–18 หลักด้วยอินเทอร์พรีเตอร์ FizzBuzz bytecode
  • ลูปหลักของช่วงที่สามตั้งเป้าสร้างเอาต์พุต 64 ไบต์ต่อ 4 clock cycle
    • bytecode 1 ไบต์สร้างเอาต์พุต 1 ไบต์
    • โหลด bytecode 32 ไบต์ แล้วใช้ vpshufb, vpsubb ฯลฯ เพื่อสร้างเอาต์พุต 32 ไบต์
    • หมายเลขบรรทัดคำนวณด้วยค่าประมาณและการแก้ไขจาก bytecode แทนการแปลงตัวเลขแบบทั่วไปในทุกบรรทัด
  • implementation นี้พึ่งพาแพลตฟอร์มอย่างมาก
    • ต้องใช้ Linux ที่ไม่เก่าเกินไป
    • ต้องเป็นโปรเซสเซอร์ x86-64 ที่รองรับ AVX2
    • หาก standard output ไม่ใช่ pipe จะขึ้น error ตั้งแต่เริ่ม
    • ผู้เขียนระบุว่าในบางกรณีอาจให้เอาต์พุตผิดหากอยู่ในโครงสร้าง pipe ที่มี splice แทรกอยู่

กลยุทธ์ร่วมที่ implementation ภาษาอื่นเลือกใช้

  • implementation ใน C, Go, Java, Rust, Python, Julia, Ruby, C# ก็วนกลับมาใช้แนวทาง optimization คล้ายกัน
    • คลี่แพตเทิร์น FizzBuzz 15 บรรทัด
    • ลดจำนวนครั้งของการแปลงตัวเลขเป็นสตริง
    • รวมข้อมูลแล้วเขียนออกทีเดียวด้วยบัฟเฟอร์ใหญ่
    • ทำงานสร้างข้อมูลแบบขนานด้วย thread หรือ goroutine
    • ใช้ barrier, channel, queue, mutex เพื่อรักษาลำดับเอาต์พุต
  • ผลลัพธ์นี้จึงมองเป็นการเปรียบเทียบ “ประสิทธิภาพของภาษา” แบบตรง ๆ ได้ยาก
    • เวอร์ชันของ pv, ขนาด pipe, การใช้ vmsplice ได้หรือไม่, CPU affinity, huge page, การ optimize ของคอมไพเลอร์, และการ inline ของ memcpy ล้วนเปลี่ยน throughput ได้มาก
    • มีคอมเมนต์ว่าบางคำตอบเร็วมากบนบางเครื่อง แต่ไม่สามารถทำซ้ำตัวเลขเดิมได้ในสภาพแวดล้อมอื่น

ขอบเขตที่ไม่ได้ครอบคลุม

  • ในบันทึกอินพุตมีการระบุว่ามี source chunk บางส่วนถูกตัดออกเพราะข้อจำกัดด้านความยาวและต้นทุน จึงไม่ได้ครอบคลุมคำตอบทั้ง 46 รายการและโค้ดหรือคอมเมนต์ทั้งหมดแบบครบถ้วน
  • ส่วนที่ถูกละไว้ อาจรวมถึง submission รายภาษาบางส่วน, เนื้อหาโค้ดยาว ๆ, บันทึกการจูนอย่างละเอียด, และการถกเถียงในคอมเมนต์

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

 
GN⁺ 2023-07-04
ความคิดเห็นจาก Hacker News
  • สิ่งที่น่าประทับใจที่สุดคือ Linux สามารถทำให้ข้อมูลที่ถูก pipe จากโปรแกรมหนึ่งไปยังอีกโปรแกรมหนึ่งคงอยู่แค่ใน L2 cache ได้ โดยไม่ต้องแตะหน่วยความจำหลักเลย
    มองว่านี่เป็นงานออกแบบสถาปัตยกรรมระบบที่น่าทึ่ง เพราะส่วนต่างๆ หลายส่วนของเคอร์เนล Linux ทั่วไปทำงานประสานกันจนเกิด fast path แบบนี้ได้
    สงสัยว่าใน Mach port ของ Mac OSX หรือ Named Pipes ของ Windows จะทำแบบนี้ได้ไหม

    • ถ้าแคชของ CPU ใช้ physical address tag และ page table ของทั้งสองโปรเซสแชร์ physical page เดียวกัน CPU ก็จะยอมให้ทั้งสองโปรเซสใช้ข้อมูลในแคชได้ ตราบใดที่ระบบปฏิบัติการไม่ได้ล้างหรือลบล้างแคชอย่างชัดเจนตอนสลับบริบท
    • นี่เป็นหนึ่งในหลายเหตุผลที่ทำให้อุตสาหกรรม high-frequency trading ทั้งวงการรันอยู่บน Linux
  • สมกับชื่อผู้ใช้ “ais523 - high effort answers” จริงๆ เพราะถึงแม้จะเป็นคนที่รันโปรแกรมไม่ได้ ก็ยังเขียนคอมเมนต์ที่ลงแรงมาก และข้อสรุปคือ
    “โปรแกรมน่าจะถูกคอมไพล์โดยบังเอิญในสถานะที่เปิด ASLR อยู่ ในกรณีนี้ dynamic linker จะไม่เคารพการจัดแนว 4MiB ของ BSS segment ทำให้ .align ของผมถูกมองข้ามไปในทางปฏิบัติ และนั่นน่าจะเป็นสาเหตุของบั๊ก”

    • อย่างน้อยสิ่งหนึ่งที่ผมรู้คือ ถ้าคนนี้มาสมัครงานตำแหน่งเดียวกับผม ผมคงไม่มีทางชนะ เป็น leet coder ขั้นสุด
  • ทุกครั้งที่บทความนี้ถูกดันกลับขึ้นมา ผมจะกลับมาหัวเราะกับคอมเมนต์นี้เสมอ
    “@chx: ผมมีวิทยานิพนธ์ปริญญาโทอยู่แล้วนะ อันนี้ยากกว่าอีก – ais523 - high effort answers Oct 29, 2021 at 1:17”

  • ลองทำเวอร์ชันง่ายๆ แบบไม่ optimize ด้วย Rust, Python3 และ C โดยใช้แค่ if/else/while ธรรมดากับการพิมพ์ออกมาตรฐาน
    Rust -> 23.2MiB/s
    Python3 -> 28.6MiB/s
    C -> 238MiB/s
    สงสัยว่ามีใครรู้ไหมว่าทำไม ประสิทธิภาพของ Rust ถึงไปอยู่ในช่วงใกล้กับ Python3 ทั้งที่คิดว่าน่าจะใกล้ C มากกว่า

    • ฟังก์ชัน print ของ Rust จะล็อกโดยปริยาย เป็นเรื่องของความปลอดภัย แต่ C ไม่ทำแบบนั้น ดูรายละเอียดได้ในเอกสาร Rust: https://doc.rust-lang.org/std/macro.print.html
      ถ้าจะให้ได้ประสิทธิภาพใกล้ C ก็น่าจะต้องจัดการล็อกนี้เอง
      let mut lock = stdout().lock();
      write!(lock, "hello world").unwrap();
      และต้องตั้งค่า ขนาดบัฟเฟอร์ ของ standard output ให้เท่ากับของ C ด้วย
    • เป็นโพสต์เก่าที่เคยเขียนไว้นานแล้ว แต่อาจมีประโยชน์
      https://ismailmaj.github.io/tinkering-with-fizz-buzz-and-con...
    • C กับ Python ใช้ adaptive buffering สำหรับ standard output ถ้าปลายทางเป็นเทอร์มินัลก็จะ flush ทุกครั้งที่ขึ้นบรรทัดใหม่ ไม่เช่นนั้นจะ flush ก็ต่อเมื่อบัฟเฟอร์ภายในเต็ม
      ในโปรแกรม C ที่นับเลขโดยหน่วงเวลา 1ms คอลัมน์ที่สองคือเวลาที่ผ่านไปนับจาก read() ครั้งก่อน
      $ ./out | rtss
      4.7ms 4.7ms | 1
      4.7ms | 2
      4.7ms | 3
      4.7ms | 4
      4.8ms exit status: 0
      จะเห็นว่ามันเขียนออกมาทีเดียวทั้งหมด ถ้าจัดสรรเทอร์มินัลให้ก็จะออกมาทีละบรรทัด
      $ rtss --pty ./out
      0.8ms 0.8ms | 1
      1.9ms 1.1ms | 2
      3.0ms 1.1ms | 3
      4.1ms 1.1ms | 4
      4.3ms exit status: 0
      Rust ไม่มีพฤติกรรมแบบปรับตามสภาพนี้สำหรับ output จึงทำงานเหมือนผลลัพธ์แบบที่สองเสมอ ไม่ว่าจะเป็นเทอร์มินัลหรือไม่ก็ตาม
      ในเชิงเทคนิค standard output จะถูกครอบด้วย LineWriter(https://doc.rust-lang.org/std/io/struct.LineWriter.html) เสมอ และถ้าเจอการเขียนที่มีขึ้นบรรทัดใหม่ก็จะ flush ทันที ถ้าต้องการ throughput สูงสุดก็ควรครอบด้วย BufWriter เพื่อเขียนหลายบรรทัดรวมกัน
    • Rust ต้องคอมไพล์ด้วย --release และ C ต้องคอมไพล์ด้วย -O3
    • แทบจะแน่ใจได้เลยว่าคอขวดอยู่ที่การ output นี่แหละ น่าจะเป็นปัญหาเรื่อง buffering หรือการล็อก
  • ทำให้อดสงสัยไม่ได้ว่าถ้าทุกอย่างเขียนด้วยแอสเซมบลีทั้งหมดจะเร็วได้แค่ไหน
    ในงานพัฒนาเสียง การเขียน โค้ด DSP ด้วยแอสเซมบลีเป็นเรื่องที่พบได้บ่อยมาก

    • เหตุผลที่มันเร็วไม่ได้เป็นเพียงเพราะเป็น แอสเซมบลี อย่างเดียว นี่เป็นสิ่งจำเป็นสำหรับแนวทางนี้ก็จริง แต่ไม่ใช่เงื่อนไขที่เพียงพอเลย
      ต้องอาศัยการวิจัยอัลกอริทึมแบบสุดขั้ว ความรู้เชิงลึกเกี่ยวกับ Linux system call และการปรับแต่งเฉพาะแพลตฟอร์มรวมกันจึงจะทำได้ ผู้เขียน Alex Smith พูดไว้ว่า:
      “@chx: ผมมีวิทยานิพนธ์ปริญญาโทอยู่แล้ว งานนี้ยากกว่านั้นอีก”
      มันเป็นคนละระดับกับสิ่งที่สรุปง่าย ๆ ว่า “แค่ทำด้วยแอสเซมบลีก็พอ” โดยสิ้นเชิง
    • ต่อให้เขียนทั้งหมดด้วย Java อย่างเดียว ก็น่าจะยังดีกว่าวิธีปัจจุบันที่เอา Chrome ไปใส่ใน Python instance แล้วรันเว็บเซิร์ฟเวอร์ JavaScript เพื่อเรนเดอร์เอกสาร
    • สุดท้ายก็คงจะลงเอยเหมือนเรื่อง Geoworks ของ Steve Yegge
      “โอเค ผมเรียนอยู่ที่ University of Washington แล้วก็ไปทำงานที่บริษัทชื่อ Geoworks และเขียนโปรแกรมภาษาแอสเซมบลีอยู่ 5 ปี พวกเราที่ Geoworks เขียนทั้งระบบปฏิบัติการ ไลบรารี ไดรเวอร์ แอป ทุกอย่างของเดสก์ท็อประบบปฏิบัติการด้วยแอสเซมบลีทั้งหมด เป็น 8086 assembly! แถมไม่ใช่แอสเซมบลีที่ดีด้วย! มีรีจิสเตอร์แค่สี่ตัว! ถ้านับถึง 386 ก็มีรีจิสเตอร์ si ด้วย แต่มันน่ากลัวมาก
      ที่จริงพวกเราค่อนข้างชอบมันนะ เพราะมันเป็น object-oriented assembly นี่แหละเรื่องน่าขันจริง ๆ ของทั้งหมดนี้ ว่าคนเราสามารถโน้มน้าวตัวเองให้ชอบอะไรสักอย่างได้มากแค่ไหน สำหรับพวกเรา C++ คือความเสื่อมโทรมแบบโรมันขั้นสุด ราวกับไปอาเจียนเพื่อจะได้กลับมากินต่อ พวกเขามี IF! ส่วนพวกเรามี jump CX zero! พวกเขามี ‘object’! พวกเราก็มีเหมือนกัน แต่พวกเขามีไวยากรณ์ไว้รองรับ มันดูอ่อนแอมาก และตอนนั้นเรารู้ว่าเราสามารถสร้างโค้ดที่เร็วกว่าคอมไพเลอร์ไหน ๆ ได้ และเราก็ทำได้จริง!
      แล้วเกิดอะไรขึ้นล่ะ? บริษัทล้มละลาย ทำไมเหรอ? เรื่องนี้ผมน่าจะเห็นต่างจากทุกคนที่ Geoworks และผมก็รู้ว่าจริง ๆ แล้วอาจมีแค่ผมคนเดียวที่เชื่อแบบนี้ แต่เหตุผลก็คือเราเขียนภาษาแอสเซมบลี 8086 ไป 15 ล้านบรรทัด เรามีเครื่องมือที่ยอดเยี่ยม ระดับโลกเลย เชื่อผมเถอะ ของแบบนั้นจำเป็นมาก แต่เมื่อถึงจุดหนึ่ง...
      ปัญหาคือ ลองนึกภาพมดตัวหนึ่งพยายามเดินเป็นเส้นตรงข้ามพื้นโรงรถดู มันเดินเป็นเส้นตรงไม่ได้ เรามองเห็นภาพกว้างกว่าเลยรู้ มดจะเดินวนไปมา ปรับให้เหมาะเฉพาะจุดกับก้อนหินก้อนนี้ แล้วค่อยไปทางนั้น
      ตอนเราเขียนระบบภาษาแอสเซมบลีขนาดยักษ์ก็เป็นแบบนั้น สุดท้าย Microsoft ก็ออกแพลตฟอร์มอุปกรณ์พกพาที่เร็วกว่าเรามาก ผมเข้าไปพร้อมดีบักเกอร์แล้วคิดว่า ‘อะไรกันเนี่ย? ทำไมเป็นแบบนี้? การเรนเดอร์ช้ามาก กระตุกสุด ๆ’ พอเข้าไปดูพบว่าทุกครั้งที่รีเฟรชหน้าจอ title bar บางอันถูกเรนเดอร์ซ้ำ 140 ครั้ง และไม่ได้มีแค่ title bar เท่านั้น ทุกอย่างถูกเรียกหลายรอบหมด
      เพราะเราไม่สามารถมองเห็นได้อีกต่อไประบบทำงานอย่างไร!
      ระบบที่เล็กไม่เพียงแค่ปรับแต่งได้ง่ายกว่า แต่ยังปรับแต่งได้จริงด้วย หมายความว่าสามารถทำ global optimization ได้”
      http://steve-yegge.blogspot.com/2008/05/dynamic-languages-st...
    • แอสเซมบลีทุกวันนี้ห่างไกลจากซอร์สโค้ดที่เราเขียนกันมากเกินไป จนแทบไม่สมจริงที่จะคิดในแบบนั้น แต่ถ้าบริษัทซอฟต์แวร์ใส่ใจเรื่อง ประสิทธิภาพ กันจริง ๆ ก็น่าลองจินตนาการว่าทุกอย่างจะเร็วขึ้นได้แค่ไหน
      ทุกวันนี้เว็บไซต์และซอฟต์แวร์ราว 99% น่าจะทำให้เร็วขึ้นได้อย่างน้อย 50% ด้วยการเปลี่ยนแปลงพื้นฐานง่าย ๆ ถ้าใส่ใจกับประสิทธิภาพของแอปพลิเคชันสักหน่อย เช่น การทำ caching ให้ถูกต้อง การปรับแต่ง asset การแทนที่ third-party library ที่เทอะทะด้วย native call พื้นฐานที่ทำงานเดียวกัน และการตั้งค่าเซิร์ฟเวอร์กับฐานข้อมูลให้เหมาะสม
      อีกไม่กี่ปี AI อาจให้การ optimize แบบ one-click กับ repository ได้ โดยนำ best practices มาใช้ หรือไม่ก็เขียนโค้ดเดิมใหม่เป็นแอสเซมบลีที่เร็วกว่าให้เลย
    • รายละเอียดที่มักถูกมองข้ามในข้อถกเถียงที่ว่าภาษาระดับล่างกว่าจะให้โปรแกรมที่เร็วกว่า คือถ้าการจะขยับไปข้างหน้าในการเขียนโค้ดแต่ละนิดเป็นเรื่องทรมาน คุณก็จะพยายามอย่างมากที่จะทำให้น้อยที่สุดเท่าที่จำเป็น
      ข้อจำกัดของทรัพยากร สามารถทำให้เกิดความชัดเจนของโฟกัสได้
  • แม้การทดลองนี้จะทั้งสนุกและให้ความรู้ แต่ก็ดูมีข้อบกพร่องอยู่เล็กน้อย แทนที่จะวัดว่าแก้ปัญหาที่ซับซ้อนได้เร็วแค่ไหน มันกลับทดสอบปัญหารอบข้างเป็นหลัก คือประสิทธิภาพในการดึงหน่วยความจำออกจากโปรเซสหนึ่งแล้วส่งต่อไปยังอีกโปรเซสหนึ่ง
    เลยดูเหมือนว่าโปรเซสที่สองกำลังเขียนลงคอนโซลหรือไฟล์อยู่ตลอด แต่ในทางเทคนิคแล้วไม่ใช่แบบนั้น การรัน pv >/dev/null โดยแก่นแท้แล้วแทบจะไม่ทำอะไรเลย และ system call write ก็คืนค่ากลับแทบจะทันที
    vmsplice เป็นความสามารถคล้าย shared memory ที่ทำให้อีกโปรเซสเข้าถึงบัฟเฟอร์/หน่วยความจำของโปรเซสหนึ่งได้ เป็นไปได้มากว่าข้อกำหนดการแข่งขันช่วงแรกจะกำกวม จึงดูไม่ชัดเจนว่าตามกติกาถือว่าใช้ได้หรือไม่

    • คุณสามารถเลื่อนขึ้นไปดูคำถามต้นฉบับเพื่อตรวจสอบได้ว่าข้อกำหนดเริ่มต้นกำกวมหรือไม่ และยังดูประวัติการแก้ไขได้ด้วยว่ามันไม่ได้เปลี่ยนหลังจากเริ่มชาเลนจ์
      “เขียนโปรแกรม FizzBuzz รันมัน แล้ว pipe เอาต์พุตไปที่ | pv > /dev/null ยิ่ง throughput สูงก็ยิ่งทำได้ดี”
      “เอาต์พุตของโปรแกรมต้องเป็น fizzbuzz ที่ถูกต้องทั้งหมด ห้ามโกงด้วยการแทรก null byte คั่นระหว่างเอาต์พุตที่ถูกต้อง เช่น null byte ที่มองไม่เห็นบนคอนโซลแต่ไปถูกนับใน throughput ของ pv”
      และ vmsplice(2) ก็สร้าง byte stream ลงบน stdout pipe จริง ๆ โดยที่ pv(1) สามารถ splice มันไปยัง /dev/null หรือ cat(1) จะคัดลอกมันไปยังเทอร์มินัลก็ได้
      ไม่ใช่มีแค่ผลงานชิ้นนี้ที่ใช้ vmsplice(2) ผู้ส่งผลงานคนอื่นก็พบเช่นกันว่านี่ไม่ใช่วิธีวิเศษอะไร แม้จะข้ามกำแพง I/O ไปได้แล้ว ก็ยังเหลืองานอีกมากในการสร้างหน้าข้อมูลเอาต์พุตให้เร็วที่สุดเท่าที่จะทำได้
    • ผมรู้สึกว่า “ประสิทธิภาพในการดึงหน่วยความจำจากโปรเซสหนึ่งแล้วส่งต่อไปยังอีกโปรเซสหนึ่ง” แทบจะเป็นปัญหาทั้งหมดอยู่แล้วแทบทุกครั้ง
      โค้ดส่วนใหญ่ติดคอขวดที่ หน่วยความจำและ I/O ปัญหาที่ซับซ้อนส่วนมากก็มักติดที่ความเร็วในการย้ายข้อมูลจากที่หนึ่งไปอีกที่หนึ่ง ไม่ค่อยติดที่การคำนวณข้อมูล แม้ในฐานะคนที่ใช้เวลาทั้งวันกับการปรับแต่ง GPU assembly ก็ยังพบว่า ต่อให้มีกรณีที่การคำนวณเป็นคอขวดจริง ๆ ซึ่งก็เกิดไม่บ่อย พอปรับแต่งส่วนนั้นเสร็จแล้ว สุดท้ายหน่วยความจำก็จะกลายเป็นคอขวดอยู่ดี
    • ไม่เห็นด้วย คุณจะไปถึงจุดที่คอขวดเป็น “ประสิทธิภาพในการดึงหน่วยความจำจากโปรเซสหนึ่งแล้วส่งต่อไปยังอีกโปรเซสหนึ่ง” ไม่ได้เลย ถ้ายังไม่ได้ทำ การปรับแต่งที่เฉพาะกับ FizzBuzz ไปมากพอ
      ตัวอย่างเช่น มีการแทนค่าด้วยบิตอย่างชาญฉลาดที่ทำให้การทดในเลขฐานสิบเกิดขึ้นได้อย่างเป็นธรรมชาติ
      ข้อกำหนดการแข่งขันช่วงแรกก็ไม่ได้กำกวมอะไรนักในประเด็นนี้ เพราะระบุชัดให้วัด throughput ด้วย | pv > /dev/null และยังบอกไว้ด้วยว่า:
      “อนุญาตการปรับแต่งเฉพาะสถาปัตยกรรม / assembly ด้วย นี่ไม่ใช่การแข่งขันจริงจังอะไร แค่อยากเห็นว่าผู้คนจะดัน fizz buzz ไปได้สุดแค่ไหน แม้มันจะทำงานได้เฉพาะในสภาพแวดล้อม/แพลตฟอร์มพิเศษก็ตาม”
    • I/O เป็นสิ่งที่ทุกโปรแกรมต้องทำอย่างแท้จริง โค้ด 99% ที่รันบนฮาร์ดแวร์สมัยใหม่ก็ติดคอขวดตรงนี้ การย้ายไบต์จากที่หนึ่งไปยังอีกที่หนึ่งเป็นสิ่งจำเป็นและค่อนข้างช้า
      การเข้าใจวิธีจัดการ memory I/O และ file I/O ให้มีประสิทธิภาพ เป็นทักษะที่เกี่ยวข้องกับทุกโปรแกรมและโปรแกรมเมอร์
  • มีการบอกให้บันทึกเป็น fizzbuzz.S เลยสงสัยว่าความต่างระหว่างนามสกุล .S กับ .s คืออะไร

    • ตัว S พิมพ์ใหญ่จะรัน preprocessor ก่อน
      ตามคู่มือ:
      file.s
      โค้ดแอสเซมเบลอร์
      file.S
      file.sx
      โค้ดแอสเซมเบลอร์ที่ต้องผ่าน preprocessing
    • เท่าที่จำได้ ตามธรรมเนียมเดิมมันต่างกันที่ว่าจะส่งอินพุตผ่าน preprocessor (.S) หรือไม่ (.s)
      ไม่แน่ใจว่าใน toolchain สมัยใหม่ยังมีความต่างอยู่อีกไหม
    • ตามธรรมเนียมที่ผมคุ้นเคย .S ใช้กับไฟล์แอสเซมบลีที่มนุษย์เขียนเอง ซึ่งมักถูกติดตามใน git ส่วน .s ใช้กับแอสเซมบลีที่เครื่องสร้างขึ้นและเขียนทับได้ถ้าจำเป็น
    • GCC และอื่น ๆ จะไม่เขียนทับ .S แต่ถ้าคุณสั่งให้สร้างแอสเซมบลีออกมา (เช่น gcc -S xyz.c) มันจะเขียนทับ .s
  • โพสต์ก่อนหน้า:
    https://news.ycombinator.com/item?id=29031488
    https://news.ycombinator.com/item?id=29413656

  • ตอนแรกผมอ่านเป็น FritzBox 55 GiB/s FritzBox เป็นเราเตอร์ที่ได้รับความนิยมในยุโรปแถบประเทศที่ใช้ภาษาเยอรมัน
    ISP ของผมก็เพิ่งทวีตเมื่อสัปดาห์ที่แล้วเกี่ยวกับกล่อง OPNSense ที่รองรับ 60 GiB/s ซึ่งจะเปิดให้ใช้เร็ว ๆ นี้[1]
    [1] https://twitter.com/init7/status/1674920410889043973

    • ผมใช้ DEC750 ที่เพิ่มหน่วยความจำเป็น 16GB อยู่ มันเป็นอุปกรณ์ที่ยอดเยี่ยมเพราะรองรับ 10GbE, ทำ WireGuard ได้ 2.3GbE, เงียบ และใช้ไฟขณะ idle แค่ 8W พอ ๆ กับเคเบิลโมเด็ม ผมเลยไม่คิดจะเปลี่ยนมันก่อนช่วงปี 2030~2035
      ผมยังเสียบ USB WiFi adapter ไว้ด้วย ดังนั้นถ้าสายเคเบิลล่ม VLAN บางตัวจะ fail over ได้ และใช้โทรศัพท์มือถือเพื่อรักษาการเชื่อมต่อสำคัญสำหรับงานเอาไว้
      มันไม่ถูก แต่ถ้าคุณอยากสนับสนุนโครงการ OPNSense ทางการเงินพร้อมกับได้ฮาร์ดแวร์ดี ๆ อุปกรณ์ของ Deciso ก็นึกข้อโต้แย้งยากมาก มันประหยัดพลังงาน ใช้ชิ้นส่วนที่เน้นความทนทาน และใช้งานได้ดีเฉย ๆ
      ดีใจที่ได้เห็นไลน์ผลิตภัณฑ์เชิงพาณิชย์แข็งแกร่งขึ้น