]> git.lizzy.rs Git - rust.git/commitdiff
shootout-mandelbrot: Precalc initial values & use SIMD in the main loop. +80-100%
authorKevin Butler <haqkrs@gmail.com>
Wed, 14 May 2014 18:08:06 +0000 (19:08 +0100)
committerAlex Crichton <alex@alexcrichton.com>
Thu, 15 May 2014 20:50:39 +0000 (13:50 -0700)
src/test/bench/shootout-mandelbrot.rs

index 5302bd1dd6312b98c22c56347d1f69f0ae7b3bf8..debd12874da1ee6e1c485096df7ef1ae3143e7b1 100644 (file)
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 #![feature(macro_rules)]
+#![feature(simd)]
+#![allow(experimental)]
 
 // ignore-pretty very bad with line comments
 
 extern crate sync;
 
 use std::io;
+use std::os;
+use std::unstable::simd::f64x2;
 use sync::Future;
+use sync::Arc;
 
 static ITER: int = 50;
 static LIMIT: f64 = 2.0;
+static WORKERS: uint = 16;
 
-macro_rules! core_loop(
-    ($pow:expr ~ $mask:expr: $ctx:ident, $b:ident) => (
-        {
-            let r = $ctx.r;
-            let i = $ctx.i;
+#[inline(always)]
+fn mandelbrot<W: io::Writer>(w: uint, mut out: W) -> io::IoResult<()> {
+    assert!(WORKERS % 2 == 0);
 
-            $ctx.r = r * r - i * i + $ctx.init_r;
-            $ctx.i = 2.0 * r * i + $ctx.init_i;
+    // Ensure w and h are multiples of 8.
+    let w = (w + 7) / 8 * 8;
+    let h = w;
+
+    let chunk_size = h / WORKERS;
 
-            if r * r + i * i > LIMIT * LIMIT {
-                $b |= $pow;
-                if $b == $mask { break; }
+    // Account for remainders in workload division, e.g. 1000 / 16 = 62.5
+    let first_chunk_size = if h % WORKERS != 0 {
+        chunk_size + h % WORKERS
+    } else {
+        chunk_size
+    };
+
+    // precalc values
+    let inverse_w_doubled = 2.0 / w as f64;
+    let inverse_h_doubled = 2.0 / h as f64;
+    let v_inverses = f64x2(inverse_w_doubled, inverse_h_doubled);
+    let v_consts = f64x2(1.5, 1.0);
+
+    // A lot of this code assumes this (so do other lang benchmarks)
+    assert!(w == h);
+    let mut precalc_r = Vec::with_capacity(w);
+    let mut precalc_i = Vec::with_capacity(h);
+
+    let precalc_futures = Vec::from_fn(WORKERS, |i| {
+        Future::spawn(proc () {
+            let mut rs = Vec::with_capacity(w / WORKERS);
+            let mut is = Vec::with_capacity(w / WORKERS);
+
+            let start = i * chunk_size;
+            let end = if i == 0 {
+                first_chunk_size
+            } else {
+                (i + 1) * chunk_size
+            };
+
+            // This assumes w == h
+            for x in range(start, end) {
+                let xf = x as f64;
+                let xy = f64x2(xf, xf);
+
+                let f64x2(r, i) = xy * v_inverses - v_consts;
+                rs.push(r);
+                is.push(i);
             }
-        }
-    );
-)
 
-#[inline(always)]
-fn write_line(init_i: f64, vec_init_r: &[f64], res: &mut Vec<u8>) {
-    struct Context { r: f64, i: f64, init_i: f64, init_r: f64 }
-    impl Context {
-        #[inline(always)]
-        fn new(i: f64, r: f64) -> Context {
-            Context { r: r, i: i, init_r: r, init_i: i }
-        }
+            (rs, is)
+        })
+    });
+
+    for res in precalc_futures.move_iter() {
+        let (rs, is) = res.unwrap();
+        precalc_r.push_all_move(rs);
+        precalc_i.push_all_move(is);
+    }
+
+    assert_eq!(precalc_r.len(), w);
+    assert_eq!(precalc_i.len(), h);
+
+    let arc_init_r = Arc::new(precalc_r);
+    let arc_init_i = Arc::new(precalc_i);
+
+    let data = Vec::from_fn(WORKERS, |i| {
+        let vec_init_r = arc_init_r.clone();
+        let vec_init_i = arc_init_i.clone();
+
+        Future::spawn(proc () {
+            let mut res: Vec<u8> = Vec::with_capacity((chunk_size * w) / 8);
+            let init_r_slice = vec_init_r.as_slice();
+            for &init_i in vec_init_i.slice(i * chunk_size, (i + 1) * chunk_size).iter() {
+                write_line(init_i, init_r_slice, &mut res);
+            }
+
+            res
+        })
+    });
+
+    try!(writeln!(&mut out as &mut Writer, "P4\n{} {}", w, h));
+    for res in data.move_iter() {
+        try!(out.write(res.unwrap().as_slice()));
     }
+    out.flush()
+}
+
+fn write_line(init_i: f64, vec_init_r: &[f64], res: &mut Vec<u8>) {
+    let v_init_i : f64x2 = f64x2(init_i, init_i);
+    let v_2 : f64x2 = f64x2(2.0, 2.0);
+    static LIMIT_SQUARED: f64 = LIMIT * LIMIT;
 
-    let mut cur_byte;
-    let mut i;
-    let mut bit_1;
-    let mut bit_2;
-    let mut b;
     for chunk_init_r in vec_init_r.chunks(8) {
-        cur_byte = 0xff;
-        i = 0;
+        let mut cur_byte = 0xff;
+        let mut i = 0;
 
         while i < 8 {
-            bit_1 = Context::new(init_i, chunk_init_r[i]);
-            bit_2 = Context::new(init_i, chunk_init_r[i + 1]);
+            let v_init_r = f64x2(chunk_init_r[i], chunk_init_r[i + 1]);
+            let mut cur_r = v_init_r;
+            let mut cur_i = v_init_i;
+            let mut r_sq = v_init_r * v_init_r;
+            let mut i_sq = v_init_i * v_init_i;
 
-            b = 0;
+            let mut b = 0;
             for _ in range(0, ITER) {
-                core_loop!(2 ~ 3: bit_1, b);
-                core_loop!(1 ~ 3: bit_2, b);
+                let r = cur_r;
+                let i = cur_i;
+
+                cur_i = v_2 * r * i + v_init_i;
+                cur_r = r_sq - i_sq + v_init_r;
+
+                let f64x2(bit1, bit2) = r_sq + i_sq;
+
+                if bit1 > LIMIT_SQUARED {
+                    b |= 2;
+                    if b == 3 { break; }
+                }
+
+                if bit2 > LIMIT_SQUARED {
+                    b |= 1;
+                    if b == 3 { break; }
+                }
+
+                r_sq = cur_r * cur_r;
+                i_sq = cur_i * cur_i;
             }
 
             cur_byte = (cur_byte << 2) + b;
             i += 2;
         }
-        res.push(cur_byte^-1);
-    }
-}
-
-fn mandelbrot<W: io::Writer>(w: uint, mut out: W) -> io::IoResult<()> {
-    // Ensure w and h are multiples of 8.
-    let w = (w + 7) / 8 * 8;
-    let h = w;
-    let inverse_w_doubled = 2.0 / w as f64;
-    let inverse_h_doubled = 2.0 / h as f64;
-    let chunk_size = h / 16;
-
-    let data: Vec<Future<Vec<u8>>> = range(0u, 16).map(|i| Future::spawn(proc () {
-        let vec_init_r = Vec::from_fn(w, |x| (x as f64) * inverse_w_doubled - 1.5);
-        let mut res: Vec<u8> = Vec::with_capacity((chunk_size * w) / 8);
-        for y in range(i * chunk_size, (i + 1) * chunk_size) {
-            let init_i = (y as f64) * inverse_h_doubled - 1.0;
-            write_line(init_i, vec_init_r.as_slice(), &mut res);
-        }
-        res
-    })).collect();
 
-    try!(writeln!(&mut out as &mut Writer, "P4\n{} {}", w, h));
-    for res in data.move_iter() {
-        try!(out.write(res.unwrap().as_slice()));
+        res.push(cur_byte^-1);
     }
-    out.flush()
 }
 
 fn main() {
-    let args = std::os::args();
+    let args = os::args();
     let args = args.as_slice();
     let res = if args.len() < 2 {
         println!("Test mode: do not dump the image because it's not utf8, \
                   which interferes with the test runner.");
-        mandelbrot(1000, std::io::util::NullWriter)
+        mandelbrot(1000, io::util::NullWriter)
     } else {
-        mandelbrot(from_str(args[1]).unwrap(), std::io::stdout())
+        mandelbrot(from_str(args[1]).unwrap(), io::stdout())
     };
     res.unwrap();
 }