Last active
August 23, 2021 15:13
-
-
Save fxn/61b2c0226cd60c20161b55725185bf2c to your computer and use it in GitHub Desktop.
Mandelbrot set generator from "Programming Rust", with ports to Crystal and Ruby.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Mandelbrot set generator from "Programming Rust", with I/O removed (the | |
// original code writes a PNG file). | |
use std::env; | |
use std::str::FromStr; | |
use num::Complex; | |
fn main() { | |
let args: Vec<String> = env::args().collect(); | |
if args.len() != 5 { | |
eprintln!("Usage: {} FILE PIXELS UPPERLEFT LOWERRIGHT", args[0]); | |
eprintln!("Example: {} mandel.png 1000x750 -1.20,0.35 -1,0.20", args[0]); | |
std::process::exit(1); | |
} | |
let bounds = parse_pair(&args[2], 'x').expect("error parsing image dimensions"); | |
let upper_left = parse_complex(&args[3]).expect("error parsing upper left corner point"); | |
let lower_right = parse_complex(&args[4]).expect("error parsing lower right corner point"); | |
let mut pixels = vec![0; bounds.0 * bounds.1]; | |
render(&mut pixels, bounds, upper_left, lower_right); | |
} | |
fn parse_complex(s: &str) -> Option<Complex<f64>> { | |
match parse_pair(s, ',') { | |
Some((re, im)) => Some(Complex { re, im }), | |
None => None | |
} | |
} | |
fn parse_pair<T: FromStr>(s: &str, separator: char) -> Option<(T, T)> { | |
match s.find(separator) { | |
None => None, | |
Some(index) => { | |
match (T::from_str(&s[..index]), T::from_str(&s[index + 1..])) { | |
(Ok(l), Ok(r)) => Some((l, r)), | |
_ => None | |
} | |
} | |
} | |
} | |
fn pixel_to_point(bounds: (usize, usize), pixel: (usize, usize), upper_left: Complex<f64>, lower_right: Complex<f64>) -> Complex<f64> { | |
let (width, height) = (lower_right.re - upper_left.re, upper_left.im - lower_right.im); | |
Complex { | |
re: upper_left.re + pixel.0 as f64 * width / bounds.0 as f64, | |
im: upper_left.im - pixel.1 as f64 * height / bounds.1 as f64 | |
} | |
} | |
fn escape_time(c: Complex<f64>, limit: usize) -> Option<usize> { | |
let mut z = Complex { re: 0.0, im: 0.0 }; | |
for i in 0..limit { | |
if z.norm_sqr() > 4.0 { | |
return Some(i); | |
} | |
z = z * z + c; | |
} | |
None | |
} | |
fn render(pixels: &mut [u8], bounds: (usize, usize), upper_left: Complex<f64>, lower_right: Complex<f64>) { | |
assert!(pixels.len() == bounds.0 * bounds.1); | |
for row in 0..bounds.1 { | |
for column in 0..bounds.0 { | |
let point = pixel_to_point(bounds, (column, row), upper_left, lower_right); | |
pixels[row * bounds.0 + column] = match escape_time(point, 255) { | |
None => 0, | |
Some(count) => 255 - count as u8 | |
}; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Port of the Rust code for benchmarking purposes, does not mimick error | |
# handling because it wouldn't make a difference. | |
require "complex" | |
class MandelbrotGenerator | |
def generate | |
bounds = ARGV[1].split('x').map(&.to_i) | |
upper_left = parse_complex(ARGV[2]) | |
lower_right = parse_complex(ARGV[3]) | |
pixels = Array.new(value: 0, size: bounds[0] * bounds[1]) | |
render(pixels, bounds, upper_left, lower_right) | |
end | |
private def parse_complex(str : String) : Complex | |
real, imag = str.split(',').map(&.to_f) | |
Complex.new(real: real, imag: imag) | |
end | |
private def escape_time(c : Complex, limit : Int32) : Int32? | |
# We could save one iteration, but let's do it like it is done in the book. | |
z = Complex.zero | |
(0...limit).each do |i| | |
return i if z.abs2 > 4 | |
z = z * z + c | |
end | |
nil | |
end | |
private def pixel_to_point(bounds, pixel, upper_left, lower_right) | |
width = lower_right.real - upper_left.real | |
height = upper_left.imag - lower_right.imag | |
Complex.new( | |
real: upper_left.real + pixel[0] * width / bounds[0], | |
imag: upper_left.imag - pixel[1] * height / bounds[1] | |
) | |
end | |
private def render(pixels, bounds, upper_left, lower_right) | |
(0...bounds[1]).each do |row| | |
(0...bounds[0]).each do |col| | |
point = pixel_to_point(bounds, {col, row}, upper_left, lower_right) | |
count = escape_time(point, 255) | |
pixels[row * bounds[0] + col] = count ? 255 - count : 0 | |
end | |
end | |
end | |
end | |
MandelbrotGenerator.new.generate |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# Port of the Rust code for benchmarking purposes, does not mimick error | |
# handling because it wouldn't make a difference. | |
require "complex" | |
class MandelbrotGenerator | |
def generate | |
bounds = ARGV[1].split('x').map(&:to_i) | |
upper_left = parse_complex(ARGV[2]) | |
lower_right = parse_complex(ARGV[3]) | |
pixels = Array.new(bounds[0] * bounds[1], 0) | |
render(pixels, bounds, upper_left, lower_right) | |
end | |
private def parse_complex(str) | |
real, imag = str.split(',').map(&:to_f) | |
Complex(real, imag) | |
end | |
private def escape_time(c, limit) | |
# We could save one iteration, but let's do it like it is done in the book. | |
z = Complex(0.0, 0.0) | |
(0...limit).each do |i| | |
return i if z.abs2 > 4 | |
z = z * z + c | |
end | |
nil | |
end | |
private def pixel_to_point(bounds, pixel, upper_left, lower_right) | |
width = lower_right.real - upper_left.real | |
height = upper_left.imag - lower_right.imag | |
Complex( | |
upper_left.real + pixel[0] * width / bounds[0], | |
upper_left.imag - pixel[1] * height / bounds[1] | |
) | |
end | |
private def render(pixels, bounds, upper_left, lower_right) | |
(0...bounds[1]).each do |row| | |
(0...bounds[0]).each do |col| | |
point = pixel_to_point(bounds, [col, row], upper_left, lower_right) | |
count = escape_time(point, 255) | |
pixels[row * bounds[0] + col] = count ? 255 - count : 0 | |
end | |
end | |
end | |
end | |
MandelbrotGenerator.new.generate |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
% hyperfine './mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' 'target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' | |
Benchmark #1: ./mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20 | |
Time (mean ± σ): 3.528 s ± 0.021 s [User: 3.516 s, System: 0.012 s] | |
Range (min … max): 3.500 s … 3.564 s 10 runs | |
Benchmark #2: target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20 | |
Time (mean ± σ): 3.431 s ± 0.038 s [User: 3.423 s, System: 0.005 s] | |
Range (min … max): 3.383 s … 3.506 s 10 runs | |
Summary | |
'target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' ran | |
1.03 ± 0.01 times faster than './mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
% hyperfine './mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' './mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20' | |
Benchmark #1: ./mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20 | |
Time (mean ± σ): 3.639 s ± 0.074 s [User: 3.600 s, System: 0.013 s] | |
Range (min … max): 3.549 s … 3.818 s 10 runs | |
Benchmark #2: ./mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20 | |
Time (mean ± σ): 398.077 s ± 2.257 s [User: 397.740 s, System: 0.209 s] | |
Range (min … max): 395.474 s … 403.707 s 10 runs | |
Summary | |
'./mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' ran | |
109.39 ± 2.30 times faster than './mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mandelbrot [main] % hyperfine 'target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' './mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20' | |
Benchmark #1: target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20 | |
Time (mean ± σ): 3.486 s ± 0.032 s [User: 3.479 s, System: 0.005 s] | |
Range (min … max): 3.435 s … 3.530 s 10 runs | |
Benchmark #2: ./mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20 | |
Time (mean ± σ): 5.177 s ± 0.050 s [User: 7.003 s, System: 0.232 s] | |
Range (min … max): 5.118 s … 5.289 s 10 runs | |
Summary | |
'target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' ran | |
1.49 ± 0.02 times faster than './mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment