main.odin ¶
1package simd_example
2
3import "base:intrinsics"
4import "core:fmt"
5import "core:math/rand"
6import "core:simd"
7import "core:time"
8
9NUM_DATA :: #config(NUM, 10_000_000)
10NUM_REPETITIONS :: #config(REP, 100)
11WIDTH :: #config(WIDTH, 16)
12
13
14
15Point :: struct {
16
17 pos_x, pos_y, vel_x, vel_y: f32,
18}
19
20
21
22update_points_scalar :: proc (points: #soa[]Point, bounds: [2][2]f32, dt: f32) {
23 for &point in points {
24 if point.pos_x <= bounds[0].x { point.vel_x = +abs(point.vel_x) }
25 if point.pos_x >= bounds[1].x { point.vel_x = -abs(point.vel_x) }
26 if point.pos_y <= bounds[0].y { point.vel_y = +abs(point.vel_y) }
27 if point.pos_y >= bounds[1].y { point.vel_y = -abs(point.vel_y) }
28
29 point.pos_x += dt * point.vel_x
30 point.pos_y += dt * point.vel_y
31 }
32}
33
34
35
36update_points_simd :: proc (points: #soa[]Point, bounds: [2][2]f32, dt: f32) {
37 process_chunk :: proc (points: #soa[]Point, mask: #simd[WIDTH]u32, bounds: [2][2]f32, dt: f32) {
38 px_ptr := cast(^#simd[WIDTH]f32)points.pos_x
39 py_ptr := cast(^#simd[WIDTH]f32)points.pos_y
40 vx_ptr := cast(^#simd[WIDTH]f32)points.vel_x
41 vy_ptr := cast(^#simd[WIDTH]f32)points.vel_y
42
43
44
45
46
47
48
49
50
51
52
53 px := simd.masked_load(px_ptr, cast(#simd[WIDTH]f32)0, mask)
54 py := simd.masked_load(py_ptr, cast(#simd[WIDTH]f32)0, mask)
55 vx := simd.masked_load(vx_ptr, cast(#simd[WIDTH]f32)0, mask)
56 vy := simd.masked_load(vy_ptr, cast(#simd[WIDTH]f32)0, mask)
57
58
59
60
61
62
63
64
65
66
67
68 min_x_mask := simd.lanes_le(px, cast(#simd[WIDTH]f32)bounds[0].x)
69 vx = simd.select(min_x_mask, +simd.abs(vx), vx)
70
71
72 min_y_mask := simd.lanes_le(py, cast(#simd[WIDTH]f32)bounds[0].y)
73 vy = simd.select(min_y_mask, +simd.abs(vy), vy)
74
75
76 max_x_mask := simd.lanes_ge(px, cast(#simd[WIDTH]f32)bounds[1].x)
77 vx = simd.select(max_x_mask, -simd.abs(vx), vx)
78
79 max_y_mask := simd.lanes_ge(py, cast(#simd[WIDTH]f32)bounds[1].y)
80 vy = simd.select(max_y_mask, -simd.abs(vy), vy)
81
82
83 px += dt * vx
84 py += dt * vy
85
86
87
88
89 simd.masked_store(px_ptr, px, mask)
90 simd.masked_store(py_ptr, py, mask)
91 simd.masked_store(vx_ptr, vx, mask)
92 simd.masked_store(vy_ptr, vy, mask)
93 }
94
95 points := points
96 for len(points) >= WIDTH {
97 process_chunk(points, max(u32), bounds, dt)
98 points = points[WIDTH:]
99 }
100
101 if len(points) > 0 {
102 index := iota(#simd[WIDTH]i32)
103 mask := simd.lanes_lt(index, cast(#simd[WIDTH]i32)len(points))
104 process_chunk(points, mask, bounds, dt)
105 }
106}
107
108main :: proc() {
109 if ODIN_OPTIMIZATION_MODE <= .Minimal {
110 fmt.println("WARNING: For best results, run benchmarks in an optimized build!")
111 }
112
113 bounds := [2][2]f32 {
114 {-100, -100},
115 {+100, +100},
116 }
117 dt := f32(0.1)
118
119 points := make(#soa[]Point, NUM_DATA, context.temp_allocator)
120 for &point in points {
121 point.pos_x = rand.float32_range(bounds[0].x, bounds[1].x)
122 point.pos_y = rand.float32_range(bounds[0].y, bounds[1].y)
123 point.vel_x = rand.float32_range(-1, +1)
124 point.vel_y = rand.float32_range(-1, +1)
125 }
126
127 fmt.printfln("Motion (Scalar): %v", benchmark(update_points_scalar, points, bounds, dt))
128 fmt.printfln("Motion (SIMD): %v", benchmark(update_points_simd, points, bounds, dt))
129}
130
131benchmark :: proc (p: proc (points: #soa[]Point, bounds: [2][2]f32, dt: f32), points: #soa[]Point, bounds: [2][2]f32, dt: f32) -> time.Duration {
132 best_elapsed := max(time.Duration)
133 for _ in 0..<NUM_REPETITIONS {
134 start := time.tick_now()
135 p(points, bounds, dt)
136 best_elapsed = min(time.tick_since(start), best_elapsed)
137 }
138 return best_elapsed
139}
140
141iota :: proc ($V: typeid/#simd[$N]$E) -> (result: V) {
142 for i in 0..<N {
143 result = simd.replace(result, i, E(i))
144 }
145 return
146}
147