A short note on benchmarking the M3 MacBook Pro using Python

Short and simple Python benchmarking scripts for the new Macbook Pro M3, measuring CPU, memory, and I/O performance.
Author
Affiliation
Sid Metcalfe

Cartesian Mathematics Foundation

Published

November 8, 2023

Introduction

Apple’s Silicon chips (M1 to M3 so far) have been a game-changer in the world of computing with a combination fo exceptional performance and efficiency. The M3 MacBook Pro recently has garnered attention for its ability to handle intensive tasks with ease. As developers and tech enthusiasts, we often want to quantify this performance to understand how it stacks up against other hardware. In this post, we’ll delve into benchmarking the M3 MacBook Pro using Python, providing insights into its computational prowess.

Setting Up the Environment

Before we begin, ensure that you have Python installed on your M3 MacBook Pro. The easiest way to manage Python and its packages on macOS is through the Homebrew package manager. If you haven’t installed Homebrew, you can do so by running the following command in the terminal:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Once Homebrew is installed, you can install Python by executing:

brew install python

With Python in place, we can proceed to write our benchmarking scripts.

Benchmarking CPU Performance

To assess the CPU performance, we’ll use the timeit module in Python, which provides a simple way to time small bits of Python code. It has both a command-line interface and a callable one. Here’s a script that benchmarks the CPU by calculating Fibonacci numbers:

import timeit
## Function to calculate Fibonacci numbers
def fibonacci(n):
    if n <= 1:
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)
## Benchmarking function
def benchmark_fibonacci():
    number_of_runs = 5
    fib_number = 30    
    time_taken = timeit.timeit('fibonacci(fib_number)', globals=globals(), number=number_of_runs)
    
    average_time = time_taken / number_of_runs
    print(f"Average time to calculate Fibonacci({fib_number}) over {number_of_runs} runs: {average_time:.6f} seconds")
benchmark_fibonacci()

This script calculates the 30th Fibonacci number several times and averages the execution time, similar to how we might benchmark the performance of a processor like the AMD Ryzen 5 5600X, widely regarded as a budget king for gamers. It’s a simple CPU-bound task that can give us a quick insight into the processor’s performance.

Benchmarking Memory and I/O Operations

Memory and I/O operations are crucial for overall system performance, especially for data-intensive tasks. To benchmark these, we can use Python’s built-in libraries to read and write large files and measure the time taken.

import time
import os
def benchmark_io_operations(file_size_mb=100):
    file_name = "test_file"
    data = os.urandom(1024 * 1024)  ## Generate 1MB of random data    
    ## Write the file
    start_time = time.time()
    with open(file_name, 'wb') as f:
        for _ in range(file_size_mb):
            f.write(data)
    write_time = time.time() - start_time    
    ## Read the file
    start_time = time.time()
    with open(file_name, 'rb') as f:
        while f.read(1024 * 1024):
            pass
    read_time = time.time() - start_time    
    ## Clean up
    os.remove(file_name)
    
    print(f"Time taken to write a {file_size_mb}MB file: {write_time:.6f} seconds")
    print(f"Time taken to read a {file_size_mb}MB file: {read_time:.6f} seconds")
benchmark_io_operations()

This script writes and reads a 100MB file, timing each operation. It’s a practical way to measure the speed of the SSD in the M3 MacBook Pro and the efficiency of the memory subsystem.

Benchmarking with Real-World Libraries

For a more realistic benchmark, we can use libraries like numpy for numerical computations. Here’s a script that benchmarks matrix multiplication, a common operation in scientific computing:

import numpy as np
import time
def benchmark_matrix_multiplication(size=1000):
    A = np.random.rand(size, size)
    B = np.random.rand(size, size)
    
    start_time = time.time()
    C = np.dot(A, B)
    time_taken = time.time() - start_time    
    print(f"Time taken to multiply two {size}x{size} matrices: {time_taken:.6f} seconds")
benchmark_matrix_multiplication()

This script uses numpy to multiply two large matrices and times the operation. It’s a good test of the M3’s CPU and its optimized math libraries.

Conclusion

Benchmarking the M3 MacBook Pro with Python provides valuable insights into its performance capabilities. The timeit module, along with other Python libraries, allows us to measure various aspects of the system, from CPU speed to memory and I/O operations. The M3 chip has shown impressive results in these benchmarks, often outperforming its predecessors and competitors. However, it’s important to note that benchmarks are not the be-all and end-all. Real-world usage can differ significantly from synthetic tests. Additionally, performance can vary based on system load, background processes, and thermal conditions. Always consider these factors when evaluating benchmark results. The M3 MacBook Pro is a powerful machine that has set a new standard for laptop performance. With Python and the right benchmarking tools, we can quantify this performance and better understand the capabilities of Apple’s innovative chip.

References

  • Python timeit documentation: https://docs.python.org/3/library/timeit.html

  • Homebrew: https://brew.sh/

  • numpy documentation: https://numpy.org/doc/stable/


This post is a high-level overview of benchmarking the M3 MacBook Pro using Python. For more detailed analysis and comprehensive benchmarks, consider using specialized benchmarking suites and comparing results across different systems and configurations.