0% found this document useful (0 votes)
15 views11 pages

Visvesvaraya Technological University Jnana Sangama, Belagavi-590018

Uploaded by

p.shreya1074
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views11 pages

Visvesvaraya Technological University Jnana Sangama, Belagavi-590018

Uploaded by

p.shreya1074
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

VISVESVARAYA TECHNOLOGICAL UNIVERSITY

Jnana Sangama, Belagavi-590018

REPORT ON
“Parallelize a sparse matrix multiplication using OpenMP”

Submitted in partial fulfilment of the requirement for the award of the

DEGREE OF BACHELOR OF ENGINEERING IN


COMPUTER SCIENCE & ENGINEERING
submitted by

P SHREYA
1AT22CS069
&
SANDHYA S
1AT22CS085
Under the guidance of

Prof. Tanmoy Kundu


Assistant professor, CSE Dept

Atria Institute of Technology


Department of Computer Science & Engineering
Bangalore-560024
Abstract
This report presents a comprehensive study on parallelizing sparse matrix multiplication using
OpenMP to enhance computational performance. The implementation compares the execution time of
sparse and dense matrix multiplications across varying matrix sizes and sparsity levels under different
thread configurations. Experimental evaluations were conducted on a multicore environment using
OpenMP directives to exploit data-level parallelism. Results show that sparse matrix multiplication
significantly reduces computation time by eliminating operations involving zero elements, achieving
up to 2–3× speedup compared to dense multiplication. The study also analyzes the impact of sparsity
on thread workload distribution, demonstrating that moderate sparsity levels provide optimal
performance, while excessive sparsity can lead to load imbalance and reduced efficiency. The findings
highlight that OpenMP-based parallelization effectively improves computational throughput for large-
scale matrix operations and emphasizes the role of sparsity optimization in achieving high-
performance parallel computing.

Table of Contents
1. Introduction and Methodology
- 1.1 Problem Statement
-1.2 What is a Sparse Matrix
- 1.3 Mathematical Formula
- 1.4 Experimental Setup
2. Implementation and Results
- 2.1 Key Code Implementation
- 2.2 Experimental Results
3. Analysis and Conclusion
- 3.1 Key Findings
- 3.2 Challenges and Solutions
- 3.3 Conclusion
Appendix
- Compilation and Execution Commands

Introduction and Methodology


1.1 Problem Statement

We study the performance of parallel matrix multiplication for sparse matrices and compare it with
dense matrix multiplication. The goal is to determine how varying sparsity levels affect overall
execution time, parallel speedup, and load balance when using OpenMP on a shared-memory
architecture. We evaluate:
1. Execution time of sparse vs dense multiplication.
2. Parallel speedup and parallel efficiency for varying thread counts.
3. Work imbalance across threads (per-thread operation counts / CoV).
4. Effects of sparsity pattern (uniform random vs skewed) on performance

1.2 What is a Sparse Matrix

A sparse matrix is a matrix in which the majority of elements are zero. In contrast, a dense matrix has
relatively few zeros. Sparse matrices arise in many applications: graphs (adjacency matrices), finite-
element methods, recommendation systems, natural-language processing (term-document matrices), etc.
Key points:
• Representations: dense A[i][j] vs compressed formats such as CSR (Compressed Sparse Row),
CSC (Compressed Sparse Column), and coordinate (COO).
• Why special handling matters: storing only nonzero entries reduces memory, and iterating only
nonzeros reduces arithmetic work — but introduces irregular memory access and potential load
imbalance.

1.3 Mathematical Formula:

- Matrix multiplication (dense)

- Given two 𝑛 × 𝑛matrices 𝐴and 𝐵, the standard dense matrix multiplication computes

- 𝐶𝑖𝑗 = ∑𝑛−1
𝑘=0 𝐴𝑖𝑘 ⋅ 𝐵𝑘𝑗 ∀ 0 ≤ 𝑖, 𝑗 < 𝑛.

- Number of scalar multiplications (FLOPs multiplier-add pairs) = 𝑛3 . Time complexity: 𝑂(𝑛3 ).

- Sparsity definition

- Define sparsity 𝑠as the fraction of elements that are zero:


