قِس قبل أن تُحسِّن: timeit وcProfile، وما يحجبه GIL فعليًا، واقتصاد المولّدات في الذاكرة، وفخاخ الذاكرة في السلاسل النصية والشرائح.
افتح هذا الدرس في Kodokonلا تخمّن أبدًا، بل قِس. تعزل timeit قياسًا مصغّرًا موثوقًا: فهي تعطّل جامع القمامة وتكرّر التنفيذ لتنعيم الضجيج. أما cProfile فترسم خريطة استدعاءات برنامج كامل - رتّب حسب cumtime (الزمن التراكمي، بما فيه الاستدعاءات الفرعية) لتحديد المذنبين الحقيقيين. والقاعدة الذهبية في أيّ عمل على الأداء: السطر البطيء لا يكاد يكون أبدًا السطر الذي تشكّ فيه.
python -m timeit "sum(x * x for x in range(1000))"
python -m timeit -s "s = 'abc' * 100" "s.upper()"
python -m cProfile -s cumtime -m timeit "min(range(50))"يضمن GIL (القفل العام للمفسّر) أن خيطًا واحدًا فقط ينفّذ بايت كود Python في كل لحظة داخل العملية الواحدة. والنتيجة: الخيوط لا تسرّع الحساب الصرف. لكنها تبقى مثالية للإدخال/الإخراج، لأن GIL يُحرَّر أثناء القراءة من الشبكة أو القرص، وكذلك من قِبل مكتبات C كثيرة مثل NumPy. ولإشباع عدة أنوية بالحساب، مُرّ عبر multiprocessing أو concurrent.futures.ProcessPoolExecutor. ويقدّم Python 3.13 نسخة تجريبية بلا GIL تُسمى "free-threaded"، لا تزال نادرة في الإنتاج.
import sys
squares_list = [x * x for x in range(1_000_000)]
squares_gen = (x * x for x in range(1_000_000))
print(sys.getsizeof(squares_list), "bytes")
print(sys.getsizeof(squares_gen), "bytes")
print(sum(squares_gen))يزن المولّد بضع مئات من البايتات مهما بلغ طول التسلسل: فهو لا يخزّن سوى حالة تنفيذه ويُنتِج كل قيمة عند الطلب. اربطها في خطوط معالجة - sum(x * x for x in data) - لمعالجة التدفقات دون تجسيد أيّ قائمة وسيطة. وهناك مقايضة يجب الانتباه إليها: المولّد يُستخدَم مرة واحدة. فما إن يُستنفَد، لا يُنتِج شيئًا بعد ذلك، وبصمت - وهو مصدر كلاسيكي لأخطاء برمجية يبدو فيها التكرار الثاني "فارغًا".
import timeit
def concat(n: int) -> str:
out = ""
for i in range(n):
out += str(i)
return out
def join(n: int) -> str:
return "".join(str(i) for i in range(n))
t1 = timeit.timeit(lambda: concat(20_000), number=10)
t2 = timeit.timeit(lambda: join(20_000), number=10)
print(f"concat: {t1:.3f} s / join: {t2:.3f} s")sys.getsizeof لمولّد ضئيلة مقارنة بالقائمة المكافئة؟''.join(parts) على out += part المكرَّر داخل حلقة؟