2 คะแนน โดย GN⁺ 2025-09-24 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • ภาษา Go ได้เพิ่มการรองรับ Valgrind อย่างเป็นทางการ
  • การเปลี่ยนแปลงนี้ช่วยเสริมความสามารถด้าน การตรวจจับข้อผิดพลาดของหน่วยความจำ และ การดีบัก
  • นักพัฒนาสามารถตรวจพบ หน่วยความจำรั่วและข้อผิดพลาดในการเข้าถึงหน่วยความจำ ได้ง่ายขึ้น
  • การปรับปรุงความเข้ากันได้กับ Valgrind ช่วยให้งานพอร์ตและการบำรุงรักษาเป็นไปอย่าง มีประสิทธิภาพ
  • ทำให้ประเมิน ความเสถียรของโค้ด Go ได้ง่ายขึ้นบนหลากหลายแพลตฟอร์ม

ความสำคัญของการเพิ่มการรองรับ Valgrind ใน Go

  • มีการเพิ่ม การรองรับ Valgrind ใน Go ทำให้นักพัฒนาสามารถใช้เครื่องมือตรวจจับข้อผิดพลาดของหน่วยความจำได้อย่างเป็นทางการ
  • การเปลี่ยนแปลงนี้ทำให้สามารถตรวจพบปัญหาอย่าง use-after-free, หน่วยความจำรั่ว, การเข้าถึงหน่วยความจำที่ไม่ถูกต้อง ในโค้ด Go ได้
  • Valgrind ถูกใช้อย่างแพร่หลายในการตรวจหาปัญหาหน่วยความจำในหลายภาษา และสำหรับชุมชน Go นี่เป็นการเปลี่ยนแปลงสำคัญเพื่อเสริม ความน่าเชื่อถือและความทนทาน
  • ฟีเจอร์ที่เพิ่มเข้ามาช่วยให้งานต่าง ๆ กับโปรแกรม Go บนหลายแพลตฟอร์ม เช่น การดีบัก การตรวจสอบคุณภาพ และการประเมินความเสถียร ทำได้สะดวกขึ้น
  • จุดสำคัญของอัปเดตครั้งนี้คือมีการเพิ่ม โค้ด instrumentation สำหรับ Valgrind เข้าไปในชั้นรันไทม์ของ Go

Valgrind คืออะไร?

  • Valgrind เป็นเครื่องมือโอเพนซอร์สสำหรับนักพัฒนาที่ใช้ตรวจสอบ ข้อผิดพลาดของหน่วยความจำ ข้อผิดพลาดของเธรด และหน่วยความจำรั่ว
  • ถูกใช้อย่างแพร่หลายในภาษาระดับระบบอย่าง C, C++ และให้ การตรวจจับที่แม่นยำ สำหรับปัญหาด้านการจัดการหน่วยความจำ

สรุปการเพิ่มฟีเจอร์ครั้งนี้

  • code instrumentation ที่เกิดจากการเปลี่ยนแปลงครั้งนี้ เป็นความสามารถที่ทำให้ Valgrind ติดตามเหตุการณ์ที่เกี่ยวข้องกับหน่วยความจำที่ถูกจัดสรรแบบไดนามิกในรันไทม์ของ Go ได้อย่างแม่นยำ
  • นักพัฒนาสามารถรันโปรแกรม Go ด้วย Valgrind เพื่อวิเคราะห์ ปัญหาหน่วยความจำที่อาจเกิดขึ้น หรือการเข้าถึงพอยน์เตอร์ที่ไม่ถูกต้องได้อย่างมีประสิทธิภาพ
  • ผลลัพธ์คือ โครงสร้างพื้นฐานหรือบริการที่พัฒนาด้วย Go จะได้ประโยชน์ด้าน การรักษาคุณภาพโค้ดสูงและการป้องกันปัญหาเชิงรุก

