libfoedus-core
FOEDUS Core Library
foedus::cache::CacheHashtable Class Referencefinal

A NUMA-local hashtable of cached snapshot pages. More...

Detailed Description

A NUMA-local hashtable of cached snapshot pages.

Structure Overview
Each key (SnapshotPagePointer) has its own best position calculated from its hash value. If the key exists in this table, it is either in some bucket between the best position (including) and best position + kHopNeighbors (excluding) or in the overflow linked list. Simple, stupid, thus fast.
Modularity for Testability
Yes, we love them. Classes in this file are totally orthogonal to the actual page pool and other stuffs in the engine. This class only handles content (eg offset in page pool) as the data linked to the key. It's left to the caller on how the content is created, consumed, or reclaimed so that we can test/debug/tune the classes easily. In fact, test_hash_table.cpp (the testcase for this class) doesn't even instantiate an engine.
Hash Function in cache table
We have a fixed type of key, SnapshotPagePointer. For the fastest calculation of the hash we simply divide by the size of hashtable, which we adjust to be almost a prime number (in many cases actually a prime). We use assorted::ConstDiv to speed up the division.
Some history on choice of algorithm
We were initially based on Herlihy's Hopscotch, but we heavily departed from it to exploit our loose requirements for eliminitating all locks and atomic operations. The only Currently, we even don't do the bucket migration in hopscotch, so it's no longer correct to call this a hop-scotch. Instead, we use an overflow linked list, which should be almost always empty or close-to-empty.

Definition at line 233 of file cache_hashtable.hpp.

#include <cache_hashtable.hpp>

Collaboration diagram for foedus::cache::CacheHashtable:

Classes

struct  EvictArgs
 Parameters for evict() More...
 
struct  Stat
 

Public Types

enum  Constants { kMaxFindBatchSize = 32 }
 

Public Member Functions

 CacheHashtable (BucketId physical_buckets, uint16_t numa_node)
 
ContentId find (storage::SnapshotPagePointer page_id) const __attribute__((always_inline))
 Returns an offset for the given page ID opportunistically. More...
 
ErrorCode find_batch (uint16_t batch_size, const storage::SnapshotPagePointer *page_ids, ContentId *out) const
 Batched version of find(). More...
 
ErrorCode install (storage::SnapshotPagePointer page_id, ContentId content)
 Called when a cached page is not found. More...
 
void evict (EvictArgs *args)
 Evict some entries from the hashtable. More...
 
BucketId get_logical_buckets () const __attribute__((always_inline))
 
BucketId get_physical_buckets () const __attribute__((always_inline))
 
BucketId get_bucket_number (storage::SnapshotPagePointer page_id) const __attribute__((always_inline))
 Returns a bucket number the given page ID should belong to. More...
 
ErrorStack verify_single_thread () const
 only for debugging. More...
 
const CacheBucketget_bucket (BucketId bucket_id) const
 
Stat get_stat_single_thread () const
 only for debugging. More...
 

Protected Member Functions

BucketId evict_main_loop (EvictArgs *args, BucketId cur, uint16_t loop)
 
void evict_overflow_loop (EvictArgs *args, uint16_t loop)
 

Protected Attributes

const uint16_t numa_node_
 
const uint32_t overflow_buckets_count_
 
const HashFunc hash_func_
 
memory::AlignedMemory buckets_memory_
 
memory::AlignedMemory refcounts_memory_
 
memory::AlignedMemory overflow_buckets_memory_
 
CacheBucketbuckets_
 
CacheRefCountrefcounts_
 
CacheOverflowEntryoverflow_buckets_
 
OverflowPointer overflow_buckets_head_
 This forms a singly-linked list of active overflow entries. More...
 
OverflowPointer overflow_free_buckets_head_
 This forms another singly-linked list of free overflow entries. More...
 
soc::SharedMutex overflow_free_buckets_mutex_
 The mutex to protect free overflow entries. More...
 
BucketId clockhand_
 We previously stopped eviction here for usual buckets. More...
 

Friends

std::ostream & operator<< (std::ostream &o, const CacheHashtable &v)
 

Member Enumeration Documentation

Enumerator
kMaxFindBatchSize 

Max size for find_batch()

Definition at line 235 of file cache_hashtable.hpp.

235  {
237  kMaxFindBatchSize = 32,
238  };

Constructor & Destructor Documentation

foedus::cache::CacheHashtable::CacheHashtable ( BucketId  physical_buckets,
uint16_t  numa_node 
)

Definition at line 66 of file cache_hashtable.cpp.

References foedus::memory::AlignedMemory::alloc(), foedus::cache::CacheOverflowEntry::bucket_, buckets_, buckets_memory_, foedus::cache::CacheRefCount::count_, foedus::memory::AlignedMemory::get_block(), foedus::memory::AlignedMemory::kNumaAllocOnnode, foedus::cache::CacheOverflowEntry::next_, overflow_buckets_, overflow_buckets_count_, overflow_buckets_head_, overflow_buckets_memory_, overflow_free_buckets_head_, foedus::cache::CacheOverflowEntry::padding_, foedus::cache::CacheOverflowEntry::refcount_, refcounts_, refcounts_memory_, and foedus::cache::CacheBucket::reset().

