Skip to content

Commit 64a686f

Browse files
committed
examples: speed up mandelbrot.v by using a constant size thread pool, processing smaller chunks
1 parent 50ab2cf commit 64a686f

File tree

1 file changed

+73
-29
lines changed

1 file changed

+73
-29
lines changed

examples/gg/mandelbrot.v

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const pwidth = 800
77

88
const pheight = 600
99

10+
const chunk_height = 2 // the image is recalculated in chunks, each chunk processed in a separate thread
11+
1012
const zoom_factor = 1.1
1113

1214
struct ViewRect {
@@ -17,62 +19,104 @@ mut:
1719
y_max f64
1820
}
1921

22+
fn (v &ViewRect) width() f64 {
23+
return v.x_max - v.x_min
24+
}
25+
26+
fn (v &ViewRect) height() f64 {
27+
return v.y_max - v.y_min
28+
}
29+
2030
struct AppState {
2131
mut:
2232
gg &gg.Context = 0
2333
iidx int
24-
pixels []u32 = []u32{len: pwidth * pheight}
25-
npixels []u32 = []u32{len: pwidth * pheight} // all drawing happens here, results are copied at the end
34+
pixels &u32 = unsafe { vcalloc(pwidth * pheight * sizeof(u32)) }
35+
npixels &u32 = unsafe { vcalloc(pwidth * pheight * sizeof(u32)) } // all drawing happens here, results are swapped at the end
2636
view ViewRect = ViewRect{-2.7610033817025625, 1.1788897130338223, -1.824584023871934, 2.1153096311072788}
2737
ntasks int = runtime.nr_jobs()
2838
}
2939

3040
const colors = [gx.black, gx.blue, gx.red, gx.green, gx.yellow, gx.orange, gx.purple, gx.white,
31-
gx.indigo, gx.violet, gx.black]
41+
gx.indigo, gx.violet, gx.black].map(u32(it.abgr8()))
42+
43+
struct MandelChunk {
44+
cview ViewRect
45+
ymin f64
46+
ymax f64
47+
}
3248

