1 คะแนน โดย GN⁺ 2 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • รีลีสนี้เน้นที่ เมธอดแบบเจเนริก พร้อมปรับปรุง struct literal และ type inference รวมถึงเพิ่มฟีเจอร์ที่ใช้งานได้จริงทั่วทั้ง standard library, runtime และ toolchain
  • การจัดสรรหน่วยความจำเฉพาะตามขนาด ลดต้นทุนการจัดสรรบางส่วนที่น้อยกว่า 80 ไบต์ได้สูงสุด 30% โดยโปรแกรมที่เน้นการจัดสรรคาดว่าจะได้ประสิทธิภาพเพิ่มขึ้นราว 1% และไบนารีใหญ่ขึ้นประมาณ 60KB
  • encoding/json/v2 พร้อมใช้งานอย่างเป็นทางการ และ encoding/json เดิมก็ใช้ v2 ภายในด้วย พร้อมเพิ่มแพ็กเกจ UUID มาตรฐาน และลายเซ็น ML-DSA ตาม FIPS 204
  • โปรไฟล์ goroutineleak, label ของ pprof ใน traceback, synctest.Sleep สำหรับเวลาเสมือน และเซิร์ฟเวอร์ทดสอบ HTTP ในหน่วยความจำ ช่วยให้ การวินิจฉัยและการทดสอบ concurrency ง่ายขึ้น
  • การทำให้ช่อง timer เป็นแบบไม่มีบัฟเฟอร์, การอ่าน response body ของ HTTP จนหมดโดยอัตโนมัติ และการเพิ่มความเข้มงวดข้อจำกัด //go:linkname อาจกระทบโค้ดเดิม จึงต้อง ตรวจสอบความเข้ากันได้ก่อนย้ายไป Go 1.27

ภาษาและระบบชนิดข้อมูล

  • เมธอดแบบเจเนริก ซึ่งเป็นหัวใจของ Go 1.27 สามารถประกาศ type parameter ของตัวเองที่เป็นอิสระจาก receiver ได้
    • ก่อนหน้านี้มีเพียงฟังก์ชันระดับบนสุดเท่านั้นที่เป็นเจเนริกได้ ทำให้ operation ที่เป็นของ type ก็ต้องเขียนเป็นฟังก์ชันของแพ็กเกจ
    • สามารถนิยาม operation ที่คืนค่า element type ต่างจาก input เป็นเมธอดได้ เช่น Box[T].Map[U]
    • อินเทอร์เฟซยังคงประกาศเมธอดที่มี type parameter ไม่ได้ และไม่สามารถ implement อินเทอร์เฟซด้วยเมธอดแบบเจเนริกได้เช่นกัน
  • คีย์ของ struct literal สามารถใช้ field selector ที่ถูกต้องได้ ไม่ใช่แค่ชื่อฟิลด์ระดับบนสุด
    • สามารถ initialize ฟิลด์ที่ถูก promote จาก embedded struct ได้โดยตรง เช่น User{ID: 7}
  • การอนุมานชนิดของฟังก์ชัน ขยายไปถึง conversion และ composite literal
    • อนุมาน type argument ของฟังก์ชันเจเนริกจากชนิดฟังก์ชันที่คาดหวัง เช่น []func([]int) int{first, last}
    • ก่อนหน้านี้ต้อง instantiate เอง เช่น first[int], last[int]