67  : numa_node_(numa_node),
69  hash_func_(physical_buckets),
70  clockhand_(0) {
72  sizeof(CacheBucket) * physical_buckets,
73  1U << 21,
75  numa_node);
77  sizeof(CacheRefCount) * physical_buckets,
78  1U << 21,
80  numa_node);
81  buckets_ = reinterpret_cast<CacheBucket*>(buckets_memory_.get_block());
82  refcounts_ = reinterpret_cast<CacheRefCount*>(refcounts_memory_.get_block());
83 
84  // index-0 should be never used. 0 means null.
85  buckets_[0].reset();
86  refcounts_[0].count_ = 0;
87 
88  // for overflow list
90  sizeof(CacheOverflowEntry) * overflow_buckets_count_,
91  1U << 21,
93  numa_node);
94  overflow_buckets_ = reinterpret_cast<CacheOverflowEntry*>(overflow_buckets_memory_.get_block());
96  // index-0 should be never used. 0 means null.
98  overflow_buckets_[0].next_ = 0;
101 
102  // initially all entries are in free list.
104  for (OverflowPointer i = 1U; i < overflow_buckets_count_; ++i) {
105  if (i < overflow_buckets_count_ - 1U) {
106  overflow_buckets_[i].next_ = i + 1U;
107  } else {
108  overflow_buckets_[i].next_ = 0;
109  }
110  }
111 }
numa_alloc_onnode() and numa_free().
BucketId clockhand_
We previously stopped eviction here for usual buckets.
CacheOverflowEntry * overflow_buckets_
memory::AlignedMemory buckets_memory_
memory::AlignedMemory refcounts_memory_
uint32_t OverflowPointer
Position in the overflow buckets.
void alloc(uint64_t size, uint64_t alignment, AllocType alloc_type, int numa_node) noexcept
Allocate a memory, releasing the current memory if exists.
uint32_t determine_overflow_list_size(BucketId physical_buckets)
OverflowPointer overflow_free_buckets_head_
This forms another singly-linked list of free overflow entries.
memory::AlignedMemory overflow_buckets_memory_
void * get_block() const
Returns the memory block.
void reset(ContentId id=0, PageIdTag tag=0)
OverflowPointer next_
Note that we don't have to atomically maintain/follow this pointer thanks to the loose requirements...
OverflowPointer overflow_buckets_head_
This forms a singly-linked list of active overflow entries.

Here is the call graph for this function:

Member Function Documentation

void foedus::cache::CacheHashtable::evict ( CacheHashtable::EvictArgs args)

Evict some entries from the hashtable.

Compared to traditional bufferpools, this is much simpler and more scalable thanks to the loose requirements and epoch-based reclamation of the evicted pages. This method only evicts the hashtable entries, so reclaiming the pages pointed from the entries is done by the caller.

Definition at line 171 of file cache_hashtable.cpp.

References ASSERT_ND, clockhand_, evict_main_loop(), evict_overflow_loop(), foedus::cache::CacheHashtable::EvictArgs::evicted_count_, get_physical_buckets(), numa_node_, overflow_buckets_head_, and foedus::cache::CacheHashtable::EvictArgs::target_count_.

Referenced by foedus::cache::CacheManagerPimpl::handle_cleaner_evict_pages().

171  {
172  LOG(INFO) << "Snapshot-Cache eviction starts at node-" << numa_node_
173  << ", clockhand_=" << clockhand_ << ", #target=" << args->target_count_;
174  const BucketId end = get_physical_buckets();
175  BucketId cur = clockhand_;
176 
177  // we check each entry in refcounts_, which are 2 bytes each.
178  // for quicker checks, we want 8-byte aligned access.
179  // also, we should anyway do prefetch, so make it 64-byte aligned
180  cur = (cur >> 5) << 5;
181  ASSERT_ND(cur % (1U << 5) == 0);
182  if (cur >= end) {
183  cur = 0;
184  }
185 
186  // evict on the normal buckets first.
187  args->evicted_count_ = 0;
188  uint16_t loops;
189  const uint16_t kMaxLoops = 16; // if we need more loops than this, something is wrong...
190  for (loops = 0; loops < kMaxLoops; ++loops) {
191  cur = evict_main_loop(args, cur, loops);
192  if (cur >= get_physical_buckets()) {
193  cur = 0;
194  // we went over all buckets in usual entries. now check the overflow linked list.
196  evict_overflow_loop(args, loops);
197  }
198  }
199  if (args->evicted_count_ >= args->target_count_) {
200  break;
201  } else {
202  ASSERT_ND(cur == 0); // we checked all buckets and wrapped around, right? go on to next loop
203  }
204  }
205 
206  clockhand_ = cur;
207  LOG(INFO) << "Snapshot-Cache eviction completed at node-" << numa_node_
208  << ", clockhand_=" << clockhand_ << ", #evicted=" << args->evicted_count_
209  << ", looped-over the whole hashtable for " << loops << " times";
210 }
void evict_overflow_loop(EvictArgs *args, uint16_t loop)
BucketId clockhand_
We previously stopped eviction here for usual buckets.
uint32_t BucketId
Offset in hashtable bucket.
BucketId get_physical_buckets() const __attribute__((always_inline))
BucketId evict_main_loop(EvictArgs *args, BucketId cur, uint16_t loop)
#define ASSERT_ND(x)
A warning-free wrapper macro of assert() that has no performance effect in release mode even when 'x'...
Definition: assert_nd.hpp:72
OverflowPointer overflow_buckets_head_
This forms a singly-linked list of active overflow entries.

