8ea0a2c1bd
Summary: Implement the ```WaitAll()``` interface in ```LRUCache``` to allow callers to issue multiple lookups in parallel and wait for all of them to complete. Modify ```MultiGet``` to use this to parallelize the secondary cache lookups in order to reduce the overall latency. A call to ```cache->Lookup()``` returns a handle that has an incomplete value (nullptr), and the caller can call ```cache->IsReady()``` to check whether the lookup is complete, and pass a vector of handles to ```WaitAll``` to wait for completion. If any of the lookups fail, ```MultiGet``` will read the block from the SST file. Another change in this PR is to rename ```SecondaryCacheHandle``` to ```SecondaryCacheResultHandle``` as it more accurately describes the return result of the secondary cache lookup, which is more like a future. Tests: 1. Add unit tests in lru_cache_test 2. Benchmark results with no secondary cache configured Master - ``` readrandom : 41.175 micros/op 388562 ops/sec; 106.7 MB/s (7277999 of 7277999 found) readrandom : 41.217 micros/op 388160 ops/sec; 106.6 MB/s (7274999 of 7274999 found) multireadrandom : 10.309 micros/op 1552082 ops/sec; (28908992 of 28908992 found) multireadrandom : 10.321 micros/op 1550218 ops/sec; (29081984 of 29081984 found) ``` This PR - ``` readrandom : 41.158 micros/op 388723 ops/sec; 106.8 MB/s (7290999 of 7290999 found) readrandom : 41.185 micros/op 388463 ops/sec; 106.7 MB/s (7287999 of 7287999 found) multireadrandom : 10.277 micros/op 1556801 ops/sec; (29346944 of 29346944 found) multireadrandom : 10.253 micros/op 1560539 ops/sec; (29274944 of 29274944 found) ``` Pull Request resolved: https://github.com/facebook/rocksdb/pull/8405 Reviewed By: zhichao-cao Differential Revision: D29190509 Pulled By: anand1976 fbshipit-source-id: 6f8eff6246712af8a297cfe22ea0d1c3b2a01bb0
236 lines
6.3 KiB
C++
236 lines
6.3 KiB
C++
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
//
|
|
// Copyright (c) 2012 The LevelDB Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
|
|
#pragma once
|
|
|
|
#include <cassert>
|
|
#include "port/likely.h"
|
|
#include "rocksdb/cache.h"
|
|
#include "rocksdb/cleanable.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
// CachableEntry is a handle to an object that may or may not be in the block
|
|
// cache. It is used in a variety of ways:
|
|
//
|
|
// 1) It may refer to an object in the block cache. In this case, cache_ and
|
|
// cache_handle_ are not nullptr, and the cache handle has to be released when
|
|
// the CachableEntry is destroyed (the lifecycle of the cached object, on the
|
|
// other hand, is managed by the cache itself).
|
|
// 2) It may uniquely own the (non-cached) object it refers to (examples include
|
|
// a block read directly from file, or uncompressed blocks when there is a
|
|
// compressed block cache but no uncompressed block cache). In such cases, the
|
|
// object has to be destroyed when the CachableEntry is destroyed.
|
|
// 3) It may point to an object (cached or not) without owning it. In this case,
|
|
// no action is needed when the CachableEntry is destroyed.
|
|
// 4) Sometimes, management of a cached or owned object (see #1 and #2 above)
|
|
// is transferred to some other object. This is used for instance with iterators
|
|
// (where cleanup is performed using a chain of cleanup functions,
|
|
// see Cleanable).
|
|
//
|
|
// Because of #1 and #2 above, copying a CachableEntry is not safe (and thus not
|
|
// allowed); hence, this is a move-only type, where a move transfers the
|
|
// management responsibilities, and leaves the source object in an empty state.
|
|
|
|
template <class T>
|
|
class CachableEntry {
|
|
public:
|
|
CachableEntry() = default;
|
|
|
|
CachableEntry(T* value, Cache* cache, Cache::Handle* cache_handle,
|
|
bool own_value)
|
|
: value_(value)
|
|
, cache_(cache)
|
|
, cache_handle_(cache_handle)
|
|
, own_value_(own_value)
|
|
{
|
|
assert(value_ != nullptr ||
|
|
(cache_ == nullptr && cache_handle_ == nullptr && !own_value_));
|
|
assert(!!cache_ == !!cache_handle_);
|
|
assert(!cache_handle_ || !own_value_);
|
|
}
|
|
|
|
CachableEntry(const CachableEntry&) = delete;
|
|
CachableEntry& operator=(const CachableEntry&) = delete;
|
|
|
|
CachableEntry(CachableEntry&& rhs)
|
|
: value_(rhs.value_)
|
|
, cache_(rhs.cache_)
|
|
, cache_handle_(rhs.cache_handle_)
|
|
, own_value_(rhs.own_value_)
|
|
{
|
|
assert(value_ != nullptr ||
|
|
(cache_ == nullptr && cache_handle_ == nullptr && !own_value_));
|
|
assert(!!cache_ == !!cache_handle_);
|
|
assert(!cache_handle_ || !own_value_);
|
|
|
|
rhs.ResetFields();
|
|
}
|
|
|
|
CachableEntry& operator=(CachableEntry&& rhs) {
|
|
if (UNLIKELY(this == &rhs)) {
|
|
return *this;
|
|
}
|
|
|
|
ReleaseResource();
|
|
|
|
value_ = rhs.value_;
|
|
cache_ = rhs.cache_;
|
|
cache_handle_ = rhs.cache_handle_;
|
|
own_value_ = rhs.own_value_;
|
|
|
|
assert(value_ != nullptr ||
|
|
(cache_ == nullptr && cache_handle_ == nullptr && !own_value_));
|
|
assert(!!cache_ == !!cache_handle_);
|
|
assert(!cache_handle_ || !own_value_);
|
|
|
|
rhs.ResetFields();
|
|
|
|
return *this;
|
|
}
|
|
|
|
~CachableEntry() {
|
|
ReleaseResource();
|
|
}
|
|
|
|
bool IsEmpty() const {
|
|
return value_ == nullptr && cache_ == nullptr && cache_handle_ == nullptr &&
|
|
!own_value_;
|
|
}
|
|
|
|
bool IsCached() const {
|
|
assert(!!cache_ == !!cache_handle_);
|
|
|
|
return cache_handle_ != nullptr;
|
|
}
|
|
|
|
T* GetValue() const { return value_; }
|
|
Cache* GetCache() const { return cache_; }
|
|
Cache::Handle* GetCacheHandle() const { return cache_handle_; }
|
|
bool GetOwnValue() const { return own_value_; }
|
|
|
|
void Reset() {
|
|
ReleaseResource();
|
|
ResetFields();
|
|
}
|
|
|
|
void TransferTo(Cleanable* cleanable) {
|
|
if (cleanable) {
|
|
if (cache_handle_ != nullptr) {
|
|
assert(cache_ != nullptr);
|
|
cleanable->RegisterCleanup(&ReleaseCacheHandle, cache_, cache_handle_);
|
|
} else if (own_value_) {
|
|
cleanable->RegisterCleanup(&DeleteValue, value_, nullptr);
|
|
}
|
|
}
|
|
|
|
ResetFields();
|
|
}
|
|
|
|
void SetOwnedValue(T* value) {
|
|
assert(value != nullptr);
|
|
|
|
if (UNLIKELY(value_ == value && own_value_)) {
|
|
assert(cache_ == nullptr && cache_handle_ == nullptr);
|
|
return;
|
|
}
|
|
|
|
Reset();
|
|
|
|
value_ = value;
|
|
own_value_ = true;
|
|
}
|
|
|
|
void SetUnownedValue(T* value) {
|
|
assert(value != nullptr);
|
|
|
|
if (UNLIKELY(value_ == value && cache_ == nullptr &&
|
|
cache_handle_ == nullptr && !own_value_)) {
|
|
return;
|
|
}
|
|
|
|
Reset();
|
|
|
|
value_ = value;
|
|
assert(!own_value_);
|
|
}
|
|
|
|
void SetCachedValue(T* value, Cache* cache, Cache::Handle* cache_handle) {
|
|
assert(cache != nullptr);
|
|
assert(cache_handle != nullptr);
|
|
|
|
if (UNLIKELY(value_ == value && cache_ == cache &&
|
|
cache_handle_ == cache_handle && !own_value_)) {
|
|
return;
|
|
}
|
|
|
|
Reset();
|
|
|
|
value_ = value;
|
|
cache_ = cache;
|
|
cache_handle_ = cache_handle;
|
|
assert(!own_value_);
|
|
}
|
|
|
|
void UpdateCachedValue() {
|
|
assert(cache_ != nullptr);
|
|
assert(cache_handle_ != nullptr);
|
|
|
|
value_ = static_cast<T*>(cache_->Value(cache_handle_));
|
|
}
|
|
|
|
bool IsReady() {
|
|
if (!own_value_) {
|
|
assert(cache_ != nullptr);
|
|
assert(cache_handle_ != nullptr);
|
|
return cache_->IsReady(cache_handle_);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
void ReleaseResource() {
|
|
if (LIKELY(cache_handle_ != nullptr)) {
|
|
assert(cache_ != nullptr);
|
|
cache_->Release(cache_handle_);
|
|
} else if (own_value_) {
|
|
delete value_;
|
|
}
|
|
}
|
|
|
|
void ResetFields() {
|
|
value_ = nullptr;
|
|
cache_ = nullptr;
|
|
cache_handle_ = nullptr;
|
|
own_value_ = false;
|
|
}
|
|
|
|
static void ReleaseCacheHandle(void* arg1, void* arg2) {
|
|
Cache* const cache = static_cast<Cache*>(arg1);
|
|
assert(cache);
|
|
|
|
Cache::Handle* const cache_handle = static_cast<Cache::Handle*>(arg2);
|
|
assert(cache_handle);
|
|
|
|
cache->Release(cache_handle);
|
|
}
|
|
|
|
static void DeleteValue(void* arg1, void* /* arg2 */) {
|
|
delete static_cast<T*>(arg1);
|
|
}
|
|
|
|
private:
|
|
T* value_ = nullptr;
|
|
Cache* cache_ = nullptr;
|
|
Cache::Handle* cache_handle_ = nullptr;
|
|
bool own_value_ = false;
|
|
};
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|