Re: [PATCH] speed up ordered dequeueing in IPC-DirQueue 0.05

[email protected] ((Justin Mason)) Thu, 05 Jan 2006 18:57:37 -0800
Newsgroups perl.ipc.dirqueue
Message-ID <[email protected]>
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

(cc'd mailing list)

Anton Berezin writes:
> Justin,
> 
> On Thu, Dec 29, 2005 at 02:18:06PM +0100, Anton Berezin wrote:
> 
> > I don't think it is possibly to do much with the algorithm as such,
> > since the sorting needs to be done, and the results cannot really be
> > cached if we want the ordered queue to be really ordered, but I found a
> > definite thing to improve.
> 
> On the further thought, even the existing ordered method does not
> provide us with an absolute guarantee that the things are going to be
> processed in order, since there is a race between readdir and newer
> enqueue operations.  So it is possible to somewhat relax it even further
> and achieve a really good speed up for some, but not all, cases.
> 
> The idea is to keep a last_enqueued file in the queue dir.  We are only
> interested in its mtime.  Then, on a dequeue operation, we cache the
> result of readdir and only re-read the file list if the cached file list
> is empty or if the mtime of last_enqueued file has changed.
> 
> This obviously won't help much if there are a lot of concurrent enqueue
> operations, but for the case "first enqueue a bunch of jobs, then launch
> several processors, then repeat after some time" it helps absolutely
> tremendously.

Hi Anton --

many thanks for this work!  The patches are welcome.

I've applied the ordered-dequeueing speedup patch from the previous mail
- -- with a few changes to match latest SVN (see below).  It's in as r2353.
I never thought the interpreter-level code path would have such a large
effect on execution speed; I was assuming it'd all be system calls, which
*should* be a lot slower.  Looks like I was wrong. ;)

However, regarding the patch from *this* mail, it's important to note that
the underlying code changed in SVN at the start of December; r2255 added a
"last-enqueue" flag file, initially at least.  I then realised that the
queue directory *itself* can be used for this purpose, in a
backwards-compatible way, so now it just adds similar semantics to the
directory inode itself.

I found that this change was necessitated to support Reiserfs and
XFS-hosted queue directories, since those filesystems didn't modify the
mtime of a directory when changes were made within it, as ext3 did.   (I
was assuming that this was a POSIX requirement; again, looks like I was
wrong. ;)

There were then several more changes since then to SVN trunk, made to fix
important bugs (including another race condition) and add new,
experimental functionality.

This patch looks very useful -- I'd be happy to add it, as a patch against
current SVN, if that'd be possible.  Also, it may be worthwhile making it
the ordered default, assuming I can't think of a good reason why not ;)
Right now though, it doesn't apply cleanly to SVN.

by the way there is a mailing list; <[email protected]> to
subscribe.  it might be worth sending future patches there so other users can
comment on them too.

- --j.

