← Back to list

High Performance and Parallel Computing — Week 3

Parallel Algorithms and Performance Analysis

Ayoade Akintayo (PhD) · 2025-11-14 12:56 · 0 claps · 6.2 min read
#parallel-algorithm #decomposition #data-parallelism #amdahls-law #gustafsons-law
Open on Medium ↗
Wiki topics: VIS · Visual & Graphic Design 💻 · Programming ⚖️ · Law & Justice

High Performance and Parallel Computing — Week 3

Parallel Algorithms and Performance Analysis

1. Introduction to Parallel Algorithm Design

Welcome to Week 3, where we transition from understanding parallel hardware and programming models to the very heart of the matter: designing the algorithms that run on them.

A parallel algorithm is a recipe for solving a computational problem by breaking it down into smaller sub-problems that can be executed simultaneously on multiple processors. The goal is not just to make a program run faster, but to design it in a way that efficiently utilizes parallel resources. Designing a parallel algorithm is fundamentally different from designing a sequential one. We must think about coordination, communication, and the division of labor from the very beginning. A poorly designed parallel algorithm can be slower than its sequential counterpart due to the overhead of managing parallelism. The design process typically involves four key steps: decomposition, assignment, orchestration, and mapping, which we will explore in detail next.

2. Decomposition and Task Partitioning Strategies

The first and most crucial step in parallel algorithm design is decomposition, which involves breaking down the overall computation into smaller, manageable units of work, called tasks. Think of it like planning a large banquet. You wouldn’t have one chef cook every dish from start to finish; you would decompose the work into tasks like preparing salads, grilling meats, and baking desserts. In computing, tasks can be based on the data or the functions to be performed.

Once tasks are identified, we need a partitioning strategy to divide them among processors. The two primary strategies are static partitioning and dynamic partitioning. Static partitioning assigns tasks to processors at the beginning of the computation, and the assignment does not change. This is efficient when all tasks are roughly the same size and take the same amount of time to complete. For example, when processing a large image by dividing it into equal-sized tiles, we can statically assign each tile to a different processor.

However, if the tasks have highly variable or unpredictable execution times, static partitioning can lead to load imbalance, where some processors finish early and sit idle while others are still working. Imagine if one chef was assigned to cook a simple salad and another was assigned a complex soufflé; the first would finish quickly and have nothing to do. In such cases, dynamic partitioning is superior. Here, tasks are placed into a pool, and processors grab a new task from the pool as soon as they finish their current one. This ensures that all processors stay busy until the work is complete, effectively balancing the load. This approach is common in applications like ray-tracing in computer graphics, where the time to render each pixel can vary significantly.

3. Data vs. Task Parallelism

After decomposition, we can identify the primary form of parallelism in our algorithm. The two fundamental paradigms are data parallelism and task parallelism.

Data Parallelism involves applying the same operation simultaneously to different elements of a dataset. It is a “single instruction, multiple data” (SIMD) style of operation. A classic example is applying a filter to every pixel in an image or adding two large arrays together. Each processor works on a different portion of the data, but they all execute the same instruction. This model is straightforward to implement and is exceptionally well-suited for architectures like GPUs, which are designed to perform the same operation on thousands of data points in lockstep.

In contrast, Task Parallelism involves executing different operations or functions concurrently. This is a “multiple instruction, multiple data” (MIMD) approach. An example would be a web server handling multiple simultaneous requests: one task might be serving an HTML page, another processing a database query, and a third handling a file upload. Each task is independent and can be executed on a separate processor. Task parallelism is powerful for building responsive systems and for pipelining, where data flows through a series of distinct processing stages.

Many real-world applications are hybrids. Consider a video transcoding service. At a high level, it uses task parallelism to transcode multiple different videos at the same time. For each individual video, it uses data parallelism by splitting each frame into blocks and processing them concurrently on a GPU.

4. Amdahl’s Law and the Limits of Parallelization

As we strive for higher performance, a critical question arises: what is the maximum speedup we can possibly achieve? This is formally addressed by Amdahl’s Law, formulated by Gene Amdahl in 1967. This law provides a sobering reminder of the limitations of parallelization. It states that the maximum speedup of a program is limited by the fraction of the program that must run sequentially.

Let us define P as the fraction of the program that is perfectly parallelizable, and S as the fraction that is strictly sequential (so P + S = 1). Amdahl’s Law states that the maximum speedup achievable on N processors is:

Speedup(N) ≤ 1 / (S + P/N)

