1515 */
1616#include "postgres.h"
1717
18+ #include "funcapi.h"
19+ #include "utils/tuplestore.h"
20+ #include "access/relation.h"
21+ #include "common/pg_prng.h"
22+ #include "utils/rel.h"
1823#include "pgstat.h"
1924#include "port/atomics.h"
2025#include "storage/buf_internals.h"
@@ -64,6 +69,25 @@ typedef struct
6469 * StrategyNotifyBgWriter.
6570 */
6671 int bgwprocno ;
72+
73+ /*
74+ * EXPERIMENT INSTRUMENT (throwaway, never posted): clock-sweep work
75+ * counters + per-relation eviction counts. sweepTicks/sweepVictims give
76+ * ticks-per-victim (the 0..5-grind metric); evictRel/evictCnt classify the
77+ * eviction stream by relation offline. Exposed via bcs_sweepstats() and
78+ * bcs_evictions().
79+ */
80+ #define BCS_EVICT_SLOTS 8192
81+ pg_atomic_uint64 sweepTicks ;
82+ pg_atomic_uint64 sweepVictims ;
83+ pg_atomic_uint64 evictRel [BCS_EVICT_SLOTS ];
84+ pg_atomic_uint64 evictCnt [BCS_EVICT_SLOTS ];
85+ /* ghost observer (measurement only) */
86+ #define BCS_GHOST_SLOTS (1<<20)
87+ pg_atomic_uint64 ghostReads ;
88+ pg_atomic_uint64 ghostReloads ;
89+ pg_atomic_uint64 ghostReloadsCold ;
90+ pg_atomic_uint32 ghostTag [BCS_GHOST_SLOTS ]; /* low 31 bits of taghash, bit31=cold */
6791} BufferStrategyControl ;
6892
6993/* Pointers to shared state */
@@ -204,6 +228,8 @@ ClockSweepTick(void)
204228 victim = MyBatchPos % NBuffers ;
205229 MyBatchPos ++ ;
206230
231+ pg_atomic_fetch_add_u64 (& StrategyControl -> sweepTicks , 1 ); /* EXPERIMENT */
232+
207233 return victim ;
208234}
209235
@@ -366,6 +392,8 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
366392 AddBufferToRing (strategy , buf );
367393 * buf_state = local_buf_state ;
368394
395+ pg_atomic_fetch_add_u64 (& StrategyControl -> sweepVictims , 1 ); /* EXPERIMENT */
396+
369397 TrackNewBufferPin (BufferDescriptorGetBuffer (buf ));
370398
371399 return buf ;
@@ -477,6 +505,15 @@ StrategyCtlShmemInit(void *arg)
477505 /* No pending notification */
478506 StrategyControl -> bgwprocno = -1 ;
479507
508+ /* EXPERIMENT: instrument counters */
509+ pg_atomic_init_u64 (& StrategyControl -> sweepTicks , 0 );
510+ pg_atomic_init_u64 (& StrategyControl -> sweepVictims , 0 );
511+ for (int i = 0 ; i < BCS_EVICT_SLOTS ; i ++ )
512+ {
513+ pg_atomic_init_u64 (& StrategyControl -> evictRel [i ], 0 );
514+ pg_atomic_init_u64 (& StrategyControl -> evictCnt [i ], 0 );
515+ }
516+
480517 /*
481518 * Decide whether to batch the clock sweep.
482519 *
@@ -499,6 +536,17 @@ StrategyCtlShmemInit(void *arg)
499536 (uint32 ) NBuffers );
500537 else
501538 StrategyControl -> batchSize = 1 ;
539+
540+ /*
541+ * EXPERIMENT (throwaway): allow forcing the batch size via env, so a
542+ * single binary can be run at batch 1 / 16 / 64 to isolate the NUMA
543+ * batching effect. Not part of the proposed patch.
544+ */
545+ {
546+ const char * ov = getenv ("BCS_BATCH_OVERRIDE" );
547+ if (ov != NULL && atoi (ov ) >= 1 )
548+ StrategyControl -> batchSize = Min ((uint32 ) atoi (ov ), (uint32 ) NBuffers );
549+ }
502550}
503551
504552
@@ -862,3 +910,143 @@ StrategyRejectBuffer(BufferAccessStrategy strategy, BufferDesc *buf, bool from_r
862910
863911 return true;
864912}
913+
914+ /* EXPERIMENT INSTRUMENT (throwaway, never posted). */
915+ void
916+ BcsRecordEviction (RelFileNumber relNumber )
917+ {
918+ uint32 h = ((uint32 ) relNumber ) % BCS_EVICT_SLOTS ;
919+
920+ for (int probe = 0 ; probe < 64 ; probe ++ )
921+ {
922+ uint32 i = (h + probe ) % BCS_EVICT_SLOTS ;
923+ uint64 cur = pg_atomic_read_u64 (& StrategyControl -> evictRel [i ]);
924+
925+ if (cur == (uint64 ) relNumber )
926+ { pg_atomic_fetch_add_u64 (& StrategyControl -> evictCnt [i ], 1 ); return ; }
927+ if (cur == 0 )
928+ {
929+ uint64 expected = 0 ;
930+ if (pg_atomic_compare_exchange_u64 (& StrategyControl -> evictRel [i ], & expected , (uint64 ) relNumber )
931+ || pg_atomic_read_u64 (& StrategyControl -> evictRel [i ]) == (uint64 ) relNumber )
932+ { pg_atomic_fetch_add_u64 (& StrategyControl -> evictCnt [i ], 1 ); return ; }
933+ }
934+ }
935+ pg_atomic_fetch_add_u64 (& StrategyControl -> evictCnt [0 ], 1 );
936+ }
937+
938+ PG_FUNCTION_INFO_V1 (bcs_sweepstats );
939+ Datum
940+ bcs_sweepstats (PG_FUNCTION_ARGS )
941+ {
942+ ReturnSetInfo * rsi = (ReturnSetInfo * ) fcinfo -> resultinfo ;
943+ Datum values [2 ];
944+ bool nulls [2 ] = {false, false};
945+
946+ InitMaterializedSRF (fcinfo , 0 );
947+ values [0 ] = Int64GetDatum ((int64 ) pg_atomic_read_u64 (& StrategyControl -> sweepTicks ));
948+ values [1 ] = Int64GetDatum ((int64 ) pg_atomic_read_u64 (& StrategyControl -> sweepVictims ));
949+ tuplestore_putvalues (rsi -> setResult , rsi -> setDesc , values , nulls );
950+ return (Datum ) 0 ;
951+ }
952+
953+ PG_FUNCTION_INFO_V1 (bcs_evictions );
954+ Datum
955+ bcs_evictions (PG_FUNCTION_ARGS )
956+ {
957+ ReturnSetInfo * rsi = (ReturnSetInfo * ) fcinfo -> resultinfo ;
958+
959+ InitMaterializedSRF (fcinfo , 0 );
960+ for (int i = 0 ; i < BCS_EVICT_SLOTS ; i ++ )
961+ {
962+ uint64 rel = pg_atomic_read_u64 (& StrategyControl -> evictRel [i ]);
963+ uint64 cnt = pg_atomic_read_u64 (& StrategyControl -> evictCnt [i ]);
964+ Datum values [2 ];
965+ bool nulls [2 ] = {false, false};
966+
967+ if (cnt == 0 ) continue ;
968+ values [0 ] = Int64GetDatum ((int64 ) rel );
969+ values [1 ] = Int64GetDatum ((int64 ) cnt );
970+ tuplestore_putvalues (rsi -> setResult , rsi -> setDesc , values , nulls );
971+ }
972+ return (Datum ) 0 ;
973+ }
974+
975+ /*
976+ * EXPERIMENT INSTRUMENT (throwaway, never posted).
977+ * bcs_thrash(rel, n): pin+release n random MAIN_FORKNUM blocks of rel with the
978+ * normal (global-sweep) strategy, forcing n buffer allocations with almost no
979+ * other work -- so StrategyGetBuffer / nextVictimBuffer dominates the cost.
980+ * Isolates the clock-sweep mechanism from executor/protocol overhead.
981+ */
982+ PG_FUNCTION_INFO_V1 (bcs_thrash );
983+ Datum
984+ bcs_thrash (PG_FUNCTION_ARGS )
985+ {
986+ Oid relid = PG_GETARG_OID (0 );
987+ int64 n = PG_GETARG_INT64 (1 );
988+ Relation rel = relation_open (relid , AccessShareLock );
989+ BlockNumber nblocks = RelationGetNumberOfBlocks (rel );
990+ uint64 sum = 0 ;
991+ pg_prng_state prng ;
992+
993+ pg_prng_seed (& prng , (uint64 ) MyProcPid ^ (uint64 ) n );
994+ for (int64 i = 0 ; i < n && nblocks > 0 ; i ++ )
995+ {
996+ BlockNumber blk = (BlockNumber ) (pg_prng_uint64 (& prng ) % nblocks );
997+ Buffer buf = ReadBufferExtended (rel , MAIN_FORKNUM , blk ,
998+ RBM_NORMAL , NULL );
999+
1000+ sum += blk ;
1001+ ReleaseBuffer (buf );
1002+ CHECK_FOR_INTERRUPTS ();
1003+ }
1004+ relation_close (rel , AccessShareLock );
1005+ PG_RETURN_INT64 ((int64 ) sum );
1006+ }
1007+
1008+
1009+ /* ---- ghost observer (measurement only, never posted) ---- */
1010+ /* Record that a block (identified by its buffer tag hash) was evicted, and
1011+ * whether it was cold at eviction. Stores low 31 bits of hash + cold bit. */
1012+ void
1013+ BcsGhostRecordEviction (uint32 taghash , bool cold )
1014+ {
1015+ uint32 slot = taghash & (BCS_GHOST_SLOTS - 1 );
1016+ uint32 val = (taghash & 0x7fffffff ) | (cold ? 0x80000000u : 0 );
1017+ pg_atomic_write_u32 (& StrategyControl -> ghostTag [slot ], val );
1018+ }
1019+
1020+ /* On a real read/alloc of a block, probe the ghost table: was this exact block
1021+ * recently evicted? If so it's a reload; note if it was cold when evicted. */
1022+ void
1023+ BcsGhostProbeRead (uint32 taghash )
1024+ {
1025+ uint32 slot = taghash & (BCS_GHOST_SLOTS - 1 );
1026+ uint32 val = pg_atomic_read_u32 (& StrategyControl -> ghostTag [slot ]);
1027+ pg_atomic_fetch_add_u64 (& StrategyControl -> ghostReads , 1 );
1028+ if ((val & 0x7fffffff ) == (taghash & 0x7fffffff ) && val != 0 )
1029+ {
1030+ pg_atomic_fetch_add_u64 (& StrategyControl -> ghostReloads , 1 );
1031+ if (val & 0x80000000u )
1032+ pg_atomic_fetch_add_u64 (& StrategyControl -> ghostReloadsCold , 1 );
1033+ /* clear so we don't double-count the same resurrection */
1034+ pg_atomic_write_u32 (& StrategyControl -> ghostTag [slot ], 0 );
1035+ }
1036+ }
1037+
1038+ PG_FUNCTION_INFO_V1 (bcs_ghost );
1039+ Datum
1040+ bcs_ghost (PG_FUNCTION_ARGS )
1041+ {
1042+ ReturnSetInfo * rsi = (ReturnSetInfo * ) fcinfo -> resultinfo ;
1043+ Datum v [3 ];
1044+ bool n [3 ] = {false, false, false};
1045+
1046+ InitMaterializedSRF (fcinfo , 0 );
1047+ v [0 ] = Int64GetDatum ((int64 ) pg_atomic_read_u64 (& StrategyControl -> ghostReads ));
1048+ v [1 ] = Int64GetDatum ((int64 ) pg_atomic_read_u64 (& StrategyControl -> ghostReloads ));
1049+ v [2 ] = Int64GetDatum ((int64 ) pg_atomic_read_u64 (& StrategyControl -> ghostReloadsCold ));
1050+ tuplestore_putvalues (rsi -> setResult , rsi -> setDesc , v , n );
1051+ return (Datum ) 0 ;
1052+ }
0 commit comments