Here is the call graph for this function:

Here is the caller graph for this function:

BucketId foedus::cache::CacheHashtable::evict_main_loop ( CacheHashtable::EvictArgs args,
BucketId  cur,
uint16_t  loop 
)
protected

Definition at line 212 of file cache_hashtable.cpp.

References foedus::cache::CacheHashtable::EvictArgs::add_evicted(), ASSERT_ND, ASSUME_ALIGNED, buckets_, clockhand_, foedus::cache::CacheBucket::data_, foedus::cache::CacheRefCount::decrement(), foedus::debugging::StopWatch::elapsed_us(), foedus::cache::CacheHashtable::EvictArgs::evicted_count_, get_physical_buckets(), foedus::assorted::kCachelineSize, LIKELY, numa_node_, foedus::assorted::prefetch_cachelines(), refcounts_, foedus::debugging::StopWatch::stop(), and foedus::cache::CacheHashtable::EvictArgs::target_count_.

Referenced by evict().

215  {
216  ASSERT_ND(cur % (1U << 5) == 0);
217  ASSERT_ND((assorted::kCachelineSize >> 5) == sizeof(CacheRefCount));
218  const uint16_t decrements = 1U << loop;
219  debugging::StopWatch watch;
220 
221  // the main idea is as follows.
222  // whenever the bucket has never been used or been used but released without unlucky races,
223  // the corresponding refcount is zero. hence, we just check for non-zeros in refcounts_.
224  // this is trivially vectorized and the only observable cost is L1 cache miss.
225  // we reduce L1 cache miss cost by prefetching a lot.
226  uint32_t cur_cacheline = cur >> 5;
227  const uint32_t end_cacheline = (get_physical_buckets() >> 5) + 1ULL;
228  // for example, we prefetch cacheline 16-23 while reading cacheline 0-7.
229  const uint16_t kL1PrefetchBatch = 8;
230  const uint16_t kL1PrefetchAhead = 16;
231  for (; cur_cacheline < end_cacheline; ++cur_cacheline) {
232  if (cur_cacheline / kL1PrefetchBatch == 0) {
234  refcounts_ + ((cur_cacheline + kL1PrefetchAhead) << 5),
235  kL1PrefetchBatch);
236  }
237 
238  BucketId bucket = cur_cacheline << 5;
239  // gcc, you should be smart enough to optimize this. at least with O3.
240  uint64_t* ints = reinterpret_cast<uint64_t*>(ASSUME_ALIGNED(refcounts_ + bucket, 64));
241  bool all_zeros = true;
242  for (uint16_t i = 0; i < 8U; ++i) {
243  if (ints[i] != 0) {
244  all_zeros = false;
245  break;
246  }
247  }
248 
249  if (LIKELY(all_zeros)) {
250  continue;
251  } else {
252  // this should be a rare case as far as we keep the hashtable sparse.
253  CacheRefCount* base = reinterpret_cast<CacheRefCount*>(refcounts_ + bucket);
254  for (uint16_t i = 0; i < 32U; ++i) {
255  if (base[i].count_ > 0) {
256  bool still_non_zero = base[i].decrement(decrements);
257  if (!still_non_zero) {
258  args->add_evicted(buckets_[bucket + i].get_content_id());
259  buckets_[bucket + i].data_ = 0;
260  }
261  }
262  }
263  }
264 
265  if (args->evicted_count_ >= args->target_count_) {
266  break;
267  }
268  }
269 
270  watch.stop();
271  LOG(INFO) << "Snapshot-Cache eviction main_loop at node-" << numa_node_ << ", checked "
272  << ((cur_cacheline << 5) - clockhand_) << " buckets in " << watch.elapsed_us() << "us";
273 
274  return cur_cacheline << 5;
275 }
BucketId clockhand_
We previously stopped eviction here for usual buckets.
uint32_t BucketId
Offset in hashtable bucket.
BucketId get_physical_buckets() const __attribute__((always_inline))
void prefetch_cachelines(const void *address, int cacheline_count)
Prefetch multiple contiguous cachelines to L1 cache.
Definition: cacheline.hpp:66
#define ASSUME_ALIGNED(x, y)
Wraps GCC's __builtin_assume_aligned.
Definition: compiler.hpp:111
#define LIKELY(x)
Hints that x is highly likely true.
Definition: compiler.hpp:103
const uint16_t kCachelineSize
Byte count of one cache line.
Definition: cacheline.hpp:42
#define ASSERT_ND(x)
A warning-free wrapper macro of assert() that has no performance effect in release mode even when 'x'...
Definition: assert_nd.hpp:72

Here is the call graph for this function:

Here is the caller graph for this function:

void foedus::cache::CacheHashtable::evict_overflow_loop ( CacheHashtable::EvictArgs args,
uint16_t  loop 
)
protected

Definition at line 277 of file cache_hashtable.cpp.

