Skip to content

Commit f756662

Browse files
petergeogheganCommitfest Bot
authored andcommitted
Reduce malloc/free traffic by caching batches
Instead of immediately freeing a batch, stash it in a cache (a small fixed-size array), for reuse by the same scan. There's room for improvement: - Keeping some of the batch pieces (killItems, itemsvisibility, ...) instead of freeing them in index_batch_release. - Allocating only space we need (both index_batch_alloc calls use MaxTIDsPerBTreePage, and thus malloc - because of ALLOC_CHUNK_LIMIT).
1 parent a1f9de1 commit f756662

File tree

5 files changed

+176
-24
lines changed

5 files changed

+176
-24
lines changed

src/backend/access/index/indexam.c

Lines changed: 159 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2196,6 +2196,10 @@ index_batch_init(IndexScanDesc scan)
21962196
scan->batchState->headBatch = 0; /* initial head batch */
21972197
scan->batchState->nextBatch = 0; /* initial batch starts empty */
21982198

2199+
/* XXX init the cache of batches, capacity 16 is arbitrary */
2200+
scan->batchState->batchesCacheSize = 16;
2201+
scan->batchState->batchesCache = NULL;
2202+
21992203
scan->batchState->batches =
22002204
palloc(sizeof(IndexScanBatchData *) * scan->batchState->maxBatches);
22012205

@@ -2328,34 +2332,102 @@ static void
23282332
index_batch_end(IndexScanDesc scan)
23292333
{
23302334
index_batch_reset(scan, true);
2335+
2336+
if (scan->batchState)
2337+
{
2338+
if (scan->batchState->batches)
2339+
pfree(scan->batchState->batches);
2340+
2341+
if (scan->batchState->batchesCache)
2342+
{
2343+
for (int i = 0; i < scan->batchState->batchesCacheSize; i++)
2344+
{
2345+
if (scan->batchState->batchesCache[i] == NULL)
2346+
continue;
2347+
2348+
pfree(scan->batchState->batchesCache[i]);
2349+
}
2350+
2351+
pfree(scan->batchState->batchesCache);
2352+
}
2353+
pfree(scan->batchState);
2354+
}
23312355
}
23322356

