@@ -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
23282332index_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 */
23392365IndexScanBatch
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+ }
0 commit comments