Fix quadratic performance of regexp match/split functions

Lists: pgsql-hackers
From: Andrew Gierth <andrew(at)tao11(dot)riddles(dot)org(dot)uk>
To: pgsql-hackers(at)postgresql(dot)org
Subject: Fix quadratic performance of regexp match/split functions
Date: 2018-08-13 03:32:17
Message-ID: 87pnyn55qh.fsf@news-spur.riddles.org.uk
Views: Whole Thread | Raw Message | Download mbox | Resend email
Lists: pgsql-hackers

While poking at that xml issue I tried to compare the memory usage of
the xml queries with vaguely comparable regexp_split_to_table queries,
and found that I could not do so; the regexp queries simply did not
complete in any sensible timeframe.

Investigation showed that in UTF8 (or other multibyte encodings), the
performance of regexp_matches and regexp_split_to_* is subject to
quadratic slowdowns thanks to the use of character offsets and calls to
text_substr, which must scan the string from the start each time to
count characters.

This patch changes that by keeping the wide-char copy of the string
available rather than freeing it, and converting the required substrings
back to the db encoding via a pre-allocated conversion buffer.

This gives noticable speedups on even very small strings (all timings
below are with -O2 --disable-cassert):

select count(*)
from (select 'aaaaa,aaaaa,aaaaa'::text as content
from generate_series(1,1000000) i offset 0) s,
regexp_split_to_table(content, ',') r;

-- ~10% faster with patch: 2.8 s -> 2.5 s

but on strings of even modest size (~1kb) the improvement is vast:

select count(*)
from (select repeat(repeat('a',10) || ',', 100+(i*0))::text
as content -- 1100 bytes, 101 matches
from generate_series(1,100000) i offset 0) s,
regexp_split_to_table(content, ',') r;

-- over 8 times faster: 51.8 sec -> 6.3 sec

and it only gets bigger:

select count(*)
from regexp_split_to_table(repeat('aaa,',10000), ',') r; -- 40KB

-- 270 times faster: 1628ms -> 6ms

This patch passes regression but I haven't yet tested it on complex
multibyte data (or non-UTF8 encodings but that shouldn't matter).

Comments?

--
Andrew (irc:RhodiumToad)

Attachment Content-Type Size
qregex.patch text/x-patch 8.4 KB

From: Andrew Gierth <andrew(at)tao11(dot)riddles(dot)org(dot)uk>
To: pgsql-hackers(at)postgresql(dot)org
Subject: Re: Fix quadratic performance of regexp match/split functions
Date: 2018-08-15 12:21:45
Message-ID: 87h8jv4zue.fsf@news-spur.riddles.org.uk
Views: Whole Thread | Raw Message | Download mbox | Resend email
Lists: pgsql-hackers

>>>>> "Andrew" == Andrew Gierth <andrew(at)tao11(dot)riddles(dot)org(dot)uk> writes:

Patch take 2. Changes:

1. Remove cleanup function with retail pfree()s; this was added in
commit ae65ca312 (Aug 2007) to fix an actual memory leak, but obsoleted
by commit ff428cded (Feb 2008); since then, the pfrees were pointless
since all the freed objects were in a memory context that was
immediately destroyed.

2. Use presence of a conversion buffer as a flag rather than call
pg_database_encoding_max_length() everywhere.

3. Increase limit on number of matches to 134 million and provide an
error message when it is reached (rather than an ugly invalid memory
request error). This limit could be removed by using repalloc_huge, but
that should probably be paired with equivalent changes in RE_execute and
done in a separate patch.

4. Disuse size_t in favour of int for sizes that can't overflow an int,
to avoid any chance of signed/unsigned mixups.

5. Remove special-case "substring to end of string" logic in the
single-byte case; adding an end-of-string position to the end of the
matches array makes it unnecessary and there's no performance benefit.

6. Moar commentz.

--
Andrew (irc:RhodiumToad)

Attachment Content-Type Size
qregex.patch text/x-patch 11.3 KB

From: Kaiting Chen <ktchen14(at)gmail(dot)com>
To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Cc: Andrew Gierth <andrew(at)tao11(dot)riddles(dot)org(dot)uk>
Subject: Re: Fix quadratic performance of regexp match/split functions
Date: 2018-08-16 03:41:01
Message-ID: 153439086187.1388.4636958239048294213.pgcf@coridan.postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Lists: pgsql-hackers

Applied cleanly for me. Here are my performance test results:

count
---------
3000000
(1 row)

Time: 3167.836 ms (00:03.168)
count
----------
10100000
(1 row)

Time: 6074.369 ms (00:06.074)
count
-------
10001
(1 row)

Time: 8.159 ms

The performance improves substantially in case 2 as advertised:

count
---------
3000000
(1 row)

Time: 3387.612 ms (00:03.388)
count
----------
10100000
(1 row)

Time: 43794.224 ms (00:43.794)
count
-------
10001
(1 row)

Time: 1436.587 ms (00:01.437)

I'll do some more testing to determine how this behaves in the presence of multibyte characters in UTF-8.


From: Andrew Gierth <andrew(at)tao11(dot)riddles(dot)org(dot)uk>
To: Kaiting Chen <ktchen14(at)gmail(dot)com>
Cc: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: Fix quadratic performance of regexp match/split functions
Date: 2018-08-16 05:55:10
Message-ID: 87d0ui500q.fsf@news-spur.riddles.org.uk
Views: Whole Thread | Raw Message | Download mbox | Resend email
Lists: pgsql-hackers

>>>>> "Kaiting" == Kaiting Chen <ktchen14(at)gmail(dot)com> writes:

Kaiting> I'll do some more testing to determine how this behaves in the
Kaiting> presence of multibyte characters in UTF-8.

Excellent, thanks!

--
Andrew (irc:RhodiumToad)