افزایش سرعت پایتون با Mojo

افزایش سرعت پایتون با Mojo
فهرست مقاله [نمایش]

    افزایش سرعت اجرای کد با استفاده از Mojo فوق سریع

    به‌عنوان مهندسان یادگیری ماشین و توسعه‌دهندگان نرم‌افزار، بهینه‌سازی عملکرد کدهایمان می‌تواند یک دغدغه‌ی جدی باشد. اگر کاربر پایتون باشید، حتماً با برخی از ضعف‌های آن در این زمینه آشنا هستید. پایتون معمولاً به‌عنوان یک زبان کند شناخته می‌شود و احتمالاً بارها شنیده‌اید که بخش زیادی از این مشکل به خاطر مکانیزم Global Interpreter Lock (GIL) است.

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

    در جدیدترین نسخه‌های پایتون، امکان اجرای کد بدون استفاده از GIL فراهم شده است.

    می‌توانیم از کتابخانه‌های پرقدرتی مانند NumPy برای محاسبات عددی سنگین استفاده کنیم.

    روش‌های مختلفی برای پردازش موازی و هم‌زمان (parallel & concurrent) نیز اکنون در خود زبان پایتون تعبیه شده است.

    راه دیگر این است که بخش‌های حساس از نظر زمان اجرا را به زبان‌های سریع‌تر واگذار کنیم و آن‌ها را مستقیماً از داخل پایتون فراخوانی کنیم. این دقیقاً همان چیزی است که در این مقاله بررسی می‌کنیم: فراخوانی کد Mojo از پایتون.

    Mojo چیست؟

    اگر تاکنون نام Mojo به گوشتان نخورده، این توضیح کوتاه برایتان مفید خواهد بود.

    Mojo یک زبان نسبتاً جدید در سطح سیستم است که توسط شرکت Modular Inc. (یک شرکت زیرساخت هوش مصنوعی که در سال ۲۰۲۲ توسط Chris Lattner، خالق LLVM و Swift، و Tim Davis، رهبر سابق Google TPUs، تأسیس شد) توسعه یافته و برای نخستین بار در می ۲۰۲۳ معرفی عمومی شد.

    این زبان از یک مشکل ساده متولد شد: همان ضعف عملکردی پایتون که در بالا گفتیم. Mojo به‌طور مستقیم این مسئله را هدف گرفته و با ترکیب نگارش پایتونی (Pythonic Syntax) با یک زنجیره‌ی کامپایلری مبتنی بر LLVM/MLIR امکانات زیر را فراهم می‌کند:

    انتزاعات بدون هزینه (Zero-cost Abstractions)

    تایپ ایستا (Static Typing)

    مدیریت حافظه مبتنی بر مالکیت (Ownership-based Memory Management)

    بردارسازی خودکار (Automatic Vectorisation)

    تولید کد یکپارچه برای CPU و GPU

    در آزمایش‌های اولیه هنگام معرفی، Mojo توانست در برخی بارهای کاری فشرده تا ۳۵,۰۰۰ برابر سریع‌تر از پایتون معمولی عمل کند. این نتایج نشان دادند Mojo می‌تواند از نظر توان خام، حتی با C یا CUDA رقابت کند یا برتر باشد، در حالی‌که توسعه‌دهندگان همچنان در فضای آشنای پایتونی باقی می‌مانند.

     

    چرا Mojo از داخل پایتون اهمیت دارد؟

    با وجود تمام مزایا، یک مانع جدی وجود دارد: مقاومت افراد در مهاجرت کامل به یک زبان جدید.
    من هم جزو همین افرادم، و برای همین وقتی خبر رسید که از چند هفته پیش امکان فراخوانی مستقیم کد Mojo از پایتون فراهم شده، بسیار خوشحال شدم.

    این یعنی آیا می‌توانیم بهترینِ هر دو دنیا را داشته باشیم؟ سادگی پایتون + عملکرد بی‌رقیب Mojo؟

     

    آزمایش عملکرد

    برای بررسی این موضوع، قصد داریم قطعه‌کدی را ابتدا با پایتون ساده (Vanilla Python) بنویسیم. سپس همان را با NumPy پیاده‌سازی کنیم و در نهایت نسخه‌ای از همان کد را اجرا کنیم که بخشی از محاسبات خود را به یک ماژول Mojo می‌سپارد.

    در پایان، زمان اجرای هر نسخه را با یکدیگر مقایسه خواهیم کرد.

    آیا شاهد بهبود چشمگیر سرعت خواهیم بود؟ ادامه مطلب را بخوانید تا متوجه شوید.

    معمولاً برای این کار از conda استفاده می‌کنم، اما چون این روزها تقریباً همه (حتی مادربزرگ‌ها!) در حال مهاجرت به مدیر بسته‌ی جدید یعنی uv هستند، من هم تصمیم گرفتم این بار از آن استفاده کنم.

    برای نصب uv چند روش وجود دارد:

     

    $ curl -LsSf https://astral.sh/uv/install.sh | sh
    

    یا اینکه:

     

    $ pip install uv
    

     

    ایجاد یک پروژه جدید

    حالا یک پروژه را مقداردهی اولیه می‌کنیم:

     

    $ uv init mojo-test 
    $ cd mojo-test
    $ uv venv
    $ source .venv/bin/activate
    

    خروجی:

     

    Initialized project `mojo-test` at `/home/tom/projects/mojo-test`
    (mojo-test) $ cd mojo-test
    (mojo-test) $ ls -al
    total 28
    drwxr-xr-x  3 tom tom 4096 Jun 27 09:20 .
    drwxr-xr-x 15 tom tom 4096 Jun 27 09:20 ..
    drwxr-xr-x  7 tom tom 4096 Jun 27 09:20 .git
    -rw-r--r--  1 tom tom  109 Jun 27 09:20 .gitignore -rw-r--r--  1 tom tom    5 Jun 27 09:20 .python-version -rw-r--r--  1 tom tom    0 Jun 27 09:20 README.md -rw-r--r--  1 tom tom   87 Jun 27 09:20 main.py -rw-r--r--  1 tom tom  155 Jun 27 09:20 pyproject.toml 

     

    نصب کتابخانه‌های خارجی موردنیاز

    در نهایت، هر کتابخانه‌ی خارجی که نیاز داریم را اضافه می‌کنیم:

     

    (mojo-test) $ uv pip install modular numpy matplotlib
    

     

    فراخوانی Mojo از داخل پایتون چگونه کار می‌کند؟

    فرض کنید یک تابع ساده در Mojo داریم که یک متغیر پایتون را به‌عنو ان ورودی دریافت می‌کند و عدد ۲ را به آن اضافه می‌کند. برای مثال:

     

    # mojo_func.mojo
    #
    fn add_two(py_obj: PythonObject) raises -> Python
        var n = Int(py_obj)
        return n + 2
    

    وقتی پایتون تلاش می‌کند تابع add_two را بارگذاری کند، به‌دنبال تابعی به نام PyInit_add_two() می‌گردد.
    درون PyInit_add_two() باید تمام توابع و نوع‌هایی که قرار است از پایتون قابل فراخوانی باشند را با استفاده از کتابخانه‌ی PythonModuleBuilder معرفی کنیم.

    در نتیجه، کد نهایی Mojo ما چیزی شبیه به این خواهد شد:

     

    from python import PythonObject
    from python.bindings import PythonModuleBuilder
    from os import abort
    
    @export
    fn PyInit_mojo_module() -> PythonObject:
        try:
            var m = PythonModuleBuilder("mojo_func")
            m.def_function[add_two]("add_two", docstring="Add 2 to n")
            return m.finalize()
        except e:
            return abort[PythonObject](String("Error creating Python Mojo module:", e))
    
    fn add_two(py_obj: PythonObject) raises -> PythonObject:
        var n = Int(py_obj)
        n + 2
    

     

    کد پایتون موردنیاز

    برای اینکه این ماژول در پایتون درست کار کند، باید کمی کد اضافی (boilerplate) هم اضافه کنیم. مثلاً به این شکل:

     

    import max.mojo.importer
    import sys
    
    sys.path.insert(0, "")
    
    import mojo_func
    
    print(mojo_func.add_two(5))
    
    

     

    مثال‌های کد

    برای هر مثالی که بررسی می‌کنیم، سه نسخه متفاوت از کد را نشان خواهم داد:

    یک نسخه با پایتون خالص (Pure Python)

    یک نسخه با استفاده از NumPy برای سرعت بیشتر

    یک نسخه با جایگزینی بخشی از پردازش با Mojo

    توجه داشته باشید: فراخوانی کد Mojo از پایتون هنوز در مراحل اولیه توسعه قرار دارد. بنابراین می‌توانید انتظار داشته باشید که API و نحوه‌ی استفاده در آینده تغییرات زیادی داشته باشد.

     

    مثال ۱ — محاسبه مجموعه Mandelbrot

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

    برای این مثال به چهار فایل نیاز داریم:

     

    1. mandelbrot_pure_py.py (نسخه پایتون خالص)

     

    # mandelbrot_pure_py.py def compute(width, height, max_iters):
        """Generates a Mandelbrot set image using pure Python."""
        image = [[0] * width for _ in range(height)]
        for row in range(height):
            for col in range(width):
                c = complex(-2.0 + 3.0 * col / width, -1.5 + 3.0 * row / height)
                z = 0
                n = 0
                while abs(z) <= 2 and n < max_iters:
                    z = z*z + c
                    n += 1
                image[row][col] = n
        return image
    

     

    2. mandelbrot_numpy.py (نسخه NumPy)

     

    # mandelbrot_numpy.py
    
    import numpy as np
    
    def compute(width, height, max_iters):
        """Generates a Mandelbrot set using NumPy for vectorized computation."""
        x = np.linspace(-2.0, 1.0, width)
        y = np.linspace(-1.5, 1.5, height)
        c = x[:, np.newaxis] + 1j * y[np.newaxis, :]
        z = np.zeros_like(c, dtype=np.complex128)
        image = np.zeros(c.shape, dtype=int)
    
        for n in range(max_iters):
            not_diverged = np.abs(z) <= 2
            image[not_diverged] = n
            z[not_diverged] = z[not_diverged]**2 + c[not_diverged]
            
        image[np.abs(z) <= 2] = max_iters
        return image.T
    

     

    3. mandelbrot_mojo.mojo (نسخه Mojo)

     

    # mandelbrot_mojo.mojo 
    
    from python import PythonObject, Python
    from python.bindings import PythonModuleBuilder
    from os import abort
    from complex import ComplexFloat64
    
    # This is the core logic that will run fast in Mojo
    fn compute_mandel_pixel(c: ComplexFloat64, max_iters: Int) -> Int:
        var z = ComplexFloat64(0, 0)
        var n: Int = 0
        while n < max_iters:
            # abs(z) > 2 is the same as z.norm() > 4, which is faster
            if z.norm() > 4.0:
                break
            z = z * z + c
            n += 1
        return n
    
    # This is the function that Python will call
    fn mandelbrot_mojo_compute(width_obj: PythonObject, height_obj: PythonObject, max_iters_obj: PythonObject) raises -> PythonObject:
        
        var width = Int(width_obj)
        var height = Int(height_obj)
        var max_iters = Int(max_iters_obj)
    
        # We will build a Python list in Mojo to return the results
        var image_list = Python.list()
    
        for row in range(height):
            # We create a nested list to represent the 2D image
            var row_list = Python.list()
            for col in range(width):
                var c = ComplexFloat64(
                    -2.0 + 3.0 * col / width,
                    -1.5 + 3.0 * row / height
                )
                var n = compute_mandel_pixel(c, max_iters)
                row_list.append(n)
            
            image_list.append(row_list)
                
        return image_list
    
    # This is the special function that "exports" our Mojo function to Python
    @export
    fn PyInit_mandelbrot_mojo() -> PythonObject:
        try:
            var m = PythonModuleBuilder("mandelbrot_mojo")
            m.def_function[mandelbrot_mojo_compute]("compute", "Generates a Mandelbrot set.")
            return m.finalize()
        except e:
            return abort[PythonObject]("error creating mandelbrot_mojo module")
    

     

    4. main.py (اجرای تست و مصورسازی)

     

    # main.py (Final version with visualization)
    
    import time
    import numpy as np
    import sys
    import matplotlib.pyplot as plt # Now, import pyplot
    
    # --- Mojo Setup --- try:
        import max.mojo.importer
    except ImportError:
        print("Mojo importer not found. Please ensure the MODULAR_HOME and PATH are set correctly.")
        sys.exit(1)
    
    sys.path.insert(0, "")
    
    # --- Import Our Modules --- import mandelbrot_pure_py
    import mandelbrot_numpy
    import mandelbrot_mojo
    
    # --- Visualization Function --- def visualize_mandelbrot(image_data, title="Mandelbrot Set"):
        """Displays the Mandelbrot set data as an image using Matplotlib."""
        print(f"Displaying image for: {title}")
        plt.figure(figsize=(10, 8))
        # 'hot', 'inferno', and 'plasma' are all great colormaps for this
        plt.imshow(image_data, cmap='hot', interpolation='bicubic')
        plt.colorbar(label="Iterations")
        plt.title(title)
        plt.xlabel("Width")
        plt.ylabel("Height")
        plt.show()
    
    # --- Test Runner --- def run_test(name, compute_func, *args):
        """A helper function to run and time a test."""
        print(f"Running {name} version...")
        start_time = time.time()
        # The compute function returns the image data
        result_data = compute_func(*args)
        duration = time.time() - start_time
        print(f"-> {name} version took: {duration:.4f} seconds")
        # Return the data so we can visualize it
        return result_data
    
    if __name__ == "__main__":
        WIDTH, HEIGHT, MAX_ITERS = 800, 600, 5000
        
        print("Starting Mandelbrot performance comparison...")
        print("-" * 40)
    
        # Run Pure Python Test
        py_image = run_test("Pure Python", mandelbrot_pure_py.compute, WIDTH, HEIGHT, MAX_ITERS)
        visualize_mandelbrot(py_image, "Pure Python Mandelbrot")
    
        print("-" * 40)
    
        # Run NumPy Test
        np_image = run_test("NumPy", mandelbrot_numpy.compute, WIDTH, HEIGHT, MAX_ITERS)
        # uncomment the below line if you want to see the plot
        #visualize_mandelbrot(np_image, "NumPy Mandelbrot")
    
        print("-" * 40)
    
        # Run Mojo Test
        mojo_list_of_lists = run_test("Mojo", mandelbrot_mojo.compute, WIDTH, HEIGHT, MAX_ITERS)
        # Convert Mojo's list of lists into a NumPy array for visualization
        mojo_image = np.array(mojo_list_of_lists)
        # uncomment the below line if you want to see the plot  
        #visualize_mandelbrot(mojo_image, "Mojo Mandelbrot")
    
        print("-" * 40)
        print("Comparison complete.")
    

     

    خروجی تست‌ها

    خروجی نشان داد که:

    Mojo تقریباً ۲۰ برابر سریع‌تر از نسخه پایتون خالص عمل کرد.

    همچنین Mojo حدود ۵ برابر سریع‌تر از نسخه NumPy بود.

    این یک شروع بسیار چشمگیر برای Mojo است. 

    مثال ۲ — انتگرال‌گیری عددی (Numerical Integration)

    در این مثال، انتگرال‌گیری عددی را با استفاده از قاعده‌ی سیمپسون (Simpson’s Rule) انجام می‌دهیم تا مقدار تابع sin(x) را در بازه‌ی ۰ تا π محاسبه کنیم.

    قاعده‌ی سیمپسون روشی برای تقریب مقدار انتگرال است و به‌صورت زیر تعریف می‌شود:

    ∫≈h3[f(x0)+4f(x1)+2f(x2)+4f(x3)+…+2f(xn−2)+4f(xn−1)+f(xn)]\int \approx \frac{h}{3} \Big[f(x₀) + 4f(x₁) + 2f(x₂) + 4f(x₃) + … + 2f(x_{n-2}) + 4f(x_{n-1}) + f(xₙ)\Big]∫≈3h​[f(x0​)+4f(x1​)+2f(x2​)+4f(x3​)+…+2f(xn−2​)+4f(xn−1​)+f(xn​)]

    که در آن:

    h عرض هر بازه است.

    ضرایب به ترتیب: ۱، ۴، ۲، ۴، ۲، …، ۴، ۱ هستند.

    اولین و آخرین نقطه ضریب ۱ دارند.

    نقاط با اندیس فرد ضریب ۴ دارند.

    نقاط با اندیس زوج ضریب ۲ دارند.

    مقدار واقعی انتگرال موردنظر ما ۲ است. ببینیم روش‌های مختلف چه دقت و سرعتی دارند.

    مانند مثال قبلی، به چهار فایل نیاز داریم:

     

    1. integration_pure_py.py (پایتون خالص)

     

    # integration_pure_py.py import math
    
    def compute(start, end, n):
        """Calculates the definite integral of sin(x) using Simpson's rule."""
        if n % 2 != 0:
            n += 1 # Simpson's rule requires an even number of intervals
        
        h = (end - start) / n
        integral = math.sin(start) + math.sin(end)
    
        for i in range(1, n, 2):
            integral += 4 * math.sin(start + i * h)
        
        for i in range(2, n, 2):
            integral += 2 * math.sin(start + i * h)
            
        integral *= h / 3
        return integral
    

     

    2. integration_numpy.py (نسخه NumPy)

     

    # integration_numpy.py import numpy as np
    
    def compute(start, end, n):
        """Calculates the definite integral of sin(x) using NumPy."""
        if n % 2 != 0:
            n += 1
        
        x = np.linspace(start, end, n + 1)
        y = np.sin(x)
        
        # Apply Simpson's rule weights: 1, 4, 2, 4, ..., 2, 4, 1
        integral = (y[0] + y[-1] + 4 * np.sum(y[1:-1:2]) + 2 * np.sum(y[2:-1:2]))
        
        h = (end - start) / n
    

     

    3. integration_mojo.mojo (نسخه Mojo)

     

    # integration_mojo.mojo
    from python import PythonObject, Python
    from python.bindings import PythonModuleBuilder
    from os import abort
    from math import sin
    
    # Note: The 'fn' keyword is used here as it's compatible with all versions.
    fn compute_integral_mojo(start_obj: PythonObject, end_obj: PythonObject, n_obj: PythonObject) raises -> PythonObject:
        # Bridge crossing happens ONCE at the start.
        var start = Float64(start_obj)
        var end = Float64(end_obj)
        var n = Int(n_obj)
    
        if n % 2 != 0:
            n += 1
        
        var h = (end - start) / n
        
        # All computation below is on NATIVE Mojo types. No Python interop.
        var integral = sin(start) + sin(end)
    
        # First loop for the '4 * f(x)' terms
        var i_1: Int = 1
        while i_1 < n:
            integral += 4 * sin(start + i_1 * h)
            i_1 += 2
    
        # Second loop for the '2 * f(x)' terms
        var i_2: Int = 2
        while i_2 < n:
            integral += 2 * sin(start + i_2 * h)
            i_2 += 2
            
        integral *= h / 3
        
        # Bridge crossing happens ONCE at the end.
        return Python.float(integral)
    
    @export
    fn PyInit_integration_mojo() -> PythonObject:
        try:
            var m = PythonModuleBuilder("integration_mojo")
            m.def_function[compute_integral_mojo]("compute", "Calculates a definite integral in Mojo.")
            return m.finalize()
        except e:
            return abort[PythonObject]("error creating integration_mojo module")
    

     

    4. main.py (اجرای تست‌ها)

     

    import time
    import sys
    import numpy as np
    
    # --- Mojo Setup --- try:
        import max.mojo.importer
    except ImportError:
        print("Mojo importer not found. Please ensure your environment is set up correctly.")
        sys.exit(1)
    sys.path.insert(0, "")
    
    # --- Import Our Modules --- import integration_pure_py
    import integration_numpy
    import integration_mojo
    
    # --- Test Runner --- def run_test(name, compute_func, *args):
        print(f"Running {name} version...")
        start_time = time.time()
        result = compute_func(*args)
        duration = time.time() - start_time
        print(f"-> {name} version took: {duration:.4f} seconds")
        print(f"   Result: {result}")
    
    # --- Main Test Execution --- if __name__ == "__main__":
        # Use a very large number of steps to highlight loop performance
        START = 0.0
        END = np.pi 
        NUM_STEPS = 100_000_000 # 100 million steps
        
        print(f"Calculating integral of sin(x) from {START} to {END:.2f} with {NUM_STEPS:,} steps...")
        print("-" * 50)
    
        run_test("Pure Python", integration_pure_py.compute, START, END, NUM_STEPS)
        print("-" * 50)
        run_test("NumPy", integration_numpy.compute, START, END, NUM_STEPS)
        print("-" * 50)
        run_test("Mojo", integration_mojo.compute, START, END, NUM_STEPS)
        print("-" * 50)
        print("Comparison complete.")
    

     

    نتایج اجرای تست

     

    Calculating integral of sin(x) from 0.0 to 3.14 with 100,000,000 steps...
    -------------------------------------------------- Running Pure Python version...
    -> Pure Python version took: 4.9484 seconds
       Result: 2.0000000000000346 -------------------------------------------------- Running NumPy version...
    -> NumPy version took: 0.7425 seconds
       Result: 1.9999999999999998 -------------------------------------------------- Running Mojo version...
    -> Mojo version took: 0.8902 seconds
       Result: 2.0000000000000346 --------------------------------------------------
    Comparison complete.
    

     

    تحلیل نتایج

    در این آزمایش، برخلاف مثال Mandelbrot، کد NumPy کمی سریع‌تر از Mojo بود و همچنین نتیجه‌ی نهایی آن کمی دقیق‌تر به مقدار واقعی نزدیک شد.

    این موضوع یک نکته‌ی مهم در محاسبات با کارایی بالا (HPC) را نشان می‌دهد:
    همیشه یک تبادل (Trade-off) بین بردارسازی (Vectorisation) و حلقه‌های کامپایل‌شده JIT وجود دارد.

    قدرت NumPy در بردارسازی عملیات است. این کتابخانه یک بلوک بزرگ از حافظه را اختصاص می‌دهد و سپس کد C از پیش بهینه‌شده را که از ویژگی‌های مدرن CPU مانند SIMD استفاده می‌کند، اجرا می‌کند. این پردازش دسته‌ای (Burst Processing) بسیار کارآمد است.

    Mojo در مقابل، حلقه‌های ساده‌ی ما را به کد ماشین بسیار کارآمد JIT-Compile می‌کند. این روش از تخصیص حافظه‌ی بزرگ اولیه‌ی NumPy اجتناب می‌کند، اما در این مثال خاص، توان خام بردارسازی NumPy باعث شد کمی سریع‌تر عمل کند.

    مثال ۳ — تابع سیگموید (Sigmoid Function)

    تابع سیگموید یکی از مفاهیم کلیدی در هوش مصنوعی است؛ زیرا پایه‌ی اصلی طبقه‌بندی دودویی (Binary Classification) محسوب می‌شود.

    این تابع که به نام تابع لجستیک (Logistic Function) هم شناخته می‌شود، به‌صورت زیر تعریف شده است:

    تابع سیگموید هر ورودی حقیقی x را گرفته و به‌صورت نرم و پیوسته در بازه‌ی باز (0, 1) فشرده می‌کند. به زبان ساده، مهم نیست چه مقداری به تابع سیگموید داده شود، خروجی همیشه عددی بین صفر و یک خواهد بود.

    برای مثال:

     

    S(-197865)   = 0

     S(-2)        = 0.0009111 S(3)      

       = 0.9525741 S(10776.87)  = 1

    این ویژگی باعث می‌شود که سیگموید انتخاب مناسبی برای نمایش احتمالات باشد.

    از آنجایی که کد پایتون این مثال ساده‌تر است، می‌توانیم آن را مستقیم در اسکریپت بنچمارک قرار دهیم. بنابراین این بار فقط به دو فایل نیاز داریم.

     

    1. sigmoid_mojo.mojo

     

    from python               import Python, PythonObject
    from python.bindings      import PythonModuleBuilder
    from os                   import abort
    from math                 import exp
    from time                 import perf_counter
    
    # ----------------------------------------------------------------------
    #   Fast Mojo routine (no Python calls inside)
    # ----------------------------------------------------------------------
    fn sigmoid_sum(n: Int) -> (Float64, Float64):
        # deterministic fill, sized once
        var data = List[Float64](length = n, fill = 0.0)
        for i in range(n):
            data[i] = (Float64(i) / Float64(n)) * 10.0 - 5.0   # [-5, +5]
    
        var t0: Float64 = perf_counter()
        var total: Float64 = 0.0
        for x in data:                       # single tight loop
            total += 1.0 / (1.0 + exp(-x))
        var elapsed: Float64 = perf_counter() - t0
        return (total, elapsed)
    
    # ----------------------------------------------------------------------
    #   Python-visible wrapper
    # ----------------------------------------------------------------------
    fn py_sigmoid_sum(n_obj: PythonObject) raises -> PythonObject:
        var n: Int = Int(n_obj)                        # validates arg
        var (tot, secs) = sigmoid_sum(n)
    
        # safest container: build a Python list (auto-boxes scalars)
        var out = Python.list()
        out.append(tot)
        out.append(secs)
        return out                                     # -> PythonObject
    
    # ----------------------------------------------------------------------
    #   Module initialiser  (name must match:  PyInit_sigmoid_mojo)
    # ----------------------------------------------------------------------
    @export
    fn PyInit_sigmoid_mojo() -> PythonObject:
        try:
            var m = PythonModuleBuilder("sigmoid_mojo")
            m.def_function[py_sigmoid_sum](
                "sigmoid_sum",
                "Return [total_sigmoid, elapsed_seconds]"
            )
            return m.finalize()
        except e:
            # if anything raises, give Python a real ImportError
            return abort[PythonObject]("error creating sigmoid_mojo module")
    

     

    2. main.py

     

    # bench_sigmoid.py import time, math, numpy as np
    
    N = 50_000_000  
    
    # --------------------------- pure-Python -----------------------------------
    py_data = [(i / N) * 10.0 - 5.0 for i in range(N)]
    t0 = time.perf_counter()
    py_total = sum(1 / (1 + math.exp(-x)) for x in py_data)
    print(f"Pure-Python : {time.perf_counter()-t0:6.3f} s  - Σσ={py_total:,.1f}")
    
    # --------------------------- NumPy -----------------------------------------
    np_data = np.linspace(-5.0, 5.0, N, dtype=np.float64)
    t0 = time.perf_counter()
    np_total = float(np.sum(1 / (1 + np.exp(-np_data))))
    print(f"NumPy       : {time.perf_counter()-t0:6.3f} s  - Σσ={np_total:,.1f}")
    
    # --------------------------- Mojo ------------------------------------------ import max.mojo.importer          # installs .mojo import hook import sigmoid_mojo               # compiles & loads shared object
    
    mj_total, mj_secs = sigmoid_mojo.sigmoid_sum(N)
    print(f"Mojo        : {mj_secs:6.3f} s  - Σσ={mj_total:,.1f}")
    

     

    خروجی

     

    $ python sigmoid_bench.py Pure-Python :  1.847 s  - Σσ=24,999,999.5 NumPy       :  0.323 s  - Σσ=25,000,000.0 Mojo        :  0.150 s  - Σσ=24,999,999.5

    خروجی Σσ=… مجموع تمام مقادیر محاسبه‌شده‌ی سیگموید را نشان می‌دهد. به‌طور نظری، این مقدار باید دقیقاً برابر با N / 2 باشد وقتی N به سمت بی‌نهایت میل می‌کند.

    همان‌طور که می‌بینیم، پیاده‌سازی Mojo توانست نسبت به NumPy که خودش بسیار سریع است، بیش از دو برابر سریع‌تر باشد و در مقایسه با پایتون خالص بیش از ۱۲ برابر سریع‌تر عمل کند.

     

    جمع‌بندی

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

    Mojo که یک زبان نسبتاً جدید در سطح سیستم از شرکت Modular است، با وعده‌ی کارایی در سطح C و نگارشی آشنا برای پایتون، سعی دارد محدودیت‌های تاریخی سرعت پایتون را برطرف کند.

    برای آزمودن این وعده، سه سناریوی سنگین محاسباتی را در سه نسخه‌ی پایتون خالص، NumPy بهینه‌سازی‌شده و ترکیب Python-Mojo پیاده‌سازی و بنچمارک کردیم:

    تولید مجموعه Mandelbrot

    انتگرال‌گیری عددی

    محاسبه تابع سیگموید

    نتایج نشان دادند که:

    در الگوریتم‌هایی با حلقه‌های سنگین که داده‌ها به‌طور کامل با انواع بومی Mojo پردازش می‌شوند، Mojo می‌تواند به‌طور چشمگیری بهتر از پایتون خالص و حتی NumPy عمل کند.

    اما در وظایفی که دقیقاً با قابلیت بردارسازی NumPy و توابع C از پیش کامپایل‌شده هم‌راستا هستند، NumPy همچنان می‌تواند اندکی بر Mojo برتری داشته باشد.

    این بررسی نشان داد که هرچند Mojo ابزاری قدرتمند برای افزایش سرعت پایتون است، برای رسیدن به بیشترین کارایی باید به دقت هزینه‌ی رفت‌وآمد بین دو زمان‌اجرای پایتون و Mojo (Bridge-Crossing Overhead) را مدیریت کنیم.

    در نهایت:
    ✅ همیشه قبل از اعمال تغییرات، تست، تست، تست. این تنها معیار واقعی است که مشخص می‌کند یک بهینه‌سازی ارزشمند است یا خیر.

    اطلاعات نویسنده

    ارسال دیدگاه

    برای افزودن دیدگاه خود، نیاز است ابتدا وارد حساب کاربری‌تان شوید


    دیدگاه کاربران


    }