References foedus::cache::CacheHashtable::EvictArgs::add_evicted(), foedus::cache::CacheOverflowEntry::bucket_, foedus::cache::CacheBucket::data_, foedus::cache::CacheRefCount::decrement(), foedus::debugging::StopWatch::elapsed_us(), foedus::cache::CacheBucket::get_content_id(), foedus::cache::CacheOverflowEntry::next_, numa_node_, overflow_buckets_, overflow_buckets_head_, overflow_free_buckets_mutex_, foedus::cache::CacheOverflowEntry::refcount_, and foedus::debugging::StopWatch::stop().

Referenced by evict().

277  {
278  const uint16_t decrements = 1U << loop;
279  uint32_t checked_count = 0;
280 
281  // store evicted entries into
282  OverflowPointer evicted_head = 0; // evicted
283  debugging::StopWatch watch;
284  {
285  // We block this method entirely with the free buckets mutex.
286  // This does NOT block usual transactions unless they actually have to newly add to overflow,
287  // which should be very rare. This is cheap yet enough to make the free-list safe.
288  soc::SharedMutexScope scope(&overflow_free_buckets_mutex_);
289 
290  // no interesting optimization. overflow list should be empty or almost empty.
292  if (head != 0) {
293  // skip the head. we handle it at the last.
294  OverflowPointer prev = head;
295  for (OverflowPointer cur = overflow_buckets_[prev].next_; cur != 0;) {
296  CacheOverflowEntry* cur_entry = overflow_buckets_ + cur;
297  OverflowPointer next = cur_entry->next_;
298  bool still_non_zero = cur_entry->refcount_.decrement(decrements);
299  if (!still_non_zero) {
300  args->add_evicted(cur_entry->bucket_.get_content_id());
301  CacheOverflowEntry* prev_entry = overflow_buckets_ + prev;
302  prev_entry->next_ = next;
303  cur_entry->bucket_.data_ = 0;
304  cur_entry->next_ = evicted_head;
305  evicted_head = cur;
306  }
307 
308  prev = cur;
309  cur = next;
310  ++checked_count;
311  }
312 
313  // finally check the head
314  CacheOverflowEntry* cur_entry = overflow_buckets_ + head;
315  bool still_non_zero = cur_entry->refcount_.decrement(decrements);
316  if (!still_non_zero) {
317  args->add_evicted(cur_entry->bucket_.get_content_id());
318  overflow_buckets_head_ = cur_entry->next_;
319  cur_entry->bucket_.data_ = 0;
320  cur_entry->next_ = evicted_head;
321  evicted_head = head;
322  }
323  ++checked_count;
324  }
325  }
326  watch.stop();
327  LOG(INFO) << "Snapshot-Cache eviction overflow_loop at node-" << numa_node_ << ", checked "
328  << (checked_count) << " buckets in " << watch.elapsed_us() << "us";
329 }
soc::SharedMutex overflow_free_buckets_mutex_
The mutex to protect free overflow entries.
CacheOverflowEntry * overflow_buckets_
uint32_t OverflowPointer
Position in the overflow buckets.
bool decrement(uint16_t subtract) __attribute__((always_inline))
returns whether the counter is still non-zero
OverflowPointer next_
Note that we don't have to atomically maintain/follow this pointer thanks to the loose requirements...
OverflowPointer overflow_buckets_head_
This forms a singly-linked list of active overflow entries.

Here is the call graph for this function:

Here is the caller graph for this function:

ContentId foedus::cache::CacheHashtable::find ( storage::SnapshotPagePointer  page_id) const
inline

Returns an offset for the given page ID opportunistically.

Parameters
[in]page_idPage ID to look for
Returns
offset that contains the page. 0 if not found.

This doesn't take a lock, so a concurrent thread might have inserted the wanted page concurrrently. It's fine. Then we read the page from snapshot, just wasting a bit. Instead, an entry is removed from the cache gracefully, thus an offset we observed will not become invalid soon (pages are garbage collected with grace period). No precise concurrency control needed.

However, it might (very occasionally) cause a false negative due to the way we verify (each CacheBucket contains only a compressed version of the page Id). So, the caller must check whether the returned ContentId really points to a correct page, and invoke install() in that case. Again, no precise concurrency control required. Even for false positives/negatives, we just get a bit slower. No correctness issue.

Definition at line 415 of file cache_hashtable.hpp.

References ASSERT_ND, foedus::cache::CacheOverflowEntry::bucket_, buckets_, get_bucket_number(), foedus::cache::CacheBucket::get_content_id(), get_logical_buckets(), foedus::cache::HashFunc::get_tag(), foedus::cache::CacheBucket::get_tag(), foedus::cache::CacheRefCount::increment(), foedus::cache::kHopNeighbors, foedus::cache::CacheOverflowEntry::next_, overflow_buckets_, overflow_buckets_head_, foedus::assorted::prefetch_cachelines(), foedus::cache::CacheOverflowEntry::refcount_, and refcounts_.

Referenced by foedus::thread::ThreadPimpl::find_or_read_a_snapshot_page().

