>From 569398929d899100b769abfd919bc3383626ac9f Mon Sep 17 00:00:00 2001
From: Daniel Bausch <bausch@dvs.tu-darmstadt.de>
Date: Tue, 22 Oct 2013 15:22:25 +0200
Subject: [PATCH 1/4] Quick proof-of-concept for indexscan prefetching

This implements a prefetching queue of tuples whose tid is read ahead.
Their block number is quickly checked for random properties (not current
block and not the block prefetched last).  Random reads are prefetched.
Up to 32 tuples are considered by default.  The tids are queued in a
fixed ring buffer.

The prefetching is implemented in the generic part of the index scan, so
it applies to all access methods.
---
 src/backend/access/index/indexam.c | 96 ++++++++++++++++++++++++++++++++++++++
 src/include/access/relscan.h       | 12 +++++
 2 files changed, 108 insertions(+)

diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index b878155..1c54ef5 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -251,6 +251,12 @@ index_beginscan(Relation heapRelation,
 	scan->heapRelation = heapRelation;
 	scan->xs_snapshot = snapshot;
 
+#ifdef USE_PREFETCH
+	scan->xs_prefetch_head = scan->xs_prefetch_tail = -1;
+	scan->xs_last_prefetch = -1;
+	scan->xs_done = false;
+#endif
+
 	return scan;
 }
 
@@ -432,6 +438,55 @@ index_restrpos(IndexScanDesc scan)
 	FunctionCall1(procedure, PointerGetDatum(scan));
 }
 
+static int
+index_prefetch_queue_space(IndexScanDesc scan)
+{
+	if (scan->xs_prefetch_tail < 0)
+		return INDEXSCAN_PREFETCH_COUNT;
+
+	Assert(scan->xs_prefetch_head >= 0);
+
+	return (INDEXSCAN_PREFETCH_COUNT
+			- (scan->xs_prefetch_tail - scan->xs_prefetch_head + 1))
+		% INDEXSCAN_PREFETCH_COUNT;
+}
+
+/* makes copy of ItemPointerData */
+static bool
+index_prefetch_queue_push(IndexScanDesc scan, ItemPointer tid)
+{
+	Assert(index_prefetch_queue_space(scan) > 0);
+
+	if (scan->xs_prefetch_tail == -1)
+		scan->xs_prefetch_head = scan->xs_prefetch_tail = 0;
+	else
+		scan->xs_prefetch_tail =
+			(scan->xs_prefetch_tail + 1) % INDEXSCAN_PREFETCH_COUNT;
+
+	scan->xs_prefetch_queue[scan->xs_prefetch_tail] = *tid;
+
+	return true;
+}
+
+static ItemPointer
+index_prefetch_queue_pop(IndexScanDesc scan)
+{
+	ItemPointer res;
+
+	if (scan->xs_prefetch_head < 0)
+		return NULL;
+
+	res = &scan->xs_prefetch_queue[scan->xs_prefetch_head];
+
+	if (scan->xs_prefetch_head == scan->xs_prefetch_tail)
+		scan->xs_prefetch_head = scan->xs_prefetch_tail = -1;
+	else
+		scan->xs_prefetch_head =
+			(scan->xs_prefetch_head + 1) % INDEXSCAN_PREFETCH_COUNT;
+
+	return res;
+}
+
 /* ----------------
  * index_getnext_tid - get the next TID from a scan
  *
@@ -444,12 +499,52 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction)
 {
 	FmgrInfo   *procedure;
 	bool		found;
+	ItemPointer	from_queue;
+	BlockNumber	pf_block;
 
 	SCAN_CHECKS;
 	GET_SCAN_PROCEDURE(amgettuple);
 
 	Assert(TransactionIdIsValid(RecentGlobalXmin));
 
+#ifdef USE_PREFETCH
+	while (!scan->xs_done && index_prefetch_queue_space(scan) > 0) {
+		/*
+		 * The AM's amgettuple proc finds the next index entry matching the
+		 * scan keys, and puts the TID into scan->xs_ctup.t_self.  It should
+		 * also set scan->xs_recheck and possibly scan->xs_itup, though we pay
+		 * no attention to those fields here.
+		 */
+		found = DatumGetBool(FunctionCall2(procedure,
+										   PointerGetDatum(scan),
+										   Int32GetDatum(direction)));
+		if (found)
+		{
+			index_prefetch_queue_push(scan, &scan->xs_ctup.t_self);
+			pf_block = ItemPointerGetBlockNumber(&scan->xs_ctup.t_self);
+			/* prefetch only if not the current buffer and not exactly the
+			 * previously prefetched buffer (heuristic random detection)
+			 * because sequential read-ahead would be redundant */
+			if ((!BufferIsValid(scan->xs_cbuf) ||
+				 pf_block != BufferGetBlockNumber(scan->xs_cbuf)) &&
+				pf_block != scan->xs_last_prefetch)
+			{
+				PrefetchBuffer(scan->heapRelation, MAIN_FORKNUM, pf_block);
+				scan->xs_last_prefetch = pf_block;
+			}
+		}
+		else
+			scan->xs_done = true;
+	}
+	from_queue = index_prefetch_queue_pop(scan);
+	if (from_queue)
+	{
+		scan->xs_ctup.t_self = *from_queue;
+		found = true;
+	}
+	else
+		found = false;
+#else
 	/*
 	 * The AM's amgettuple proc finds the next index entry matching the scan
 	 * keys, and puts the TID into scan->xs_ctup.t_self.  It should also set
@@ -459,6 +554,7 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction)
 	found = DatumGetBool(FunctionCall2(procedure,
 									   PointerGetDatum(scan),
 									   Int32GetDatum(direction)));
+#endif
 
 	/* Reset kill flag immediately for safety */
 	scan->kill_prior_tuple = false;
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 3a86ca4..bccc1a4 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -93,6 +93,18 @@ typedef struct IndexScanDescData
 
 	/* state data for traversing HOT chains in index_getnext */
 	bool		xs_continue_hot;	/* T if must keep walking HOT chain */
+
+#ifdef USE_PREFETCH
+# ifndef INDEXSCAN_PREFETCH_COUNT
+#  define INDEXSCAN_PREFETCH_COUNT 32
+# endif
+	/* prefetch queue - ringbuffer */
+	ItemPointerData xs_prefetch_queue[INDEXSCAN_PREFETCH_COUNT];
+	int			xs_prefetch_head;
+	int			xs_prefetch_tail;
+	BlockNumber	xs_last_prefetch;
+	bool		xs_done;
+#endif
 }	IndexScanDescData;
 
 /* Struct for heap-or-index scans of system tables */
-- 
2.0.5

