summaryrefslogtreecommitdiff
path: root/pp.c
diff options
context:
space:
mode:
authorDavid Mitchell <davem@iabyn.com>2012-07-26 16:04:09 +0100
committerDavid Mitchell <davem@iabyn.com>2012-09-08 15:42:06 +0100
commit6502e08109cd003b2cdf39bc94ef35e52203240b (patch)
treeae4071332e6a7fd61354d33941476643066d5f56 /pp.c
parent2c7b5d7698f52b86acffe19a7ec15e85c99337fe (diff)
downloadperl-6502e08109cd003b2cdf39bc94ef35e52203240b.tar.gz
Don't copy all of the match string buffer
When a pattern matches, and that pattern contains captures (or $`, $&, $' or /p are present), a copy is made of the whole original string, so that $1 et al continue to hold the correct value even if the original string is subsequently modified. This can have severe performance penalties; for example, this code causes a 1Mb buffer to be allocated, copied and freed a million times: $&; $x = 'x' x 1_000_000; 1 while $x =~ /(.)/g; This commit changes this so that, where possible, only the needed substring of the original string is copied: in the above case, only a 1-byte buffer is copied each time. Also, it now reuses or reallocs the buffer, rather than freeing and mallocing each time. Now that PL_sawampersand is a 3-bit flag indicating separately whether $`, $& and $' have been seen, they each contribute only their own individual penalty; which ones have been seen will limit the extent to which we can avoid copying the whole buffer. Note that the above code *without* the $& is not currently slow, but only because the copying is artificially disabled to avoid the performance hit. The next but one commit will remove that hack, meaning that it will still be fast, but will now be correct in the presence of a modified original string. We achieve this by by adding suboffset and subcoffset fields to the existing subbeg and sublen fields of a regex, to indicate how many bytes and characters have been skipped from the logical start of the string till the physical start of the buffer. To avoid copying stuff at the end, we just reduce sublen. For example, in this: "abcdefgh" =~ /(c)d/ subbeg points to a malloced buffer containing "c\0"; sublen == 1, and suboffset == 2 (as does subcoffset). while if $& has been seen, subbeg points to a malloced buffer containing "cd\0"; sublen == 2, and suboffset == 2. If in addition $' has been seen, then subbeg points to a malloced buffer containing "cdefgh\0"; sublen == 6, and suboffset == 2. The regex engine won't do this by default; there are two new flag bits, REXEC_COPY_SKIP_PRE and REXEC_COPY_SKIP_POST, which in conjunction with REXEC_COPY_STR, request that the engine skip the start or end of the buffer (it will still copy in the presence of the relevant $`, $&, $', /p). Only pp_match has been enhanced to use these extra flags; substitution can't easily benefit, since the usual action of s///g is to copy the whole string first time round, then perform subsequent matching iterations against the copy, without further copying. So you still need to copy most of the buffer.
Diffstat (limited to 'pp.c')
-rw-r--r--pp.c3
1 files changed, 3 insertions, 0 deletions
diff --git a/pp.c b/pp.c
index 29db8ed715..1c7b18a680 100644
--- a/pp.c
+++ b/pp.c
@@ -5549,6 +5549,9 @@ PP(pp_split)
if (rex_return == 0)
break;
TAINT_IF(RX_MATCH_TAINTED(rx));
+ /* we never pass the REXEC_COPY_STR flag, so it should
+ * never get copied */
+ assert(!RX_MATCH_COPIED(rx));
if (RX_MATCH_COPIED(rx) && RX_SUBBEG(rx) != orig) {
m = s;
s = orig;