#zeros
-𝑠= , 0 ≤ 𝑠 ≤ 1.
𝑛2

- Density (fraction of nonzeros) is 𝑑 = 1 − 𝑠.

- For random sparsity (entries zero independently with probability 𝑠), the expected number of
multiply-adds per output entry is approximately:

- expected nonzero k’s per pair ≈ 𝑛 ⋅ 𝑑 2 = 𝑛(1 − 𝑠)2 ,

- so expected total multiply-adds ≈ 𝑛3 (1 − 𝑠)2 .

- Thus, expected FLOP reduction factor ≈ (1 − 𝑠)2 . For example, 𝑠 = 0.8(80% zeros) gives (1 −
0.8)2 = 0.04— so only ~4% of dense FLOPs.

- Parallel speedup & efficiency

- Speedup for 𝑝threads:


𝑇1
- 𝑆(𝑝) = .
𝑇𝑝

- Parallel efficiency:
𝑆(𝑝) 𝑇1
- 𝐸(𝑝) = = .
𝑝 𝑝𝑇𝑝

- Amdahl-like considerations: serial overhead 𝑇serial and imbalance degrade

1.4 Experimental Setup

Environment

All experiments were conducted on Google Colab, which provides a virtualized Linux environment with
limited but consistent computational resources suitable for OpenMP-based performance analysis.
• Processor: Intel(R) Xeon CPU @ 2.20GHz (2 physical cores, 4 threads available)

• Memory: 12 GB RAM

• Operating System: Ubuntu 22.04 LTS (Google Colab backend)

• Compiler: g++ (Ubuntu 11.4.0) with OpenMP support

• Compilation Command:

!g++ -O2 -fopenmp sparse_vs_dense.cpp -o sparse_vs_dense

• Execution Command:

!OMP_NUM_THREADS=4 ./sparse_vs_dense
Implementation and Results

2.1 Key Code Implementation

%%writefile matrix_mult.cpp
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <omp.h>
using namespace std;

// Function to generate a random dense matrix


void generateDenseMatrix(vector<vector<double>> &mat, int n) {
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
mat[i][j] = rand() % 10;
}

// Function to generate a random sparse matrix (with sparsity%)


void generateSparseMatrix(vector<vector<double>> &mat, int n, double sparsity) {
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
double r = (rand() % 100) / 100.0;
if (r < sparsity)
mat[i][j] = 0;
else
mat[i][j] = rand() % 10;
}
}
}

// Dense matrix multiplication


void denseMultiply(const vector<vector<double>> &A, const vector<vector<double>> &B,
vector<vector<double>> &C, int n) {
#pragma omp parallel for collapse(2)
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
double sum = 0;
for(int k = 0; k < n; k++) {
sum += A[i][k] * B[k][j];
}
C[i][j] = sum;
}
}
}

// Sparse matrix multiplication (skip zero elements)


void sparseMultiply(const vector<vector<double>> &A, const vector<vector<double>> &B,
vector<vector<double>> &C, int n) {
#pragma omp parallel for collapse(2)
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
double sum = 0;

for(int k = 0; k < n; k++) {


if (A[i][k] != 0 && B[k][j] != 0)
sum += A[i][k] * B[k][j];
}
C[i][j] = sum;
}
}
}

int main() {
srand(time(0));
int n = 300; // You can adjust this (200–400)
double sparsity = 0.8; // 80% zeros

vector<vector<double>> A(n, vector<double>(n));


vector<vector<double>> B(n, vector<double>(n));
vector<vector<double>> C(n, vector<double>(n, 0));

cout << "Matrix size: " << n << "x" << n << endl;
cout << "Sparsity: " << sparsity * 100 << "%" << endl;

generateSparseMatrix(A, n, sparsity);
generateSparseMatrix(B, n, sparsity);

double start = omp_get_wtime();


sparseMultiply(A, B, C, n);
double end = omp_get_wtime();
double sparseTime = end - start;
cout << "Sparse multiplication time: " << sparseTime << " seconds\n";

generateDenseMatrix(A, n);
generateDenseMatrix(B, n);

start = omp_get_wtime();
denseMultiply(A, B, C, n);
end = omp_get_wtime();
double denseTime = end - start;
cout << "Dense multiplication time: " << denseTime << " seconds\n";

cout << "\nSpeedup (dense/sparse): " << (denseTime / sparseTime) << "x faster for sparse.\n";

return 0;
}
2.2 Experimental Results

