38 คะแนน โดย GN⁺ 2026-01-02 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ผลลัพธ์เบนช์มาร์กที่วัด ตัวเลขด้านประสิทธิภาพของการคำนวณ หน่วยความจำ และอินพุต/เอาต์พุตของ Python อย่างเป็นระบบ โดยทำให้เวลาและการใช้หน่วยความจำของแต่ละการทำงานเป็นค่าที่วัดได้
  • ในด้าน ความเร็ว มีการแสดงเวลาแฝงสัมพัทธ์ของการทำงานหลากหลายประเภท เช่น การเข้าถึงแอตทริบิวต์ 14ns, การเพิ่มข้อมูลลงลิสต์ 29ns, การเปิดไฟล์ 9μs, การตอบสนองของ FastAPI 8.6μs
  • ในด้าน หน่วยความจำ มีการให้ตัวเลขเฉพาะเจาะจง เช่น สตริงว่าง 41 ไบต์, จำนวนเต็ม 28 ไบต์, ลิสต์ว่าง 56 ไบต์, ดิกชันนารีว่าง 64 ไบต์, โปรเซสว่าง 16MB
  • มีการเปรียบเทียบความต่างด้านประสิทธิภาพของไลบรารีมาตรฐานและไลบรารีทางเลือกในแต่ละด้าน เช่น โครงสร้างข้อมูล การซีเรียลไลซ์ และการประมวลผลแบบอะซิงก์ (orjson, msgspec เป็นต้น)
  • บทเรียนสำคัญ ที่เน้นคือ โอเวอร์เฮดหน่วยความจำของออบเจ็กต์ Python ที่สูง, การค้นหาที่รวดเร็วของ dict/set, ผลของ __slots__ ในการลดการใช้หน่วยความจำ, และการตระหนักถึงโอเวอร์เฮดของการประมวลผลแบบอะซิงก์

ภาพรวม

  • เอกสารที่รวบรวม ตัวชี้วัดด้านประสิทธิภาพ ที่นักพัฒนา Python ควรรู้ โดยแสดงค่าจริงที่วัดได้ของความเร็วในการทำงานและการใช้หน่วยความจำ
  • เบนช์มาร์กดำเนินการบนสภาพแวดล้อม CPython 3.14.2, Mac Mini M4 Pro (ARM, 14 คอร์, RAM 24GB)
  • ผลลัพธ์เน้นที่ การเปรียบเทียบเชิงสัมพัทธ์ และมีการเปิดเผยโค้ดกับข้อมูลไว้ในคลัง GitHub

การใช้หน่วยความจำ (Memory Costs)

  • โปรเซส Python ว่าง ใช้หน่วยความจำ 15.73MB
  • สตริง มีขนาดพื้นฐาน 41 ไบต์ และเพิ่มอีก 1 ไบต์ต่ออักขระ 1 ตัว
    • ตัวอย่าง: สตริงว่าง 41B, สตริงยาว 100 ตัวอักษร 141B
  • ชนิดตัวเลข: จำนวนเต็มขนาดเล็ก (0–256) 28B, จำนวนเต็มขนาดใหญ่ (1000) ก็ 28B, จำนวนเต็มขนาดใหญ่มาก (10ⁱ⁰⁰) 72B, จำนวนทศนิยมแบบลอยตัว 24B
  • ขนาดพื้นฐานของ คอลเลกชัน: ลิสต์ 56B, ดิกชันนารี 64B, เซต 216B
    • เมื่อมี 1,000 รายการ: ลิสต์ 35.2KB, ดิกชันนารี 63.4KB, เซต 59.6KB
  • อินสแตนซ์ของคลาส: คลาสทั่วไป (5 แอตทริบิวต์) 694B, คลาส __slots__ 212B
    • เมื่อมีอินสแตนซ์ 1,000 ตัว: คลาสทั่วไป 165.2KB, คลาส __slots__ 79.1KB

การทำงานพื้นฐาน (Basic Operations)

  • การคำนวณเลขคณิต: การบวกจำนวนเต็ม 19ns, การบวกจำนวนจริง 18.4ns, การคูณจำนวนเต็ม 19.4ns
  • การทำงานกับสตริง: การต่อสตริง 39.1ns, f-string 64.9ns, .format() 103ns, การฟอร์แมตแบบ % 89.8ns
  • การทำงานกับลิสต์: append() 28.7ns, list comprehension (1,000 รายการ) 9.45μs, for loop แบบเดียวกัน 11.9μs
    • list comprehension เร็วกว่า for loop ราว 26%