> I did not think it was a good idea to modify the default "ordered"
> behavior, so my patch makes "ordered" options to be a tri-value, with
> "2" denoting this "approximately ordered" way of doing things.
> 
> The result of the same tests as yesterday, with ordered => 2 is
> 
> tobez@heechee ~/_> time perl eq 10000 
> perl eq 10000  1.73s user 1.90s system 44% cpu 8.180 total
> tobez@heechee ~/_> time perl deq 10000
> perl deq 10000  1.39s user 2.12s system 94% cpu 3.726 total
> 
> This represents a 30-fold improvement for the dequeueing case in
> comparison to the yesterdays "optimized" version.
> 
> Below is the patch which also incorporates my previous changes.
> 
> --- DirQueue.pm.orig	Thu Dec 29 14:14:10 2005
> +++ DirQueue.pm	Fri Dec 30 12:53:41 2005
> @@ -101,10 +101,12 @@ The C<chmod>-style file mode for queue c
>  specified as a string with a leading 0.  It will be affected by the
>  current process C<umask>.
>  
> -=item ordered => { 0 | 1 } (default: 1)
> +=item ordered => { 0 | 1 | 2 } (default: 1)
>  
>  Whether the jobs should be processed in order of submission, or
> -in no particular order.
> +in no particular order.  The special value of 2 denotes "approximately
> +ordered", which does not make as strong guarantees about processing
> +order as simply ordered queue, but is much faster in a number of cases.
>  
>  =item buf_size => $number (default: 65536)
>  
> @@ -344,6 +346,7 @@ sub enqueue_backend {
>      goto failure;
>    }
>  
> +  $self->touch_last_enqueued() if $self->{ordered} == 2;
>    # my $pathqueue_created = 1;     # not required, we're done!
>    return 1;
>  
> @@ -360,6 +363,17 @@ failure:
>    return;
>  }
>  
> +sub touch_last_enqueued
> +{
> +	my ($self) = @_;
> +	my $last_enqueued_file = $self->q_dir().SLASH."last_enqueued";
> +	if (sysopen(LAST, $last_enqueued_file, O_WRONLY|O_CREAT, 0666)) {
> +		my $s = "X";
> +		syswrite(LAST, $s, 1);
> +		close LAST;
> +	}
> +}
> +
>  ###########################################################################
>  
>  =item $job = $dq->pickup_queued_job();
> @@ -384,12 +398,12 @@ sub pickup_queued_job {
>    $self->ensure_dir_exists ($pathactivedir);
>  
>    my $ordered = $self->{ordered};
> -  my @files;
> +  my $files;
>    my $dirfh;
>  
>    if ($ordered) {
> -    @files = $self->get_dir_filelist_sorted($pathqueuedir);
> -    if (scalar @files <= 0) {
> +    $files = $self->get_dir_filelist_sorted($pathqueuedir);
> +    if (@$files <= 0) {
>        return if $self->queuedir_is_bad($pathqueuedir);
>      }
>    } else {
> @@ -406,7 +420,7 @@ sub pickup_queued_job {
>      my $pathtmpactive;
>  
>      if ($ordered) {
> -      $nextfile = shift @files;
> +      $nextfile = shift @$files;
>      } else {
>        $nextfile = readdir($dirfh);
>      }
> @@ -638,12 +652,12 @@ sub visit_all_jobs {
>    my $pathactivedir = $self->q_subdir('active');
>  
>    my $ordered = $self->{ordered};
> -  my @files;
> +  my $files;
>    my $dirfh;
>  
>    if ($ordered) {
> -    @files = $self->get_dir_filelist_sorted($pathqueuedir);
> -    if (scalar @files <= 0) {
> +    $files = $self->get_dir_filelist_sorted($pathqueuedir);
> +    if (@$files <= 0) {
>        return if $self->queuedir_is_bad($pathqueuedir);
>      }
>    } else {
> @@ -658,7 +672,7 @@ sub visit_all_jobs {
>    my $nextfile;
>    while (1) {
>      if ($ordered) {
> -      $nextfile = shift @files;
> +      $nextfile = shift @$files;
>      } else {
>        $nextfile = readdir($dirfh);
>      }
> @@ -728,14 +742,30 @@ sub finish_job {
>  
>  sub get_dir_filelist_sorted {
>    my ($self, $dir) = @_;
> +  my $last_enqueued_file = $self->q_dir().SLASH."last_enqueued";
> +
> +  if ($self->{ordered} == 2 && $self->{filelist_sorted}{$dir}) {
> +    my $mtime = (stat($last_enqueued_file))[9];
> +    return $self->{filelist_sorted}{$dir}
> +      if defined $mtime && defined $self->{filelist_sorted_mtime}{$dir} &&
> +         $mtime <= $self->{filelist_sorted_mtime}{$dir} &&
> +		 @{$self->{filelist_sorted}{$dir}};
> +  }
>  
>    if (!opendir (DIR, $dir)) {
>      return ();          # no dir?  nothing queued
>    }
>    # have to read the lot, to sort them.
> -  my @files = sort { $a cmp $b } grep { /^\d/ } readdir(DIR);
> +  my @files = sort grep { /^\d/ } readdir(DIR);
>    closedir DIR;
> -  return @files;
> +
> +  if ($self->{ordered} == 2) {
> +    $self->{filelist_sorted_mtime}{$dir} = (stat($last_enqueued_file))[9];
> +    $self->{filelist_sorted}{$dir} = \@files;
> +    return $self->{filelist_sorted}{$dir};
> +  } else {
> +    return \@files;
> +  }
>  }
>  
>  ###########################################################################
> @@ -998,7 +1028,7 @@ sub new_q_filename {
>          $job->{pri},
>          $gmt[5]+1900, $gmt[4]+1, $gmt[3], $gmt[2], $gmt[1], $gmt[0],
>          $job->{time_submitted_msecs},
> -        hash_string_to_filename ($self->gethostname().$$));
> +        hash_string_to_filename ($self->gethostname().$$.$self->get_random_int()));
>  
>    # normally, this isn't used.  but if there's a collision,
>    # all retries after that will do this; in this case, the
> 
> \Anton.
> -- 
> An undefined problem has an infinite number of solutions.
> -- Robert A. Humphrey
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Exmh CVS

iD8DBQFDvdyhMJF5cimLx9ARAgM4AJ0bCfFNHsx9SjdGYXLtzOjhPW9MeQCgguxc
Ng8JAoBN7/thHp5CBgpSIIU=
=l6su
-----END PGP SIGNATURE-----