2 คะแนน โดย GN⁺ 2024-11-30 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

ทำไมไปป์ถึงเหมือน "ค้าง": การบัฟเฟอร์

  • คำอธิบายปัญหา: เมื่อรันคำสั่ง tail -f /some/log/file | grep thing1 | grep thing2 เพื่อค้นหาเอาต์พุตบางอย่างจากไฟล์ล็อก หากมีการเพิ่มบรรทัดล็อกเข้ามาช้า ๆ อาจเกิดปัญหาที่ไม่มีเอาต์พุตแสดงออกมา ดูเหมือนว่าไปป์จะค้าง แต่จริง ๆ แล้วเป็นเพราะโปรแกรมยังไม่เขียนข้อมูลลงไปในไปป์

สาเหตุของการบัฟเฟอร์

  • เหตุผลที่มีการบัฟเฟอร์: โดยทั่วไปโปรแกรมมักจะทำการบัฟเฟอร์ก่อนเขียนข้อมูลลงไปยังไปป์หรือไฟล์ เพื่อเพิ่มประสิทธิภาพ แทนที่จะเขียนเอาต์พุตทุกครั้งทันที ก็จะสะสมข้อมูลให้ได้ปริมาณหนึ่งแล้วค่อยเขียนออกครั้งเดียว
  • ตัวอย่าง: grep thing1 อาจเก็บข้อมูลที่แมตช์ไว้จนกว่าจะครบ 8KB ซึ่งทำให้เอาต์พุตอาจยังไม่แสดงออกมา

เวลาเขียนไปยังเทอร์มินัลจะไม่บัฟเฟอร์แบบเดียวกัน

  • ความต่างระหว่างเทอร์มินัลกับไปป์: grep จะใช้ line buffering เมื่อเอาต์พุตส่งไปยังเทอร์มินัล แต่จะใช้ block buffering เมื่อส่งไปยังไปป์ โดยตัดสินจากฟังก์ชัน isatty

คำสั่งที่บัฟเฟอร์และคำสั่งที่ไม่บัฟเฟอร์

  • คำสั่งที่ไม่บัฟเฟอร์: tail, cat, tee เป็นต้น จะไม่บัฟเฟอร์
  • คำสั่งที่บัฟเฟอร์: grep, sed, awk, tcpdump, jq, tr, cut เป็นต้น จะมีการบัฟเฟอร์ และบางตัวสามารถปิดการบัฟเฟอร์ได้ด้วยแฟล็กเฉพาะ

การบัฟเฟอร์เอาต์พุตเริ่มต้นของภาษาโปรแกรม

  • ภาษาที่มีการบัฟเฟอร์: C, Python, Ruby, Perl เป็นต้น จะมีการบัฟเฟอร์เอาต์พุตโดยปริยาย และสามารถปิดได้ด้วยวิธีเฉพาะ

ข้อมูลในบัฟเฟอร์สูญหายเมื่อกด Ctrl-C

  • คำอธิบายปัญหา: เมื่อกด Ctrl-C เนื้อหาที่อยู่ในบัฟเฟอร์จะสูญหาย เพราะสัญญาณ SIGINT ถูกส่งไปก่อน
  • วิธีแก้: หา PID ของ tcpdump แล้วรัน kill -TERM $PID เพื่อ flush บัฟเฟอร์

รีไดเร็กต์ไปยังไฟล์ก็ยังมีการบัฟเฟอร์

  • การรีไดเร็กต์ไฟล์: แม้จะรีไดเร็กต์ไปยังไฟล์ก็ยังเกิดการบัฟเฟอร์ แต่จะไม่เกิดปัญหาข้อมูลในบัฟเฟอร์สูญหายจาก Ctrl-C

หลายวิธีในการหลีกเลี่ยงการบัฟเฟอร์

  • วิธีแก้ 1: รันโปรแกรมที่จบการทำงานได้เร็ว
  • วิธีแก้ 2: ใช้แฟล็ก --line-buffered ของ grep
  • วิธีแก้ 3: ใช้ awk
  • วิธีแก้ 4: ใช้ stdbuf
  • วิธีแก้ 5: ใช้ unbuffer