รันไทม์และประสิทธิภาพ

  • คอมไพเลอร์เรียก รูทีนจัดสรรเฉพาะตามขนาด เพื่อลดต้นทุนการจัดสรรหน่วยความจำบางส่วนที่น้อยกว่า 80 ไบต์ได้สูงสุด 30%
    • คาดว่าประสิทธิภาพโดยรวมของโปรแกรมจริงที่เน้นการจัดสรรจะดีขึ้นประมาณ 1%
    • ไม่ต้องแก้โค้ด แต่ไบนารีจะใหญ่ขึ้นประมาณ 60KB ไม่ว่า workload จะเป็นแบบใด
    • ปิดได้ด้วย GOEXPERIMENT=nosizespecializedmalloc แต่ตัวเลือกนี้มีแผนจะถูกลบใน Go 1.28
  • เพิ่มการ optimize ของคอมไพเลอร์ 3 รายการที่เปิดใช้โดยค่าเริ่มต้น
    • known bits data-flow pass ที่ติดตามบิตซึ่งรู้แน่ว่าเป็น 0 หรือ 1 ในค่า เพื่อลบ operation ที่ซ้ำซ้อน
    • loop-invariant code motion ที่ย้ายการคำนวณที่ไม่เปลี่ยนระหว่างวนลูปออกไปนอกลูป
    • คอมไพล์ switch ที่เข้าเงื่อนไขเป็น lookup table รวมถึง fallthrough
  • GOEXPERIMENT=mapsplitgroup แบบทดลองจะเปลี่ยน layout หน่วยความจำของกลุ่ม map จาก KVKVKVKV เป็น KKKKVVVV โดยค่าเริ่มต้นปิดอยู่
  • linker ย้าย type descriptor และ itab ไปยัง section เฉพาะ .go.type และลบ typelinks กับ itablinks
    • เนื่องจาก reflect.typelinks คืนค่า type แทน offset ไลบรารีที่เข้าถึง symbol นี้ด้วย //go:linkname อาจได้รับผลกระทบ

การวินิจฉัย goroutine และ profiling

  • โมดูลที่ go.mod เป็น Go 1.27 ขึ้นไป จะแสดง label ของ runtime/pprof ใน header ของ goroutine ใน traceback
    • บริบทอย่าง {request: 42} ที่ติดผ่าน pprof.Do จะปรากฏใน crash dump, SIGQUIT และ output ของ runtime.Stack
    • ยังสามารถใช้ GODEBUG=tracebacklabels=0 ต่อไปได้ เพื่อไม่ให้ label ที่ละเอียดอ่อนถูกเปิดเผยใน traceback
  • ตัวตรวจจับ goroutine leak ซึ่งเป็นฟีเจอร์ทดลองใน Go 1.26 ได้เลื่อนเป็นโปรไฟล์ goroutineleak อย่างเป็นทางการ
    • รัน GC เพื่อค้นหา goroutine ที่ไม่สามารถคืบหน้าต่อได้ตลอดไป เช่น ติดอยู่ที่ channel หรือ mutex แล้วพิมพ์ stack ออกมา
    • ในบริการสามารถเก็บผ่าน endpoint /debug/pprof/goroutineleak ได้ โดยไม่ต้องใช้ GOEXPERIMENT

การเข้ารหัสและตัวระบุ

  • แพ็กเกจ crypto/mldsa ใหม่ implement ลายเซ็น post-quantum ML-DSA ของ FIPS 204
    • ชุด parameter 3 แบบ ได้แก่ MLDSA44, MLDSA65, MLDSA87 แลกเปลี่ยนระหว่างขนาดคีย์/ลายเซ็นกับระดับความปลอดภัย
    • เพิ่ม ML-DSA ให้กับคีย์และลายเซ็นของ crypto/x509 รวมถึง signature scheme ของ TLS 1.3 ใน crypto/tls
  • แพ็กเกจ uuid ใหม่ใน standard library สร้างและ parse UUID ตาม RFC 9562 ด้วยเลขสุ่มที่ปลอดภัยเชิงเข้ารหัส
    • uuid.New() เลือกวิธีที่เหมาะกับการใช้งานทั่วไป ส่วน NewV4() สร้างแบบสุ่มล้วน และ NewV7() สร้าง UUID เรียงตามเวลาที่สร้าง
    • UUID ที่ประกอบจากเลขสุ่มสามารถเปรียบเทียบได้ จึงใช้ == ได้โดยตรง
    • UUID v7 ที่เรียงตามเวลาเหมาะกับการใช้เป็นคีย์ฐานข้อมูล