23332357
/*
23342358
* XXX Both index_batch_alloc() calls in btree use MaxTIDsPerBTreePage,
23352359
* which seems unfortunate - it increases the allocation sizes, even if
2336-
* the index would be fine with smaller arrays. This means all batches exceed
2337-
* ALLOC_CHUNK_LIMIT, forcing a separate malloc (expensive).
2360+
* the index would be fine with smaller arrays. This means all batches
2361+
* exceed ALLOC_CHUNK_LIMIT, forcing a separate malloc (expensive). The
2362+
* cache helps for longer queries, not for queries that only create a
2363+
* single batch, etc.
23382364
*/
23392365
IndexScanBatch
2340-
index_batch_alloc(int maxitems, bool want_itup)
2366+
index_batch_alloc(IndexScanDesc scan, int maxitems, bool want_itup)
23412367
{
2342-
IndexScanBatch batch = palloc(offsetof(IndexScanBatchData, items) +
2343-
sizeof(IndexScanBatchPosItem) * maxitems);
2368+
IndexScanBatch batch = NULL;
23442369

2370+
/*
2371+
* try to find a batch in the cache
2372+
*
2373+
* XXX We can get here with batchState==NULL for bitmapscans. Could that
2374+
* mean bitmapscans have issues with malloc/free on batches too? But the
2375+
* cache can't help with that, when it's in batchState.
2376+
*/
2377+
if ((scan->batchState != NULL) &&
2378+
(scan->batchState->batchesCache != NULL))
2379+
{
2380+
/*
2381+
* try to find a batch in the cache, with maxitems high enough
2382+
*
2383+
* XXX Maybe should look for a batch with lowest maxitems? That should
2384+
* increase probability of cache hits in the future?
2385+
*/
2386+
for (int i = 0; i < scan->batchState->batchesCacheSize; i++)
2387+
{
2388+
if ((scan->batchState->batchesCache[i] != NULL) &&
2389+
(scan->batchState->batchesCache[i]->maxitems >= maxitems))
2390+
{
2391+
batch = scan->batchState->batchesCache[i];
2392+
scan->batchState->batchesCache[i] = NULL;
2393+
break;
2394+
}
2395+
}
2396+
}
2397+
2398+
/* found a batch in the cache? */
2399+
if (batch)
2400+
{
2401+
/* for IOS, we expect to already have the currTuples */
2402+
Assert(!(want_itup && (batch->currTuples == NULL)));
2403+
2404+
/* XXX maybe we could keep these allocations too */
2405+
Assert(batch->pos == NULL);
2406+
Assert(batch->itemsvisibility == NULL);
2407+
}
2408+
else
2409+
{
2410+
batch = palloc(offsetof(IndexScanBatchData, items) +
2411+
sizeof(IndexScanBatchPosItem) * maxitems);
2412+
2413+
batch->maxitems = maxitems;
2414+
2415+
/*
2416+
* If we are doing an index-only scan, we need a tuple storage
2417+
* workspace. We allocate BLCKSZ for this, which should always give
2418+
* the index AM enough space to fit a full page's worth of tuples.
2419+
*/
2420+
batch->currTuples = NULL;
2421+
if (want_itup)
2422+
batch->currTuples = palloc(BLCKSZ);
2423+
}
2424+
2425+
/* shared initialization */
23452426
batch->firstItem = -1;
23462427
batch->lastItem = -1;
23472428
batch->killedItems = NULL;
23482429
batch->numKilled = 0;
23492430

2350-
/*
2351-
* If we are doing an index-only scan, we need a tuple storage workspace.
2352-
* We allocate BLCKSZ for this, which should always give the index AM
2353-
* enough space to fit a full page's worth of tuples.
2354-
*/
2355-
batch->currTuples = NULL;
2356-
if (want_itup)
2357-
batch->currTuples = palloc(BLCKSZ);
2358-
23592431
batch->buf = InvalidBuffer;
23602432
batch->pos = NULL;
23612433
batch->itemsvisibility = NULL; /* per-batch IOS visibility */
@@ -2396,3 +2468,76 @@ index_batch_unlock(Relation rel, bool dropPin, IndexScanBatch batch)
23962468
ReleaseBuffer(batch->buf);
23972469
batch->buf = InvalidBuffer; /* defensive */
23982470
}
2471+
2472+
/* add the buffer to the cache, or free it */
2473+
void
2474+
index_batch_release(IndexScanDesc scan, IndexScanBatch batch)
2475+
{
2476+
/*
2477+
* first free some allocations
2478+
*
2479+
* XXX We could keep/reuse some of those.
2480+
*/
2481+
2482+
if (batch->killedItems != NULL)
2483+
{
2484+
pfree(batch->killedItems);
2485+
batch->killedItems = NULL;
2486+
}
2487+
2488+
if (batch->itemsvisibility != NULL)
2489+
{
2490+
pfree(batch->itemsvisibility);
2491+
batch->itemsvisibility = NULL;
2492+
}
2493+
2494+
/* XXX a bit unclear what's release by AM vs. indexam */
2495+
Assert(batch->pos == NULL);
2496+
2497+
/*
2498+
* try adding it to the cache - finds a slot that's either empty or has a
2499+
* lower maxitems value (and replace that batch)
2500+
*
2501+
* XXX maybe we should track the number of empty slots, and minimum value
2502+
* of maxitems, so that we can skip pointless searches?
2503+
*
2504+
* XXX ignores cases with batchState=NULL (can we get here with bitmap
2505+
* scans?)
2506+
*/
2507+
if (scan->batchState != NULL)
2508+
{
2509+
/* lowest maxitems we found in the cache (to replace with batch) */
2510+
int maxitems = batch->maxitems;
2511+
int slot = scan->batchState->batchesCacheSize;
2512+
2513+
/* first time through, initialize the cache */
2514+
if (scan->batchState->batchesCache == NULL)
2515+
scan->batchState->batchesCache
2516+
= palloc0_array(IndexScanBatch,
2517+
scan->batchState->batchesCacheSize);
2518+
2519+
for (int i = 0; i < scan->batchState->batchesCacheSize; i++)
2520+
{
2521+
/* found empty slot, we're done */
2522+
if (scan->batchState->batchesCache[i] == NULL)
2523+
{
2524+
scan->batchState->batchesCache[i] = batch;
2525+
return;
2526+
}
2527+
2528+
/* update lowest maxitems? */
2529+
if (scan->batchState->batchesCache[i]->maxitems < maxitems)
2530+
{
2531+
maxitems = scan->batchState->batchesCache[i]->maxitems;
2532+
slot = i;
2533+
}
2534+
}
2535+
2536+
/* found a batch to replace? */
2537+
if (maxitems < batch->maxitems)
2538+
{
2539+
pfree(scan->batchState->batchesCache[slot]);
2540+
scan->batchState->batchesCache[slot] = batch;
2541+
}
2542+
}
2543+
}