415  {
416  ASSERT_ND(page_id > 0);
417  BucketId bucket_number = get_bucket_number(page_id);
418  ASSERT_ND(bucket_number < get_logical_buckets());
419 
420  // we prefetch up to 128 bytes (16 entries).
421  assorted::prefetch_cachelines(buckets_ + bucket_number, 2);
422 
423  PageIdTag tag = HashFunc::get_tag(page_id);
424  ASSERT_ND(tag != 0);
425  for (uint16_t i = 0; i < kHopNeighbors; ++i) {
426  const CacheBucket& bucket = buckets_[bucket_number + i];
427  if (bucket.get_tag() == tag) {
428  // found (probably)!
429  refcounts_[bucket_number + i].increment();
430  return bucket.get_content_id();
431  }
432  }
433 
434  // Not found. let's check overflow list
436  for (OverflowPointer i = overflow_buckets_head_; i != 0;) {
437  if (overflow_buckets_[i].bucket_.get_tag() == tag) {
440  }
441  i = overflow_buckets_[i].next_;
442  }
443  }
444 
445  return 0;
446 }
CacheOverflowEntry * overflow_buckets_
uint32_t BucketId
Offset in hashtable bucket.
static PageIdTag get_tag(storage::SnapshotPagePointer page_id) __attribute__((always_inline))
Returns another hash value used to differentiate IDs with similar hash values.
uint32_t OverflowPointer
Position in the overflow buckets.
void prefetch_cachelines(const void *address, int cacheline_count)
Prefetch multiple contiguous cachelines to L1 cache.
Definition: cacheline.hpp:66
void increment() __attribute__((always_inline))
uint32_t PageIdTag
This is a lossy-compressed representation of SnapshotPagePointer used to quickly identify whether the...
const uint16_t kHopNeighbors
Starting from the given position, we consider this many buckets for the entry.
ContentId get_content_id() const
BucketId get_logical_buckets() const __attribute__((always_inline))
#define ASSERT_ND(x)
A warning-free wrapper macro of assert() that has no performance effect in release mode even when 'x'...
Definition: assert_nd.hpp:72
BucketId get_bucket_number(storage::SnapshotPagePointer page_id) const __attribute__((always_inline))
Returns a bucket number the given page ID should belong to.
OverflowPointer next_
Note that we don't have to atomically maintain/follow this pointer thanks to the loose requirements...
OverflowPointer overflow_buckets_head_
This forms a singly-linked list of active overflow entries.

Here is the call graph for this function:

Here is the caller graph for this function:

ErrorCode foedus::cache::CacheHashtable::find_batch ( uint16_t  batch_size,
const storage::SnapshotPagePointer page_ids,
ContentId out 
) const

Batched version of find().

Parameters
[in]batch_sizeBatch size. Must be kMaxFindBatchSize or less.
[in]page_idsArray of Page IDs to look for, size=batch_size
[out]outOutput
Returns
Only possible error is kErrorCodeInvalidParameter for too large batch_size

This might perform much faster because of parallel prefetching, SIMD-ized hash calculattion (planned, not implemented yet) etc.

Definition at line 376 of file cache_hashtable.cpp.

References ASSERT_ND, foedus::cache::CacheOverflowEntry::bucket_, buckets_, get_bucket_number(), foedus::cache::CacheBucket::get_content_id(), get_logical_buckets(), foedus::cache::HashFunc::get_tag(), foedus::cache::CacheBucket::get_tag(), foedus::cache::CacheRefCount::increment(), foedus::kErrorCodeInvalidParameter, foedus::kErrorCodeOk, foedus::cache::kHopNeighbors, kMaxFindBatchSize, foedus::cache::CacheOverflowEntry::next_, overflow_buckets_, overflow_buckets_head_, foedus::assorted::prefetch_cachelines(), foedus::cache::CacheOverflowEntry::refcount_, refcounts_, and UNLIKELY.

Referenced by foedus::thread::ThreadPimpl::find_or_read_snapshot_pages_batch().