Matrix Sparsity Sparse Dense Speedup


Threads Observation
Size (%) Time (s) Time (s) (Dense/Sparse)
0%
200×200 4 0.62 0.60 0.97× Sparse = Dense (no zeros)
(Dense)
Sparse faster; half
400×400 50% 4 0.74 1.41 1.90×
computations skipped
Sparse much faster; fewer
400×400 80% 4 0.36 2.32 6.44×
non-zeros
Very sparse → high
400×400 90% 4 0.28 2.40 8.57×
speedup
Moderate gain; more data
600×600 70% 4 1.62 4.92 3.03×
movement
Excellent improvement but
600×600 90% 4 0.93 6.10 6.56×
less balanced load
1) High-level summary — what changes when a matrix gets sparse
• Less arithmetic work — fewer nonzero × nonzero products, so total FLOPs drop roughly
proportional to (1 − sparsity)² for random sparsity. Less work → faster runtime in serial and
parallel.
• Imbalanced work distribution — if nonzeros are not uniformly distributed across rows/columns,
some threads get much more work than others → poor parallel efficiency.
• Lower arithmetic intensity — fewer FLOPs per memory access (depending on format); memory
latency/bandwidth or pointer chasing can dominate.
• Irregular memory access — sparse formats (CSR/CSC) traverse different columns/rows, hurting
cache locality and vectorization.
• Overhead of zero-checks — naive sparse code that checks if (a!=0 && b!=0) inside hot loops
adds branch overhead; using sparse formats removes many checks but introduces indirection.
• Diminishing returns with extreme sparsity — when matrices are very sparse, the work left may
be too little to utilize all cores effectively (Amdahl-limit + thread scheduling overhead).
• Synchronization / atomic updates — some sparse multiplication algorithms require carefully
synchronized accumulation into result entries — can reduce parallelism.

2) What this means for parallel performance (concrete)


• Parallel speedup = (serial time) / (parallel time). For moderate sparsity, speedup may increase
because less work is easier to parallelize — but only if work is balanced.
• Parallel efficiency = speedup / #threads. Efficiency drops when imbalance, memory stalls, or
synchronization dominate.
• Scalability: Dense matmul scales well (regular loop nests, vectorizable). Sparse SpGEMM
(sparse-generalized matrix multiply) scales worse unless you use good partitioning + sparse data
structures, because of irregular work.
• Best-case: uniform sparsity + enough work per thread → near-linear speedup.
• Worst-case: clustered sparsity or very high sparsity → load imbalance + overhead → poor
scaling; sometimes parallel sparse is slower than serial dense for same size.

3) Practical optimization strategies


1. Use sparse formats: CSR/CSC for storage — avoid explicit zero checks in hot loops.
2. Partition by nonzeros, not by rows: assign work in chunks of (approx) equal nonzero-count to
each thread to reduce imbalance.
3. Dynamic scheduling or guided scheduling: #pragma omp parallel for schedule(guided) or
explicit work queues helps when per-row work varies.
4. Blocking / tiling: keep submatrices that fit in cache to improve locality (for semi-dense blocks).
5. Private accumulators + reduce: avoid atomic updates to result matrix by letting each thread
accumulate into private buffers for a block, then merge.
6. Hybrid formats: treat dense rows/columns specially (convert to small dense blocks) and sparse
ones in CSR.
7. Avoid branch-heavy inner loops — use index arrays (CSR) and iterate only over nonzeros.
8. Measure per-thread work (counts of multiply-adds) to diagnose imbalance.
9. Vectorize where possible: if there are runs of consecutive nonzeros (blocks) use dense kernel on
them
Analysis and Conclusion

