مقایسه سلام و پایتون: سرعت کد بومی با نحوی ساده؟

سلام تقریباً مثل پایتون خوانده می‌شود، اما با گونه‌های ایستا و بدون زباله‌روب به کد ماشین کامپایل می‌شود. در این نوشته یک برنامه را در هر دو زبان می‌بینید، فاصله‌ی سرعتِ اندازه‌گیری‌شده را، و موارد زیادی را که پایتون هنوز انتخاب بهتری است.

در این مقاله
  1. مقایسه سلام و پایتون در یک نگاه
  2. یک برنامه در هر دو زبان
  3. گونه‌های ایستا در برابر گونه‌های پویا
  4. زبان سلام چقدر از پایتون سریع‌تر است؟
  5. شکاف اکوسیستم
  6. حافظه و موازی‌سازی
  7. روند کار: اسکریپت، REPL و نوت‌بوک
  8. سلام یا پایتون: کدام را انتخاب کنیم؟
  9. پرسش‌های پرتکرار

سلام و پایتون یک هدف مشترک دارند: کدی کوتاه و خوانا. اما از دو راه مخالف به آن می‌رسند. پایتون زبانی با گونه‌های پویاست و معمولاً روی مفسر اجرا می‌شود؛ زبان برنامه‌نویسی سلام گونه‌های ایستا دارد و به کد ماشین بومی کامپایل می‌شود. در سنجش (Benchmark) پروژه روی ۳۹ برنامه‌ی کوچک که در همه‌ی زبان‌ها الگوریتم یکسانی دارند، پایتون حدود ۵۶ برابر سلام زمان برد. در عوض، پایتون یکی از بزرگ‌ترین اکوسیستم‌های کتابخانه را در میان همه‌ی زبان‌ها دارد، شیوه‌ی کار تعاملی‌ای که دانشمندان داده به آن تکیه می‌کنند، و چند دهه آموزش، پرسش و پاسخ و تجربه‌ی محیط عملیاتی. سلام کتابخانه‌ی استاندارد گسترده‌ای دارد و جامعه‌ای کوچک.

پس سلام جایگزینی نیست که بی‌دردسر جای پایتون بنشیند. گزینه‌ای است برای آن بخش‌هایی از یک پروژه که سرعت پایتون، مصرف حافظه یا شیوه‌ی استقرارش دست‌وپاگیر می‌شود، با نحوی که یک برنامه‌نویس پایتون از همان روز اول می‌تواند بخواند.

مقایسه سلام و پایتون در یک نگاه

پایتونسلام
اجرابایت‌کد تفسیری (CPython)؛ پیاده‌سازی‌های دیگری مثل PyPy هم هستکامپایل پیش از اجرا به کد بومی؛ یک مفسر (salam exec) برای اجرای سریع
گونه‌هاپویا؛ راهنمای گونه‌ی اختیاری (type hints) که ابزارهای بیرونی بررسی‌اش می‌کنندایستا با استنتاج گونه، بررسی‌شده به دست کامپایلر
بلوک‌هاتورفتگیدونقطه بلوک را باز می‌کند و پایان (end) آن را می‌بندد
حافظهشمارش ارجاع به‌همراه یک جمع‌آور چرخه‌هادستی: آزادکن() و دیرکن، با بررسی‌های زمان کامپایل
موازی‌سازیGIL در ساخت پیش‌فرض؛ ساخت اختیاری بدون GIL (آزمایشی در ۳٫۱۳، پشتیبانی‌شده در ۳٫۱۴)نخ‌های سیستم‌عامل، استخر کار، کانال، عملیات اتمی
سرعت در سنجشحدود ۵۶ برابر زمان سلامدر رده‌ی C و Rust
استقرارمفسر به‌همراه وابستگی‌ها، یا یک بسته‌سازیک فایل اجرایی بومی
اکوسیستمعظیم: PyPI، NumPy، pandas، PyTorch، Django، Jupyterکتابخانه‌ی استاندارد بزرگ، بسته‌های شخص ثالث اندک
زبان کد منبعواژه‌های کلیدی انگلیسیواژه‌های کلیدی انگلیسی یا فارسی، با یک کامپایلر

