Skip to content

Commit c09ed80

Browse files
Evdokimov Iliahackorum
authored andcommitted
ANALYZE: hash-accelerate MCV tracking for equality-only types
compute_distinct_stats() still performs a linear search of track[] for each sampled value. At higher statistics targets, that lookup cost can dominate ANALYZE time for equality-only datatypes. When the type cache's default hash support matches the equality operator, and the statistics target is at least ANALYZE_HASH_THRESHOLD, maintain a simplehash table that maps tracked values to their current track[] slots. That reduces match lookup from linear to O(1) on average. Entries that move or are replaced update the hash table so track[] and the lookup structure stay in sync, while the existing linear path remains available as a fallback.
1 parent dc3e025 commit c09ed80

1 file changed

Lines changed: 160 additions & 11 deletions

File tree

src/backend/commands/analyze.c

Lines changed: 160 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include "utils/sortsupport.h"
5656
#include "utils/syscache.h"
5757
#include "utils/timestamp.h"
58+
#include "utils/typcache.h"
5859

5960
/* Per-index data for ANALYZE */
6061
typedef struct AnlIndexData
@@ -1902,6 +1903,12 @@ ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull)
19021903
*/
19031904
#define WIDTH_THRESHOLD 1024
19041905

1906+
/*
1907+
* Build a hash table for distinct/MCV tracking only when the statistics
1908+
* target is large enough to justify the overhead of maintaining it.
1909+
*/
1910+
#define ANALYZE_HASH_THRESHOLD 100
1911+
19051912
#define swapInt(a,b) do {int _tmp; _tmp=a; a=b; b=_tmp;} while(0)
19061913
#define swapDatum(a,b) do {Datum _tmp; _tmp=a; a=b; b=_tmp;} while(0)
19071914

@@ -1920,7 +1927,29 @@ typedef struct
19201927
int *tupnoLink;
19211928
} CompareScalarsContext;
19221929

1930+
/* Entries in the simplehash hash table used by compute_distinct_stats */
1931+
typedef struct DistinctHashEntry
1932+
{
1933+
Datum value; /* the value represented by this entry */
1934+
int index; /* its index in the relevant track */
1935+
uint32 hash; /* hash code for the Datum */
1936+
char status; /* status code used by simplehash.h */
1937+
} DistinctHashEntry;
1938+
1939+
/* private_data for the simplehash hash table */
1940+
typedef struct DistinctHashContext
1941+
{
1942+
FmgrInfo *eqfunc; /* the equality operator */
1943+
FmgrInfo *hashfunc; /* the hash function to use */
1944+
Oid collation; /* collation to use equality and hash calls */
1945+
} DistinctHashContext;
1946+
19231947