The profound implication of this law is that even with an infinite number of processors, the maximum speedup is capped at 1/S. For example, if only 90% of your program can be parallelized (P=0.9, S=0.1), the maximum speedup, even with a million processors, is 10. The sequential 10% becomes a bottleneck that cannot be overcome by adding more parallel resources. This is why in HPC, immense effort is spent on minimizing and optimizing the sequential parts of a code.

5. Gustafson’s Law and Scalability in Large Systems

While Amdahl’s Law paints a pessimistic picture for fixed-size problems, Gustafson’s Law (1988) offers a more optimistic and practical perspective for the world of supercomputing. Gustafson observed that in practice, scientists and engineers are not usually interested in solving the same problem faster; they want to solve larger, more complex problems in the same amount of time.

Gustafson’s Law argues that as we get access to more processors, we tend to increase the problem size to fully utilize them. In this scenario, the sequential portion of the work does not grow with the problem size, or it grows very slowly. For instance, the time spent setting up a simulation or collecting final results (the sequential part) might remain constant, while the time spent on the core parallel computation (e.g., solving equations for a finer grid) increases.

Gustafson’s Law is often expressed as:

Speedup(N) = N — α(N — 1)

where α is the sequential fraction of the parallel execution. The key takeaway is that if the problem size is scaled up along with the number of processors, the speedup can approach linear. This explains why massive supercomputers are not bound by Amdahl’s Law for many of their applications — they are used to tackle problems that were previously unimaginable, not just to solve small problems faster.

6. Case Study: Parallel Matrix Multiplication and Image Processing

Let’s solidify these concepts with a classic case study: parallel matrix multiplication. Matrix multiplication is a cornerstone of scientific computing and machine learning, with a computational complexity of O(n³) for two n x n matrices. The sequential algorithm uses three nested loops.

A simple data-parallel approach is to use a decomposition where each task is responsible for computing one element (or one row) of the result matrix. Since each element in the result matrix can be computed independently, this is an embarrassingly parallel problem. We can use OpenMP to parallelize the outer loop, assigning different rows to different threads within a shared-memory node. This provides a good speedup, but it is limited by the memory bandwidth of the single node.

To scale to massive matrices, we use a distributed-memory approach with MPI. We decompose the matrices into blocks and distribute these blocks across different nodes. The famous Fox algorithm or Cannon’s algorithm are designed for this. Each node holds a sub-matrix and communicates with its neighbors to perform local multiplications and summations. Here, performance is heavily influenced by the communication overhead between nodes. The efficiency of this algorithm depends on how well we can overlap computation with communication.

This directly translates to image processing. Applying a filter (like a blur or edge detection) to a high-resolution image is a data-parallel task. The image can be partitioned into tiles (static partitioning), and each tile can be processed by a different thread (OpenMP) or process (MPI). A service like Google Photos uses this principle at a colossal scale, employing GPU-accelerated clusters to run object detection and style transfer algorithms on billions of images by leveraging both data and task parallelism across thousands of machines.

Summary / Key Takeaways

Parallel algorithm design is a structured process involving decomposition, assignment, orchestration, and mapping.

Decomposition can be static (fixed assignment) or dynamic (work-pool) to handle load imbalance.

Data Parallelism applies the same operation to multiple data items, while Task Parallelism executes different operations concurrently.

Amdahl’s Law places a hard limit on speedup for fixed-size problems, dictated by the sequential portion of the code.

Gustafson’s Law provides a more scalable view, showing that speedup can be nearly linear if the problem size is increased along with the number of processors.

Real-world applications, like large-scale matrix multiplication and image processing, require careful selection of parallel paradigms and partitioning strategies to minimize communication overhead and maximize resource utilization.

Reflection Question

As you analyze your lab results this week, consider the following: In designing parallel algorithms, which factors determine whether computation time decreases or increases with more processors? Beyond the theoretical laws, think about practical overheads such as the time spent starting new threads or processes, the cost of synchronizing them, the latency of communication between them, and the inevitable load imbalance when tasks are not perfectly uniform. How might the memory hierarchy (cache, main memory) and network topology further influence this delicate balance?


메타데이터
post_id
c59134ff37cf
slug
high-performance-and-parallel-computing-week-3-c59134ff37cf
url
https://medium.com/@ayoadeakin234/high-performance-and-parallel-computing-week-3-c59134ff37cf
canonical_url
https://medium.com/@ayoadeakin234/high-performance-and-parallel-computing-week-3-c59134ff37cf
author_url
https://medium.com/@ayoadeakin234
status
ok
fetched_at
2026-07-17 02:44:42