Analysis
• Sparse matrices contain many zero elements, reducing the total number of computations
required during multiplication.
• OpenMP parallelization efficiently distributes the workload across multiple CPU cores,
improving performance for both sparse and dense matrices.
• With increasing sparsity (more zeros), computation time decreases since fewer non-zero
multiplications are performed.
• However, excessive sparsity may lead to load imbalance — some threads complete early due
to fewer non-zero elements, reducing overall parallel efficiency.
• Dense matrices, though heavier computationally, maintain uniform thread workloads,
achieving more consistent parallel utilization.
• The execution time and speedup depend on factors like matrix size, sparsity percentage, and
available processor cores.

Conclusion
• Sparse matrix multiplication shows a clear performance advantage over dense multiplication
due to reduced computations.
• OpenMP significantly enhances performance by parallelizing independent operations in the
multiplication process.
• The best performance is observed at moderate sparsity levels (e.g., 70–90%), where
computation reduction and thread utilization are well balanced.
• Sparse matrix operations are faster but may be less scalable at extreme sparsity due to thread
imbalance.
• Overall, OpenMP-based parallelization effectively improves the efficiency of matrix
multiplication, confirming that sparsity and parallelism together yield optimal speedups.
3.1 Key Findings

• OpenMP parallelization significantly improved performance for both sparse and dense
matrix multiplication.

• Sparse matrices achieved faster execution due to a reduced number of non-zero element
computations.

• Higher sparsity levels (70–90%) resulted in lower computation time, showing that skipping
zero multiplications is highly effective.

• Dense matrices fully utilized all threads, providing stable but slower performance because
of the higher arithmetic workload.

• Excessive sparsity sometimes caused thread load imbalance, reducing parallel efficiency in
certain cases.

• Optimal performance was achieved at moderate sparsity, where computation reduction and
thread workload balance were best maintained.

• The speedup ratio (dense/sparse) showed that sparse matrices can be up to two to three
times faster under parallel execution.

• The experiment confirmed that combining parallel processing with sparsity optimization
yields the best computational efficiency.

3.2 Challenges and Solutions

Challenges
• Implementing efficient sparse matrix multiplication required additional logic to skip zero
elements without increasing overhead.
• Ensuring load balancing among OpenMP threads was difficult when sparsity was very high, as
some threads completed earlier than others.
• Choosing appropriate matrix sizes and sparsity levels was necessary to obtain measurable
performance differences within limited execution time.
• Measuring accurate execution time on shared environments (like Google Colab) was
challenging due to fluctuating CPU availability.
• Managing memory efficiently for large matrices was important to prevent runtime or memory
allocation errors.

Outcomes
• Successful parallelization of sparse matrix multiplication using OpenMP was achieved and
tested against dense matrix multiplication.
• Sparse matrices showed a significant reduction in computation time, particularly at higher
sparsity levels.
• The experiment confirmed that OpenMP parallelism improves efficiency, with noticeable
speedups compared to serial execution.
• Results demonstrated that moderate sparsity offers the best trade-off between computation
reduction and thread utilization.
• The findings validated that parallel computation combined with data sparsity is a powerful
approach for optimizing large-scale numerical operations.
3.3 Learning Outcomes

• Gained a strong understanding of sparse and dense matrix representations and their
impact on computational efficiency.
• Learned how to implement matrix multiplication using OpenMP, applying
concepts of parallel programming and multithreading.
• Understood how sparsity affects parallelism, execution time, and thread workload
balance.
• Developed skills to measure and compare performance metrics (such as execution
time and speedup) for different matrix types.
• Learned to identify and handle load imbalance issues that arise in highly sparse
matrices during parallel execution.
• Acquired experience in optimizing scientific computations through the use of
parallel programming techniques.
• Strengthened understanding of experimental analysis, including setup, performance
observation, and interpretation of results.

Appendix: Compilation and Execution Commands


Since Colab does not natively support direct compilation of .cpp files through the interface, use the
following commands in Colab code cells:

Step 1: Create a file and paste your code

%%writefile sparse_vs_dense.cpp

(Paste your full C++ OpenMP code below this line in the same cell.)

Step 2: Compile the code with OpenMP support

!g++ -fopenmp sparse_vs_dense.cpp -o sparse_vs_dense

Step 3: Execute the compiled program

!./sparse_vs_dense

Step 4 (Optional): Control number of threads in Colab

%env OMP_NUM_THREADS=4

You might also like