Гүйцэтгэлийн оновчлол
"Эрт оновчлох нь бүх муугийн үндэс" гэж алдартай хэлдэг — эхлээд зөв ажиллуулж, дараа нь хурдлуул. Гэхдээ программ удааширч эхлэхэд хаана гацаж байгааг олж, зориудаар оновчлох чадвар маш чухал. Python-д үүний тулд timeit, cProfile болон хэд хэдэн ухаалаг техник байдаг.
Хурдыг хэмжих
Таамаглалаар оновчлол хийхгүй — эхлээд хэмж:
import timeit
# Жижиг кодын хурд хэмжих
удаан = timeit.timeit(
"[x**2 for x in range(1000)]",
number=10_000
)
хурдан = timeit.timeit(
"list(map(lambda x: x**2, range(1000)))",
number=10_000
)
print(f"List comprehension: {удаан:.4f}с")
print(f"map + lambda: {хурдан:.4f}с")
Том функцийн аль хэсэг удаан байгааг олохд cProfile хэрэглэнэ:
python -m cProfile -s cumulative миний_програм.py
import cProfile
def удаан_функц() -> int:
нийт = 0
for i in range(100_000):
нийт += i * i
return нийт
cProfile.run("удаан_функц()")
# ncalls, tottime, percall гэх мэт статистик хэвлэнэ
List comprehension ба generator
for давталтын оронд list comprehension нь CPython-д оновчилсон байдлаар ажилладаг тул ихэвчлэн хурдан:
import timeit
# For давталт
def for_арга() -> list[int]:
үр_дүн = []
for i in range(10_000):
if i % 2 == 0:
үр_дүн.append(i * i)
return үр_дүн
# List comprehension
def comprehension_арга() -> list[int]:
return [i * i for i in range(10_000) if i % 2 == 0]
print(timeit.timeit(for_арга, number=1000))
print(timeit.timeit(comprehension_арга, number=1000))
Generator нь бүх утгыг санах ойд нэгэн зэрэг ачаалахгүй — нэг нэгээр нь үйлдвэрлэдэг. Том өгөгдөлтэй ажиллахад санах ойг хэмнэдэг:
# List — бүгдийг санах ойд хадгална
тоонуудын_жагсаалт = [i * i for i in range(1_000_000)] # ~8MB
# Generator — зөвхөн нэг утга нэг удаа
тоонуудын_generator = (i * i for i in range(1_000_000)) # ~200 байт
# Generator-г for давталтаар ашиглах
нийт = sum(i * i for i in range(1_000_000))
print(нийт)
lru_cache — үр дүнг цээжлэх
Нэг аргументаар дахин дахин дуудагддаг тооцооллын хүнд функцэд memoization хэрэглэнэ — functools.lru_cache декоратор хариуг цээжилж, давтан тооцохгүй:
import functools
import timeit
# Цээжлэхгүй хувилбар
def фибоначи(n: int) -> int:
if n <= 1:
return n
return фибоначи(n - 1) + фибоначи(n - 2)
# Цээжлэх хувилбар
@functools.lru_cache(maxsize=None)
def фибоначи_хурдан(n: int) -> int:
if n <= 1:
return n
return фибоначи_хурдан(n - 1) + фибоначи_хурдан(n - 2)
# Харьцуулах (n=35)
удаан = timeit.timeit(lambda: фибоначи(35), number=3)
хурдан = timeit.timeit(lambda: фибоначи_хурдан(35), number=3)
print(f"Цээжлэхгүй: {удаан:.2f}с")
print(f"lru_cache: {хурдан:.6f}с")
# Цээжлэхгүй: ~4.50с → lru_cache: ~0.000003с
Өгөгдлийн бүтэц сонгох
Зөв өгөгдлийн бүтэц сонгох нь алгоритмоос ч илүү нөлөөтэй байж болно:
import timeit
их_жагсаалт = list(range(100_000))
их_олонлог = set(range(100_000))
# Жагсаалтаас хайх — O(n)
хугацаа_жагсаалт = timeit.timeit(
"99_999 in их_жагсаалт",
globals={"их_жагсаалт": их_жагсаалт},
number=10_000
)
# Олонлогоос хайх — O(1)
хугацаа_олонлог = timeit.timeit(
"99_999 in их_олонлог",
globals={"их_олонлог": их_олонлог},
number=10_000
)
print(f"list хайлт: {хугацаа_жагсаалт:.4f}с")
print(f"set хайлт: {хугацаа_олонлог:.4f}с")
# set нь жагсаалтаас ~100 дахин хурдан байна
Гүйцэтгэлийн оновчлол бол таамаглах биш — хэмжиж, хаана удааширч байгааг олж, тодорхой аргаар шийдэх ажил юм. Энэ хичээлд сурсан аргуудаа бодит кодондоо хэрэглэж туршаарай.
Дараагийн хичээлд:
Machine Learning-ийн ертөнцөд орно — scikit-learn ашиглан анхны загвараа бүтээж, өгөгдлийг ангилж, урьдчилан таамаглаж сурна. Урьдчилсан мэдлэг огт шаардлагагүй.