JSON และ API ประมวลผลข้อมูล

  • encoding/json/v2 และ encoding/json/jsontext ระดับต่ำพร้อมใช้งานโดยไม่ต้องมี GOEXPERIMENT=jsonv2
    • encoding/json v1 เดิมก็ใช้ implementation v2 ภายในด้วย
    • ไม่จำเป็นต้อง migrate เพราะยังคงพฤติกรรมเดิมไว้ ยกเว้นข้อความ error บางส่วน
    • หากมีปัญหาความเข้ากันได้ สามารถใช้ GOEXPERIMENT=nojsonv2 เพื่อคืน implementation v1 เดิมได้
    • เพื่อประสิทธิภาพ v2 จะไม่ sort คีย์ของ map โดยค่าเริ่มต้น ดังนั้นใน test ที่ต้องการ output แบบเสถียรควรกำหนด json.Deterministic
  • strings.CutLast และ bytes.CutLast แยกค่าจากตัวคั่นตัวสุดท้าย
    • หากไม่มีตัวคั่น จะคืนค่า input ทั้งหมด, ส่วนท้ายว่าง และ false
  • Hasher[T] แบบเจเนริกของ hash/maphash นิยาม กลยุทธ์ hash และ equality ของค่าไว้ด้วยกัน
    • ค่าที่เท่ากันต้องมี hash เดียวกันเสมอ
    • มี ComparableHasher[T] สำหรับ type ที่เปรียบเทียบได้ และสามารถ implement กลยุทธ์กำหนดเอง เช่น string แบบไม่สนตัวพิมพ์เล็กใหญ่ได้
  • math/big.Int.Divide คำนวณ quotient และ remainder พร้อมกัน โดยใช้วิธีปัดเศษที่ระบุชัดเจน ได้แก่ Trunc, Floor, Round, Ceil
    • remainder จะเปลี่ยนตามวิธีปัดเศษด้วย ช่วยเสริมข้อจำกัดของ Quo และ Mod เดิมที่ตัดเข้าหาศูนย์ในโค้ดการเงินและตัวเลข
  • math/rand/v2.(*Rand).N สร้างเลขสุ่มแบบจำกัดช่วงสำหรับ integer หรือ duration type จาก source *Rand ของตัวเอง

SIMD ที่พกพาได้

  • แพ็กเกจ simd แบบทดลองให้ SIMD API ที่ไม่ขึ้นกับขนาดเวกเตอร์ และเปิดใช้ด้วย GOEXPERIMENT=simd
    • ในสภาพแวดล้อมที่รองรับ จะคอมไพล์เป็นคำสั่งเวกเตอร์ของฮาร์ดแวร์จริง มิฉะนั้นจะ fallback เป็น emulation ด้วย Go ล้วน
    • จำนวน lane ของ type อย่าง Float32s ไม่ตายตัว แต่ขึ้นกับความกว้างฮาร์ดแวร์ของแต่ละเครื่อง
  • standard library ใช้ intrinsic ภายใน simd/archsimd อยู่แล้ว
    • MemHash32, MemHash64, StrHash ของ Swiss Table map ถูก implement ใหม่โดยใช้ฐาน SIMD ใหม่

การทดสอบแบบกำหนดผลลัพธ์ได้

  • testing/synctest.Sleep ทำทั้งการเดินเวลาเสมือนและรอให้ goroutine ทั้งหมดนิ่งในครั้งเดียว
    • ภายใน bubble ของ synctest เวลาจริงจะไม่เดิน ดังนั้นการรอ 2 วินาทีก็เสร็จทันที
    • แทนที่โค้ดซ้ำ ๆ ที่เคยใช้ time.Sleep ร่วมกับ synctest.Wait
  • httptest.NewTestServer รันเซิร์ฟเวอร์ด้วย เครือข่ายเสมือนในหน่วยความจำ แทนพอร์ต TCP จริง
    • srv.Client() เชื่อมต่อ handler โดยตรงผ่าน pipe ภายใน process
    • เคลียร์อัตโนมัติด้วย t.Cleanup จึงไม่ต้องใช้ defer srv.Close()
    • เมื่อใช้ร่วมกับ testing/synctest จะทดสอบ HTTP round trip ในเวลาเสมือนได้อย่างรวดเร็วและ deterministic

