Skip to content

Commit 1b0c17a

Browse files
authored
adding analysis script for slide 24 questions
1 parent 71021f9 commit 1b0c17a

File tree

1 file changed

+207
-0
lines changed

1 file changed

+207
-0
lines changed

analysis.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
"""
2+
proj2_analysis.py
3+
4+
Fill in the lists in the USER INPUT SECTION with your own timings.
5+
Run with: python proj2_analysis.py
6+
7+
Outputs:
8+
- Printed tables for OMP & MPI speedup and efficiency
9+
- Suggested "optimal" core/thread counts
10+
- Optional weak-scaling tables if you provide those data
11+
- Matplotlib plots that pop up and are also saved as PNG files
12+
"""
13+
14+
import numpy as np
15+
import matplotlib.pyplot as plt
16+
17+
# ===================== USER INPUT SECTION =====================
18+
19+
# Serial runtime (1 core, 1M-sample query)
20+
serial_time = 0.0 # <-- put your measured serial time in seconds here, e.g. 2.34
21+
22+
# OMP: list of #threads tested and corresponding runtimes (same 1M problem)
23+
omp_threads = [1, 2, 3, 4] # example thread counts
24+
omp_times = [0.0, 0.0, 0.0, 0.0] # <-- fill with your OMP runtimes in seconds
25+
26+
# MPI: list of #processes tested and corresponding runtimes (same 1M problem)
27+
mpi_procs = [1, 2, 3, 4] # example process counts
28+
mpi_times = [0.0, 0.0, 0.0, 0.0] # <-- fill with your MPI runtimes in seconds
29+
30+
# OPTIONAL (for part (c)): weak-scaling data, where problem size grows
31+
# proportionally with #threads/#processes.
32+
# Example: 1M, 2M, 3M, 4M samples when using 1, 2, 3, 4 threads.
33+
weak_problem_sizes_omp = [] # e.g. [1_000_000, 2_000_000, 3_000_000, 4_000_000]
34+
weak_times_omp = [] # your OMP runtimes for those sizes
35+
36+
weak_problem_sizes_mpi = [] # e.g. [1_000_000, 2_000_000, 3_000_000, 4_000_000]
37+
weak_times_mpi = [] # your MPI runtimes for those sizes
38+
39+
# =================== END USER INPUT SECTION ===================
40+
41+
42+
def compute_speedup_efficiency(serial_t, parallel_times, procs):
43+
parallel_times = np.array(parallel_times, dtype=float)
44+
procs = np.array(procs, dtype=float)
45+
speedup = serial_t / parallel_times
46+
efficiency = speedup / procs
47+
return speedup, efficiency
48+
49+
50+
def estimate_parallel_fraction(speedup, procs):
51+
"""
52+
Estimate parallel fraction f from Amdahl's law:
53+
54+
S(p) = 1 / ((1 - f) + f / p)
55+
56+
Solve for f:
57+
1/S = 1 - f + f/p
58+
f = (1 - 1/S) / (1 - 1/p)
59+
60+
Only meaningful for p > 1.
61+
"""
62+
S = np.array(speedup, dtype=float)
63+
p = np.array(procs, dtype=float)
64+
return (1.0 - 1.0 / S) / (1.0 - 1.0 / p)
65+
66+
67+
def amdahl_speedup(p, f):
68+
p = np.array(p, dtype=float)
69+
return 1.0 / ((1.0 - f) + f / p)
70+
71+
72+
def print_table(name, procs, times, speedup, efficiency):
73+
print(f"\n{name} results (1M-sample strong-scaling)")
74+
print(f"{'p':>3} {'time(s)':>12} {'speedup':>12} {'efficiency':>12}")
75+
for p, t, s, e in zip(procs, times, speedup, efficiency):
76+
print(f"{p:3d} {t:12.6f} {s:12.3f} {e:12.3f}")
77+
78+
79+
def print_weak_scaling(name, procs, problem_sizes, times):
80+
if not problem_sizes or not times:
81+
print(f"\nNo weak-scaling data provided for {name}.")
82+
return
83+
84+
procs = np.array(procs, dtype=int)
85+
problem_sizes = np.array(problem_sizes, dtype=float)
86+
times = np.array(times, dtype=float)
87+
88+
print(f"\n{name} weak scaling (problem size grows with p)")
89+
print(f"{'p':>3} {'problem size':>15} {'time(s)':>12} {'N/p':>15}")
90+
for p, n, t in zip(procs, problem_sizes, times):
91+
print(f"{p:3d} {int(n):15d} {t:12.6f} {n/p:15.2f}")
92+
93+
94+
def choose_optimal(procs, speedup, efficiency):
95+
procs = np.array(procs, dtype=int)
96+
speedup = np.array(speedup, dtype=float)
97+
efficiency = np.array(efficiency, dtype=float)
98+
99+
p_best_speedup = int(procs[np.argmax(speedup)])
100+
p_best_eff = int(procs[np.argmax(efficiency)])
101+
102+
return p_best_speedup, p_best_eff
103+
104+
105+
def main():
106+
# ---- sanity checks ----
107+
if serial_time <= 0.0:
108+
raise ValueError("Please set 'serial_time' to a positive value.")
109+
110+
if len(omp_threads) != len(omp_times):
111+
raise ValueError("omp_threads and omp_times must have the same length.")
112+
if len(mpi_procs) != len(mpi_times):
113+
raise ValueError("mpi_procs and mpi_times must have the same length.")
114+
115+
# ---- OMP analysis ----
116+
omp_speedup, omp_eff = compute_speedup_efficiency(
117+
serial_time, omp_times, omp_threads
118+
)
119+
print_table("OMP", omp_threads, omp_times, omp_speedup, omp_eff)
120+
121+
# estimate parallel fraction from runs with p > 1
122+
if len(omp_threads) > 1:
123+
p_gt1 = [p for p in omp_threads if p > 1]
124+
s_gt1 = [s for p, s in zip(omp_threads, omp_speedup) if p > 1]
125+
if p_gt1:
126+
f_omp = estimate_parallel_fraction(s_gt1, p_gt1)
127+
print(f"\nEstimated OMP parallel fraction f (Amdahl) ~ {np.mean(f_omp):.3f}")
128+
129+
# ---- MPI analysis ----
130+
mpi_speedup, mpi_eff = compute_speedup_efficiency(
131+
serial_time, mpi_times, mpi_procs
132+
)
133+
print_table("MPI", mpi_procs, mpi_times, mpi_speedup, mpi_eff)
134+
135+
if len(mpi_procs) > 1:
136+
p_gt1 = [p for p in mpi_procs if p > 1]
137+
s_gt1 = [s for p, s in zip(mpi_procs, mpi_speedup) if p > 1]
138+
if p_gt1:
139+
f_mpi = estimate_parallel_fraction(s_gt1, p_gt1)
140+
print(f"\nEstimated MPI parallel fraction f (Amdahl) ~ {np.mean(f_mpi):.3f}")
141+
142+
# ---- Optimal core/thread choice (part (b)) ----
143+
omp_best_S, omp_best_E = choose_optimal(omp_threads, omp_speedup, omp_eff)
144+
mpi_best_S, mpi_best_E = choose_optimal(mpi_procs, mpi_speedup, mpi_eff)
145+
146+
print("\n=== Suggested 'optimal' choices ===")
147+
print(f"OMP: max speedup at {omp_best_S} threads; "
148+
f"max efficiency at {omp_best_E} threads.")
149+
print(f"MPI: max speedup at {mpi_best_S} processes; "
150+
f"max efficiency at {mpi_best_E} processes.")
151+
152+
# ---- Weak scaling (part (c)) ----
153+
print_weak_scaling("OMP", omp_threads, weak_problem_sizes_omp, weak_times_omp)
154+
print_weak_scaling("MPI", mpi_procs, weak_problem_sizes_mpi, weak_times_mpi)
155+
156+
# ==================== PLOTS ====================
157+
158+
# Strong scaling: speedup
159+
plt.figure()
160+
plt.plot(omp_threads, omp_speedup, marker='o', label="OMP")
161+
plt.plot(mpi_procs, mpi_speedup, marker='s', label="MPI")
162+
plt.xlabel("Number of threads / processes (p)")
163+
plt.ylabel("Observed speedup S(p)")
164+
plt.title("Speedup vs. p (strong scaling, 1M samples)")
165+
plt.grid(True)
166+
plt.legend()
167+
plt.tight_layout()
168+
plt.savefig("speedup_strong_scaling.png")
169+
170+
# Strong scaling: efficiency
171+
plt.figure()
172+
plt.plot(omp_threads, omp_eff, marker='o', label="OMP")
173+
plt.plot(mpi_procs, mpi_eff, marker='s', label="MPI")
174+
plt.xlabel("Number of threads / processes (p)")
175+
plt.ylabel("Efficiency E(p) = S(p)/p")
176+
plt.title("Efficiency vs. p (strong scaling, 1M samples)")
177+
plt.grid(True)
178+
plt.legend()
179+
plt.tight_layout()
180+
plt.savefig("efficiency_strong_scaling.png")
181+
182+
# Weak scaling plots (if data exist)
183+
if weak_problem_sizes_omp and weak_times_omp:
184+
plt.figure()
185+
plt.plot(omp_threads, weak_times_omp, marker='o')
186+
plt.xlabel("Number of threads (p)")
187+
plt.ylabel("Runtime (s)")
188+
plt.title("OMP weak scaling runtime")
189+
plt.grid(True)
190+
plt.tight_layout()
191+
plt.savefig("omp_weak_scaling_runtime.png")
192+
193+
if weak_problem_sizes_mpi and weak_times_mpi:
194+
plt.figure()
195+
plt.plot(mpi_procs, weak_times_mpi, marker='o')
196+
plt.xlabel("Number of processes (p)")
197+
plt.ylabel("Runtime (s)")
198+
plt.title("MPI weak scaling runtime")
199+
plt.grid(True)
200+
plt.tight_layout()
201+
plt.savefig("mpi_weak_scaling_runtime.png")
202+
203+
plt.show()
204+
205+
206+
if __name__ == "__main__":
207+
main()

0 commit comments

Comments
 (0)