-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfactorial.zig
More file actions
140 lines (117 loc) · 5.76 KB
/
Copy pathfactorial.zig
File metadata and controls
140 lines (117 loc) · 5.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Factorial - Parallel Range Multiplication
//
// Demonstrates parallel factorial computation using divide-and-conquer.
// Splits the multiplication range and combines results.
//
// Key concepts:
// - Parallel range multiplication
// - Uses fork-join pattern
// - Big number demonstration (u128)
//
// Usage: zig build sample-factorial
const std = @import("std");
const zigparallel = @import("loom");
const joinOnPool = zigparallel.joinOnPool;
const ThreadPool = zigparallel.ThreadPool;
const SEQUENTIAL_THRESHOLD = 100;
pub fn main() !void {
const allocator = std.heap.page_allocator;
std.debug.print("╔═══════════════════════════════════════════════════════════╗\n", .{});
std.debug.print("║ Parallel Factorial Computation ║\n", .{});
std.debug.print("╚═══════════════════════════════════════════════════════════╝\n\n", .{});
const pool = try ThreadPool.init(allocator, .{ .num_threads = 8 });
defer pool.deinit();
std.debug.print("Thread pool: 8 workers\n\n", .{});
// ========================================================================
// Verification
// ========================================================================
std.debug.print("--- Verification (Small Factorials) ---\n", .{});
const test_values = [_]u64{ 0, 1, 5, 10, 15, 20 };
const expected = [_]u128{ 1, 1, 120, 3628800, 1307674368000, 2432902008176640000 };
for (test_values, expected) |n, exp| {
const result = parallelFactorial(pool, n);
const match = result == exp;
std.debug.print(" {d}! = {d} {s}\n", .{ n, result, if (match) "✓" else "✗" });
}
// ========================================================================
// Performance benchmark
// ========================================================================
std.debug.print("\n--- Performance Benchmark ---\n", .{});
const bench_values = [_]u64{ 1000, 5000, 10000 };
for (bench_values) |n| {
std.debug.print("\n{d}! computation:\n", .{n});
// Parallel
const par_start = std.time.nanoTimestamp();
const par_result = parallelFactorial(pool, n);
const par_end = std.time.nanoTimestamp();
const par_ms = @as(f64, @floatFromInt(par_end - par_start)) / 1_000_000.0;
// Sequential
const seq_start = std.time.nanoTimestamp();
const seq_result = sequentialFactorial(n);
const seq_end = std.time.nanoTimestamp();
const seq_ms = @as(f64, @floatFromInt(seq_end - seq_start)) / 1_000_000.0;
const speedup = seq_ms / par_ms;
std.debug.print(" Parallel: {d:.3}ms\n", .{par_ms});
std.debug.print(" Sequential: {d:.3}ms\n", .{seq_ms});
std.debug.print(" Speedup: {d:.2}x\n", .{speedup});
std.debug.print(" Match: {}\n", .{par_result == seq_result});
// Show last digits (mod 10^18 to fit in u64 for display)
const display_mod: u128 = 1_000_000_000_000_000_000;
std.debug.print(" Last 18 digits: {d}\n", .{@as(u64, @intCast(par_result % display_mod))});
}
// ========================================================================
// Explanation
// ========================================================================
std.debug.print("\n--- Algorithm ---\n", .{});
std.debug.print("n! = 1 × 2 × 3 × ... × n\n\n", .{});
std.debug.print("Parallel strategy:\n", .{});
std.debug.print(" - Split range [1, n] into two halves\n", .{});
std.debug.print(" - Compute product of each half in parallel\n", .{});
std.debug.print(" - Multiply the two partial products\n", .{});
std.debug.print(" - Recursively apply to sub-ranges\n", .{});
std.debug.print("\n╔═══════════════════════════════════════════════════════════╗\n", .{});
std.debug.print("║ Sample Complete ║\n", .{});
std.debug.print("╚═══════════════════════════════════════════════════════════╝\n", .{});
}
/// Parallel factorial using fork-join divide-and-conquer
fn parallelFactorial(pool: *ThreadPool, n: u64) u128 {
if (n <= 1) return 1;
return parallelRangeProduct(pool, 1, n);
}
fn parallelRangeProduct(pool: *ThreadPool, lo: u64, hi: u64) u128 {
if (hi < lo) return 1;
if (hi == lo) return lo;
if (hi - lo < SEQUENTIAL_THRESHOLD) {
return sequentialRangeProduct(lo, hi);
}
const mid = lo + (hi - lo) / 2;
// Fork-join: compute products of both halves in parallel
const left_product, const right_product = joinOnPool(
pool,
struct {
fn computeLeft(p: *ThreadPool, l: u64, m: u64) u128 {
return parallelRangeProduct(p, l, m);
}
}.computeLeft,
.{ pool, lo, mid },
struct {
fn computeRight(p: *ThreadPool, m: u64, h: u64) u128 {
return parallelRangeProduct(p, m + 1, h);
}
}.computeRight,
.{ pool, mid, hi },
);
return left_product *% right_product; // Wrapping multiply for overflow
}
fn sequentialFactorial(n: u64) u128 {
if (n <= 1) return 1;
return sequentialRangeProduct(1, n);
}
fn sequentialRangeProduct(lo: u64, hi: u64) u128 {
var result: u128 = 1;
var i = lo;
while (i <= hi) : (i += 1) {
result *%= i; // Wrapping multiply
}
return result;
}