379  {
380  if (batch_size == 0) {
381  return kErrorCodeOk;
382  }
383  ASSERT_ND(batch_size <= kMaxFindBatchSize);
384  if (UNLIKELY(batch_size > kMaxFindBatchSize)) {
386  }
387 
388  // first, calculate hash values and prefetch
389  BucketId bucket_numbers[kMaxFindBatchSize];
390  for (uint16_t b = 0; b < batch_size; ++b) {
391  if (page_ids[b] == 0) {
392  continue;
393  } else if (b > 0 && page_ids[b - 1] == page_ids[b]) {
394  continue;
395  }
396 
397  bucket_numbers[b] = get_bucket_number(page_ids[b]);
398  ASSERT_ND(bucket_numbers[b] < get_logical_buckets());
399  // we prefetch up to 128 bytes (16 entries).
400  assorted::prefetch_cachelines(buckets_ + bucket_numbers[b], 2);
401  }
402 
403  for (uint16_t b = 0; b < batch_size; ++b) {
404  out[b] = 0;
405  if (page_ids[b] == 0) {
406  continue;
407  } else if (b > 0 && page_ids[b - 1] == page_ids[b]) {
408  out[b] = out[b - 1];
409  continue;
410  }
411 
412  PageIdTag tag = HashFunc::get_tag(page_ids[b]);
413  ASSERT_ND(tag != 0);
414  BucketId bucket_number = bucket_numbers[b];
415  for (uint16_t i = 0; i < kHopNeighbors; ++i) {
416  const CacheBucket& bucket = buckets_[bucket_number + i];
417  if (bucket.get_tag() == tag) {
418  // found (probably)!
419  refcounts_[bucket_number + i].increment();
420  out[b] = bucket.get_content_id();
421  break;
422  }
423  }
424 
425  // Not found. let's check overflow list
426  if (out[b] == 0 && overflow_buckets_head_) {
427  for (OverflowPointer i = overflow_buckets_head_; i != 0;) {
428  if (overflow_buckets_[i].bucket_.get_tag() == tag) {
431  break;
432  }
433  i = overflow_buckets_[i].next_;
434  }
435  }
436  }
437 
438  return kErrorCodeOk;
439 }
0x0002 : "GENERAL: Invalid parameter given" .
Definition: error_code.hpp:106
CacheOverflowEntry * overflow_buckets_
uint32_t BucketId
Offset in hashtable bucket.
static PageIdTag get_tag(storage::SnapshotPagePointer page_id) __attribute__((always_inline))
Returns another hash value used to differentiate IDs with similar hash values.
uint32_t OverflowPointer
Position in the overflow buckets.
void prefetch_cachelines(const void *address, int cacheline_count)
Prefetch multiple contiguous cachelines to L1 cache.
Definition: cacheline.hpp:66
void increment() __attribute__((always_inline))
0 means no-error.
Definition: error_code.hpp:87
uint32_t PageIdTag
This is a lossy-compressed representation of SnapshotPagePointer used to quickly identify whether the...
const uint16_t kHopNeighbors
Starting from the given position, we consider this many buckets for the entry.
ContentId get_content_id() const
BucketId get_logical_buckets() const __attribute__((always_inline))
#define UNLIKELY(x)
Hints that x is highly likely false.
Definition: compiler.hpp:104
#define ASSERT_ND(x)
A warning-free wrapper macro of assert() that has no performance effect in release mode even when 'x'...
Definition: assert_nd.hpp:72
BucketId get_bucket_number(storage::SnapshotPagePointer page_id) const __attribute__((always_inline))
Returns a bucket number the given page ID should belong to.
OverflowPointer next_
Note that we don't have to atomically maintain/follow this pointer thanks to the loose requirements...
OverflowPointer overflow_buckets_head_
This forms a singly-linked list of active overflow entries.

Here is the call graph for this function:

Here is the caller graph for this function:

const CacheBucket& foedus::cache::CacheHashtable::get_bucket ( BucketId  bucket_id) const
inline

Definition at line 326 of file cache_hashtable.hpp.

References buckets_.

326 { return buckets_[bucket_id]; }
BucketId foedus::cache::CacheHashtable::get_bucket_number ( storage::SnapshotPagePointer  page_id) const
inline

Returns a bucket number the given page ID should belong to.

Definition at line 319 of file cache_hashtable.hpp.

References foedus::cache::HashFunc::get_bucket_number(), and hash_func_.

Referenced by find(), find_batch(), and install().

319  {
320  return hash_func_.get_bucket_number(page_id);
321  }
BucketId get_bucket_number(storage::SnapshotPagePointer page_id) const __attribute__((always_inline))
Returns a bucket number the given snapshot page ID should belong to.

Here is the call graph for this function:

Here is the caller graph for this function:

BucketId foedus::cache::CacheHashtable::get_logical_buckets ( ) const
inline

Definition at line 313 of file cache_hashtable.hpp.

References hash_func_, and foedus::cache::HashFunc::logical_buckets_.

Referenced by find(), and find_batch().

313 { return hash_func_.logical_buckets_; }
const BucketId logical_buckets_
Number of logical (actually used) buckets in this table.

Here is the caller graph for this function:

BucketId foedus::cache::CacheHashtable::get_physical_buckets ( ) const
inline

Definition at line 314 of file cache_hashtable.hpp.

References hash_func_, and foedus::cache::HashFunc::physical_buckets_.

Referenced by evict(), evict_main_loop(), get_stat_single_thread(), and verify_single_thread().

314 { return hash_func_.physical_buckets_; }
const BucketId physical_buckets_
Number of buckets that are physically allocated, at least kHopNeighbors more than logical...

Here is the caller graph for this function:

CacheHashtable::Stat foedus::cache::CacheHashtable::get_stat_single_thread ( ) const

only for debugging.

you can call this in a race, but the results are a bit inaccurate.

Definition at line 351 of file cache_hashtable.cpp.

References buckets_, get_physical_buckets(), foedus::cache::CacheOverflowEntry::next_, foedus::cache::CacheHashtable::Stat::normal_entries_, overflow_buckets_, overflow_buckets_head_, and foedus::cache::CacheHashtable::Stat::overflow_entries_.

351  {
352  Stat result;
353  result.normal_entries_ = 0;
354  result.overflow_entries_ = 0;
355 
357  for (BucketId i = 0; i < end; ++i) {
358  if (buckets_[i].is_content_set()) {
359  ++result.normal_entries_;
360  }
361  }
362 
364  for (OverflowPointer i = overflow_buckets_head_; i != 0;) {
365  if (overflow_buckets_[i].bucket_.is_content_set()) {
366  ++result.overflow_entries_;
367  }
368  i = overflow_buckets_[i].next_;
369  }
370  }
371 
372  return result;
373 }
CacheOverflowEntry * overflow_buckets_
uint32_t BucketId
Offset in hashtable bucket.
BucketId get_physical_buckets() const __attribute__((always_inline))
uint32_t OverflowPointer
Position in the overflow buckets.
OverflowPointer next_
Note that we don't have to atomically maintain/follow this pointer thanks to the loose requirements...
OverflowPointer overflow_buckets_head_
This forms a singly-linked list of active overflow entries.

Here is the call graph for this function:

ErrorCode foedus::cache::CacheHashtable::install ( storage::SnapshotPagePointer  page_id,
ContentId  content 
)

Called when a cached page is not found.

Returns
the only possible error code is kErrorCodeCacheTooManyOverflow, which is super-rare.

This method installs the new content to this hashtable. We are anyway doing at least 4kb memory copy in this case, so no need for serious optimization.

Definition at line 114 of file cache_hashtable.cpp.

References ASSERT_ND, foedus::cache::CacheOverflowEntry::bucket_, buckets_, foedus::cache::CacheRefCount::count_, get_bucket_number(), foedus::cache::HashFunc::get_tag(), foedus::kErrorCodeCacheTooManyOverflow, foedus::kErrorCodeOk, foedus::cache::kHopNeighbors, foedus::assorted::memory_fence_release(), foedus::cache::CacheOverflowEntry::next_, overflow_buckets_, overflow_buckets_count_, overflow_buckets_head_, overflow_free_buckets_head_, overflow_free_buckets_mutex_, foedus::cache::CacheOverflowEntry::refcount_, refcounts_, and foedus::cache::CacheBucket::reset().

Referenced by foedus::thread::ThreadPimpl::find_or_read_a_snapshot_page(), and foedus::thread::ThreadPimpl::find_or_read_snapshot_pages_batch().

114  {
115  ASSERT_ND(content != 0);
116 
117  // Grab a bucket to install a new page.
118  // The bucket does not have to be the only bucket to serve the page, so
119  // the logic below is much simpler than typical bufferpool.
120  BucketId ideal_bucket = get_bucket_number(page_id);
121  PageIdTag tag = HashFunc::get_tag(page_id);
122  ASSERT_ND(tag != 0);
123 
124  CacheBucket new_bucket;
125  new_bucket.reset(content, tag);
126 
127  // An opportunistic optimization. if the exact bucket already has the same page_id,
128  // most likely someone else is trying to install it at the same time. let's wait.
129  for (BucketId bucket = ideal_bucket; bucket < ideal_bucket + kHopNeighbors; ++bucket) {
130  if (!buckets_[bucket].is_content_set()) {
131  // looks like this is empty!
132  buckets_[bucket] = new_bucket; // 8-byte implicitly-atomic write
133  refcounts_[bucket].count_ = 1;
134  // this might be immediately overwritten by someone else, but that's fine.
135  // that only causes a future cache miss. no correctness issue.
136  return kErrorCodeOk;
137  }
138  }
139 
140  // unlucky, no empty slot. If this happens often, we are seriously troubled.
141  DVLOG(0) << "Ohhh, we have to add this to overflow list! This should be really rare."
142  << " page_id=" << assorted::Hex(page_id)
143  << ", content=" << assorted::Hex(content)
144  << ", ideal_bucket=" << assorted::Hex(ideal_bucket)
145  << ", tag=" << assorted::Hex(tag)
146  << ", page_id=" << assorted::Hex(page_id);
147 
148  // we come here anyway very occasionally, so taking mutex here wouldn't cause performance issue.
149  // note that this mutex just protects the free-list, which is rarely used.
150  soc::SharedMutexScope scope(&overflow_free_buckets_mutex_);
151  OverflowPointer new_overflow_entry = overflow_free_buckets_head_;
152  if (new_overflow_entry == 0) {
153  LOG(ERROR) << "Oh my god. we consumed all overflow entries, which means we have too many"
154  << " hash collisions. page_id=" << assorted::Hex(page_id)
155  << ", content=" << assorted::Hex(content)
156  << ", ideal_bucket=" << assorted::Hex(ideal_bucket)
157  << ", tag=" << assorted::Hex(tag)
158  << ", page_id=" << assorted::Hex(page_id);
160  }
161  ASSERT_ND(new_overflow_entry < overflow_buckets_count_);
163  overflow_buckets_[new_overflow_entry].next_ = overflow_buckets_head_;
164  overflow_buckets_[new_overflow_entry].refcount_.count_ = 1;
165  overflow_buckets_[new_overflow_entry].bucket_ = new_bucket;
167  overflow_buckets_head_ = new_overflow_entry;
168  return kErrorCodeOk;
169 }
soc::SharedMutex overflow_free_buckets_mutex_
The mutex to protect free overflow entries.
CacheOverflowEntry * overflow_buckets_
uint32_t BucketId
Offset in hashtable bucket.
static PageIdTag get_tag(storage::SnapshotPagePointer page_id) __attribute__((always_inline))
Returns another hash value used to differentiate IDs with similar hash values.
uint32_t OverflowPointer
Position in the overflow buckets.
OverflowPointer overflow_free_buckets_head_
This forms another singly-linked list of free overflow entries.
0 means no-error.
Definition: error_code.hpp:87
uint32_t PageIdTag
This is a lossy-compressed representation of SnapshotPagePointer used to quickly identify whether the...
const uint16_t kHopNeighbors
Starting from the given position, we consider this many buckets for the entry.
#define ASSERT_ND(x)
A warning-free wrapper macro of assert() that has no performance effect in release mode even when 'x'...
Definition: assert_nd.hpp:72
BucketId get_bucket_number(storage::SnapshotPagePointer page_id) const __attribute__((always_inline))
Returns a bucket number the given page ID should belong to.
void memory_fence_release()
Equivalent to std::atomic_thread_fence(std::memory_order_release).
OverflowPointer next_
Note that we don't have to atomically maintain/follow this pointer thanks to the loose requirements...
OverflowPointer overflow_buckets_head_
This forms a singly-linked list of active overflow entries.
0x0903 : "SPCACHE: Hashtable for snapshot cache got too many overflow entries" .
Definition: error_code.hpp:194

