On this page
เทคนิคเพิ่มประสิทธิภาพ
พอ profile เจอจุดช้าแล้ว แก้อย่างถูกวิธี — เลือกโครงสร้างข้อมูลถูก และ cache ผลลัพธ์
หลังจาก profile เจอว่าช้าตรงไหนแล้ว หัวข้อนี้รวมเทคนิคแก้ให้เร็วขึ้นที่ได้ผลจริง โดยมากการเปลี่ยนโครงสร้างข้อมูลหรือ cache ผลลัพธ์ให้ผลกว่าการแต่งโค้ดเล็ก ๆ หลายเท่า
เลือกโครงสร้างข้อมูลให้ถูก (ผลกระทบใหญ่สุด)
การเช็คว่า "มีอยู่ใน collection ไหม" บน list เป็น O(n) (ไล่ดูทีละตัว) แต่บน set/dict เป็น O(1) (กระโดดถึงเลย) แค่เปลี่ยนชนิดก็เร็วขึ้นมหาศาลเมื่อข้อมูลเยอะ
# ❌ ช้า: ค้นใน list เป็น O(n) ต่อครั้ง
allowed_list = list(range(100000))
def check_slow(x):
return x in allowed_list # ไล่ทีละตัว
# ✅ เร็ว: ค้นใน set เป็น O(1) ต่อครั้ง
allowed_set = set(range(100000))
def check_fast(x):
return x in allowed_set # กระโดดถึงเลย
# ลอง timeit เทียบ — set เร็วกว่าหลายร้อยเท่าเมื่อข้อมูลเยอะเปลี่ยน data structure ให้เหมาะมักเร็วกว่า micro-optimization (แต่งโค้ดทีละบรรทัด) เป็นสิบเท่า — "ต้องค้นบ่อย → ใช้ set/dict" เป็นกฎที่จำไว้ได้เลย (เจาะลึกใน บท DSA)
หลีกเลี่ยง loop ซ้อนที่ไม่จำเป็น
loop ซ้อน (nested loop) มักเป็น O(n²) ถ้าใช้ set/dict ช่วยจำของที่เคยเจอ มักลดเหลือ O(n)
# ❌ O(n²): หาคู่ที่บวกกันได้ target ด้วย loop ซ้อน
def two_sum_slow(nums, target):
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return (i, j)
# ✅ O(n): ใช้ dict จำตัวที่เคยเจอ
def two_sum_fast(nums, target):
seen = {}
for i, n in enumerate(nums):
if target - n in seen:
return (seen[target - n], i)
seen[n] = icaching / memoization ด้วย lru_cache
ถ้าฟังก์ชันถูกเรียกซ้ำด้วย argument เดิมบ่อย ๆ การ cache ผลลัพธ์ช่วยได้มาก @functools.lru_cache ทำให้อัตโนมัติ — จำคำว่า decorator จากบท 1 ได้ไหม นี่คือการใช้จริง
import functools
@functools.lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(40)) # เร็วมาก เพราะผลที่คำนวณแล้วถูก cache ไว้
# ไม่มี cache: fib(40) คำนวณซ้ำเป็นล้านครั้ง ช้ามากแนวคิด "จำผลที่คำนวณแล้ว ไม่คำนวณซ้ำ" คือหัวใจของ memoization และต่อยอดเป็น Dynamic Programming ในบท DSA — lru_cache คือ DP แบบ top-down สำเร็จรูป
generator ประหยัด memory
ถ้าวนข้อมูลใหญ่ครั้งเดียว ใช้ generator (บท 1) แทน list เพื่อไม่ให้ RAM ล้น — performance ไม่ใช่แค่เรื่องเวลา แต่รวมถึง memory
# ❌ สร้าง list ใหญ่ในหน่วยความจำ
total = sum([i * i for i in range(10_000_000)])
# ✅ generator: ผลิตทีละตัว ไม่กอง RAM
total = sum(i * i for i in range(10_000_000))สรุปหัวข้อนี้
- เลือก data structure ถูก = ผลกระทบใหญ่สุด (ค้นบ่อย → set/dict O(1))
- เลี่ยง loop ซ้อน O(n²) — ใช้ dict/set จำของที่เจอ ลดเหลือ O(n)
- @functools.lru_cache cache ผลลัพธ์ฟังก์ชันที่เรียกซ้ำ (= memoization)
- generator ประหยัด memory เมื่อวนข้อมูลใหญ่ครั้งเดียว
1) เขียนโค้ดที่เช็คสมาชิกใน list แล้วเปลี่ยนเป็น set พร้อม timeit เทียบ 2) แก้ two_sum จาก O(n²) เป็น O(n) ด้วย dict 3) ใส่ @lru_cache ให้ฟังก์ชัน fib แล้วเทียบเวลากับแบบไม่มี 4) แปลง list comprehension ที่ใหญ่ใน sum() ให้เป็น generator