Update: Corrected a couple 1 off errors.
Update: Compute upper half only. Makes a copy afterwards, flips vertically, and copies into bottom half. There is a one time delay from Inline::C compiling the C code. Remember to run again.
Update: Draw lines when possible.
Update: Reduced IPC.
Finally, providing a demonstration combining MCE::Flow + GD + Inline::C.
use strict;
use warnings;
use MCE::Flow Sereal => 1;
use MCE::Queue Fast => 1;
use GD;
# based on original code at http://www.alfrog.com/mandel.html
# karlgoethebier: initial code refactor, thanks perlmonks
# marioroy: parallelization via MCE::Flow (2 user tasks) + Inline::C
# process upper half only (reduces work for writer by 50%)
# draw lines when possible (reduces work for writer by 56x)
# reduced # of times to enqueue/dequeue to lower IPC
# also see tips by BrowserUk at:
# http://www.perlmonks.org/?node_id=1128885
use Inline 'C' => Config => CCFLAGSEX => '-O2';
use Inline 'C' => <<'END_C';
int width, height, iterations, middle;
void c_init( int _width, int _height, int _iterations )
{
width = _width;
height = _height;
iterations = _iterations;
middle = _height / 2;
}
AV * c_mandel( int x1, int x2 )
{
AV *ret = newAV();
SV *line_size;
double re_c, im_c, re_z, im_z, temp;
int x, y, color, last_color;
for (x = x1; x <= x2; x++) {
for (y = 0; y <= middle; y++) {
re_c = (double) ( x - 3 * width / 4 ) / ( width / 3 );
im_c = (double) ( y - height / 2 ) / ( width / 3 );
re_z = im_z = (double) 0.0; color = 0;
while ( 1 ) {
temp = (double) re_z;
re_z = (double) re_z * re_z - im_z * im_z + re_c;
im_z = (double) 2 * temp * im_z + im_c;
++color;
if ( re_z * re_z + im_z * im_z > 4.0 )
break;
if ( color == iterations ) {
color = 0;
break;
}
}
if ( y && color == last_color ) {
// increment line size, writer draws a line when > 0
line_size = *av_fetch( ret, -1, TRUE );
sv_setiv( line_size, SvIV( line_size ) + 1 );
}
else {
av_push( ret, newSViv( x ) );
av_push( ret, newSViv( y ) );
av_push( ret, newSViv( color ) );
av_push( ret, newSViv( 0 ) );
last_color = color;
}
}
}
return sv_2mortal( ret );
}
END_C
# fasten your seat belt, enjoy the ride :)
my $width = 1280 * 1;
my $height = 1024 * 1;
my $iterations = 20;
my $Q = MCE::Queue->new();
my $num_mandels = 3;
my $num_writers = 1; # must be 1
# init C and MCE
c_init( $width, $height, $iterations );
MCE::Flow::init {
bounds_only => 1,
chunk_size => 16,
max_workers => [ $num_mandels, $num_writers ],
task_name => [ 'tsk_mandel', 'tsk_writer' ],
user_begin => sub {
my ( $mce, $task_id, $task_name ) = @_;
$mce->{ret} = [] if $task_name eq 'tsk_mandel';
},
user_end => sub {
my ( $mce, $task_id, $task_name ) = @_;
if ( $task_name eq 'tsk_mandel' ) {
$Q->enqueue( MCE->freeze( $mce->{ret} ) ) if @{ $mce->{ret} }
+;
$Q->enqueue( undef );
}
}
};
# compute mandelbrot (user tasks; sequence begin, end )
MCE::Flow::run_seq( \&mandel, \&writer, 0, $width - 1 );
MCE::Flow::finish;
# for MCE providers
sub mandel {
my ( $mce, $chunk_ref, $chunk_id ) = @_;
push @{ $mce->{ret} }, @{ c_mandel( @{ $chunk_ref } ) };
if ( @{ $mce->{ret} } > 12000 ) {
$Q->enqueue( MCE->freeze( $mce->{ret} ) );
$mce->{ret} = [];
}
}
# for MCE consumer
sub writer {
my ( $mce ) = @_;
# init image and color palette
my $image = new GD::Image( $width, $height );
my @palette = $image->colorAllocate( 0, 0, 0 ); # black
for ( 1 .. $iterations ) {
my ( $r, $g, $b ) = map { int rand 255 } 1 .. 3;
push @palette, $image->colorAllocate( $r, $g, $b );
}
# process draw requests
while (1) {
my $ret = $Q->dequeue;
if (!defined $ret) {
last unless --$num_mandels;
next;
}
my $data = MCE->thaw( $ret );
my $size = @{ $data };
for ( my $i = 0; $i < $size; $i += 4 ) {
if ( $data->[$i+3] ) { # draw line if line_size > 0
$image->line(
$data->[$i], # x1
$data->[$i+1], # y1
$data->[$i], # x2
$data->[$i+1] + $data->[$i+3], # y2
$palette[ $data->[$i+2] ] # color
);
}
else { # otherwise, set pixel
$image->setPixel(
$data->[$i], # x
$data->[$i+1], # y
$palette[ $data->[$i+2] ] # color
);
}
}
}
# copy upper half, flip vertically, then copy into bottom half
my $middle = int( $height / 2 );
my $temp = new GD::Image( $width, $middle );
$temp->copy( $image, 0, 0, 0, 0, $width, $middle );
$temp->flipVertical();
$image->copy( $temp, 0, $middle + 1, 0, 0, $width, $middle );
# save image
open my $fh, '>mandelbrot.png' || return 1;
binmode $fh;
print $fh $image->png;
close $fh;
}
|