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.730 |
1.00× |
| en |
500,000 |
21.0 |
icu_parser |
0.631 |
0.86× |
| en |
500,000 |
21.0 |
pg_trgm |
1.107 |
1.52× |
| en |
500,000 |
21.0 |
pg_bigm |
0.908 |
1.24× |
| zh |
500,000 |
16.9 |
default parser |
0.434 |
1.00× |
| zh |
500,000 |
16.9 |
icu_parser |
1.438 |
3.31× |
| zh |
500,000 |
16.9 |
pg_trgm |
0.762 |
1.76× |
| zh |
500,000 |
16.9 |
pg_bigm |
0.484 |
1.11× |
| ja |
500,000 |
22.2 |
default parser |
0.569 |
1.00× |
| ja |
500,000 |
22.2 |
icu_parser |
1.718 |
3.02× |
| ja |
500,000 |
22.2 |
pg_trgm |
1.011 |
1.78× |
| ja |
500,000 |
22.2 |
pg_bigm |
0.659 |
1.16× |
| ko |
500,000 |
19.1 |
default parser |
0.547 |
1.00× |
| ko |
500,000 |
19.1 |
icu_parser |
0.667 |
1.22× |
| ko |
500,000 |
19.1 |
pg_trgm |
0.893 |
1.63× |
| ko |
500,000 |
19.1 |
pg_bigm |
0.580 |
1.06× |
| th |
366,332 |
45.9 |
default parser |
1.083 |
1.00× |
| th |
366,332 |
45.9 |
icu_parser |
1.681 |
1.55× |
| th |
366,332 |
45.9 |
pg_trgm |
2.184 |
2.02× |
| th |
366,332 |
45.9 |
pg_bigm |
1.432 |
1.32× |
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 |
icu_parser |
1.3 |
24.0 |
| en |
pg_trgm |
1.7 |
29.5 |
| en |
pg_bigm |
1.7 |
20.4 |
| zh |
default parser |
1.2 |
38.2 |
| zh |
icu_parser |
1.5 |
14.3 |
| zh |
pg_trgm |
2.2 |
72.0 |
| zh |
pg_bigm |
2.1 |
41.8 |
| ja |
default parser |
1.3 |
39.6 |
| ja |
icu_parser |
1.7 |
18.6 |
| ja |
pg_trgm |
2.3 |
63.7 |
| ja |
pg_bigm |
2.0 |
33.9 |
| ko |
default parser |
1.2 |
30.1 |
| ko |
icu_parser |
1.3 |
28.8 |
| ko |
pg_trgm |
1.8 |
48.9 |
| ko |
pg_bigm |
1.5 |
24.3 |
| th |
default parser |
1.2 |
37.6 |
| th |
icu_parser |
1.3 |
13.7 |
| 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.167 |
123,231 |
— |
— |
| en |
200 |
icu_parser |
200 |
0.163 |
119,253 |
— |
— |
| en |
200 |
pg_trgm |
183 |
2.564 |
659,396 |
— |
— |
| en |
200 |
pg_bigm |
200 |
1.698 |
659,396 |
— |
— |
| zh |
200 |
default parser |
200 |
0.027 |
5,396 |
10.3% |
0.0% |
| zh |
200 |
icu_parser |
200 |
0.106 |
68,070 |
92.6% |
15.6% |
| zh |
200 |
pg_trgm |
22 |
21.677 |
79,536 |
100.0% |
18.4% |
| zh |
200 |
pg_bigm |
200 |
0.105 |
79,536 |
100.0% |
18.4% |
| ja |
200 |
default parser |
200 |
0.035 |
7,373 |
7.0% |
0.0% |
| ja |
200 |
icu_parser |
200 |
0.128 |
86,443 |
86.1% |
19.5% |
| ja |
200 |
pg_trgm |
57 |
18.650 |
175,927 |
100.0% |
54.0% |
| ja |
200 |
pg_bigm |
200 |
0.245 |
175,927 |
100.0% |
54.0% |
| ko |
200 |
default parser |
200 |
0.064 |
35,053 |
46.7% |
0.0% |
| ko |
200 |
icu_parser |
200 |
0.067 |
36,678 |
48.2% |
1.1% |
| ko |
200 |
pg_trgm |
91 |
16.965 |
93,655 |
100.0% |
22.0% |
| ko |
200 |
pg_bigm |
200 |
0.135 |
93,655 |
100.0% |
22.0% |
| th |
200 |
default parser |
200 |
0.037 |
6,997 |
5.3% |
0.0% |
| th |
200 |
icu_parser |
200 |
0.279 |
215,162 |
78.9% |
43.8% |
| th |
200 |
pg_trgm |
133 |
16.912 |
620,044 |
100.0% |
77.2% |
| th |
200 |
pg_bigm |
200 |
0.967 |
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% |