in reply to Re: Refactoring: Better Variable Names For Better Understanding? [SOLVED] in thread Refactoring: Better Variable Names For Better Understanding? [SOLVED]
The GD module is nearly 2x faster than Imager. Not sure why. Anyway, here is the parallel version using GD. Workers are spawned prior to the image being created by the manager process.
use strict;
use warnings;
use GD;
use MCE;
# based on original http://www.alfrog.com/mandel.html
# karlgoethebier: code refactor
# marioroy: parallelization
my $width = 1280;
my $height = 1024;
my $iterations = 20;
my @palette;
my $image;
my $mce = MCE->new(
use_threads => 0, # MCE defaults to threads on Windows
max_workers => 'auto',
chunk_size => 8,
gather => sub {
my ( $x, $y, $color );
while ( ($x, $y, $color) = splice(@_, 0, 3) ) {
$image->setPixel( $x, $y, $palette[ $color ] );
}
},
user_func => sub {
my ( $mce, $chunk_ref, $chunk_id ) = @_;
my ( $re_c, $im_c, $re_z, $im_z, $color, $temp );
my ( @set_data );
for my $x ( $chunk_ref->[0] .. $chunk_ref->[1] ) {
for my $y ( 0 .. $height - 1 ) {
$re_c = ( $x - 3 * $width / 4 ) / ( $width / 3 );
$im_c = ( $y - $height / 2 ) / ( $width / 3 );
$re_z = $im_z = $color = 0;
while ( 1 ) {
$temp = $re_z;
$re_z = $re_z * $re_z - $im_z * $im_z + $re_c;
$im_z = 2 * $temp * $im_z + $im_c;
++$color;
last if $re_z * $re_z + $im_z * $im_z > 4;
if ( $color == $iterations ) {
$color = 0;
last;
}
}
push @set_data, $x, $y, $color;
}
}
MCE->gather( @set_data );
}
);
# spawn MCE workers
$mce->spawn;
# init image and color palette
$image = new GD::Image( $width, $height );
push @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 );
}
# compute mandelbrot
$mce->process({ bounds_only => 1, sequence => [ 0, $width - 1 ] });
$mce->shutdown;
# save image
open my $fh, '>mandelbrot.png' || exit 1;
binmode $fh;
print $fh $image->png;
close $fh;
Re^3: Refactoring: Better Variable Names For Better Understanding? [SOLVED]
by marioroy (Prior) on Jun 06, 2015 at 06:40 UTC
|
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;
}
| [reply] [d/l] |
Re^3: Refactoring: Better Variable Names For Better Understanding? [SOLVED]
by marioroy (Prior) on Jun 06, 2015 at 03:05 UTC
|
Update: Corrected a couple 1 off errors.
Update: Computes the upper half only. Makes a copy afterwards, flips vertically, and copies into bottom half.
Update: Draw lines when possible.
The following is constructed using MCE::Flow with 2 tasks (many providers and one writer). Spawning workers early is not necessary for this demonstration. I went with GD due to running faster than Imager. However, one can easily change to Imager inside the writer function.
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)
# process upper half only (reduces work for writer by 50%)
# draw lines when possible (reduces work for writer by 56x)
# also see tips by BrowserUk at:
# http://www.perlmonks.org/?node_id=1128885
# 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 MCE
MCE::Flow::init {
bounds_only => 1,
chunk_size => 16,
max_workers => [ $num_mandels, $num_writers ],
task_name => [ 'tsk_mandel', 'tsk_writer' ],
user_end => sub {
my ( $mce, $task_id, $task_name ) = @_;
$Q->enqueue(undef) if ( $task_name eq 'tsk_mandel' );
}
};
# 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 ) = @_;
my ( $re_c, $im_c, $re_z, $im_z, $color, $last_color, $temp );
my ( @ret, $middle ); $middle = int( $height / 2 );
for my $x ( $chunk_ref->[0] .. $chunk_ref->[1] ) {
for my $y ( 0 .. $middle ) {
$re_c = ( $x - 3 * $width / 4 ) / ( $width / 3 );
$im_c = ( $y - $height / 2 ) / ( $width / 3 );
$re_z = $im_z = $color = 0;
while ( 1 ) {
$temp = $re_z;
$re_z = $re_z * $re_z - $im_z * $im_z + $re_c;
$im_z = 2 * $temp * $im_z + $im_c;
++$color;
last if $re_z * $re_z + $im_z * $im_z > 4;
if ( $color == $iterations ) {
$color = 0;
last;
}
}
if ( $y && $color == $last_color ) {
# increment line size, writer draws a line when > 0
$ret[-1]++;
}
else {
push @ret, $x, $y, $color, 0;
$last_color = $color;
}
}
}
# freezing here to prevent double freezing/thawing
$Q->enqueue( MCE->freeze( \@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;
}
The examples demonstrate using the Core MCE API and MCE::Flow. | [reply] [d/l] |
|
|