Method to maximize full CPU performance with Python

CK CK 3788 
Created at
Updated at  
316 0 0

Python is not designed to be inherently single-threaded, although its Global Interpreter Lock (GIL) limits true parallelism for CPU-bound tasks within a single process. While the GIL prevents multiple native threads from executing Python bytecodes simultaneously, Python supports multiprocessing to leverage multiple CPU cores and asynchronous programming techniques to handle I/O-bound operations concurrently, achieving parallelism in practice.

 

But sometimes, you may be able to see your code only use a single processor even though there are plenty of resources available with htop :

Method to maximize full CPU performance with Python

 

Running Python code to utilize multiple CPU cores effectively for computational tasks requires understanding Python's concurrency model, particularly the Global Interpreter Lock (GIL). For CPU-bound tasks, simple options primarily involve using separate processes rather than threads to achieve true parallelism.

 

Simple Options for Parallel Processing

1. multiprocessing Module

The multiprocessing module is a package that supports spawning processes using an API similar to the threading module. It allows you to leverage multiple CPU cores on a single machine by running tasks in separate processes.

  • When to use: Ideal for CPU-bound tasks where each sub-task is independent or requires minimal inter-process communication.
  • Key components:
    • Process class: For more manual control, you can create and manage individual processes. This is similar to threading.Thread.
    • Pool class: This is often the simplest and most common approach for "embarrassingly parallel" problems. A Pool manages a fixed number of worker processes (typically equal to the number of CPU cores) to which you can submit tasks.
      • pool.map(func, iterable): Applies a function func to each item in iterable in parallel and returns a list of results in order. This is synchronous.
      • pool.apply_async(func, args): Submits a single task asynchronously.
      • pool.starmap(func, iterable_of_args): Similar to map but unpacks argument tuples.

Simple Example (Conceptual Pool usage):

import multiprocessing
import os

def cpu_bound_task(number):
    """A placeholder for a CPU-intensive computation."""
    # Simulate some heavy computation
    _ = [x*x for x in range(10**6)]
    return f"Processed {number} on PID {os.getpid()}"

if __name__ == "__main__":
    # Get the number of CPU cores available
    num_cores = os.cpu_count()
    print(f"Number of CPU cores available: {num_cores}")

    # Create a Pool of worker processes
    # By default, Pool uses os.cpu_count() processes
    with multiprocessing.Pool() as pool:
        # Example using pool.map for an embarrassingly parallel task
        data_to_process = range(10) # Process 10 items
        results = pool.map(cpu_bound_task, data_to_process)

    print("\nResults:")
    for result in results:
        print(result)

 

2. concurrent.futures Module

The concurrent.futures module provides a high-level interface for asynchronously executing callables. It offers two types of executors: ThreadPoolExecutor (for threads) and ProcessPoolExecutor (for processes).

  • When to use: Offers a slightly more modern and cleaner API than direct multiprocessing for many use cases, especially when you want to submit tasks and retrieve results via "futures."
  • ProcessPoolExecutor: This executor internally uses the multiprocessing module to create a pool of processes. It is the preferred choice for CPU-bound tasks when using concurrent.futures.

Simple Example (Conceptual ProcessPoolExecutor usage):

from concurrent.futures import ProcessPoolExecutor
import os

def cpu_bound_task(number):
    """A placeholder for a CPU-intensive computation."""
    _ = [x*x for x in range(10**6)]
    return f"Processed {number} on PID {os.getpid()}"

if __name__ == "__main__":
    num_cores = os.cpu_count()
    print(f"Number of CPU cores available: {num_cores}")

    # Create a ProcessPoolExecutor
    # max_workers can be set to num_cores or left to default (os.cpu_count())
    with ProcessPoolExecutor(max_workers=num_cores) as executor:
        data_to_process = range(10)
        # map method returns results in order, similar to multiprocessing.Pool.map
        results = executor.map(cpu_bound_task, data_to_process)

    print("\nResults:")
    for result in results:
        print(result)

 

3. Leveraging Libraries with C Extensions

