Re: [HACKERS] TODO item: Implement Boyer-Moore searching (First time hacker)
"David Rowley" <[email protected]> Wed, 3 Sep 2008 00:19:26 +0100
| Newsgroups | gmane.comp.db.postgresql.devel.patches |
|---|---|
| Message-ID | <CC00B041B12F40749EE43D49D99A705D@amd64> |
I wrote:
> I look forward to receiving feedback on this.
Peter Eisentraut wrote:
> Please send a patch in diff -c format. And don't put a single patch
> file in a tar file.
Thanks for the pointer. I'm quite new to this.
I've done some more revisions to the patch. This has mostly just involved
tuning the skip table size based on the size of the search. This has
basically involved lots of benchmarks with different strings and calculating
the best size of table to use. The reason for this is to maintain fast
searches for smaller strings. The overhead of initialising a 256 element
array would probably out weigh the cost of the search if this were not done.
The size of the skip table increases with longer strings, or rather the size
that is utilised.
Performance:
For smaller searches performance of the patch and existing version are very
similar. The patched version starts to out perform the existing version when
the needle and haystack become larger.
The patch wins hands down with searches that leads the existing function in
to dead ends, for example:
SELECT STRPOS('A AA AAA AAAA AAAAA AAAAAA AAAAAAA','AAAAAAA');
When searching for very small strings, say just a single character in a
sentence the existing function marginally beats the patched version.
Outside of Postgres I've done benchmarks where I've searched for every
combination of the search string in the search string. Like:
test | t |
test | te |
test | tes |
test | test |
test | e |
test | es |
test | est |
test | s |
test | st |
test | t |
I felt this was fair for both versions. The patched version beat the
unpatched version. The test I carried out was a string of 934 characters.
I can upload more benchmarks if required.
I'm quite happy with the patch now so I'm going to submit it (in diff -c
format this time :)
David.
--
Sent via pgsql-patches mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-patches
boyer-moore_strpos_v1.1.patch
(application/octet-stream, 7.1 KB)
*** varlena.c 2008-09-02 23:45:48.000000000 +0100
--- varlena.c 2008-09-02 23:38:15.000000000 +0100
***************
*** 840,901 ****
}
}
! static int
text_position_next(int start_pos, TextPositionState *state)
{
! int pos = 0,
! p,
! px;
Assert(start_pos > 0); /* else caller error */
! if (state->len2 <= 0)
! return start_pos; /* result for empty pattern */
- if (!state->use_wchar)
- {
- /* simple case - single byte encoding */
- char *p1 = state->str1;
- char *p2 = state->str2;
! /* no use in searching str past point where search_str will fit */
! px = (state->len1 - state->len2);
- p1 += start_pos - 1;
! for (p = start_pos - 1; p <= px; p++)
! {
! if ((*p1 == *p2) && (strncmp(p1, p2, state->len2) == 0))
! {
! pos = p + 1;
! break;
! }
! p1++;
! }
! }
! else
! {
! /* not as simple - multibyte encoding */
! pg_wchar *p1 = state->wstr1;
! pg_wchar *p2 = state->wstr2;
- /* no use in searching str past point where search_str will fit */
- px = (state->len1 - state->len2);
! p1 += start_pos - 1;
! for (p = start_pos - 1; p <= px; p++)
! {
! if ((*p1 == *p2) && (pg_wchar_strncmp(p1, p2, state->len2) == 0))
! {
! pos = p + 1;
! break;
! }
! p1++;
! }
! }
! return pos;
}
static void
--- 840,1027 ----
}
}
! /* text_position_next
! * David Rowley 2008-09-01
! * Uses Boyer-Moore searching
! */
! int
text_position_next(int start_pos, TextPositionState *state)
{
!
! int skiptable[256];
! int ai;
! int hashsize;
!
! /*
! * state->len2 = needle length in chars
! * state->len1 = haystack length in chars
! */
Assert(start_pos > 0); /* else caller error */
! if (state->len2 <= 0)
! return start_pos; /* Empty needle, found it! */
! /* Changed for zero based arrays */
! start_pos--;
! /* Eliminate the impossible first, the needle is
! * too big for the haystack.
! */
! if (state->len1 + start_pos < state->len2)
! return 0;
!
! /* Here we must determine how much of the skip table we should use.
! * With haystack lengths of only a few characters we don't really want
! * to have to initalize the full 256 elements of the table. Such an
! * initialization would take too long in comparison to the rest of the
! * search time.
! * To get around this problem when searching for smaller strings we only
! * use part of the skiptable, the larger the search (or at least the longer
! * we estimate that it will take) the more we use of the skip table.
! *
! * After quite a few benchmarks a rule of thumb seems to show that the
! * best skip table size is around 10 times less than the length of the
! * haystack. Of course this is not an exact science. The needle length
! * counts and also the mix of characters in both the needle and the haystack.
! * It might look like a good idea to make the skip table around the same size
! * as the needle, however that likely means more collisions or possibly no
! * empty positions in the table. That would mean we'd never be able to
! * skip the length of the needle.
! *
! * We skiptable[<char value> & hashsize] to tell us how many characters
! * we should skip ahead in the haystack when we don't find a match.
! * It does not matter that it's a lossy hash check as we're re-checking the
! * actual character value again once the loop reiterates.
! *
! * The above method works both for char and pg_wchar. Perhaps with UTF8
! * the character mix will be greater.
! *
! * The following code calculates the hashsize. Notice that for small
! * searches we're bearly using any of the skiptable. We also allocate
! * the hash size to smaller searches first to allow them to get on
! * searching...
! *
! */
!
! if ((state->len2 - start_pos) < 16)
! hashsize = 3;
! else if ((state->len2 - start_pos) < 64)
! hashsize = 7;
! else if ((state->len2 - start_pos) < 128)
! hashsize = 15;
! else if ((state->len2 - start_pos) < 512)
! hashsize = 31;
! else if ((state->len2 - start_pos) < 2048)
! hashsize = 63;
! else if ((state->len2 - start_pos) < 4096)
! hashsize = 127;
! else
! hashsize = 255;
!
!
! /* Initalize the skip table. We set all elements to the needle length */
! for (ai = 0; ai <= hashsize; ai++)
! skiptable[ai] = state->len2;
! if (!state->use_wchar)
! {
! char *nptr;
! char *hptr;
! char *p;
! char *needle = state->str2;
! char *haystack = state->str1;
!
! /* Here we process the needle marking the last occurence
! * of each character (ignoring the very last character)
! */
! for (ai = 0; ai < state->len2 - 1; ai++)
! skiptable[(unsigned char) needle[ai] & hashsize] = state->len2 - ai - 1;
!
!
! /* Start at startpos plus the length of the needle */
! hptr = &haystack[state->len2 - 1 + start_pos];
!
! while (hptr < &haystack[state->len1])
! {
! nptr = &needle[state->len2 - 1]; /* Point to the end of the needle */
! p = hptr;
!
! while (*nptr == *p)
! {
! /* Do we have it? Return 1 based array pos */
! if (nptr-- == needle)
! return p - haystack + 1;
!
! p--;
! }
!
! /* Ask the skiptable where to look next. If it
! * finds a match then we align the two ~matching~
! * characters and start another search there.
! * Else we skip a whole needle length.
! * Of course the ~matching~ characters only have the
! * same hash value, the character value may be different.
! */
!
! hptr += skiptable[(unsigned char) *hptr & hashsize];
! }
!
! return 0; /* Not found */
!
! }
!
! else
! {
! /* The multibyte char version. This works exactly the same way.
! */
! pg_wchar *nptr;
! pg_wchar *hptr;
! pg_wchar *p;
! pg_wchar *needle = state->wstr2;
! pg_wchar *haystack = state->wstr1;
!
! /* Here we process the needle marking the last occurence
! * of each character (ignoring the very last character)
! */
! for (ai = 0; ai < state->len2 - 1; ai++)
! skiptable[needle[ai] & hashsize] = state->len2 - ai - 1;
!
!
! /* Start at start_pos plus the length of the needle */
! hptr = &haystack[state->len2 - 1 + start_pos];
!
! while (hptr < &haystack[state->len1])
! {
! nptr = &needle[state->len2 - 1]; /* Point to the end of the needle */
! p = hptr;
!
! while (*nptr == *p)
! {
!
! /* Do we have it? Return 1 based array pos */
! if (nptr-- == needle)
! return p - haystack + 1;
!
! p--;
! }
!
! /* Ask the skiptable where to look next. If it
! * finds a match then we align the two ~matching~
! * characters and start another search there.
! * Else we skip a whole needle length.
! * Of course the ~matching~ characters only have the
! * same hash value, the character value may be different.
! */
! hptr += skiptable[*hptr & hashsize];
! }
! return 0; /* Not found */
! }
}
static void