main.odin ¶
1package simd_approaches
2
3import "base:intrinsics"
4import "core:fmt"
5import "core:math/rand"
6import "core:simd"
7import "core:time"
8
9
10WIDTH :: #config(WIDTH, 8)
11
12
13NUM_OBJECTS :: #config(NUM_OBJECTS, 1_000_000)
14
15
16
17PADDING :: #config(PADDING, 0)
18
19
20Object :: struct {
21 pos, vel: [3]f32,
22 _: [PADDING]f32,
23}
24
25
26step_aos_scalar :: proc (data: []Object, dt: f32) {
27 for &obj in data {
28 obj.pos += obj.vel * dt
29 }
30}
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51load_vec :: proc (src: ^[3]f32) -> #simd[4]f32 {
52 mask := #simd[4]u32{ 0..<3 = max(u32) }
53 return simd.masked_load(cast(^#simd[4]f32)src, cast(#simd[4]f32)0, mask)
54}
55
56
57
58store_vec :: proc (dst: ^[3]f32, src: #simd[4]f32) {
59 mask := #simd[4]u32{ 0..<3 = max(u32) }
60 simd.masked_store(cast(^#simd[4]f32)dst, src, mask)
61}
62
63
64
65step_aos_within_mask :: proc (data: []Object, dt: f32) {
66 for &obj in data {
67 pos := load_vec(&obj.pos)
68 vel := load_vec(&obj.vel)
69 store_vec(&obj.pos, pos + vel * dt)
70 }
71}
72
73
74
75
76
77
78
79
80
81
82
83Object_Simd :: struct {
84 pos, vel: #simd[4]f32,
85 _: [PADDING]int,
86}
87
88step_aos_within_simd :: proc (data: []Object_Simd, dt: f32) {
89 for &obj in data {
90 obj.pos += obj.vel * dt
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
118step_aos_gather :: proc (data: []Object, dt: f32) {
119 data := data
120
121 do_add :: #force_inline proc (base: ^Object, dt: f32, mask: #simd[WIDTH]u32 = max(u32)) {
122 index := iota(#simd[WIDTH]uintptr)
123 step := index * size_of(Object)
124
125
126 px_ptr := cast(#simd[WIDTH]rawptr)(uintptr(&base.pos.x) + step)
127 vx_ptr := cast(#simd[WIDTH]rawptr)(uintptr(&base.vel.x) + step)
128 px := simd.gather(px_ptr, cast(#simd[WIDTH]f32)0, mask)
129 px += simd.gather(vx_ptr, cast(#simd[WIDTH]f32)0, mask) * dt
130 simd.scatter(px_ptr, px, mask)
131
132 py_ptr := cast(#simd[WIDTH]rawptr)(uintptr(&base.pos.y) + step)
133 vy_ptr := cast(#simd[WIDTH]rawptr)(uintptr(&base.vel.y) + step)
134 py := simd.gather(py_ptr, cast(#simd[WIDTH]f32)0, mask)
135 py += simd.gather(vy_ptr, cast(#simd[WIDTH]f32)0, mask) * dt
136 simd.scatter(py_ptr, py, mask)
137
138 pz_ptr := cast(#simd[WIDTH]rawptr)(uintptr(&base.pos.z) + step)
139 vz_ptr := cast(#simd[WIDTH]rawptr)(uintptr(&base.vel.z) + step)
140 pz := simd.gather(pz_ptr, cast(#simd[WIDTH]f32)0, mask)
141 pz += simd.gather(vz_ptr, cast(#simd[WIDTH]f32)0, mask) * dt
142 simd.scatter(pz_ptr, pz, mask)
143 }
144
145 for len(data) >= WIDTH {
146 do_add(raw_data(data), dt)
147 data = data[WIDTH:]
148 }
149
150 if len(data) > 0 {
151 mask := simd.lanes_lt(iota(#simd[WIDTH]u32), cast(#simd[WIDTH]u32)len(data))
152 do_add(raw_data(data), dt, mask)
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
179Object_Split :: struct {
180 px, py, pz: f32,
181 vx, vy, vz: f32,
182 _: [PADDING]int,
183}
184
185step_soa :: proc (data: #soa[]Object_Split, dt: f32) {
186 data := data
187
188 do_add :: #force_inline proc (base: #soa[]Object_Split, first: int, dt: f32, mask: #simd[WIDTH]u32 = max(u32)) {
189 px := intrinsics.unaligned_load(cast(^#simd[WIDTH]f32)base.px[first:])
190 px += intrinsics.unaligned_load(cast(^#simd[WIDTH]f32)base.vx[first:]) * dt
191 intrinsics.unaligned_store(cast(^#simd[WIDTH]f32)base.px[first:], px)
192
193 py := intrinsics.unaligned_load(cast(^#simd[WIDTH]f32)base.py[first:])
194 py += intrinsics.unaligned_load(cast(^#simd[WIDTH]f32)base.vy[first:]) * dt
195 intrinsics.unaligned_store(cast(^#simd[WIDTH]f32)base.py[first:], py)
196
197 pz := intrinsics.unaligned_load(cast(^#simd[WIDTH]f32)base.pz[first:])
198 pz += intrinsics.unaligned_load(cast(^#simd[WIDTH]f32)base.vz[first:]) * dt
199 intrinsics.unaligned_store(cast(^#simd[WIDTH]f32)base.pz[first:], pz)
200 }
201
202 i: int
203 for i = 0; i+WIDTH <= len(data); i += WIDTH {
204 do_add(data, i, dt)
205 }
206
207 if i < len(data) {
208 left := len(data) - i
209 mask := simd.lanes_lt(iota(#simd[WIDTH]u32), cast(#simd[WIDTH]u32)left)
210 do_add(data, i, dt, mask)
211 }
212}
213
214main :: proc () {
215 if ODIN_OPTIMIZATION_MODE <= .Minimal {
216 fmt.println("WARNING: For best results, run benchmarks in an optimized build!")
217 }
218
219 aos_data := make([]Object, 1_000_000)
220 for &o in aos_data {
221 o = make_object()
222 }
223
224 aos_simd_data := make([]Object_Simd, NUM_OBJECTS)
225 for &o in aos_simd_data {
226 temp := make_object()
227 o = {
228 pos = {temp.pos.x, temp.pos.y, temp.pos.z, 0},
229 vel = {temp.vel.x, temp.vel.y, temp.vel.z, 0},
230 }
231 }
232
233 soa_data := make(#soa[]Object_Split, NUM_OBJECTS)
234 for &o in soa_data {
235 temp := make_object()
236 o = {
237 px = temp.pos.x, py = temp.pos.y, pz = temp.pos.z,
238 vx = temp.vel.x, vy = temp.vel.y, vz = temp.vel.z,
239 }
240 }
241
242 bench_2("AoS Scalar", step_aos_scalar, aos_data, 1.0/60.0)
243 bench_2("AoS Within (Masking)", step_aos_within_mask, aos_data, 1.0/60.0)
244 bench_2("AoS Within (Vector Storage)", step_aos_within_simd, aos_simd_data, 1.0/60.0)
245 bench_2("AoS Across (Gather)", step_aos_gather, aos_data, 1.0/60.0)
246 bench_2("SoA Across", step_soa, soa_data, 1.0/60.0)
247}
248
249make_object :: proc () -> Object {
250 return {
251 pos = {
252 rand.float32_range(-1000, 1000),
253 rand.float32_range(-1000, 1000),
254 rand.float32_range(-1000, 1000),
255 },
256
257 vel = {
258 rand.float32_range(-10, 10),
259 rand.float32_range(-10, 10),
260 rand.float32_range(-10, 10),
261 },
262 }
263}
264
265bench_2 :: proc (name: string, p: proc($A, $B), a: A, b: B) {
266 fmt.print(name, "... ", sep="")
267
268 iterations, done := 1, 0
269 start := time.tick_now()
270 for time.tick_since(start) < time.Second {
271 for _ in 0..<iterations {
272 p(a, b)
273 }
274
275 done += iterations
276 iterations += iterations
277 }
278 elapsed := time.tick_since(start)
279 ips := f64(done) / f64(time.duration_seconds(elapsed))
280 fmt.println(ips, "per sec")
281}
282
283iota :: proc ($T: typeid/#simd[$N]$E) -> (result: T) {
284 for i in 0..<N {
285 result = simd.replace(result, i, E(i))
286 }
287 return
288}
289