ตัวแปรสภาพแวดล้อมสำหรับปิดการบัฟเฟอร์

  • แนวคิด: มีความเห็นว่าน่าจะมีตัวแปรสภาพแวดล้อมมาตรฐานแบบ PYTHON_UNBUFFERED ให้ใช้ร่วมกันได้ เช่นเสนอชื่อว่า NO_BUFFER

เนื้อหาที่ไม่ได้กล่าวถึง

  • หัวข้อที่ละไว้: ความต่างระหว่าง line buffering กับการไม่บัฟเฟอร์เลย, ความต่างของการบัฟเฟอร์ระหว่าง stderr และ stdout, การบัฟเฟอร์ของ TTY driver ในระบบปฏิบัติการ เป็นต้น

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

 
GN⁺ 2024-11-30
ความเห็นจาก Hacker News
  • การเข้าถึงแบบมีบัฟเฟอร์ควรถูก flush เมื่อถึงจำนวนไบต์หรือเวลาที่กำหนด เป็นวิธีทั่วไปที่ใช้แก้ปัญหาคล้ายกันในอินเทอร์เฟซฮาร์ดแวร์

    • ไลบรารีที่ทำบัฟเฟอร์ใน user space ควรตั้ง timer ที่เหมาะสมตั้งแต่ตอนเริ่มบัฟเฟอร์ข้อมูลครั้งแรก
    • พารามิเตอร์ timeout ควรถูกส่งเป็นอาร์กิวเมนต์ หรือกำหนดให้ต่ำกว่ามาตราส่วนเวลาของมนุษย์เล็กน้อย หรือแปรผันตามแบนด์วิดท์/threshold หรือแปรผันตาม overhead ของการ flush
    • ใช้ได้ทั้งกับการเขียนและการอ่าน และอาจแตกต่างกันไปตามแต่ละ data channel
  • อยากให้ระบบ flush บัฟเฟอร์ทั้งหมดเมื่อ CPU ทั้งระบบเข้าสู่สถานะ idle

    • โดยทั่วไปการทำบัฟเฟอร์คือเทคนิคเพื่อประหยัด CPU
    • เมื่อ CPU ว่าง ควรส่งสัญญาณไปยังทุกโปรเซสว่า "ให้ flush บัฟเฟอร์"
  • ใช้ระบบตระกูล NIX มานานกว่า 20 ปีแล้ว แต่ก็ยังลืมเรื่องปัญหาบัฟเฟอร์อยู่เสมอ

  • ใช้ Unix มานานกว่า 35 ปี แต่ไม่เคยเข้าใจกลไกการทำงานของบัฟเฟอร์อย่างถ่องแท้ คำอธิบายนี้มีประโยชน์มาก

  • กำลังสับสนระหว่าง "unbuffered" กับ "line buffered"

    • แบบ unbuffered อาจทำให้ประสิทธิภาพลดลง และอาจสร้างเอาต์พุตที่ผิดพลาดได้เมื่อมีหลายแหล่งเขียนลง pipe เดียวกัน
    • แบบ line buffered เป็นค่าปริยายของเทอร์มินัล และเหมาะกับ pipe
  • บัฟเฟอร์มีอยู่เพราะการเขียนลงบัฟเฟอร์ช้ากว่าการพิมพ์เอาต์พุตขึ้นหน้าจออย่างมาก

    • เป็นปัญหาที่เจอบ่อยเมื่อทำงานกับ UART และมีวิธีแก้หลายแบบ
    • มีหลายวิธี เช่น ใช้อักขระพิเศษ วิธีตามความยาว และวิธีตามเวลา
  • เมื่อกด Ctrl-C เนื้อหาในบัฟเฟอร์อาจสูญหายได้

    • หลายคนคิดว่าโปรแกรมส่วนใหญ่จะ flush บัฟเฟอร์เมื่อเจอ SIGINT
  • เคยเจอปัญหาบัฟเฟอร์บน Unix และ awk แต่ละ implementation ก็ไม่ได้ทำงานเหมือนกันทั้งหมด

  • รู้สึกเหมือนพลาดมุกเรื่องท่อที่จับตัวเป็นน้ำแข็ง