Many popular Python libraries for numerical computing and data science (e.g., NumPy, SciPy, Pandas, scikit-learn) have their core performance-critical routines implemented in optimized C, C++, or Fortran.

  • How it works: When these underlying C/C++/Fortran functions are called, they can release the GIL. This allows them to perform computations using multiple threads (often implicitly, relying on optimized linear algebra libraries like OpenBLAS or Intel MKL) without being constrained by Python's GIL.
  • Simplicity: For many common data processing and scientific computing tasks, the "simplest" way to maximize CPU cores is often to just use these libraries correctly. If you perform array operations with NumPy or matrix multiplications with SciPy, they will often automatically utilize multiple cores if the underlying libraries are compiled to do so.

Example: A complex NumPy array operation might run on multiple cores without explicit multiprocessing code from you, as the underlying C implementation handles the parallelism.

import numpy as np

# This operation might leverage multiple cores if NumPy is linked against
# a multi-threaded BLAS/LAPACK implementation (like OpenBLAS, MKL).
matrix_a = np.random.rand(5000, 5000)
matrix_b = np.random.rand(5000, 5000)

# Matrix multiplication (often parallelized by underlying C libraries)
result_matrix = matrix_a @ matrix_b
print(f"Matrix multiplication completed. Shape: {result_matrix.shape}")

 

General Considerations

  • Overhead: Creating processes has a higher overhead (memory and time) than creating threads. Only use multiprocessing when the computational gain outweighs this overhead.
  • Inter-Process Communication (IPC): If your tasks need to share large amounts of data, consider the cost and complexity of inter-process communication (e.g., queues, pipes, shared memory), which multiprocessing also supports. For simple map-like scenarios, data is often passed via pickling, which has its own overhead.
  • Number of Workers: Typically, set the number of worker processes to be equal to or slightly less than the number of physical CPU cores on your machine to avoid excessive context switching overhead.
  • I/O-bound vs. CPU-bound: Remember that multiprocessing is primarily for CPU-bound tasks. For I/O-bound tasks (e.g., reading/writing files, network requests), threading can be more suitable because the threads spend most of their time waiting, during which the GIL can be released to allow other threads to run.
Tags GIL Global Interpreter Lock NumPy Pool ProcessPoolExecutor Python Thread ThreadPoolExecutor concurrent.features htop multiprocessing Facebook X
Comments 0
Similar posts
  1. Uploading a file to a Google Drive Folder programmatically with Python
    3,307
  2. How do I get the maximum available of thread in python?
    990
  3. The || syntax in Rust - also known as a closure
    600
  4. Basic Rust Thread Example to Perform Concurrent Computation
    614
  5. Rust Std library types
    367
  6. Rust Thread Example
    345
  7. Python script to list files on your Google Drive and download one of them
    940
  8. How to run Python 3.9 on Mac?
    652
  9. Learning about Python Multiprocessing Technique
    592
  10. Simple Example to get the total number of records in BigQuery with Python
    666
  11. Implementing a Versatile DNS Server in Python: Handling A, AAAA, CNAME, and TXT Records
    1,071
  12. Building a Basic DNS Server in PHP/Python: A Beginner's Guide
    739
  13. Dynamic DNS Made Easy: Building a Python-Based Solution
    795
  14. Is PyTorch Dead? Deep Learning's Enduring Role Beyond LLMs & Essential Skills
    694
  15. Forecasting with Linear Regression and KNN Regression in Python
    925
  16. Mastering Model Persistence: Saving and Loading Trained Machine Learning Models in Python
    1,156
  17. Harnessing the Power of Random Forest Algorithm in Python
    658
  18. Understanding and Implementing K-Nearest Neighbors (KNN) Algorithm in Python
    633
  19. Memory Problems with Python Arrays? Discover Dask Array
    323
  20. Python Implementation of Linear Regression
    840
  21. Thread Application Implementation in TThread
    663
  22. Calling Your Main Thread From A Worker Thread In C#
    546
  1. From Local Python Web to Standalone
    2,039
  2. Demystifying FAISS: Your Guide to ANN(Approximate Nearest Neighbor) Search
    320
  3. TensorRT for LLMs: Speed, Scale, and Why Startups Choose It Over TensorFlow
    461
  4. Python ML Technologies used for the target Segmentation
    378
  5. Managing WHITE_LIST in FastAPI to Improve the Server Security
    349
  6. A common async example
    971
  7. Milvus and LLM Integration - Very Smart Approach!
    297
  8. Automated API Documentation with PyCharm
    892
  9. How to install python 3.9 alongside python 3.8 on CentOS 7?
    710
  10. How to run Python 3.9 on Mac?
    652
  11. Python Quizs
    501
  12. Automated OpenAPI documentation for a Python Flask API
    1,120
  13. Automated API documentation for a Django REST Framework (DRF) project using drf-yasg (Yet Another Swagger Generator)
    1,029
  14. Uploading a file to a Google Drive Folder programmatically with Python
    3,307
  15. Garbage Collection when your system does not have enough memory in your Python App
    1,011
  16. How do I get the maximum available of thread in python?
    990
  17. Using Queue Algorithm with Redis - RPUSH and LPOP
    769
  18. Use cache concept with Redis : Let's do Redis programming with Python
    531
  19. Installing Python 3.8 in a venv environment on CentOS 7
    463
  20. CUDA versus OpenCL
    578
  21. Simple gRPC server based on Python
    394
  22. Python code to convert PDF file to Excel Spreadsheet
    700
  23. Python script to list files on your Google Drive and download one of them
    940
  24. Coinbase Python API - Basic Example
    462
  25. AWS Lambda Code Example based on Python
    333
  26. Python code using GPU on CentOS with AWS
    1,251
  27. Video Scence Detection with PySceneDetect
    2,320
  28. Python Audio Separation Libraries allowing you to isolate individual tracks
    1,443
  29. Python Version Control for Library Compatibility with pyenv - Downgrading/Upgrading Python Version
    908
  30. Deleting a specific user voice from the original audio track with Python
    548