การเข้าถึงและการวนซ้ำคอลเลกชัน (Collection Access and Iteration)

  • การเข้าถึงด้วยคีย์/ดัชนี: การค้นหาในดิกชันนารี 21.9ns, การตรวจสมาชิกในเซต 19ns, การเข้าถึงดัชนีในลิสต์ 17.6ns
    • การตรวจสมาชิกในลิสต์ (1,000 รายการ) ใช้เวลา 3.85μs ซึ่งช้ากว่าเซต/ดิกชันนารีประมาณ 200 เท่า
  • การตรวจความยาว: len() สำหรับลิสต์ 18.8ns, ดิกชันนารี 17.6ns, เซต 18ns
  • การวนซ้ำ: ลิสต์ (1,000 รายการ) 7.87μs, ดิกชันนารี 8.74μs, sum() 1.87μs

คลาสและแอตทริบิวต์ (Class and Object Attributes)

  • ความเร็วในการเข้าถึงแอตทริบิวต์: ทั้งคลาสทั่วไปและคลาส __slots__ อ่านได้ที่ 14.1ns, เขียนประมาณ 16ns
  • การทำงานอื่น ๆ: การอ่าน @property 19ns, getattr() 13.8ns, hasattr() 23.8ns
  • เมื่อใช้ __slots__ จะได้ ผลในการประหยัดหน่วยความจำมากกว่า 2 เท่า โดยความเร็วในการเข้าถึงยังอยู่ในระดับเดียวกัน

JSON และการซีเรียลไลซ์ (JSON and Serialization)

  • ประสิทธิภาพของไลบรารีทางเลือกเทียบกับไลบรารีมาตรฐาน
    • orjson ซีเรียลไลซ์ออบเจ็กต์ซับซ้อนได้ใน 310ns ซึ่งเร็วกว่า json ที่ใช้ 2.65μs มากกว่า 8 เท่า
    • msgspec อยู่ที่ 445ns, ujson อยู่ที่ 1.64μs
  • ในการ ดีซีเรียลไลซ์ orjson ก็เร็วที่สุดที่ 839ns
  • Pydantic: model_dump_json() 1.54μs, model_validate_json() 2.99μs

เว็บเฟรมเวิร์ก (Web Frameworks)

  • สำหรับการตอบกลับ JSON แบบเดียวกัน: FastAPI 8.63μs, Starlette 8.01μs, Litestar 8.19μs, Flask 16.5μs, Django 18.1μs
  • FastAPI ตอบสนองได้เร็วกว่า Django ราว 2 เท่า

การอ่านเขียนไฟล์ (File I/O)

  • การเปิดและปิดไฟล์ 9.05μs, การอ่าน 1KB 10μs, การอ่าน 1MB 33.6μs
  • การเขียน: 1KB 35.1μs, 1MB 207μs
  • Pickle เร็วกว่า json ทั้งในการซีเรียลไลซ์และดีซีเรียลไลซ์ประมาณ 2 เท่า (pickle.dumps() 1.3μs, json.dumps() 2.72μs)

ฐานข้อมูลและการคงอยู่ของข้อมูล (Database and Persistence)

  • SQLite: insert 192μs, select 3.57μs, update 5.22μs
  • diskcache: set 23.9μs, get 4.25μs
  • MongoDB: insert 119μs, find_one 121μs
  • SQLite เร็วที่สุดในด้านการอ่าน และ diskcache เด่นในด้านประสิทธิภาพการเขียน

การเรียกฟังก์ชันและข้อยกเว้น (Function and Call Overhead)

  • การเรียกฟังก์ชัน: ฟังก์ชันว่าง 22.4ns, เมธอด 23.3ns, lambda 19.7ns
  • การจัดการข้อยกเว้น: try/except (กรณีปกติ) 21.5ns, เมื่อเกิดข้อยกเว้น 139ns
  • การตรวจชนิด: isinstance() 18.3ns, การเทียบ type() 21.8ns

โอเวอร์เฮดของอะซิงก์ (Async Overhead)

  • การสร้างคอรูทีน 47ns, run_until_complete 27.6μs
  • asyncio.sleep(0) 39.4μs, gather(10 coroutines) 55μs
  • เมื่อเทียบกับ การเรียกฟังก์ชันแบบซิงก์ (20ns) แล้ว การรันแบบอะซิงก์ (28μs) ช้ากว่าประมาณ 1,000 เท่า