การเปลี่ยนแปลงด้านเครือข่ายและความเข้ากันได้

  • channel ที่คืนจาก time.After, time.NewTimer, time.NewTicker ฯลฯ จะเป็น channel แบบ synchronous ไม่มีบัฟเฟอร์ เสมอ
    • การตั้งค่า GODEBUG asynctimerchan ที่เคยใช้คืนพฤติกรรมแบบมีบัฟเฟอร์ถูกลบแล้ว
  • ใน HTTP/1 http.Response.Body.Close จะอ่าน body ที่ยังไม่ได้อ่านต่อจนถึงขีดจำกัดแบบระมัดระวัง เพื่อช่วยให้ reuse connection ได้
    • หากเคยพึ่ง Close เพื่อหยุดดาวน์โหลดขนาดใหญ่ก่อนกำหนด ต้องปิดการ reuse ด้วย Transport.DisableKeepAlives
  • เซิร์ฟเวอร์ HTTP/2 เข้าใจ client priority ตาม RFC 9218 และประมวลผล stream priority สูงก่อน
    • สามารถตั้ง Server.DisableClientPriority = true เพื่อคืนพฤติกรรม round-robin เดิมได้
  • crypto/x509.SystemCertPool บน Windows และ macOS รองรับ SSL_CERT_FILE และ SSL_CERT_DIR
    • หากตั้ง variable ไว้ จะอ่าน root certificate จากดิสก์และใช้ verifier ของ Go แทน platform API
    • ปิดได้ด้วย GODEBUG=x509sslcertoverrideplatform=0
  • implementation HTTP/2 ที่เคยอยู่ในไฟล์ generated h2_bundle.go เดิม 12,226 บรรทัด ถูกแทนที่ด้วยแพ็กเกจจริง net/http/internal/http2
  • net/http เพิ่ม HTTP/3 hook แบบ pluggable ที่ยังอยู่ก่อนขั้นเปิด public และบางส่วนของ test suite รันบน HTTP/3
    • ยังไม่มี public API ที่เรียกใช้ได้ แต่มีฐานสำหรับ http.Transport บน QUIC ในอนาคต

Toolchain

  • go test รันการตรวจ vet stdversion โดยค่าเริ่มต้น เพื่อหาการใช้ symbol ของ standard library ที่ใหม่กว่าเวอร์ชัน Go ที่ประกาศใน go.mod
  • สามารถดูเอกสารของเวอร์ชันโมดูลเฉพาะได้ด้วย go doc pkg@version
  • go doc -ex แสดงรายการ example ที่รันได้ และหากระบุชื่อ example โดยตรงจะแสดง source
  • เพิ่ม analyzer สำหรับ modernization ได้แก่ atomictypes, embedlit, slicesbackward, unsafefuncs ให้กับ go fix
    • waitgroup เปลี่ยนชื่อเป็น waitgroupgo และ fmtappendf ถูกลบ
  • ในโมดูล Go 1.27 ขึ้นไป go mod tidy จะรวมบล็อก require ที่กระจัดกระจายเป็น 2 บล็อกสำหรับ direct และ indirect dependency พร้อมรักษาคอมเมนต์ไว้
  • go tool trace -http=:6060 เมื่อระบุแค่พอร์ต จะ bind เฉพาะ localhost และหากต้องการเข้าถึงจากภายนอกต้องระบุ address
  • คำสั่ง go ลบการรองรับระบบควบคุมเวอร์ชัน Bazaar (bzr)
  • compile, link, asm, cgo, cover, pack รองรับ response file (@file) รูปแบบ GCC เพื่อหลีกเลี่ยงข้อจำกัดความยาว command line

Unicode และขอบเขตความปลอดภัย

  • ข้อมูล Unicode ใน standard library อัปเดตจาก Unicode 15 เป็น Unicode 17
    • อักขระที่เพิ่มใน Unicode 16 ก็ถูกจัดหมวดหมู่เป็น symbol และ printable character ได้ถูกต้อง
  • แก้ปัญหาที่สามารถใช้ os.Root.ReadDir และ Readdir หลุดออกไปนอก root ได้
  • การใช้ //go:linkname ที่ไม่ได้รับอนุญาตทำได้ยากขึ้น
    • directive linknamestd ระบุ linkname ที่ import ได้เฉพาะ standard library
    • linker ตรวจการเข้าถึง linkname สำหรับ assembly symbol ด้วย ดังนั้นโค้ดที่พึ่ง symbol ภายในแบบไม่เป็นทางการควรทดสอบล่วงหน้า

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

 
GN⁺ 2 시간 전
ความเห็นจาก Lobste.rs
  • อาจเป็นรีลีสที่ดีที่สุดเท่าที่เคยมีมาเลยก็ได้ ในที่สุดก็มี SIMD, uuid, jsonv2 เข้ามาแล้ว

    • น่าจะต้องรองรับ UUID v5 ด้วยถึงจะย้ายมาได้อย่างสมบูรณ์
  • แม้จะออกนอกประเด็นไปหน่อย แต่นี่เป็นตัวอย่างที่ดีของบั๊กที่เกิดขึ้นเมื่อ มองทุกอย่างเป็นสตริง และไม่คำนึงถึงสัญญาและความสัมพันธ์ระหว่างเมธอดของอินเทอร์เฟซ
    Equal ไม่สนใจตัวพิมพ์เล็กพิมพ์ใหญ่ แต่ Hash ใช้ค่าที่แปลงเป็นตัวพิมพ์เล็กแล้ว อย่างไรก็ตาม EqualFold กับ ToLower ไม่ได้กำหนดความสัมพันธ์ของความเท่ากันแบบเดียวกัน
    https://github.com/golang/go/issues/52204
    "\u017F" (ſ, s แบบยาว) กับ "s" ถือว่าเท่ากันใน EqualFold แต่ผลของการ ToLower แต่ละตัวต่างกัน ดังนั้นถ้าใส่ "s" แล้วตามด้วย "ſ" แฮชจะต่างกันและถูกแทรกเป็นคนละรายการ
    ที่น่าสนใจกว่านั้นคือในเอกสารก็เตือนกรณีนี้ไว้ตรง ๆ เลย
    https://github.com/golang/go/…

    • เพื่อความเป็นธรรม ในเอกสารก็เขียนไว้อย่างชัดเจนว่าอิงตาม simple Unicode case folding
      https://github.com/golang/go/…
  • ดีใจที่มีคนมารับช่วงต่อจากจุดที่ Anton หยุดไว้

    • น่าเสียดายที่อย่างน้อยบางส่วน โดยเฉพาะส่วน การใช้งาน JSON v2 ขั้นพื้นฐาน ดูเหมือนเขียนด้วย LLM สไตล์การเขียนไม่ค่อยถูกใจ แต่เนื้อหาก็ยังน่าสนใจอยู่
    • ดูเหมือนว่า Anton จะไม่ได้หยุดนะ เพราะเขายังเขียน 1.26 ด้วย ดังนั้นบทความนี้น่าจะเป็นแค่มีคนอื่นปล่อยออกมาก่อน
  • ฉันชอบ Go แต่ก็สงสัยว่า ไวยากรณ์ของ Go ยังสง่างามพอไหม เมื่อยังคงเติมฟีเจอร์แบบนี้เข้าไปทุกรีลีส
    func (b Box[T]) Map[U any](f func(T) U) Box[U] {

    • เป็นความเห็นส่วนตัว แต่ก็ไม่คิดว่ามันแย่กว่าภาษาอื่นที่รองรับฟีเจอร์นี้ Zig คล้ายที่สุด ส่วน Rust·TypeScript·C++20 โดยทั่วไปใช้ type เป็นภาชนะของนิยามเมธอดเพื่อลดภาระการเขียนชนิดของตัวรับซ้ำในหลายเมธอด
      ในทางกลับกัน ความยืดยาวของ Go กับ Zig ก็ดีตรงที่ไม่ต้องเลื่อนไปดูที่อื่นเพื่อหานิยามของตัวรับ และที่ จุดเรียกใช้ ซึ่งอาจสำคัญกว่าส่วนที่นิยาม ทุกภาษาก็ดูแทบไม่ต่างกันและไม่จำเป็นต้องระบุ type อย่างชัดเจน
  • ห่างจาก Go ไปพักหนึ่งแล้ว เลยคาดหวังกับ jsonv2 มาก แต่ก็สงสัยว่าถ้ามันกลายเป็นค่าเริ่มต้น จะไม่เท่ากับว่า Go ทำลายคำมั่นเรื่องความเข้ากันได้ที่รักษามานานหรือ

    • ต้อง import jsonv2 จากพาธแยกคือ encoding/json/v2 จึงเป็นสิ่งที่ต้องเลือกใช้เอง ส่วน encoding/json เดิมแม้ภายในจะใช้ v2 แต่ก็ยังรับประกันพฤติกรรมแบบเดิมไว้ และใน jsonv2 ก็มีแฟล็กสำหรับจำลองพฤติกรรมยกเว้นของ v1 ด้วย จึงไม่ได้ทำลาย คำมั่นเรื่องความเข้ากันได้
    • กำลังใช้ ไลบรารี jsontext ของ v2 อยู่ในโปรเจกต์ Go 1.26 และพอใจมาก