ผลที่คาดว่าจะเกิดขึ้นจากการเปลี่ยนแปลง

  • กระบวนการ ตรวจจับข้อผิดพลาดของหน่วยความจำ และ ปรับปรุงคุณภาพโค้ด ในโครงการ Go จะมีความละเอียดแม่นยำมากขึ้น
  • คาดว่าจะช่วยให้การสร้าง ความเข้ากันได้และความน่าเชื่อถือ ของโค้ด Go ที่นำไปใช้งานบนหลากหลายแพลตฟอร์มทำได้ง่ายขึ้น

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

 
taptaps 2025-09-25

พอเห็นโพสต์เกี่ยวกับภาษา Go ทีไร เหมือนในคอมเมนต์จะต้องมี 'แต่ Rust ไม่เป็นแบบนั้นนี่', 'แต่ Rust ไม่จำเป็นต้องทำแบบนั้นนี่' อยู่ตลอดเลย 555

 
GN⁺ 2025-09-24
ความคิดเห็นจาก Hacker News
  • ผมเป็นผู้เขียน CL นี้เอง โดยตั้งใจจะใช้การปรับปรุงครั้งนี้เพื่อนำความสามารถในการติดตามการเริ่มต้นค่าเมมโมรีไปใช้ทดสอบว่าโค้ดคริปโตทำงานภายในเวลาที่คงที่หรือไม่ (การทดสอบ constant-time) ซึ่งเป็นแนวทางคล้ายกับที่ agl เคยเสนอไว้เมื่อราว 15 ปีก่อนและที่ BoringSSL เคยทำ ลิงก์ที่เกี่ยวข้อง เพราะคุณสมบัตินี้ทดสอบได้ยากมาก นอกจากนี้ยังคาดหวังผลที่น่าสนใจอีกหลายอย่าง เช่น การสำรวจว่าการเปิดใช้ Valgrind ใน Go จะช่วยให้ติดตามการจัดการหน่วยความจำของรันไทม์ได้อย่างถูกต้องหรือไม่ แต่ขอย้ำว่านี่เป็นการรองรับในขั้นทดลองเท่านั้น ยังไม่มั่นใจ 100% ว่าทุกคอนฟิกจะทำงานได้ดี และอาจมีคำเตือนที่เข้าใจยากเพิ่มขึ้น
    • สงสัยว่าชุมชนพอจะช่วยพัฒนาในส่วนไหนได้บ้างไหม
    • คิดว่าเป็นฟีเจอร์ที่เจ๋งมาก และหวังว่าจะช่วยค้นพบปัญหาอื่น ๆ ของ Go ได้ด้วย แต่ก็สงสัยว่าแทนที่จะติดตามแบบซับซ้อน เราจะไม่สามารถเอาค่าอินพุตหลากหลายแบบไปป้อนให้ฟังก์ชันเข้ารหัส แล้ววัดเวลารันตรง ๆ ถ้าผลออกมาเท่ากันก็ใช้ตรวจสอบการคงเวลาได้หรือไม่ เช่น ต่อให้มี Garbage Collection หรือสัญญาณรบกวนจาก OS ถ้าวัดเวลาจากหลายอินพุตแล้วอยู่ในช่วงคลาดเคลื่อน epsilon ก็น่าจะพอได้ อีกทั้ง CPU บางรุ่นมีตัวนับ conditional branch ซึ่ง rr debugger ก็ใช้อยู่ อาจใช้ค่านี้เปรียบเทียบจำนวน branch ก่อนและหลังถอดรหัสได้ด้วย (เชื่อมโยงกับข้อเสนอในบทความของ agl ที่ว่าถ้า branch เหมือนกันก็จะได้ constant-time) และยังอาจใช้วิธีเพิ่ม time padding หลังวัดเวลาสูงสุดของการถอดรหัส 10 ครั้งแรก โดยบังคับให้ทุกครั้งหลังจากนั้นใช้เวลาเท่ากันผ่านการรัน noop เป็นต้น ถ้าเกินเพดานเวลาที่ตั้งไว้ก็อาจ assert ให้โปรแกรมล้มได้ด้วย เพียงแต่ถ้า OS ดึงการจัดตารางออกไปจนอิงเวลาเพี้ยน วิธีนี้ก็จะลำบาก
  • ชอบมากที่หลังจากเพิ่ม Valgrind header เข้าไปใน tree แล้ว ไม่เลือกใช้ cgo เพื่อเรียก macro สำหรับ Valgrind client request แบบต่าง ๆ แต่เลือกใช้ฟังก์ชันแอสเซมบลีเพียงตัวเดียวเพื่อส่งคำสั่งที่ต้องการโดยตรงและ trigger client request แทน วิธีคิดแบบนี้เหมาะกับ bootstrap toolchain มาก คือสร้างแค่ building block ขั้นต่ำ แล้วจัดการส่วนที่เหลือทั้งหมดในระดับภาษา
    • ถ้าไม่เลือกวิธีนี้แล้วต้องหลีกเลี่ยงวิธีเดิมหรือทางเลือกอื่น ผมสงสัยว่าจะต้องทำอย่างไรถึงจะยังทำให้กระบวนการแบบ Go เรียบง่ายแต่มีประสิทธิภาพใกล้เคียงเดิมได้ คิดว่านี่เป็นโจทย์ที่ยังต้องหาคำตอบต่อไป
  • ดีใจที่เห็น rsc ยังมีส่วนร่วมอย่างแข็งขันอยู่ โดยเฉพาะการใส่คอมเมนต์ไว้ใน commit message ด้วย ยิ่งอายุมากขึ้นก็ยิ่งรู้สึกว่าความสำคัญของ commit message มีมากขึ้น ถ้าเขียนแค่ "เพิ่ม valgrind" ไว้ มันแทบไม่ช่วยอะไรตอนต้องกลับมาค้นจาก archive ภายหลัง
    • rsc เป็นนักพัฒนาระดับร็อกสตาร์จริง ๆ ตอนนี้ยังลองอะไรใหม่ ๆ เยอะมาก เช่น เอา AI มาช่วยจัดการ issue หรือ PR และคาดว่าน่าจะได้ผลลัพธ์ดีไม่น้อย
  • ฟีเจอร์นี้จะมีประโยชน์ก็ต่อเมื่อมีการรันทดสอบกับทุกแพ็กเกจเท่านั้น ไม่อย่างนั้นมันจะถูกกลบด้วยคำเตือนที่ไม่เกี่ยวข้อง ซึ่งเป็นเหตุผลที่ทำให้ใช้ Valgrind กับโค้ด Python ได้ยาก
    • ถ้าเป็นจริงแบบนั้น ก็ควรใช้กับ C, C++ ได้เหมือนกัน ในกรณีของผมเคยใช้ Valgrind กับโปรแกรมไฮบริด Python + Boost C++ แล้วใช้เวลาทำ suppressions file อยู่ประมาณชั่วโมงหนึ่ง จากนั้นก็ใช้งานได้ไม่มีปัญหา
    • ดูเหมือน local LLM จะมีประโยชน์ในการช่วยจัดระเบียบและสรุปข้อมูลที่มีมากเกินไป ซึ่งทำให้บทบาทของ toolchain แบบมีแบตเตอรี่มาครบอย่าง Valgrind สำคัญมาก
    • ในบริบทของ Go คำว่า "ทุกแพ็กเกจต้องมีการทดสอบ" หมายถึงอะไร
  • Valgrind เป็นเหมือนพลังพิเศษที่ซ่อนอยู่ สำหรับซอฟต์แวร์ส่วนใหญ่ที่ผมเขียน ผมจะรัน test case ด้วย make check แล้วรันซ้ำแบบเดิมอีกครั้งภายใต้ Valgrind ด้วย make check-valgrind อันหลังใช้เฉพาะบนเครื่องนักพัฒนา วิธีนี้ช่วยให้เจอทั้ง memory leak และบั๊กละเอียดอ่อนได้บ่อยมาก
    • เห็นด้วยบางส่วน แต่เมื่อเข้าไปสู่โลกของ multithreading (ซึ่ง Go ใช้มาก) abstraction layer ของ Valgrind จะทำงานได้ไม่ค่อยดี อย่างน้อยจากตอนที่ผมเคยเจาะลึกโค้ด C++ มาพอสมควร เพราะมันใช้ scheduler ของตัวเอง ทำให้ปัญหา concurrency จริง ๆ หรือ race condition ที่เกิดในสถานการณ์จริงมักไม่แสดงออกชัดใน Valgrind และโดยทั่วไปมันก็ช้ามากด้วย ถึงอย่างนั้นก็จริงที่มันช่วยได้มากหลายครั้ง และก็ยังดีใจที่มันยังคงมีอยู่
  • การรองรับครั้งนี้เจ๋งมาก น่าจะทำให้เห็นบั๊กเพิ่มขึ้นอีกไม่น้อย แต่สงสัยว่าทำไมถึงเลือก Valgrind เพราะผมคิดว่า Clang AddressSanitizer (asan) กับ MemorySanitizer (msan) หา error ได้หลากหลายกว่า (เช่น use-after-return) และยังเร็วกว่าเยอะ
    • Go ไม่ได้ใช้ clang/llvm ดังนั้นเครื่องมือพวกนั้นจึงใช้ไม่ได้
    • Go มีการรองรับ msan/asan ของตัวเองอยู่แล้วมาหลายปี
    • Valgrind เร็วกว่ามาก และยัง attach เข้ากับโปรแกรมที่กำลังรันอยู่ได้
    • Valgrind ยังมีความสามารถอีกหลายอย่าง เช่น การติดตามหน่วยความจำ การทำ memory profiling ฯลฯ จึงยอดเยี่ยมมากในมุมของการติดตามประสิทธิภาพด้วย
  • น่าประทับใจมาก หนึ่งในปัญหาใหญ่ของ Go คือ profiling และอาการ memory leak / memory pressure ที่เกิดบ่อย และตอนนี้ผมยังไม่รู้เครื่องมือทางเลือกที่จะแก้ปัญหานี้ได้ดีนัก
    • อยากฟังรายละเอียดเพิ่มว่าคุณเจอปัญหา profiling แบบไหนบ้าง ถ้า inuse memory profile ยังไม่พอสำหรับตาม memory leak (ไม่แน่ใจว่าเคยใช้ gorefs หรือยัง) หรือถ้า memory pressure ที่ว่าคือคนละแบบกันก็ช่วยอธิบายเพิ่มได้ repo ของ goref อนึ่ง ผมทำงานด้าน continuous profiling อยู่ที่ Datadog และมีส่วนร่วมกับ Go runtime profiling อย่างต่อเนื่อง
    • ผมสงสัยว่าในภาษาที่มี GC นั้น "memory leak แบบต่อเนื่อง" เกิดขึ้นได้อย่างไร
    • ในอุดมคติ ผมคิดว่าน่าจะมีวิธีควบคุมได้แบบชัดเจนเหมือนภาษาอื่นว่าจะเอาอะไรไว้บน stack แทนที่จะเชื่อแค่ escape analysis ตอนนี้ต้องคอยใช้ตัวเลือกอย่าง -gcflags -m=3 หรือปรับค่าประเภท "ui.codelenses", "ui.diagnostic.annotations" ในปลั๊กอิน Go ของ VSCode ซึ่งไม่ค่อยสะดวก
    • สำหรับเรื่อง "memory leak / memory pressure แบบต่อเนื่อง" ใน Go อย่าสร้าง goroutine ถ้ายังไม่รู้ชัดว่าจะ cleanup มันอย่างไร
    • pprof ก็ทำงานได้ค่อนข้างดีอยู่แล้ว แต่อยากรู้ว่าคุณต้องการฟีเจอร์เพิ่มเติมอะไรบ้าง
  • ความพยายามครั้งนี้ดูโอเค แม้ว่าจะมีความเสี่ยงถ้ากลไก client request เปลี่ยนไป แต่ header แทบไม่ค่อยเปลี่ยนอยู่แล้ว (โดยเฉพาะตอนเพิ่มแพลตฟอร์มใหม่) และ Go ก็รองรับแค่ amd64 กับ arm64 จึงไม่น่ามีปัญหามาก ข้อดีที่แท้จริงของการปรับปรุงนี้ไม่ใช่การป้องกัน memory leak แต่คือการระบุหน่วยความจำที่ยังไม่ได้ initialize ได้อย่างแม่นยำ เพราะเมื่อมีการ reuse หน่วยความจำ ถ้าไม่มีการทำ "poisoning" (ทำให้ห้ามใช้โดยตั้งใจ) ก็จะวิเคราะห์ได้ยาก ฟีเจอร์นี้ยังมีประโยชน์มากกับเครื่องมืออื่นเกือบทั้งหมด ยกเว้น cachegrind และ callgrind
  • สำหรับผม การรองรับครั้งนี้ให้ความรู้สึกเหมือนไม่ใช่ข้อได้เปรียบ แต่ค่อนข้างเป็นความล้มเหลวเล็ก ๆ ของ ecosystem ฝั่ง Go มากกว่า ผมชอบ Valgrind มาก และเคยใช้บ่อยสมัยเป็นนักพัฒนา C แต่การที่ Go ยังต้องการ Valgrind ทำให้รู้สึกเหมือนภาษาหรือ ecosystem ยังมีจุดที่ไม่สมบูรณ์ ตลอดเวลาประมาณ 6 ปีที่ใช้ Rust ผมไม่เคยรู้สึกว่าต้องการ Valgrind เลย (ยกเว้นมีเพื่อนร่วมทีมเคยใช้ครั้งหนึ่ง) แม้จะเข้าใจว่าความรู้สึกนี้คงมาจาก cgo แต่ก็ยังอดรู้สึกว่าเหมือนถอยหลังไม่ได้
    • ผมไม่ค่อยเข้าใจว่าทำไมคอมเมนต์ยอดนิยมเกี่ยวกับ Go ถึงต้องพาดพิง Rust แบบเหน็บแนมอยู่เสมอ มันให้ความรู้สึกทั้งตั้งรับและแฝงความเหนือกว่ามากขึ้นเรื่อย ๆ
    • การรองรับนี้มีเป้าหมายหลักเพื่อใช้ทดสอบโค้ด constant-time มากกว่าการติดตามหน่วยความจำให้ถูกต้อง คำอธิบายที่เกี่ยวข้อง ยังพอมีอยู่บ้าง
    • ผมก็เคยใช้ Valgrind กับ Rust มานิดหน่อย ไม่ได้ต้องใช้บ่อย แต่ก็มีความจำเป็นจริง Rust เป็นภาษาที่ใช้ได้ในสภาพแวดล้อมหลากหลายมาก ตั้งแต่โค้ดเชิงฟังก์ชันระดับสูงไปจนถึงโค้ดไมโครคอนโทรลเลอร์แบบ C-like ระดับต่ำ บางที่ไม่ใช้ unsafe เลย บางที่จำเป็นต้องใช้ และการใช้ FFI กับ C ก็เป็นเรื่องปกติ สุดท้ายจึงมีวันที่ต้องใช้มัน เมื่อก่อนตอนทำ Rust module ให้ nginx (ก่อนจะมี binding ทั้งทางการและไม่ทางการ) ผมพลาดบ่อยมากและ Valgrind ก็ช่วยไว้
    • ความถี่ในการใช้ขึ้นอยู่กับว่าคุณเขียนโค้ด unsafe มากแค่ไหน ใช้ unsafe crate มากน้อยเพียงใด หรือเชื่อมกับไลบรารี C/C++ มากแค่ไหน แม้แต่ใน Java, .NET, Node ก็มีกรณีที่จำเป็นเพราะ external dependency ได้เหมือนกัน