Recently updated
  1. Why 100% SEO Guarantees Better User Engagement and How to Achieve It in Your Service
    39
  2. Why SPF and DKIM Are Essential for Linux Email Servers?
    34
  3. How do I create animated background for Google Meet?
    20,611
  4. MaxMind GeoLite2 vs. DB-IP Lite vs. ip-api.com: Choosing the Right Free IP Geolocation Solution
    244
  5. Meet Valkey: Why Redis Changed Its License, Why Valkey is Better, and How to Use It as a Drop-in Replacement
    94
  6. How Rogue Web Scrapers Are Stealing My Content and Destroying My Server
    157
  7. Array of arguments passed to script in PHP
    90
  8. Invalid Auction Storms in Ad Tech
    98
  9. Building MRC-Compliant Instrumentation in Ad Tech: Implementation Steps and Key Challenges
    202
  10. The Two Clocks of Ad Server Architecture
    70
  11. Frequency Cap Architecture Overview
    514
  12. Collecting Proof-of-Play from 100,000 Screens
    146
  13. Vector Search in Relational Databases (RDBs): Trends & Usage Environments
    287
  14. What is RedisVL?
    109
  15. Designing a Skill Registry Architecture: Minimize Token Usage and Maximize AI Performance
    230
  16. TPU & GPU Cluster Management Tools
    243
  17. The Concept of Scaffolding in AI Agents
    233
  18. Beyond Static Pipelines: Architecting LLM Multi-Agent Systems for Autonomous Workflow Orchestration
    583
  19. AI-DLC vs. AI-SDLC: Model-Centric vs. System-Centric AI Development
    258
  20. Fine-Tuning Open-Weight LLMs: Training, Licensing, and Deploying Custom Models with Ollama
    339
  21. Inside the Next-Generation Streaming Architecture: Ultra-Low Latency, SGAI, and AI Codecs
    202
  22. Machine Learning: Training vs. Ranking
    221
  23. The Continuous Learning Machine: Designing a Loop That Actually Turns
    222
  24. The Next-Gen Low-Latency Standards: LL-HLS & MoQ
    195
  25. Designing a Data Curation System Around a Vector Database
    220
  26. Extracting Context from Live Video: The Architecture Got Simpler When We Stopped Building Features
    256
  27. The New Ad Stack Behind Live Sports Streaming: Inside SGAI, EABN, and the Standards Race to Fix Live Monetization
    656
  28. Designing MLOps Architectures to Handle Seasonal Data Drift in EV Fleet Models
    195
  29. From 15 to 40+ FPS: Accelerating PyTorch Object Detection on Automotive ECUs
    216
  30. Understanding Data Lineage
    260