src/backend/access/nbtree/nbtree.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -382,21 +382,22 @@ btfreebatch(IndexScanDesc scan, IndexScanBatch batch)
382382
if (batch->numKilled > 0)
383383
_bt_killitems(scan, batch);
384384

385-
if (batch->itemsvisibility)
386-
pfree(batch->itemsvisibility);
387-
388-
if (batch->currTuples)
389-
pfree(batch->currTuples);
390-
391385
if (batch->pos)
392386
{
393387
if (!scan->batchState || !scan->batchState->dropPin)
394388
ReleaseBuffer(batch->buf);
395389

396390
pfree(batch->pos);
391+
392+
/* XXX maybe should be done in index_batch_free? */
393+
batch->buf = InvalidBuffer;
394+
batch->pos = NULL;
397395
}
398396

399-
pfree(batch);
397+
/* XXX keep itemsvisibility, killItems and currTuples */
398+
399+
/* free the batch (or cache it for reuse) */
400+
index_batch_release(scan, batch);
400401
}
401402

402403
/*

src/backend/access/nbtree/nbtsearch.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,7 +1200,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
12001200
}
12011201

12021202
/* Allocate space for first batch */
1203-
firstbatch = index_batch_alloc(MaxTIDsPerBTreePage, scan->xs_want_itup);
1203+
firstbatch = index_batch_alloc(scan, MaxTIDsPerBTreePage, scan->xs_want_itup);
12041204
firstbatch->pos = palloc(sizeof(BTScanPosData));
12051205

12061206
/*
@@ -2237,7 +2237,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno,
22372237
BTScanPos newpos;
22382238

22392239
/* Allocate space for next batch */
2240-
newbatch = index_batch_alloc(MaxTIDsPerBTreePage, scan->xs_want_itup);
2240+
newbatch = index_batch_alloc(scan, MaxTIDsPerBTreePage, scan->xs_want_itup);
22412241
newbatch->pos = palloc(sizeof(BTScanPosData));
22422242
newpos = newbatch->pos;
22432243

src/include/access/genam.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@ extern void index_store_float8_orderby_distances(IndexScanDesc scan,
230230
bool recheckOrderBy);
231231
extern bytea *index_opclass_options(Relation indrel, AttrNumber attnum,
232232
Datum attoptions, bool validate);
233-
extern IndexScanBatch index_batch_alloc(int maxitems, bool want_itup);
233+
extern IndexScanBatch index_batch_alloc(IndexScanDesc scan, int maxitems, bool want_itup);
234+
extern void index_batch_release(IndexScanDesc scan, IndexScanBatch batch);
234235
extern void index_batch_unlock(Relation rel, bool dropPin, IndexScanBatch batch);
235236

236237

src/include/access/relscan.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ typedef struct IndexScanBatchData
187187
*/
188188
char *itemsvisibility; /* Index-only scan visibility cache */
189189

190+
int maxitems;
190191
IndexScanBatchPosItem items[FLEXIBLE_ARRAY_MEMBER];
191192
} IndexScanBatchData;
192193

@@ -266,6 +267,10 @@ typedef struct IndexScanBatchState
266267
int headBatch; /* head batch slot */
267268
int nextBatch; /* next empty batch slot */
268269

270+
/* small cache of unused batches, to reduce malloc/free traffic */
271+
int batchesCacheSize;
272+
IndexScanBatchData **batchesCache;
273+
269274
IndexScanBatchData **batches;
270275

271276
/* callback to skip prefetching in IOS etc. */

0 commit comments

Comments
 (0)