3349
fn (mut state AppState) update() {
34-
mut sw := time.new_stopwatch()
50+
mut chunk_channel := chan MandelChunk{cap: state.ntasks}
51+
mut chunk_ready_channel := chan bool{cap: 1000}
52+
mut threads := []thread{cap: state.ntasks}
53+
defer {
54+
chunk_channel.close()
55+
threads.wait()
56+
}
57+
for t in 0 .. state.ntasks {
58+
threads << go state.worker(t, chunk_channel, chunk_ready_channel)
59+
}
60+
//
3561
mut oview := ViewRect{}
62+
mut sw := time.new_stopwatch()
3663
for {
3764
sw.restart()
3865
cview := state.view
3966
if oview == cview {
4067
time.sleep(5 * time.millisecond)
4168
continue
4269
}
43-
sheight := pheight / state.ntasks
44-
mut threads := []thread{}
45-
for start := 0; start < pheight; start += sheight {
46-
threads << go state.recalc_lines(cview, start, start + sheight)
70+
// schedule chunks, describing the work:
71+
mut nchunks := 0
72+
for start := 0; start < pheight; start += chunk_height {
73+
chunk_channel <- MandelChunk{
74+
cview: cview
75+
ymin: start
76+
ymax: start + chunk_height
77+
}
78+
nchunks++
79+
}
80+
// wait for all chunks to be processed:
81+
for _ in 0 .. nchunks {
82+
_ := <-chunk_ready_channel
4783
}
48-
threads.wait()
49-
state.pixels = state.npixels
84+
// everything is done, swap the buffer pointers
85+
state.pixels, state.npixels = state.npixels, state.pixels
5086
println('$state.ntasks threads; $sw.elapsed().milliseconds() ms / frame')
5187
oview = cview
5288
}
5389
}
5490

55-
fn (mut state AppState) recalc_lines(cview ViewRect, ymin f64, ymax f64) {
56-
for y_pixel := ymin; y_pixel < ymax && y_pixel < pheight; y_pixel++ {
57-
y0 := (y_pixel / pheight) * (cview.y_max - cview.y_min) + cview.y_min
58-
for x_pixel := 0.0; x_pixel < pwidth; x_pixel++ {
59-
x0 := (x_pixel / pwidth) * (cview.x_max - cview.x_min) + cview.x_min
60-
mut x, mut y := x0, y0
61-
mut iter := 0
62-
for ; iter < 80; iter++ {
63-
x, y = x * x - y * y + x0, 2 * x * y + y0
64-
if x * x + y * y > 4 {
65-
break
91+
fn (mut state AppState) worker(id int, input chan MandelChunk, ready chan bool) {
92+
for {
93+
chunk := <-input or { break }
94+
yscale := chunk.cview.height() / pheight
95+
xscale := chunk.cview.width() / pwidth
96+
for y_pixel := chunk.ymin; y_pixel < chunk.ymax && y_pixel < pheight; y_pixel++ {
97+
y0 := y_pixel * yscale + chunk.cview.y_min
98+
for x_pixel := 0.0; x_pixel < pwidth; x_pixel++ {
99+
x0 := x_pixel * xscale + chunk.cview.x_min
100+
mut x, mut y := x0, y0
101+
mut iter := 0
102+
for ; iter < 80; iter++ {
103+
x, y = x * x - y * y + x0, 2 * x * y + y0
104+
if x * x + y * y > 4 {
105+
break
106+
}
107+
}
108+
unsafe {
109+
state.npixels[int(y_pixel * pwidth) + int(x_pixel)] = colors[iter & 7]
66110
}
67111
}
68-
state.npixels[int(y_pixel) * pwidth + int(x_pixel)] = u32(colors[iter % 8].abgr8())
69112
}
113+
ready <- true
70114
}
71115
}
72116

73117
fn (mut state AppState) draw() {
74118
mut istream_image := state.gg.get_cached_image_by_idx(state.iidx)
75-
istream_image.update_pixel_data(&state.pixels[0])
119+
istream_image.update_pixel_data(state.pixels)
76120
size := gg.window_size()
77121
state.gg.draw_image(0, 0, size.width, size.height, istream_image)
78122
}
@@ -110,17 +154,17 @@ fn graphics_frame(mut state AppState) {
110154
fn graphics_click(x f32, y f32, btn gg.MouseButton, mut state AppState) {
111155
if btn == .right {
112156
size := gg.window_size()
113-
m_x := (x / size.width) * (state.view.x_max - state.view.x_min) + state.view.x_min
114-
m_y := (y / size.height) * (state.view.y_max - state.view.y_min) + state.view.y_min
157+
m_x := (x / size.width) * state.view.width() + state.view.x_min
158+
m_y := (y / size.height) * state.view.height() + state.view.y_min
115159
state.center(m_x, m_y)
116160
}
117161
}
118162

119163
fn graphics_move(x f32, y f32, mut state AppState) {
120164
if state.gg.mouse_buttons.has(.left) {
121165
size := gg.window_size()
122-
d_x := (f64(state.gg.mouse_dx) / size.width) * (state.view.x_max - state.view.x_min)
123-
d_y := (f64(state.gg.mouse_dy) / size.height) * (state.view.y_max - state.view.y_min)
166+
d_x := (f64(state.gg.mouse_dx) / size.width) * state.view.width()
167+
d_y := (f64(state.gg.mouse_dy) / size.height) * state.view.height()
124168
state.view.x_min -= d_x
125169
state.view.x_max -= d_x
126170
state.view.y_min -= d_y
@@ -133,8 +177,8 @@ fn graphics_scroll(e &gg.Event, mut state AppState) {
133177
}
134178

135179
fn graphics_keydown(code gg.KeyCode, mod gg.Modifier, mut state AppState) {
136-
s_x := (state.view.x_max - state.view.x_min) / 5
137-
s_y := (state.view.y_max - state.view.y_min) / 5
180+
s_x := state.view.width() / 5
181+
s_y := state.view.height() / 5
138182
// movement
139183
mut d_x, mut d_y := 0.0, 0.0
140184
if code == .enter {

0 commit comments

Comments
 (0)