From c836493d4f7db7cf6bea0e4f4a2ca5d5c0cfa982 Mon Sep 17 00:00:00 2001 From: Erik Huelsmann Date: Wed, 15 May 2019 23:41:10 +0200 Subject: [PATCH] * Cherry pick performance idea from PR #61 --- lib/Promises/Deferred/AE.pm | 44 +++++++++++++++++++++--- lib/Promises/Deferred/AnyEvent.pm | 48 +++++++++++++++++++++++--- lib/Promises/Deferred/EV.pm | 56 +++++++++++++++++++++++++++---- 3 files changed, 132 insertions(+), 16 deletions(-) diff --git a/lib/Promises/Deferred/AE.pm b/lib/Promises/Deferred/AE.pm index f2b72df..00b8275 100644 --- a/lib/Promises/Deferred/AE.pm +++ b/lib/Promises/Deferred/AE.pm @@ -8,13 +8,47 @@ use AE; use parent 'Promises::Deferred'; +# Before the pipe-based approach used below, there was an AE::postpone-based +# approach for _notify_backend. The current code is much more performant: + +# Original code (on a laptop on battery power): +# Backend: Promises::Deferred::AE +# Benchmark: running one, two for at least 10 CPU seconds... +# one: 44 wallclock secs @ 3083.99/s (n=31210) +# two: 29 wallclock secs @ 1723.66/s (n=17340) + +# New approach: +# Backend: Promises::Deferred::AE +# Benchmark: running one, two for at least 10 CPU seconds... +# one: 11 wallclock secs @ 10457.90/s (n=108553) +# two: 11 wallclock secs @ 3878.69/s (n=40959) + + +my ($socket_pid, $socket_send, $socket_recv, $socket_io, + $read_buf, @io_callbacks); + +sub _do_callbacks { + my @cbs = @io_callbacks; + @io_callbacks = (); + sysread $socket_recv, $read_buf, 16; + for my $cb (@cbs) { + $cb->(); + } +} + sub _notify_backend { my ( $self, $callbacks, $result ) = @_; - AE::postpone { - foreach my $cb (@$callbacks) { - $cb->(@$result); - } - }; + + if (! $socket_pid || $socket_pid != $$) { + $socket_pid = $$; + close($socket_send) if defined $socket_send; + close($socket_recv) if defined $socket_recv; + pipe($socket_recv, $socket_send); + $socket_io = AE::io($socket_recv, 0, \&_do_callbacks); + } + + push @io_callbacks, @$callbacks; + syswrite $socket_send, ' '; } sub _timeout { diff --git a/lib/Promises/Deferred/AnyEvent.pm b/lib/Promises/Deferred/AnyEvent.pm index 8999ad6..a6d3cf8 100644 --- a/lib/Promises/Deferred/AnyEvent.pm +++ b/lib/Promises/Deferred/AnyEvent.pm @@ -8,13 +8,51 @@ use AnyEvent; use parent 'Promises::Deferred'; +# Before the pipe-based approach used below, there was an +# AnyEvent->postpone-based approach for _notify_backend. +# The current code is much more performant: + +# Original code (on a laptop on battery power): +# Backend: Promises::Deferred::AnyEvent +# Benchmark: running one, two for at least 10 CPU seconds... +# one: 47 wallclock secs @ 2754.62/s (n=32780) +# two: 37 wallclock secs @ 2450.45/s (n=24676) + +# New approach: +# Backend: Promises::Deferred::AnyEvent +# Benchmark: running one, two for at least 10 CPU seconds... +# one: 10 wallclock secs @ 10182.12/s (n=106505) +# two: 10 wallclock secs @ 3847.01/s (n=39855) + + +my ($socket_pid, $socket_send, $socket_recv, $socket_io, + $read_buf, @io_callbacks); + +sub _do_callbacks { + my @cbs = @io_callbacks; + @io_callbacks = (); + sysread $socket_recv, $read_buf, 16; + for my $cb (@cbs) { + $cb->(); + } +} + sub _notify_backend { my ( $self, $callbacks, $result ) = @_; - AnyEvent::postpone { - foreach my $cb (@$callbacks) { - $cb->(@$result); - } - }; + + if (! $socket_pid || $socket_pid != $$) { + $socket_pid = $$; + close($socket_send) if defined $socket_send; + close($socket_recv) if defined $socket_recv; + pipe($socket_recv, $socket_send); + $socket_io = AnyEvent->io( + fh => $socket_recv, + poll => 'r', + cb => \&_do_callbacks); + } + + push @io_callbacks, @$callbacks; + syswrite $socket_send, ' '; } sub _timeout { diff --git a/lib/Promises/Deferred/EV.pm b/lib/Promises/Deferred/EV.pm index 1d9762c..1a38ad6 100644 --- a/lib/Promises/Deferred/EV.pm +++ b/lib/Promises/Deferred/EV.pm @@ -8,17 +8,61 @@ use EV; use parent 'Promises::Deferred'; +# Before the pipe-based approach used below, there was an EV::timer-based +# approach for _notify_backend. The current code is much more performant: + +# Original code (on a laptop on battery power): +# Backend: Promises::Deferred::EV +# Benchmark: running one, two for at least 10 CPU seconds... +# Benchmark: running one, two for at least 10 CPU seconds... +# one: 67 wallclock secs @ 1755.16/s (n=17692) +# two: 53 wallclock secs @ 770.03/s (n=7785) + +# New approach: +# Backend: Promises::Deferred::EV +# Benchmark: running one, two for at least 10 CPU seconds... +# one: 10 wallclock secs @ 10949.19/s (n=115076) +# two: 10 wallclock secs @ 3964.58/s (n=41747) + + +my ($socket_pid, $socket_send, $socket_recv, $socket_io, + $read_buf, @io_callbacks); + +sub _do_callbacks { + my @cbs = @io_callbacks; + @io_callbacks = (); + sysread $socket_recv, $read_buf, 16; + for my $cb (@cbs) { + $cb->(); + } +} + sub _notify_backend { my ( $self, $callbacks, $result ) = @_; - my $w; $w = EV::timer( 0, 0, sub { - foreach my $cb (@$callbacks) { - $cb->(@$result); - } - undef $w; - }); + if (! $socket_pid || $socket_pid != $$) { + $socket_pid = $$; + close($socket_send) if defined $socket_send; + close($socket_recv) if defined $socket_recv; + pipe($socket_recv, $socket_send); + $socket_io = EV::io($socket_recv, EV::READ, \&_do_callbacks); + } + + push @io_callbacks, @$callbacks; + syswrite $socket_send, ' '; } +# sub _notify_backend { +# my ( $self, $callbacks, $result ) = @_; + +# my $w; $w = EV::timer( 0, 0, sub { +# foreach my $cb (@$callbacks) { +# $cb->(@$result); +# } +# undef $w; +# }); +# } + sub _timeout { my ( $self, $timeout, $callback ) = @_;