บทเรียนสำคัญ (Key Takeaways)

  • โอเวอร์เฮดหน่วยความจำของออบเจ็กต์ Python มีขนาดใหญ่ แม้แต่ลิสต์ว่างก็ใช้ 56 ไบต์
  • การค้นหาในดิกชันนารีและเซต เร็วกว่าการค้นหาในลิสต์หลายร้อยเท่า
  • ไลบรารี JSON ทางเลือกอย่าง orjson, msgspec เร็วกว่ามาตรฐาน 3–8 เท่า
  • การประมวลผลแบบอะซิงก์ มีโอเวอร์เฮดสูง จึงควรใช้เมื่อจำเป็นต้องมีการทำงานพร้อมกันจริง ๆ เท่านั้น
  • __slots__ ลดการใช้หน่วยความจำได้เหลือไม่ถึงครึ่ง โดยแทบไม่มีผลเสียด้านประสิทธิภาพ

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

 
GN⁺ 2026-01-02
ความคิดเห็นจาก Hacker News
  • หลายคนบอกว่า “ถ้าคุณต้องแคร์ตัวเลข latency ใน Python ก็ควรไปใช้ภาษาอื่น” แต่ฉันไม่เห็นด้วย
    โค้ดเบสขนาดใหญ่ของบริษัทอย่าง Instagram, Dropbox, OpenAI ก็เติบโตมาด้วย Python เหมือนกัน สุดท้ายก็ต้องเจอปัญหาเรื่องประสิทธิภาพอยู่ดี และสิ่งสำคัญคือความสามารถในการแก้ปัญหานั้นภายใน Python โดยไม่ต้องย้ายภาษา
    ปัญหาด้านประสิทธิภาพส่วนใหญ่มักไม่ได้มาจากข้อจำกัดของภาษา แต่เกิดจาก โค้ดที่ไม่มีประสิทธิภาพ มากกว่า เช่น ลูปที่เรียกฟังก์ชันซ้ำโดยไม่จำเป็นเป็นหมื่นครั้ง
    ลองดู Python latency quiz ที่ฉันทำไว้ได้ด้วย

    • ฉันรับผิดชอบงาน ปรับแต่งประสิทธิภาพ ของระบบที่เขียนด้วย Python แต่ตัวเลขพวกนี้แทบไม่มีความหมายจนกว่าจะเกิดปัญหาจริง พอมีปัญหาก็ค่อยวัดเอา ถ้าเขียนโค้ดโดยพยายามประหยัดการเรียกเมธอด คุณก็จะเสียข้อดีของ Python ไป
    • Python ช้าแม้กระทั่งกับการทำงานพื้นฐาน การทำงานง่าย ๆ อย่างการเรียกฟังก์ชันหรือเข้าถึง dictionary ก็ยังช้า ที่จริง Python อยู่รอดมาได้เพราะมี ไลบรารีที่สร้างบน C/C++ (เช่น Numpy)
    • ตัวเลขแบบนี้ไม่ได้เป็นปัญหาเฉพาะของ Python ใน Zig เองก็ต้องคิดเรื่อง CPU cycle หรือ cache miss เหมือนกัน ทุกภาษามี latency ของการทำงานบางอย่างอยู่แล้ว จะมีเหตุผลอื่นที่ไม่ใช้ Python ก็ได้ แต่เรื่องนี้ไม่ใช่เหตุผลนั้น
    • การทำงานบางอย่างดีขึ้นได้ถ้าใช้โมดูลทางเลือก ความรู้แบบนี้สำคัญ แต่ถ้าเป็นคนที่จำเป็นต้องใช้จริงก็น่าจะรู้อยู่แล้ว ถึงอย่างนั้น Python ก็ยังเป็นภาษาที่ยอดเยี่ยมสำหรับ prototyping
    • ระบบ build ของเราก็ทำด้วย Python เหมือนกัน เลยอยากปรับปรุงประสิทธิภาพไปพร้อมกับยังคงใช้ Python ต่อไป เพราะงั้นตัวเลขพวกนี้จึงสำคัญมาก
  • ในเชิงย้อนแย้ง พอถึงจุดที่ตัวเลขพวกนี้สำคัญขึ้นมา ก็แปลว่า Python ไม่ใช่เครื่องมือที่เหมาะกับงานนั้นแล้ว

    • การคงโค้ด Python ไว้ แล้ว ย้ายเฉพาะส่วนสำคัญด้านประสิทธิภาพลงไปเป็นส่วนขยาย C หรือ Rust เป็นแนวทางที่สมจริงกว่า numpy, pandas, PyTorch ก็ทำแบบนั้น
      ในทางปฏิบัติ สิ่งสำคัญคือการใส่ instrumentation ให้โค้ดและหา bottleneck ให้เจอ (ด้วยเครื่องมืออย่าง pyspy) ถ้าคุณกังวลถึงระดับความเร็วในการเพิ่มสมาชิกลง list งานนั้นก็คงไม่ควรทำใน Python อยู่แล้ว
    • ฉันทำงานกับ Python มา 20 ปี แต่ไม่เคยจำเป็นต้องรู้ตัวเลขพวกนี้เลย ฉันใช้ profiling กับเครื่องมืออย่าง Cython, SWIG, JIT เพื่อแก้ปัญหาแทน
    • ถ้าเป็นแอปพลิเคชันที่ตัวเลขพวกนี้สำคัญขนาดนั้น ฉันคิดว่า Python เป็น ภาษาระดับสูง เกินไปจนปรับแต่งได้ยาก
    • แต่ฉันเคยสร้าง data pipeline ขนาดใหญ่ ด้วย Python มาแล้ว โดยใช้คู่ turbodbc + pandas แล้วได้ความเร็วระดับ C++ ใช้หน่วยความจำมากกว่า แต่ถ้าคิดเรื่องต้นทุนคนก็ยังคุ้มกว่ามาก
      แนวทางแบบนี้เป็นไปได้เพราะ การทำงานร่วมกันระหว่าง Python กับ C และ Zig ก็ดีขึ้นเรื่อย ๆ ฉันคงไม่ใช้ Python ควบคุมเครื่องบิน แต่การมีสำนึกเรื่องทรัพยากรก็ยังสำคัญอยู่ดี
    • ตัวเลขพวกนี้เป็น ทางเลือกสุดท้าย ควรคิดถึงหลังจากจัดการ bottleneck ทั่วไปอย่าง disk I/O, network, algorithmic complexity ไปหมดแล้วเท่านั้น
  • การรู้ว่า string ว่างกินพื้นที่กี่ไบต์แทบไม่มีความหมาย สิ่งสำคัญคือเข้าใจ time·space complexity
    การรู้ว่า int มีขนาด 28 ไบต์ ไม่สำคัญเท่ากับการตัดสินได้ว่าโปรแกรมของคุณผ่านข้อกำหนดด้านประสิทธิภาพหรือไม่ และถ้าไม่ผ่านก็ควรหาอัลกอริทึมที่ดีกว่า

    • แต่ประสิทธิภาพเป็นสิ่งที่ abstraction รั่วไหล อยู่เสมอ ไม่ว่าเราจะตระหนักหรือไม่ มันก็ส่งผลต่อโค้ดทั้งระบบ
      ตัวอย่างเช่น การที่ string concatenation เป็น O(n²) ก็มีผลต่อการออกแบบ f-string ของ Python
      หรือการที่ dictionary เร็ว จึงถูกใช้แพร่หลายทั่ว Python ก็เป็นบริบทเดียวกัน
      ตัวเลขเหล่านี้มีหน้าที่ ทำให้ความรู้โดยนัยนั้นมีเหตุผลรองรับด้วยตัวเลข
    • การที่ int มีขนาด 28 ไบต์นั้นสำคัญจริงในปัญหาที่ต้อง สร้างอ็อบเจ็กต์จำนวนมาก
      ทำให้นึกถึงบทความ ที่พูดถึงปัญหาที่ Eric Raymond เจอระหว่างใช้ Reposurgeon เพื่อย้าย GCC
  • ชื่อเรื่องทำให้สับสนอยู่หน่อย เพราะจริง ๆ แล้วเป็นการล้อชื่อบทความของ Jeff Dean ปี 2012 ที่ชื่อ “Latency Numbers Every Programmer Should Know”
    มุกเล่นกับชื่อแบบนี้พบได้บ่อยในงานวิจัยสาย CS

    • ถ้ามีใครเขียนงานวิจัยชื่อ “latency numbers considered harmful is all you need” น่าจะดังมากในแวดวงวิชาการ
    • แต่ดูเหมือนผู้เขียนบทความนี้จะเขียนแบบจริงจัง ผู้อ่านจึงไม่ได้เข้าใจชื่อเรื่องผิดไปเอง
    • ถ้าชื่อเรื่องจะใช้ได้ ตัวเลขเหล่านั้นต้อง มีประโยชน์จริง แต่ในที่นี้มันเยอะเกินไปและไม่ค่อยใช้ได้จริง
    • อนึ่ง ต้นฉบับของ Jeff Dean ดูเหมือนจะเขียนก่อนปี 2012 นานพอสมควร
      มันเป็นเอกสารภายในสำหรับออกแบบ RAM vs Disk ของเสิร์ชเอนจินยุคแรกของ Google
      ต่อมาพอมี flash memory ตัวเลขก็เปลี่ยนไป และยังมีเกร็ดว่า Jeff เคยทำ อัลกอริทึมบีบอัด เพื่อเสิร์ฟข้อมูลจีโนมจากแฟลชโดยตรงด้วย
  • นักพัฒนา Python ส่วนใหญ่น่าจะควรโฟกัสกับสิ่งที่สำคัญกว่า รายละเอียดประสิทธิภาพระดับต่ำ เหล่านี้
    ข้อมูลแบบนี้เหมาะจะมีไว้อ้างอิง แต่ในทางปฏิบัติแทบไม่ค่อยจำเป็น

    • แต่ ความรู้ทั่วไป เกี่ยวกับเครื่องมือที่เราใช้ย่อมมีคุณค่าเสมอ เป็นทรัพย์สินทางปัญญาและช่วยได้มากในบางสถานการณ์
    • ถ้าชนเพดานแล้ว ก็ไปหาโมดูลที่เขียนด้วย C หรือเขียนเองได้เลย Python เติบโตมาในแบบนั้นตั้งแต่แรก
    • ส่วนใหญ่ฉันก็ทำงานโดยใช้ความรู้สึกว่า “เร็วพอแล้ว” เหมือนกัน ข้อมูลชุดนี้ช่วย ยืนยันความรู้สึกนั้นด้วยตัวเลข
  • คำอธิบายเรื่องขนาดของ string ไม่ถูกต้อง Python มี string อยู่สามชนิด ที่ใช้ 1, 2, 4 ไบต์ต่ออักขระ
    ดูรายละเอียดได้ในบล็อกนี้

  • ชื่อเรื่องกับตัวอย่างค่อนข้างไม่แม่นยำอยู่บ้าง
    เช่น “item in set เร็วกว่า item in list 200 เท่า” นั้นพูดถึง membership test ไม่ใช่การเปรียบเทียบความเร็วของการวนซ้ำ (iteration)
    ถึงอย่างนั้น โดยรวมแล้ว รูปแบบและการจัดวางก็น่าสนใจ

  • ไม่มีการวัดเวลาในการสร้างอินสแตนซ์ของคลาส
    ฉันเคยรีแฟกเตอร์โค้ดแล้วเปลี่ยนโครงสร้าง list ธรรมดาเป็นคลาส ปรากฏว่าเวลาการทำงานเพิ่มจาก ไม่กี่ไมโครวินาที → หลายวินาที
    อยากให้มีการวัดกรณีแบบนี้ด้วย

    • ฟังแล้วนึกถึงมุกที่คนไข้บอกหมอว่า “ทำแบบนี้แล้วเจ็บ” แล้วหมอตอบว่า “งั้นก็อย่าทำสิ”
      ปัญหาอาจเป็นการใช้คลาสมากเกินไปก็ได้ บางครั้งโครงสร้าง list ธรรมดาอาจเหมาะกว่า
    • โดยปกติการสร้างอินสแตนซ์ของคลาสเองไม่ใช่ปัญหาด้านประสิทธิภาพ
      กลับกันอาจเป็นเพราะใช้แนวคิดเชิงวัตถุ ผิดวิธี มากกว่า
      ลองเอาโค้ดไปลง StackOverflow หรือ CodeReview.SE เพื่อขอฟีดแบ็กน่าจะดีกว่า
  • ฉันอ่านบทความนี้ด้วยมุมมองว่า “Python ยุคใหม่มีอะไรผิดพลาดในระดับพื้นฐานหรือเปล่า” ซึ่งก็น่าสนใจดี
    แต่ฉันไม่เห็นด้วยกับคำอ้างว่าทุกคน “ควรรู้ทั้งหมด” แค่รู้สึกคุ้นกับการทำงานหลัก ๆ สักไม่กี่อย่างก็พอแล้ว

  • ช่วงของ small int caching ใน Python ไม่ใช่ 0~256 แต่เป็น -5~256
    เรื่องนี้ทำให้มือใหม่สับสนบ่อย เพราะมักแยกไม่ออกระหว่าง identity (is) กับ equality (==)

    • Java ก็มีพฤติกรรมคล้ายกัน ซึ่งอาจทำให้มือใหม่งงได้เหมือนกัน