We already explicitly save the value of $@ - local()ization is not needed
Peter Rabbitson [Thu, 4 Jul 2013 05:06:06 +0000 (07:06 +0200)]
511c05cae introduced an extension to try{} - unlike in an eval{} a try{}
block has access to the previous value of $@. Since we already have a copy,
we can do a manual restore of $@ instead of the expensive local() mechanism.

Results in a 7% speedup of bare try{} on 5.16

Changes
MANIFEST.SKIP
lib/Try/Tiny.pm
maint/bench.pl [new file with mode: 0644]

diff --git a/Changes b/Changes
index f9edd71..440be84 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,5 +1,6 @@
 
   - fix tests failing on 5.6.x due to differing DESTROY semantics
+  - excise superfluous local($@) call - 7% speedup
 
 0.12
   - doc fixes
index 02f4be1..5923545 100644 (file)
@@ -46,3 +46,5 @@
 \.gitignore$
 
 MYMETA
+
+maint
index db18b3e..9d46a76 100644 (file)
@@ -41,41 +41,35 @@ sub try (&;@) {
     }
   }
 
-  # save the value of $@ so we can set $@ back to it in the beginning of the eval
-  my $prev_error = $@;
-
-  my ( @ret, $error, $failed );
-
   # FIXME consider using local $SIG{__DIE__} to accumulate all errors. It's
   # not perfect, but we could provide a list of additional errors for
   # $catch->();
 
-  {
-    # localize $@ to prevent clobbering of previous value by a successful
-    # eval.
-    local $@;
+  # save the value of $@ so we can set $@ back to it in the beginning of the eval
+  # and restore $@ after the eval finishes
+  my $prev_error = $@;
 
-    # failed will be true if the eval dies, because 1 will not be returned
-    # from the eval body
-    $failed = not eval {
-      $@ = $prev_error;
-
-      # evaluate the try block in the correct context
-      if ( $wantarray ) {
-        @ret = $try->();
-      } elsif ( defined $wantarray ) {
-        $ret[0] = $try->();
-      } else {
-        $try->();
-      };
-
-      return 1; # properly set $fail to false
+  my ( @ret, $error );
+
+  # failed will be true if the eval dies, because 1 will not be returned
+  # from the eval body
+  my $failed = not eval {
+    $@ = $prev_error;
+
+    # evaluate the try block in the correct context
+    if ( $wantarray ) {
+      @ret = $try->();
+    } elsif ( defined $wantarray ) {
+      $ret[0] = $try->();
+    } else {
+      $try->();
     };
 
-    # copy $@ to $error; when we leave this scope, local $@ will revert $@
-    # back to its previous value
-    $error = $@;
-  }
+    return 1; # properly set $fail to false
+  } and $error = $@;
+
+  # reset the original value of $@
+  $@ = $prev_error;
 
   # set up a scope guard to invoke the finally block at the end
   my @guards =
@@ -146,7 +140,7 @@ __END__
 
 =head1 NAME
 
-Try::Tiny - minimal try/catch with proper localization of $@
+Try::Tiny - minimal try/catch with proper preservation of $@
 
 =head1 SYNOPSIS
 
@@ -329,8 +323,9 @@ More specifically, C<$@> is clobbered at the beginning of the C<eval>, which
 also makes it impossible to capture the previous error before you die (for
 instance when making exception objects with error stacks).
 
-For this reason C<try> will actually set C<$@> to its previous value (before
-the localization) in the beginning of the C<eval> block.
+For this reason C<try> will actually set C<$@> to its previous value (the one
+available before entering the C<try> block) in the beginning of the C<eval>
+block.
 
 =head2 Localizing $@ silently masks errors
 
diff --git a/maint/bench.pl b/maint/bench.pl
new file mode 100644 (file)
index 0000000..215097c
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use Benchmark::Dumb ':all';
+use Try::Tiny;
+
+my $max = 10_000;
+
+cmpthese('0.003', {
+  eval => sub { do { local $@; eval { die 'foo' } } for (1..$max) },
+  try => sub { do { try { die 'foo' } } for (1..$max) },
+});