1/*--------------------------------------------------------------------------
4 * Test integer set data structure.
6 * Copyright (c) 2019-2025, PostgreSQL Global Development Group
9 * src/test/modules/test_integerset/test_integerset.c
11 * -------------------------------------------------------------------------
22 * If you enable this, the "pattern" tests will print information about
23 * how long populating, probing, and iterating the test set takes, and
24 * how much memory the test set consumed. That can be used as
25 * micro-benchmark of various operations and input patterns (you might
26 * want to increase the number of values used in each of the test, if
27 * you do that, to reduce noise).
29 * The information is printed to the server's stderr, mostly because
30 * that's where MemoryContextStats() output goes.
39 * A struct to define a pattern of integers, for use with the test_pattern()
44 char *
test_name;
/* short name of the test, for humans */
52 "all ones",
"1111111111",
56 "alternating bits",
"0101010101",
60 "clusters of ten",
"1111111111",
64 "clusters of hundred",
65 "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
73 "sparse",
"100000000000000000000000000000001",
77 "single values, distance > 2^32",
"1",
81 "clusters, distance > 2^32",
"10101010",
85 "clusters, distance > 2^60",
"10101010",
87 23
/* can't be much higher than this, or we
100 * SQL-callable entry point to perform all tests.
105 /* Tests for various corner cases */
118 /* Test different test patterns, with lots of entries */
128 * Test with a repeating pattern, defined by the 'spec'.
142 uint64 pattern_num_values;
146 fprintf(stderr,
"-----\ntesting intset with pattern \"%s\"\n", spec->
test_name);
148 /* Pre-process the pattern, creating an array of integers from it. */
151 pattern_num_values = 0;
152 for (
int i = 0;
i < patternlen;
i++)
155 pattern_values[pattern_num_values++] =
i;
159 * Allocate the integer set.
161 * Allocate it in a separate memory context, so that we can print its
162 * memory usage easily. (intset_create() creates a memory context of its
163 * own, too, but we don't have direct access to it, so we cannot call
164 * MemoryContextStats() on it directly).
175 * Add values to the set.
181 while (n < spec->num_values)
185 for (
int i = 0;
i < pattern_num_values && n < spec->
num_values;
i++)
187 x = last_int + pattern_values[
i];
199 spec->
num_values, (
int) (endtime - starttime) / 1000);
202 * Print stats on the amount of memory used.
204 * We print the usage reported by intset_memory_usage(), as well as the
205 * stats from the memory context. They should be in the same ballpark,
206 * but it's hard to automate testing that, so if you're making changes to
207 * the implementation, just observe that manually.
214 * Also print memory usage as reported by intset_memory_usage(). It
215 * should be in the same ballpark as the usage reported by
216 * MemoryContextStats().
220 mem_usage, (
double) mem_usage / spec->
num_values);
225 /* Check that intset_get_num_entries works */
231 * Test random-access probes with intset_is_member()
235 for (n = 0; n < 100000; n++)
242 * Pick next value to probe at random. We limit the probes to the
243 * last integer that we added to the set, plus an arbitrary constant
244 * (1000). There's no point in probing the whole 0 - 2^64 range, if
245 * only a small part of the integer space is used. We would very
246 * rarely hit values that are actually in the set.
250 /* Do we expect this value to be present in the set? */
257 if (
idx >= patternlen)
265 /* Is it present according to intset_is_member() ? */
274 n, (
int) (endtime - starttime) / 1000);
284 while (n < spec->num_values)
286 for (
int i = 0;
i < pattern_num_values && n < spec->
num_values;
i++)
288 uint64 expected = last_int + pattern_values[
i];
303 n, (
int) (endtime - starttime) / 1000);
305 if (n < spec->num_values)
314 * Test with a set containing a single integer.
326 /* Create the set. */
330 /* Test intset_get_num_entries() */
332 if (num_entries != 1)
336 * Test intset_is_member() at various special values, like 0 and maximum
337 * possible 64-bit integer, as well as the value itself.
340 elog(
ERROR,
"intset_is_member failed for 0");
342 elog(
ERROR,
"intset_is_member failed for 1");
344 elog(
ERROR,
"intset_is_member failed for PG_UINT64_MAX");
346 elog(
ERROR,
"intset_is_member failed for the tested value");
362 * Test with an integer set that contains:
364 * - a given single 'value', and
365 * - all integers between 'filler_min' and 'filler_max'.
367 * This exercises different codepaths than testing just with a single value,
368 * because the implementation buffers newly-added values. If we add just a
369 * single value to the set, we won't test the internal B-tree code at all,
370 * just the code that deals with the buffer.
384 value, filler_min, filler_max);
388 iter_expected =
palloc(
sizeof(
uint64) * (filler_max - filler_min + 1));
389 if (
value < filler_min)
392 iter_expected[n++] =
value;
395 for (
x = filler_min;
x < filler_max;
x++)
398 iter_expected[n++] =
x;
401 if (
value >= filler_max)
404 iter_expected[n++] =
value;
407 /* Test intset_get_num_entries() */
409 if (num_entries != n)
413 * Test intset_is_member() at various spots, at and around the values that
414 * we expect to be set, as well as 0 and the maximum possible value.
417 value, filler_min, filler_max);
419 value, filler_min, filler_max);
421 value, filler_min, filler_max);
423 value, filler_min, filler_max);
425 value, filler_min, filler_max);
427 value, filler_min, filler_max);
429 value, filler_min, filler_max);
431 value, filler_min, filler_max);
433 value, filler_min, filler_max);
435 value, filler_min, filler_max);
437 value, filler_min, filler_max);
439 value, filler_min, filler_max);
441 value, filler_min, filler_max);
447 if (!found ||
x != iter_expected[
i])
455 if (mem_usage < 5000 || mem_usage > 500000000)
460 * Helper function for test_single_value_and_filler.
462 * Calls intset_is_member() for value 'x', and checks that the result is what
472 expected = (
x ==
value || (filler_min <=
x &&
x < filler_max));
476 if (actual != expected)
489 elog(
NOTICE,
"testing intset with empty set");
493 /* Test intset_is_member() */
495 elog(
ERROR,
"intset_is_member on empty set returned true");
497 elog(
ERROR,
"intset_is_member on empty set returned true");
499 elog(
ERROR,
"intset_is_member on empty set returned true");
508 * Test with integers that are more than 2^60 apart.
510 * The Simple-8b encoding used by the set implementation can only encode
511 * values up to 2^60. That makes large differences like this interesting
524 elog(
NOTICE,
"testing intset with distances > 2^60 between values");
529 /* Test differences on both sides of the 2^60 boundary. */
564 * We're now very close to 2^64, so can't add large values anymore. But
565 * add more smaller values to the end, to make sure that all the above
566 * values get flushed and packed into the tree structure.
568 while (num_values < 1000)
574 /* Create an IntegerSet using these values */
576 for (
int i = 0;
i < num_values;
i++)
580 * Test intset_is_member() around each of these values
582 for (
int i = 0;
i < num_values;
i++)
590 expected = (
values[
i - 1] ==
y - 1);
592 if (result != expected)
600 expected = (
i != num_values - 1) ? (
values[
i + 1] ==
y + 1) :
false;
602 if (result != expected)
610 for (
int i = 0;
i < num_values;
i++)
Datum intset(PG_FUNCTION_ARGS)
Datum idx(PG_FUNCTION_ARGS)
TimestampTz GetCurrentTimestamp(void)
static Datum values[MAXATTR]
#define fprintf(file, fmt, msg)
uint64 intset_memory_usage(IntegerSet *intset)
uint64 intset_num_entries(IntegerSet *intset)
bool intset_is_member(IntegerSet *intset, uint64 x)
void intset_begin_iterate(IntegerSet *intset)
bool intset_iterate_next(IntegerSet *intset, uint64 *next)
IntegerSet * intset_create(void)
void intset_add_member(IntegerSet *intset, uint64 x)
MemoryContext CurrentMemoryContext
void MemoryContextStats(MemoryContext context)
void MemoryContextDelete(MemoryContext context)
void MemoryContextSetIdentifier(MemoryContext context, const char *id)
#define AllocSetContextCreate
#define ALLOCSET_SMALL_SIZES
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
uint64 pg_prng_uint64_range(pg_prng_state *state, uint64 rmin, uint64 rmax)
uint32 pg_prng_uint32(pg_prng_state *state)
pg_prng_state pg_global_prng_state
static void test_pattern(const test_spec *spec)
static const bool intset_test_stats
Datum test_integerset(PG_FUNCTION_ARGS)
static void test_single_value(uint64 value)
PG_FUNCTION_INFO_V1(test_integerset)
static void test_empty(void)
static const test_spec test_specs[]
static void test_single_value_and_filler(uint64 value, uint64 filler_min, uint64 filler_max)
static void check_with_filler(IntegerSet *intset, uint64 x, uint64 value, uint64 filler_min, uint64 filler_max)
static void test_huge_distances(void)