یک برنامه در هر دو زبان

واژه‌های یک جمله را بشمارید و سه واژه‌ی پرتکرار را چاپ کنید: پرتکرارترین اول، و در صورت تساوی به ترتیب الفبا. پایتون با collections.Counter این کار را در چند خط انجام می‌دهد:

Python
from collections import Counter

text = "the cat sat on the mat and the dog sat on the log"
counts = Counter(text.split())
top = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))[:3]
for word, n in top:
    print(word, n)

سلام گونه‌ای مثل Counter برای این کار ندارد. پس برنامه شمارش‌ها را در یک نگاشت (HashMap) جمع می‌کند، آن‌ها را به یک وکتور از ساختارها منتقل می‌کند و با یک تابع مقایسه مرتب می‌کند. جمله‌ی ورودی را عمداً انگلیسی نگه داشته‌ایم تا خروجی دو برنامه دقیقاً قابل مقایسه باشد:

سلام
واردسازی مرتب سازی
واردسازی رشته

ساختار مدخل:
    همگانی واژه: رشته
    همگانی شمار: صحیح
پایان

تابع ریشه:
    متن := "the cat sat on the mat and the dog sat on the log"
    واژه‌ها := رشته.واژه ها(متن)
    دیرکن واژه‌ها.آزادکن()
    ناپایا شمارش := نگاشت {} برگردان نگاشت<رشته، صحیح>
    دیرکن شمارش.آزادکن()
    هر کلمه در واژه‌ها:
        شمارش.درج(کلمه، شمارش.دریافت با پیش فرض(کلمه، ۰) + ۱)
    پایان
    ناپایا مدخل‌ها := وکتور {} برگردان وکتور<مدخل>
    دیرکن مدخل‌ها.آزادکن()
    هر کلمه، تعداد در شمارش:
        مدخل‌ها.بیفزا(مدخل { واژه = کلمه، شمار = تعداد })
    پایان
    مرتب سازی.مرتب سازی با(مدخل‌ها، (الف: مدخل، ب: مدخل):
        اگر الف.شمار != ب.شمار: برگشت الف.شمار > ب.شمار پایان
        برگشت رشته.مقایسه(الف.واژه، ب.واژه) < ۰
    پایان)
    تکرار ۳ در ای:
        م := مدخل‌ها.بگیر(ای)
        سرچاپ م.واژه، م.شمار
    پایان
پایان
خروجیthe 4 on 2 sat 2

هر دو برنامه همان سه خط را چاپ می‌کنند. نسخه‌ی سلام بلندتر است و دلیل‌هایش را باید گفت: گونه‌ها روی ساختار اعلام می‌شوند، ظرف‌ها با دیرکن آزاد می‌شوند و گونه‌ی آماده‌ای برای شمارش وجود ندارد. از سوی دیگر، بخش زیادی از آن برای برنامه‌نویس پایتون آشناست: هر کلمه در واژه‌ها همان for w in words است، هر کلمه، تعداد در شمارش همان for w, n in counts.items()، و تکرار ۳ در ای همان for i in range(3)؛ آکولاد و نقطه‌ویرگول هم در کار نیست. سرچاپ حتی مثل print پایتون آرگومان‌هایش را با فاصله کنار هم چاپ می‌کند.

گونه‌های ایستا در برابر گونه‌های پویا

در پایتون، اشتباه گونه وقتی پیدا می‌شود که همان خط اجرا شود:

Python
def discount(price, percent):
    return price * (100 - percent) / 100

print(discount(80.0, 15))    # 68.0
print(discount(80.0, "15"))  # TypeError: unsupported operand type(s) for -: 'int' and 'str'

در سلام همین اشتباه هرگز به برنامه‌ی در حال اجرا نمی‌رسد. نسخه‌ی درست:

سلام
تابع تخفیف(قیمت: اعشار۶۴، درصد: اعشار۶۴): اعشار۶۴:
    برگشت قیمت * (۱۰۰.۰ - درصد) / ۱۰۰.۰
پایان

تابع ریشه:
    سرچاپ تخفیف(۸۰.۰، ۱۵.۰)
پایان
خروجی68

اگر فراخوانی را به تخفیف(۸۰.۰، "15") تغییر دهید، salam check با این پیام متوقف می‌شود و به همان خط اشاره می‌کند:

متن
خطا[خطا۰۱۲]: آرگومان ۲: نمی‌توان 'رشته' را به 'اعشار۶۴' پاس داد

پایتون با راهنمای گونه و ابزاری مانند mypy یا Pyright می‌تواند به این نزدیک شود و بسیاری از تیم‌ها دقیقاً همین کار را می‌کنند؛ در سلام این بررسی بخشی از کامپایلر است و نمی‌شود از آن گذشت. گونه‌ی متغیرهای محلی استنتاج می‌شود (شمار := ۰ یک عدد صحیح است و نام := "سارا" یک رشته)، پس گونه‌هایی که می‌نویسید بیشتر روی پارامترهای تابع و فیلدهای ساختار است. بهایش انعطاف است: duck typing در زمان اجرا و monkey patching وجود ندارد، و داده‌ی ناهمگون کمی تشریفات بیشتر می‌خواهد که سلام آن را با میانجی‌ها، گونه‌های عام (Generics) و گونه‌های «یا» مانند صحیح یا رشته (int or str) پوشش می‌دهد. نوشته‌ی گونه‌های ایستا سیستم گونه‌ی سلام را شرح می‌دهد.

زبان سلام چقدر از پایتون سریع‌تر است؟

در سنجشی که در صفحه‌ی سرعت منتشر شده، میانگین هندسی ۳۹ برنامه‌ی هم‌الگوریتم برای سلام ۱۰٫۶ میلی‌ثانیه و برای پایتون ۵۹۸ میلی‌ثانیه بود؛ یعنی پایتون حدود ۵۶ برابر زمان برد. این یکی از همان برنامه‌هاست، 04_collatz، که در میان عددهای کوچک‌تر از ۳۰٬۰۰۱ عددی را پیدا می‌کند که طولانی‌ترین دنباله‌ی کولاتز را دارد. کد پایتونِ سنجش:

Python
best_steps = 0
best_n = 1
for n in range(1, 30001):
    m = n
    c = 0
    while m != 1:
        if m % 2 == 0:
            m = m // 2
        else:
            m = 3 * m + 1
        c += 1
    if c > best_steps:
        best_steps = c
        best_n = n
print(best_steps)
print(best_n)

و کد سلامِ همان پوشه، این‌جا به فارسی برگردانده شده:

سلام
تابع ریشه:
    ناپایا بیشترین گام := ۰ برگردان صحیح۶۴
    ناپایا بهترین عدد := ۱ برگردان صحیح۶۴
    ناپایا ن := ۱ برگردان صحیح۶۴
    تا ن <= ۳۰۰۰۰:
        ناپایا م := ن
        ناپایا گام := ۰ برگردان صحیح۶۴
        تا م != ۱:
            اگر م % ۲ == ۰:
                م /= ۲
            وگرنه:
                م = ۳ * م + ۱
            پایان
            گام++
        پایان
        اگر گام > بیشترین گام:
            بیشترین گام = گام
            بهترین عدد = ن
        پایان
        ن++
    پایان
    سرچاپ بیشترین گام
    سرچاپ بهترین عدد
پایان
خروجی307 26623

این دو تقریباً خط به خط یکی‌اند. در سلام تا شرط: حلقه‌ای است که تا وقتی شرط برقرار است اجرا می‌شود، درست مثل while، و نام‌ها می‌توانند فاصله داشته باشند، مانند بیشترین گام. پایتون ۲۴۵٫۷ میلی‌ثانیه طول کشید و سلام ۳٫۷ میلی‌ثانیه. دلیلش ساختاری است، نه ترفندی زیرکانه: CPython بایت‌کد اجرا می‌کند و با اشیای عدد صحیحِ بسته‌بندی‌شده (boxed) کار می‌کند، در حالی که سلام حلقه را به دستورهای ماشین روی عددهای صحیح ۶۴ بیتی کامپایل می‌کند.

دو نکته این مقایسه را منصفانه نگه می‌دارد. اول، برنامه‌ها کوتاه‌اند، پس زمان راه‌اندازی مفسر هم جزو زمان پایتون است و نسبت از برنامه‌ای به برنامه‌ی دیگر فرق می‌کند: در فیبوناچی بازگشتی حدود ۱۹ برابر بود و در بازی زندگی نزدیک به هزار برابر. دوم، کد واقعی پایتون که کار عددی سنگین انجام می‌دهد کتابخانه‌هایی را صدا می‌زند که با C نوشته شده‌اند. برنامه‌های ۴۰ تا ۴۲ این مجموعه در پایتون از NumPy و در سلام از بسته‌های matrix و tensor استفاده می‌کنند: پایتون ضرب ماتریس ۴۰۰ در ۴۰۰ را در ۱۲۱٫۸ میلی‌ثانیه تمام کرد، سلام در ۱۴٫۳ میلی‌ثانیه و حلقه‌های دست‌نویس C در ۴۲٫۵ میلی‌ثانیه. پایتون با NumPy در فاصله‌ی چند برابری کد کامپایل‌شده می‌ماند؛ بدون آن، یک تا دو مرتبه‌ی بزرگی عقب است. نوشته‌ی سنجش روش کار و محدودیت‌هایش را توضیح می‌دهد.

شکاف اکوسیستم

این‌جاست که پایتون برنده است، آن هم با فاصله‌ی زیاد. PyPI صدها هزار بسته دارد. علم داده روی NumPy، pandas، SciPy و Jupyter می‌چرخد؛ یادگیری ماشین روی PyTorch، scikit-learn و کتابخانه‌های Hugging Face؛ بک‌اند وب روی Django، Flask و FastAPI؛ و تقریباً هر سرویس ابری، پایگاه داده و API اول از همه یک کلاینت پایتون منتشر می‌کند. پاسخ تقریباً هر پرسشی درباره‌ی پایتون با یک جستجو پیدا می‌شود و بیشتر برنامه‌نویس‌ها کمی پایتون بلدند.

کتابخانه‌ی استاندارد سلام برای زبانی به این جوانی به‌طور غیرعادی گسترده است: سرور و کلاینت HTTP، مسیریاب، WebSocket، gRPC، درایور برای SQLite، PostgreSQL، MySQL، Redis و ClickHouse، JSON و CSV، رمزنگاری، فشرده‌سازی، تصویر، صدا، و اجزای پایه‌ی یادگیری ماشین مانند تانسور، مشتق‌گیری خودکار، لایه‌های شبکه‌ی عصبی و بارگذاری مدل‌های ONNX و GGUF. این کتابخانه همراه کامپایلر نصب می‌شود، پس بسیاری از برنامه‌ها به هیچ کد شخص ثالثی نیاز ندارند. اما بسته‌های شخص ثالث اندک‌اند، چیزی هم‌عمق پشته‌ی علمی پایتون وجود ندارد و جامعه‌ای که آموزش می‌نویسد و به پرسش‌ها پاسخ می‌دهد کوچک است. برای یادگیری ماشین به‌طور خاص، نوشته‌ی یادگیری ماشین آنچه را هست شرح می‌دهد و نوشته‌ی سلام در برابر پایتون و جولیا بخش عددی را مقایسه می‌کند.

حافظه و موازی‌سازی

پایتون حافظه را با شمارش ارجاع و یک جمع‌آور چرخه برایتان مدیریت می‌کند و همین بخش بزرگی از دلیل خوشایند بودن نوشتن پایتون است. سلام زباله‌روب (Garbage Collector) ندارد: ظرف‌ها با آزادکن() آزاد می‌شوند، معمولاً از راه دیرکن (defer)، و تابعی که رشته یا ظرف تازه‌ای برمی‌گرداند مالکیت آن را به فراخواننده می‌سپارد. کامپایلر اشتباه‌های رایجی مانند استفاده پس از آزادسازی را گزارش می‌کند، اما آزادسازیِ فراموش‌شده یعنی نشت حافظه. در عوض، مصرف حافظه‌ی یک برنامه‌ی سلام همان چیزی است که تخصیص می‌دهد، بدون زباله‌روب و بدون اشیای بسته‌بندی‌شده برای عددهای ساده.

برای کار موازیِ پردازنده‌محور، ساخت پیش‌فرض CPython قفل سراسری مفسر (GIL) دارد، پس نخ‌ها کد پایتون را به‌طور موازی اجرا نمی‌کنند؛ پاسخ‌های معمول multiprocessing، افزونه‌های بومی یا ساخت اختیاری بدون GIL است (پشتیبانی‌شده از پایتون ۳٫۱۴). نخ‌های سلام روی همه‌ی هسته‌ها موازی اجرا می‌شوند و استخر.برای موازی (pool.ParallelFor)، استخر.نگاشت موازی (pool.Map) و واژه‌ی کلیدی برو (go) کار را میان آن‌ها پخش می‌کنند، با قفل (mutex)، عملیات اتمی و کانال برای هماهنگی. نوشته‌ی هم‌زمانی نمونه‌هایی دارد.

روند کار: اسکریپت، REPL و نوت‌بوک

چرخه‌ی ویرایش و اجرا در پایتون فوری است و نوت‌بوک‌ها کار اکتشافی را تعاملی می‌کنند. سلام بخشی از این حس را نگه می‌دارد. salam run file.salam در یک گام کامپایل و اجرا می‌کند و فایلی به جا نمی‌گذارد، salam exec وقتی ابزار کامپایل C در دسترس نیست برنامه را با مفسر داخلی اجرا می‌کند، و salam cli یک محیط تعاملی (REPL) باز می‌کند. ویرایشگر آنلاین برنامه‌ها را در یک زبانه‌ی مرورگر اجرا می‌کند. محیط نوت‌بوکی هم‌پای Jupyter وجود ندارد و کامپایل پیش از هر اجرا تأخیر کوتاهی اضافه می‌کند. نوشته‌ی salam run در برابر salam build گزینه‌ها را توضیح می‌دهد.

در سوی دیگر، استقرار به سود سلام است: salam build یک فایل اجرایی بومی می‌سازد که روی ماشین مقصد نه مفسر می‌خواهد، نه محیط مجازی پایتون (virtual environment) و نه نصب وابستگی.

سلام یا پایتون: کدام را انتخاب کنیم؟

پایتون را انتخاب کنید وقتی:

  • کار تحلیل داده، محاسبات علمی یا یادگیری ماشین است و به NumPy، pandas، PyTorch و هم‌خانواده‌هایشان تکیه دارد؛
  • کد چسب، خودکارسازی یا نمونه‌ی اولیه می‌نویسید و سرعت توسعه از زمان اجرا مهم‌تر است؛
  • کتابخانه‌ها، نیروی کار قابل استخدام یا تجربه‌ی تیمی که لازم دارید در پایتون است؛
  • به‌صورت تعاملی در نوت‌بوک کار می‌کنید.

سلام را انتخاب کنید وقتی:

  • بخشی پردازنده‌محور از برنامه در پایتون خالص بیش از حد کند است و ترجیح می‌دهید سراغ C نروید؛
  • یک فایل اجرایی بومی برای استقرار، مصرف حافظه‌ی کم و راه‌اندازی سریع می‌خواهید؛
  • می‌خواهید خطاهای گونه پیش از اجرای برنامه گرفته شوند؛
  • زبانی کامپایلی و خوانا برای آموزش می‌خواهید، از جمله به فارسی، چون هر واژه‌ی کلیدی و هر پیام خطا به هر دو زبان وجود دارد (یک زبان، دو خط).

بسیاری از پروژه‌ها از هر دو استفاده خواهند کرد: پایتون برای کاوش و هماهنگ‌سازی، و یک زبان کامپایلی برای مسیر داغ (hot path) یا سرویسی که مستقر می‌شود.

پرسش‌های پرتکرار

آیا زبان سلام از پایتون سریع‌تر است؟

بله، در کدهای پردازنده‌محور با فاصله‌ی زیاد. در سنجش ۳۹ برنامه‌ای پروژه، پایتون حدود ۵۶ برابر سلام زمان برد (میانگین هندسی ۵۹۸ میلی‌ثانیه در برابر ۱۰٫۶). وقتی NumPy کار سنگین را انجام دهد، فاصله‌ی پایتون با کد کامپایل‌شده به چند برابر کم می‌شود.

یادگیری زبان سلام برای برنامه‌نویس پایتون آسان است؟

بیشتر بله. حلقه‌ها، شرط‌ها و چاپ شبیه‌اند، آکولاد و نقطه‌ویرگول ندارد و گونه‌ی متغیرهای محلی استنتاج می‌شود. بخش‌های تازه این‌هاست: گونه‌های اعلام‌شده روی تابع‌ها و ساختارها، پایان به جای تورفتگی، و آزاد کردن حافظه‌ی هیپ با دیرکن فهرست.آزادکن().

آیا سلام می‌تواند جای پایتون را در علم داده بگیرد؟

امروز نه. سلام تانسور، ماتریس، آمار و اجزای پایه‌ی شبکه‌ی عصبی دارد، اما چیزی نزدیک به عمق NumPy، pandas، SciPy، PyTorch و Jupyter ندارد. برای سریع‌تر کردن بخش‌های مشخص یا استقرار ابزارهای آماده مناسب‌تر است.

آیا سلام مثل پایتون REPL دارد؟

بله. salam cli یک محیط تعاملی باز می‌کند، salam exec یک فایل را با مفسر اجرا می‌کند و ویرایشگر آنلاین کد را در مرورگر اجرا می‌کند. محیط نوت‌بوکی مانند Jupyter وجود ندارد.

سلام و زبان‌های دیگر

مقایسه سلام و TypeScript: یک زبان برای مرورگر و سرور؟

TypeScript هر جا جاوااسکریپت باشد اجرا می‌شود و بزرگ‌ترین اکوسیستم نرم‌افزار را پشت سر دارد. سلام راه دیگری به «یک زبان برای کل پشته» می‌رود: سرور بومی، زبان صفحه و پشتوانه‌ی جاوااسکریپت. این نوشته تفاوت‌های عملی آن دو را نشان می‌دهد.

خواندن مقاله
سلام و زبان‌های دیگر

مقایسه سلام و C++: معماری، کارایی، ابزارها و برنامه‌نویسی سیستمی

C++ زبان پیش‌فرض نرم‌افزارهای حساس به کارایی است و چهل سال کتابخانه پشت سر دارد. سلام یک زبان سیستمی جوان با سطح کوچک‌تر است. این مقایسه کد یکسان را کنار هم می‌گذارد و صریح می‌گوید هر کدام کجا برنده است.

خواندن مقاله
سلام و زبان‌های دیگر

مقایسه سلام و Zig: دو زبان کامپایلی جوان برای نرم‌افزارهای سطح پایین

Zig و سلام هر دو زبان‌های سیستمی پیش از نسخه‌ی ۱٫۰ هستند، بدون زباله‌روب، با دستور defer و واژه‌ی try. اما درباره‌ی تخصیص‌دهنده‌ها، کد زمان کامپایل و اینکه کتابخانه‌ی استاندارد چقدر کار کند، انتخاب‌های بسیار متفاوتی کرده‌اند.

خواندن مقاله