-
-
Notifications
You must be signed in to change notification settings - Fork 150
Expand file tree
/
Copy pathbench.rs
More file actions
198 lines (186 loc) · 6.56 KB
/
Copy pathbench.rs
File metadata and controls
198 lines (186 loc) · 6.56 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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
use std::time::Instant;
fn fib(n: i32) -> i32 {
if n < 2 {
return n;
}
fib(n - 1) + fib(n - 2)
}
fn bench_fibonacci() {
let start = Instant::now();
let result = fib(40);
let elapsed = start.elapsed().as_millis();
println!("fibonacci:{}", elapsed);
println!(" checksum: {}", result);
}
fn bench_loop_overhead() {
let start = Instant::now();
let mut sum: f64 = 0.0;
for _ in 0..100_000_000 {
sum += 1.0;
}
let elapsed = start.elapsed().as_millis();
println!("loop_overhead:{}", elapsed);
println!(" checksum: {:.0}", sum);
}
fn bench_array_write() {
let mut arr = vec![0.0_f64; 10_000_000];
let start = Instant::now();
for i in 0..10_000_000 {
arr[i] = i as f64;
}
let elapsed = start.elapsed().as_millis();
println!("array_write:{}", elapsed);
println!(" checksum: {:.0}", arr[9_999_999]);
}
fn bench_array_read() {
let mut arr = vec![0.0_f64; 10_000_000];
for i in 0..10_000_000 {
arr[i] = i as f64;
}
let start = Instant::now();
let mut sum: f64 = 0.0;
for i in 0..10_000_000 {
sum += arr[i];
}
let elapsed = start.elapsed().as_millis();
println!("array_read:{}", elapsed);
println!(" checksum: {:.0}", sum);
}
fn bench_math_intensive() {
let start = Instant::now();
let mut result: f64 = 0.0;
for i in 1..=50_000_000 {
result += 1.0 / i as f64;
}
let elapsed = start.elapsed().as_millis();
println!("math_intensive:{}", elapsed);
println!(" checksum: {:.6}", result);
}
struct Point {
x: f64,
y: f64,
}
fn bench_object_create() {
let start = Instant::now();
let mut sum: f64 = 0.0;
for i in 0..1_000_000 {
let p = Point {
x: i as f64,
y: i as f64 * 2.0,
};
sum += p.x + p.y;
}
let elapsed = start.elapsed().as_millis();
println!("object_create:{}", elapsed);
println!(" checksum: {:.0}", sum);
}
fn bench_nested_loops() {
let n = 3000;
let mut arr = vec![0.0_f64; n * n];
for i in 0..(n * n) {
arr[i] = i as f64;
}
let start = Instant::now();
let mut sum: f64 = 0.0;
for i in 0..n {
for j in 0..n {
sum += arr[i * n + j];
}
}
let elapsed = start.elapsed().as_millis();
println!("nested_loops:{}", elapsed);
println!(" checksum: {:.0}", sum);
}
fn bench_accumulate() {
let start = Instant::now();
let mut sum: f64 = 0.0;
for i in 0..100_000_000_i64 {
sum += (i % 1000) as f64;
}
let elapsed = start.elapsed().as_millis();
println!("accumulate:{}", elapsed);
println!(" checksum: {:.0}", sum);
}
fn bench_loop_data_dependent() {
// Verified non-foldable on rustc 1.94.1 stable as of 2026-04-25.
// `rustc -O -C codegen-units=1 --emit=asm bench.rs` produces this
// exact loop body (label LBB5_41 in the dump):
//
// LBB5_41:
// and x9, x8, #0x3f // i & 63
// lsl w10, w8, #3 // i*8
// sub w10, w10, w8 // i*8 - i = i*7
// add x8, x8, #1 // i++
// and x10, x10, #0x3f // (i*7) & 63
// ldr d1, [x19, x9, lsl #3] // load x[i & 63]
// fmul d0, d0, d1 // sum *= x[i & 63]
// ldr d1, [x19, x10, lsl #3] // load x[(i*7) & 63]
// fadd d0, d0, d1 // sum += x[(i*7) & 63]
// cmp x8, x22 // i < ITERATIONS?
// b.ne LBB5_41
//
// Scalar loop with two array loads, one fmul, one fadd, sequential
// carry through d0 (sum). Not vectorized, not unrolled, not folded.
// The sequential dependency on `sum` defeats both reassoc and the
// vectorizer; the array reads defeat constant propagation past the
// loop boundary.
//
// FP-contract caveat (matters for cross-language comparison): the
// expression `sum * a + b` can be lowered as either two instructions
// (fmul + fadd, 2 IEEE-754 roundings) or a single fused FMADDD
// (one rounding). The fused form has shorter per-iteration latency
// (~4 cycles vs ~6-8 on Apple silicon) and changes the result by at
// most 0.5 ULP per iteration. LLVM's default does NOT contract —
// observable: rustc 1.94.1 default `-O` emits the fmul+fadd shape
// shown above, and `-C target-cpu=native` does not change this.
// Apple Clang `-O2+` (and therefore Apple `g++ -O3`) DOES contract
// to FMADDD by default — verified by running `g++ -O3 bench.cpp`
// and inspecting the loop body. Go always contracts. Swift `-O`,
// V8 (Node), JSC (Bun), Java HotSpot without `-XX:+UseFMA`, and
// Perry's default codegen all stay in the no-contract pack. So
// the kernel divides the field into two clusters: FMA-contract
// (~128 ms here: Go, Apple Clang -O3) and no-contract (~225 ms:
// Rust, Swift, Perry, Node, Bun, Java). Both clusters preserve
// the dependency chain on `sum`; the win is one ISA-level fusion,
// not a fold or a vectorize.
//
// This is the honest companion to bench_loop_overhead. Where
// loop_overhead measures whether the compiler applied
// reassoc + IV-simplify to a trivially-foldable accumulator
// (a flag-aggressiveness probe), this one forces the compiler
// to actually execute work — and surfaces FP-contract as a
// secondary axis of the same flag-posture story.
const N: usize = 64;
const ITERATIONS: u64 = 100_000_000;
let mut seed: u64 = 42;
let mut x = vec![0.0_f64; N];
for i in 0..N {
seed = seed.wrapping_mul(1103515245).wrapping_add(12345) & 0x7FFF_FFFF;
// Range [0.5, 1.0): every multiplicand x < 1 so the
// multiplicative chain strictly contracts to a bounded
// fixed point. Values centered on 1.0 (e.g. [0.99, 1.01])
// overflow to Infinity because tiny mean-drift compounds
// geometrically over 100M iterations.
x[i] = 0.5 + (seed as f64 / 2_147_483_647.0) * 0.5;
}
let start = Instant::now();
let mut sum: f64 = 1.0;
for i in 0..ITERATIONS {
let i_us = i as usize;
sum = sum * x[i_us & (N - 1)] + x[(i_us * 7) & (N - 1)];
}
let elapsed = start.elapsed().as_millis();
println!("loop_data_dependent:{}", elapsed);
println!(" checksum: {:.6}", sum);
}
fn main() {
bench_fibonacci();
bench_loop_overhead();
bench_array_write();
bench_array_read();
bench_math_intensive();
bench_object_create();
bench_nested_loops();
bench_accumulate();
bench_loop_data_dependent();
}