1948+
/* forward reference */
1949+
typedef struct DistinctHash_hash DistinctHash_hash;
1950+
1951+
static uint32 distinct_hash_hash(DistinctHash_hash * tab, Datum key);
1952+
static bool distinct_hash_equal(DistinctHash_hash * tab, Datum key0, Datum key1);
19241953
static void compute_trivial_stats(VacAttrStatsP stats,
19251954
AnalyzeAttrFetchFunc fetchfunc,
19261955
int samplerows,
@@ -1942,6 +1971,53 @@ static int analyze_mcv_list(int *mcv_counts,
19421971
int samplerows,
19431972
double totalrows);
19441973

1974+
/* Define support routines for compute distinct values hash tables */
1975+
#define SH_PREFIX DistinctHash
1976+
#define SH_ELEMENT_TYPE DistinctHashEntry
1977+
#define SH_KEY_TYPE Datum
1978+
#define SH_KEY value
1979+
#define SH_HASH_KEY(tab, key) distinct_hash_hash(tab, key)
1980+
#define SH_EQUAL(tab, key0, key1) distinct_hash_equal(tab, key0, key1)
1981+
#define SH_SCOPE static inline
1982+
#define SH_STORE_HASH
1983+
#define SH_GET_HASH(tab, ent) ((ent)->hash)
1984+
#define SH_DEFINE
1985+
#define SH_DECLARE
1986+
#include "lib/simplehash.h"
1987+
1988+
1989+
/*
1990+
* Support functions for the hash tables used by compute_distinct_stats
1991+
*/
1992+
static uint32
1993+
distinct_hash_hash(DistinctHash_hash * tab, Datum key)
1994+
{
1995+
DistinctHashContext *context = (DistinctHashContext *) tab->private_data;
1996+
Datum result;
1997+
1998+
result = FunctionCall1Coll(context->hashfunc, context->collation, key);
1999+
return DatumGetUInt32(result);
2000+
}
2001+
2002+
static bool
2003+
distinct_hash_equal(DistinctHash_hash * tab, Datum key0, Datum key1)
2004+
{
2005+
DistinctHashContext *context = (DistinctHashContext *) tab->private_data;
2006+
Datum result;
2007+
2008+
result = FunctionCall2Coll(context->eqfunc, context->collation, key0, key1);
2009+
return DatumGetBool(result);
2010+
}
2011+
2012+
static inline void
2013+
distinct_hash_set_index(DistinctHash_hash * hash, Datum value,
2014+
uint32 value_hash, int index)
2015+
{
2016+
DistinctHashEntry *entry = DistinctHash_lookup_hash(hash, value, value_hash);
2017+
2018+
Assert(entry != NULL);
2019+
entry->index = index;
2020+
}
19452021

19462022
/*
19472023
* std_typanalyze -- the default type-specific typanalyze function
@@ -2131,15 +2207,20 @@ compute_distinct_stats(VacAttrStatsP stats,
21312207
bool is_varwidth = (!stats->attrtype->typbyval &&
21322208
stats->attrtype->typlen < 0);
21332209
FmgrInfo f_cmpeq;
2210+
TypeCacheEntry *typentry;
21342211
typedef struct
21352212
{
21362213
Datum value;
21372214
int count;
2215+
uint32 hash;
21382216
} TrackItem;
21392217
TrackItem *track;
21402218
int track_cnt,
21412219
track_max;
21422220
int num_mcv = stats->attstattarget;
2221+
bool use_hash;
2222+
DistinctHashContext hash_context;
2223+
DistinctHash_hash *track_hash = NULL;
21432224
int firstcount1 = 0, /* index of first singleton in track[] */
21442225
c1_cursor = 0; /* next singleton to evict (FIFO) */
21452226
StdAnalyzeData *mystats = (StdAnalyzeData *) stats->extra_data;
@@ -2154,6 +2235,26 @@ compute_distinct_stats(VacAttrStatsP stats,
21542235
track_cnt = 0;
21552236

21562237
fmgr_info(mystats->eqfunc, &f_cmpeq);
2238+
typentry = lookup_type_cache(stats->attrtypid,
2239+
TYPECACHE_HASH_PROC_FINFO | TYPECACHE_EQ_OPR);
2240+
2241+
/*
2242+
* For sufficiently large statistics targets, use a hash table to avoid
2243+
* repeated linear searches of the track[] array, but only when we can use
2244+
* the type's default hash support that matches the equality operator.
2245+
*/
2246+
use_hash = (num_mcv >= ANALYZE_HASH_THRESHOLD &&
2247+
mystats->eqopr == typentry->eq_opr &&
2248+
OidIsValid(typentry->hash_proc));
2249+
2250+
if (use_hash)
2251+
{
2252+
hash_context.eqfunc = &f_cmpeq;
2253+
hash_context.hashfunc = &typentry->hash_proc_finfo;
2254+
hash_context.collation = stats->attrcollid;
2255+
track_hash = DistinctHash_create(CurrentMemoryContext, track_max,
2256+
&hash_context);
2257+
}
21572258

21582259
for (i = 0; i < samplerows; i++)
21592260
{
@@ -2162,6 +2263,7 @@ compute_distinct_stats(VacAttrStatsP stats,
21622263
bool match;
21632264
int match_index,
21642265
j;
2266+
uint32 value_hash = 0;
21652267

21662268
vacuum_delay_point(true);
21672269

@@ -2208,20 +2310,33 @@ compute_distinct_stats(VacAttrStatsP stats,
22082310
/*
22092311
* See if the value matches anything we're already tracking.
22102312
*/
2211-
match = false;
2212-
firstcount1 = track_cnt;
2213-
for (j = 0; j < track_cnt; j++)
2313+
if (use_hash)
22142314
{
2215-
if (DatumGetBool(FunctionCall2Coll(&f_cmpeq,
2216-
stats->attrcollid,
2217-
value, track[j].value)))
2315+
DistinctHashEntry *entry;
2316+
2317+
value_hash = distinct_hash_hash(track_hash, value);
2318+
entry = DistinctHash_lookup_hash(track_hash, value, value_hash);
2319+
match = (entry != NULL);
2320+
if (match)
2321+
match_index = entry->index;
2322+
}
2323+
else
2324+
{
2325+
match = false;
2326+
firstcount1 = track_cnt;
2327+
for (j = 0; j < track_cnt; j++)
22182328
{
2219-
match = true;
2220-
match_index = j;
2221-
break;
2329+
if (DatumGetBool(FunctionCall2Coll(&f_cmpeq,
2330+
stats->attrcollid,
2331+
value, track[j].value)))
2332+
{
2333+
match = true;
2334+
match_index = j;
2335+
break;
2336+
}
2337+
if (j < firstcount1 && track[j].count == 1)
2338+
firstcount1 = j;
22222339
}
2223-
if (j < firstcount1 && track[j].count == 1)
2224-
firstcount1 = j;
22252340
}
22262341

22272342
if (match)
@@ -2236,6 +2351,18 @@ compute_distinct_stats(VacAttrStatsP stats,
22362351
{
22372352
swapDatum(track[j].value, track[j - 1].value);
22382353
swapInt(track[j].count, track[j - 1].count);
2354+
if (use_hash)
2355+
{
2356+
uint32 tmp;
2357+
2358+
tmp = track[j].hash;
2359+
track[j].hash = track[j - 1].hash;
2360+
track[j - 1].hash = tmp;
2361+
distinct_hash_set_index(track_hash, track[j].value,
2362+
track[j].hash, j);
2363+
distinct_hash_set_index(track_hash, track[j - 1].value,
2364+
track[j - 1].hash, j - 1);
2365+
}
22392366
}
22402367

22412368
/*
@@ -2283,12 +2410,34 @@ compute_distinct_stats(VacAttrStatsP stats,
22832410
insert_index = c1_cursor++;
22842411
if (c1_cursor >= track_cnt)
22852412
c1_cursor = firstcount1;
2413+
2414+
if (use_hash)
2415+
{
2416+
DistinctHashEntry *delentry;
2417+
2418+
delentry = DistinctHash_lookup_hash(track_hash,
2419+
track[insert_index].value,
2420+
track[insert_index].hash);
2421+
Assert(delentry != NULL);
2422+
DistinctHash_delete_item(track_hash, delentry);
2423+
}
22862424
}
22872425
else
22882426
continue;
22892427

22902428
track[insert_index].value = value;
22912429
track[insert_index].count = 1;
2430+
if (use_hash)
2431+
{
2432+
bool found_hash;
2433+
DistinctHashEntry *entry;
2434+
2435+
track[insert_index].hash = value_hash;
2436+
entry = DistinctHash_insert_hash(track_hash, value, value_hash,
2437+
&found_hash);
2438+
Assert(!found_hash);
2439+
entry->index = insert_index;
2440+
}
22922441
}
22932442
}
22942443

0 commit comments

Comments
 (0)