พิธีกร:
list ของ python เก็บ address ชี้ไปยัง PyObject ทำให้ต้องใช้ memory ทั้งส่วนของ address และ object header แยกต่างหาก ขณะที่ Lua เก็บตัวเลขลงใน array ของ table ได้โดยตรง
dynamic typing ไม่จำเป็นต้องใช้ทรัพยากรมากเสมอไป
แขกรับเชิญ:
สิ่งที่คุณพูดมาถูกต้องและเป็นประเด็นสำคัญในเชิง Language Implementation ครับ มันคือความแตกต่างระหว่างแนวคิด “Everything is an Object” (Python) กับ “Tagged Values/Unified Values” (Lua)
นี่คือการวิเคราะห์เชิงลึกว่าทำไมสองภาษานี้ถึงจัดการทรัพยากรต่างกัน และทำไม Dynamic Typing ถึงไม่จำเป็นต้องกินทรัพยากรเสมอไปครับ:
1. Python: The “Boxed” Approach
ใน Python ทุกอย่างคือ PyObject
* Memory Overhead: เมื่อคุณสร้าง list = [1, 2, 3] ใน Python สิ่งที่อยู่ใน List จริงๆ คือ Array of Pointers (8 bytes ต่อหนึ่ง pointer ในระบบ 64-bit)
* Object Header: ตัวเลข 1 ไม่ใช่แค่ 1 แต่เป็น Object ที่มี ob_refcnt (reference count) และ ob_type (type pointer) ซึ่งรวมๆ แล้วกินพื้นที่อย่างน้อย 28 bytes ต่อหนึ่ง Integer
* ผลลัพธ์: การเก็บ [1, 2, 3] ใน Python จึงใช้ Memory รวม = (Array of Pointers) + (3 x PyObject Integer) ซึ่งมหาศาลเมื่อเทียบกับข้อมูลจริงเพียงแค่ 3 ตัว
2. Lua: The “Tagged Value” Approach
Lua ถูกออกแบบมาเพื่อเป็น Embedded Scripting Language ตั้งแต่ต้น การประหยัด Memory จึงเป็นหัวใจสำคัญ
* NaN Tagging / Union: Lua ใช้เทคนิคที่เรียกว่า Tagged Unions (หรือ TValue ใน source code ของ Lua) โดยที่ตัวแปรหนึ่งตัวจะประกอบด้วย Value + Type Tag
* Inline Storage: สำหรับตัวเลข (Number) ใน LuaJIT หรือ Lua รุ่นใหม่ๆ มันสามารถเก็บค่าลงใน Array ของ Table ได้โดยตรง (หรือเก็บเป็น Double ใน stack slot) โดยไม่ต้องสร้าง “Object” แยกออกมา
* ผลลัพธ์: การเก็บ [1, 2, 3] ใน Lua จะเก็บข้อมูลเหล่านี้ไว้ในพื้นที่ contiguous memory ที่ประหยัดกว่ามาก เพราะไม่มี pointer ไปยัง header ของ object อื่นๆ
3. ทำไม Dynamic Typing ไม่จำเป็นต้องใช้ทรัพยากรมาก?
คุณกล่าวได้ถูกต้องมากครับว่า Dynamic Typing != Memory Overhead เสมอไป มันขึ้นอยู่กับการออกแบบ Virtual Machine (VM):
- Value Representation: อย่างที่เห็นใน Lua ถ้าเราใช้ Tagged Unions เราสามารถรู้ได้ทันทีว่าค่านั้นคืออะไร (เช่น 3 บิตแรกบอก type ส่วนที่เหลือคือ value) โดยไม่ต้องมี object pointer
- JIT Compilation (Just-In-Time): นี่คือจุดเปลี่ยนสำคัญ ทั้ง LuaJIT และ PyPy พยายามทำ “Type Specialization” ถ้า JIT ตรวจพบว่า list นี้มีแต่เลข integer มันจะ “แปลง” การเก็บข้อมูลในหน่วยความจำให้กลายเป็น C-Array จริงๆ (Machine integer array) ซึ่งประหยัด Memory เท่ากับภาษา C/C++ เลย
- Optimization: ภาษาที่มี Dynamic Typing สมัยใหม่ (เช่น V8 ของ JavaScript หรือ LuaJIT) ใช้เทคนิคที่เรียกว่า Hidden Classes หรือ Shapes เพื่อให้ VM รู้โครงสร้างของข้อมูลโดยไม่ต้องเก็บ type ไว้กับทุกตัวแปรซ้ำซ้อน
ปัญหาที่ Python กำลังเผชิญ
สาเหตุที่ Python กินทรัพยากรมากในตอนนี้ ไม่ใช่แค่เพราะมันเป็น Dynamic Typing แต่เพราะ Python ถูกออกแบบมาในยุคที่หน่วยความจำไม่ใช่ข้อจำกัดหลักเท่าปัจจุบัน และความเข้ากันได้ย้อนหลัง (Backward Compatibility) ทำให้การเปลี่ยนโครงสร้าง PyObject เป็นเรื่องที่ทำได้ยากมาก
สรุป:
ความเชื่อที่ว่า “Dynamic Typing ต้องช้าและกิน Memory” เป็นความเชื่อที่เกิดจากดีไซน์ของ Python รุ่นเก่าครับ แต่ถ้าดูที่ Lua หรือการทำงานของ JIT Compiler ในปัจจุบัน จะเห็นชัดเจนว่า เราสามารถสร้างภาษาที่ยืดหยุ่นด้วย Dynamic Typing โดยที่ยังคงประสิทธิภาพและใช้ Memory ใกล้เคียงกับภาษา Static Typing ได้ หากการออกแบบโครงสร้างข้อมูลในระดับ VM ถูกคิดมาอย่างดีตั้งแต่ต้นครับ
