Benchmark results

PostgreSQL PostgreSQL 18.4 (Homebrew) on aarch64-apple-darwin24.6.0, compiled by Apple clang version 17.0.0 (clang-1700.6.4.2), 64-bit
ICU 78.3
CPU Apple M4 Pro
Corpus Wikipedia article titles, dump 20260901, 500000 titles per language
Runs median of 5 after one warm-up run, one backend, no parallel workers, JIT off
LC_CTYPE C.UTF-8

Tokenizing

Time to tokenize every title once, minus the time to scan the table. “vs default” divides by the default parser’s time.

Language Titles Avg bytes Method µs per title vs default
en 500,000 21.0 default parser 0.748 1.00×
en 500,000 21.0 intl_simple 0.700 0.94×
en 500,000 21.0 intl_search 1.000 1.34×
en 500,000 21.0 intl_search_ngram 1.081 1.45×
en 500,000 21.0 pg_trgm 1.218 1.63×
en 500,000 21.0 pg_bigm 0.934 1.25×
zh 500,000 16.9 default parser 0.455 1.00×
zh 500,000 16.9 intl_simple 1.587 3.49×
zh 500,000 16.9 intl_search 2.109 4.64×
zh 500,000 16.9 intl_search_ngram 2.325 5.11×
zh 500,000 16.9 pg_trgm 0.773 1.70×
zh 500,000 16.9 pg_bigm 0.485 1.07×
ja 500,000 22.2 default parser 0.602 1.00×
ja 500,000 22.2 intl_simple 1.888 3.14×
ja 500,000 22.2 intl_search 2.367 3.93×
ja 500,000 22.2 intl_search_ngram 2.661 4.42×
ja 500,000 22.2 pg_trgm 1.020 1.70×
ja 500,000 22.2 pg_bigm 0.660 1.10×
ko 500,000 19.1 default parser 0.555 1.00×
ko 500,000 19.1 intl_simple 0.745 1.34×
ko 500,000 19.1 intl_search 1.056 1.91×
ko 500,000 19.1 intl_search_ngram 1.412 2.55×
ko 500,000 19.1 pg_trgm 0.898 1.62×
ko 500,000 19.1 pg_bigm 0.632 1.14×
th 366,332 45.9 default parser 1.094 1.00×
th 366,332 45.9 intl_simple 1.831 1.67×
th 366,332 45.9 intl_search 2.498 2.28×
th 366,332 45.9 intl_search_ngram 3.434 3.14×
th 366,332 45.9 pg_trgm 2.235 2.04×
th 366,332 45.9 pg_bigm 1.576 1.44×

GIN index build

Single-process CREATE INDEX with maintenance_work_mem = 1GB.

Language Method Build seconds Index size MB
en default parser 1.4 25.5
en intl_simple 1.3 23.7
en intl_search 1.5 23.3
en intl_search_ngram 1.5 23.4
en pg_trgm 1.7 29.5
en pg_bigm 1.7 20.4
zh default parser 1.3 38.2
zh intl_simple 1.6 14.0
zh intl_search 1.8 13.3
zh intl_search_ngram 2.2 17.1
zh pg_trgm 2.7 72.0
zh pg_bigm 2.5 41.8
ja default parser 1.4 39.6
ja intl_simple 1.7 18.4
ja intl_search 2.0 18.2
ja intl_search_ngram 3.0 20.7
ja pg_trgm 2.9 63.7
ja pg_bigm 2.5 33.9
ko default parser 1.4 30.1
ko intl_simple 1.6 28.6
ko intl_search 1.7 28.7
ko intl_search_ngram 2.6 24.5
ko pg_trgm 2.3 48.9
ko pg_bigm 2.0 24.3
th default parser 1.5 37.6
th intl_simple 1.5 13.7
th intl_search 2.2 13.6
th intl_search_ngram 5.8 45.5
th pg_trgm 1.6 23.4
th pg_bigm 1.6 18.8

Word queries

Each language runs its query terms one at a time as count(*), through each method’s index. The tsvector methods use plainto_tsquery, and the n-gram methods use LIKE '%term%'. Recall and overmatch come from the labeled sample below: recall is the share of real matches the method returned, and overmatch is the share of the method’s returned titles that aren’t real matches.

Language Terms Method Queries using the index Avg ms per query Hits Recall Overmatch
en 200 default parser 200 0.234 123,406 — —
en 200 intl_simple 200 0.275 119,996 — —
en 200 intl_search 200 0.266 121,265 — —
en 200 intl_search_ngram 200 0.272 121,265 — —
en 200 pg_trgm 182 3.060 708,250 — —
en 200 pg_bigm 200 2.063 708,250 — —
zh 200 default parser 200 0.037 5,396 10.3% 0.0%
zh 200 intl_simple 200 0.164 68,070 92.6% 15.6%
zh 200 intl_search 200 0.185 97,880 92.6% 15.6%
zh 200 intl_search_ngram 200 0.336 111,835 99.0% 17.6%
zh 200 pg_trgm 22 23.104 79,536 100.0% 18.4%
zh 200 pg_bigm 200 0.111 79,536 100.0% 18.4%
ja 200 default parser 200 0.036 7,373 7.0% 0.0%
ja 200 intl_simple 200 0.132 86,443 86.1% 19.5%
ja 200 intl_search 200 0.140 88,779 86.1% 19.5%
ja 200 intl_search_ngram 200 0.530 184,736 100.0% 53.6%
ja 200 pg_trgm 57 19.852 175,927 100.0% 54.0%
ja 200 pg_bigm 200 0.285 175,927 100.0% 54.0%
ko 200 default parser 200 0.070 35,053 46.7% 0.0%
ko 200 intl_simple 200 0.099 36,678 48.2% 1.1%
ko 200 intl_search 200 0.082 36,678 48.2% 1.1%
ko 200 intl_search_ngram 200 0.398 93,757 100.0% 22.0%
ko 200 pg_trgm 91 17.704 93,655 100.0% 22.0%
ko 200 pg_bigm 200 0.139 93,655 100.0% 22.0%
th 200 default parser 200 0.037 6,997 5.3% 0.0%
th 200 intl_simple 200 0.294 215,162 78.9% 43.8%
th 200 intl_search 200 0.292 215,162 78.9% 43.8%
th 200 intl_search_ngram 200 0.586 447,620 98.2% 69.4%
th 200 pg_trgm 133 17.208 620,044 100.0% 77.2%
th 200 pg_bigm 200 1.089 620,044 100.0% 77.2%

Labeled sample

For each language, bench/pairs.sql draws term/title pairs at random from every title in the sample that contains one of the query terms as a substring, and bench/labels.csv marks each pair match or over. A pair is a match when the title uses the term as a word: on its own, with grammatical endings (Korean particles, Japanese inflection), or as part of a compound whose meaning includes it (機場 in 國際機場). It is over when the characters are there but the word isn’t: inside a transliterated name (阿拉 in 阿拉巴马州), across a word boundary (京都 in 東京都), or inside an unrelated word. Every method is scored on the same pairs, so a method that returns every substring scores 100% recall and pays in overmatch.

Language Labeled pairs Real matches
zh 250 81.6%
ja 250 46.0%
ko 250 78.0%
th 250 22.8%