Here is the call graph for this function:

Here is the caller graph for this function:

ErrorStack foedus::cache::CacheHashtable::verify_single_thread ( ) const

only for debugging.

don't call this in a race

Definition at line 340 of file cache_hashtable.cpp.

References ASSERT_ND, buckets_, get_physical_buckets(), and foedus::kRetOk.

340  {
341  for (BucketId i = 0; i < get_physical_buckets(); ++i) {
342  if (buckets_[i].is_content_set()) {
343  ASSERT_ND(buckets_[i].get_tag() != 0);
344  } else {
345  ASSERT_ND(buckets_[i].get_tag() == 0);
346  }
347  }
348  return kRetOk;
349 }
uint32_t BucketId
Offset in hashtable bucket.
BucketId get_physical_buckets() const __attribute__((always_inline))
const ErrorStack kRetOk
Normal return value for no-error case.
#define ASSERT_ND(x)
A warning-free wrapper macro of assert() that has no performance effect in release mode even when 'x'...
Definition: assert_nd.hpp:72

Here is the call graph for this function:

Friends And Related Function Documentation

std::ostream& operator<< ( std::ostream &  o,
const CacheHashtable v 
)
friend

Member Data Documentation

CacheBucket* foedus::cache::CacheHashtable::buckets_
protected
memory::AlignedMemory foedus::cache::CacheHashtable::buckets_memory_
protected

Definition at line 341 of file cache_hashtable.hpp.

Referenced by CacheHashtable().

BucketId foedus::cache::CacheHashtable::clockhand_
protected

We previously stopped eviction here for usual buckets.

We will resume from this number +1 next time. We will then sequetnially check. The overflow list is fully checked after each wrap-around of this clockhand, so we don't have a dedicated clock hand for overflow list.

Definition at line 378 of file cache_hashtable.hpp.

Referenced by evict(), and evict_main_loop().

const HashFunc foedus::cache::CacheHashtable::hash_func_
protected
const uint16_t foedus::cache::CacheHashtable::numa_node_
protected

Definition at line 338 of file cache_hashtable.hpp.

Referenced by evict(), evict_main_loop(), and evict_overflow_loop().

CacheOverflowEntry* foedus::cache::CacheHashtable::overflow_buckets_
protected
const uint32_t foedus::cache::CacheHashtable::overflow_buckets_count_
protected

Definition at line 339 of file cache_hashtable.hpp.

Referenced by CacheHashtable(), and install().

OverflowPointer foedus::cache::CacheHashtable::overflow_buckets_head_
protected

This forms a singly-linked list of active overflow entries.

This is initially 0 (null), and usually remains 0.

Definition at line 355 of file cache_hashtable.hpp.

Referenced by CacheHashtable(), evict(), evict_overflow_loop(), find(), find_batch(), get_stat_single_thread(), and install().

memory::AlignedMemory foedus::cache::CacheHashtable::overflow_buckets_memory_
protected

Definition at line 343 of file cache_hashtable.hpp.

Referenced by CacheHashtable().

OverflowPointer foedus::cache::CacheHashtable::overflow_free_buckets_head_
protected

This forms another singly-linked list of free overflow entries.

A new entry is consumed from the head (by transactions). A returned entry is added back to the head (by cleaner). This overflow free-list is the only data structure we access with atomic operation/mutex. As a new entry is added to overflow list very occasionally, this should be fine.

Definition at line 364 of file cache_hashtable.hpp.

Referenced by CacheHashtable(), and install().

soc::SharedMutex foedus::cache::CacheHashtable::overflow_free_buckets_mutex_
protected

The mutex to protect free overflow entries.

Actually this is not a shared mutex, but we reuse the class to reduce code.

Definition at line 370 of file cache_hashtable.hpp.

Referenced by evict_overflow_loop(), and install().

CacheRefCount* foedus::cache::CacheHashtable::refcounts_
protected

Definition at line 347 of file cache_hashtable.hpp.

Referenced by CacheHashtable(), evict_main_loop(), find(), find_batch(), and install().

memory::AlignedMemory foedus::cache::CacheHashtable::refcounts_memory_
protected

Definition at line 342 of file cache_hashtable.hpp.

Referenced by CacheHashtable().


The documentation for this class was generated from the following files: