คีย์ขนาด 7.8 MB ที่ผมจองหน่วยความจำไว้เพียงเพื่อจะทิ้งไป

Profiler เผยให้เห็นต้นทุนที่ซ่อนอยู่ในโค้ดของผม

Request handler ของผมอ่าน token จาก string ขนาดใหญ่และรวมน้ำหนักของพวกมัน ตรรกะการทำงานนั้นถูกต้อง แต่หน่วยความจำกลับมีปัญหา

ภายใน hot loop ผมจองหน่วยความจำสำหรับ string ถึง 7.8 MB โดยสร้างคีย์ใหม่หนึ่งคีย์สำหรับการค้นหาใน dictionary ทุกครั้ง แล้วก็ทิ้งคีย์นั้นไปทันที

ตัวการคือ Substring เพราะการตัดแบ่งแต่ละครั้งจะสร้าง string object ใหม่ขึ้นมาเสมอ

200,000 tokens

  • Method A (Substring): 23.4 ms, จองหน่วยความจำไป 7,812 KB
  • Method B (Span Lookup): 14.9 ms, จองหน่วยความจำไป 0 KB

Method B ทำงานเร็วกว่า 1.5 เท่า และไม่สร้างขยะ (garbage) เลย

ใน .NET 9 คุณสามารถเรียกใช้ GetAlternateLookup ได้ แทนที่จะใช้ string ใหม่ คุณสามารถส่ง ReadOnlySpan<char> เข้าไปแทน ซึ่งเป็นเพียงหน้าต่างที่มองไปยัง string ต้นฉบับโดยไม่มีการคัดลอกข้อมูล

Dictionary จะทำการ hash ตัว span เทียบกับคีย์ที่มีอยู่โดยตรง ทำให้ได้ผลลัพธ์แบบเดียวกันโดยไม่ต้องจองหน่วยความจำเพิ่ม

สิ่งที่ควรจำ

  • ใช้งานร่วมกับ StringComparer.Ordinal และ StringComparer.OrdinalIgnoreCase ได้
  • จะเกิด error ในขณะ runtime หากคุณใช้ custom comparer ที่ไม่รองรับ alternate lookup
  • เหมาะอย่างยิ่งสำหรับ hot paths เช่น parser, log processor หรือ CSV scanner
  • ไม่จำเป็นต้องใช้สำหรับการค้นหาแบบง่ายๆ ที่มีคีย์เพียงไม่กี่ตัว

ตอนนี้ผมคอยไล่เช็กโค้ดของตัวเองเพื่อหา Substring ที่ตามด้วย TryGetValue เพราะรูปแบบนั้นสิ้นเปลืองหน่วยความจำมหาศาล

Source: https://dev.to/ssukhpinder/78-mb-of-keys-i-allocated-just-to-throw-away-50mn

Optional learning community: https://github.com/ssukhpinder/dev-to-code-samples/tree/main/023-dictionary-alternate-lookup