CS330 · Operating Systems · Prince Sultan University

More cores, more speed? We measured it.

A two-phase study of parallelism on real hardware. Phase I splits matrix multiplication across Linux processes with fork() and wait(). Phase II splits a sum of cubes across Java threads. Each configuration was timed, compared with its baseline and explained.

5.79× @ 8 threads fork() × 4
~/os-project/src booting
5.79×
Peak speedup, 8 Java threads vs 1
82.7%
Less execution time at 8 threads
1,000,000
Array elements cubed and summed
78
Timed runs across 11 configurations
00 · Abstract

Parallelism only pays when the work outweighs the overhead.

The project asks a simple question: does splitting work across more processes or threads make a program faster? We answered it twice, with two operating system mechanisms, on two machines.

In Phase I, a C program multiplied matrices of size 1200 to 2400 inside an Ubuntu VM, using one process or four. With four processes the program was never faster, and at N = 1800 it was 3× slower. At these sizes, fork(), scheduling and wait() cost more than the parallel work saved.

In Phase II, a Java program summed the cubes of 1,000,000 integers using 1, 2, 4, 6 and 8 threads on an Apple M2. Two threads were 15% slower than one. Eight threads were 5.79× faster.

01 · Two phases

Same question, two mechanisms: processes, then threads.

Each phase has its own setup, method, raw data and report. Pick one to explore.

phase / 01

Process-based matrix multiplication

C and GCC on Ubuntu 24.04 (ARM64) in a UTM virtual machine with 4 cores and 4 GB RAM. Rows of the matrix are divided among child processes created with fork().

Configs
6
Runs each
3
Best speedup
1.00×
Four processes were never faster than one. Process overhead dominated at every tested size.
phase / 02

Multithreaded sum of cubes

Java on OpenJDK 25 on an 8-core Apple M2. The array is split into contiguous chunks, and each Summer thread returns a partial sum that is combined after join().

Configs
5
Runs each
12
Best speedup
5.79×
Two threads lost time and four gained little. Six and eight threads cut runtime by more than 80%.
02 · Thread lab

Split 1,000,000 cubes across threads and watch the clock.

Pick a thread count. The array is divided exactly as parallelSum() divides it, and the timer runs for a time proportional to the measured average for that configuration.

ArraySum.parallelSum(data, n)
threads
int[] data · index 0999,999
Avg time
0.649ms
Speedup
5.79×
vs 1 thread
+82.7%
12 runs / config System.nanoTime() values < 100
03 · Scaling curve

Below the baseline at 2 threads, above 5× from 6.

Speedup is T(1) / T(n). The dashed line shows ideal linear scaling. The measured curve dips below 1× at two threads, stays near 1× at four, then climbs steeply. The gains came only once there were enough threads to outweigh the cost of creating and joining them.

speedup · T(1) / T(n) measuredideal
phase i · avg seconds, 1 vs 4 processes PROCS = 1PROCS = 4
N = 1200300 rows/proc
0.0016
1.00×
N = 1800450 rows/proc
0.00100.0030
0.33×
N = 2400600 rows/proc
0.00130.0033
0.39×
04 · The Phase I twist

Four processes, zero gain.

Each fork() creates a whole new process with its own copy-on-write address space, and the parent must wait() for every child. With runtimes of about a millisecond, that fixed cost was larger than the matrix work it split up. At N = 1800 and 2400, the speedup fell below 0.4×.

05 · The team

Five people, one handoff chain.

Each phase had clear owners. Code, measurement, metrics, graphs and the report were passed from one person to the next. Select a member to see what they did.

CS330 · Introduction to Operating Systems

Read the full reports, or run the code yourself.

Both reports cover the method, tables, graphs and discussion. The repository contains the source and all documentation.