Minimize accessing multiple objects in Version::Get()
Summary: One of our profilings shows that Version::Get() sometimes is slow when getting pointer of user comparators or other global objects. In this patch: (1) we keep pointers of immutable objects in Version to avoid accesses them though option objects or cfd objects (2) table_reader is directly cached in FileMetaData so that table cache don't have to go through handle first to fetch it (3) If level 0 has less than 3 files, skip the filtering logic based on SST tables' key range. Smallest and largest key are stored in separated memory locations, which has potential cache misses Test Plan: make all check Reviewers: haobo, ljin Reviewed By: haobo CC: igor, yhchiang, nkg-, leveldb Differential Revision: https://reviews.facebook.net/D17739
This commit is contained in:
parent
e37dd216f9
commit
fa430bfd04
@ -3219,7 +3219,7 @@ Status DBImpl::GetImpl(const ReadOptions& options,
|
|||||||
PERF_TIMER_START(get_from_output_files_time);
|
PERF_TIMER_START(get_from_output_files_time);
|
||||||
|
|
||||||
sv->current->Get(options, lkey, value, &s, &merge_context, &stats,
|
sv->current->Get(options, lkey, value, &s, &merge_context, &stats,
|
||||||
*cfd->options(), value_found);
|
value_found);
|
||||||
have_stat_update = true;
|
have_stat_update = true;
|
||||||
PERF_TIMER_STOP(get_from_output_files_time);
|
PERF_TIMER_STOP(get_from_output_files_time);
|
||||||
RecordTick(options_.statistics.get(), MEMTABLE_MISS);
|
RecordTick(options_.statistics.get(), MEMTABLE_MISS);
|
||||||
@ -3334,7 +3334,7 @@ std::vector<Status> DBImpl::MultiGet(
|
|||||||
// Done
|
// Done
|
||||||
} else {
|
} else {
|
||||||
super_version->current->Get(options, lkey, value, &s, &merge_context,
|
super_version->current->Get(options, lkey, value, &s, &merge_context,
|
||||||
&mgd->stats, *cfd->options());
|
&mgd->stats);
|
||||||
mgd->have_stat_update = true;
|
mgd->have_stat_update = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ Status DBImplReadOnly::Get(const ReadOptions& options,
|
|||||||
} else {
|
} else {
|
||||||
Version::GetStats stats;
|
Version::GetStats stats;
|
||||||
super_version->current->Get(options, lkey, value, &s, &merge_context,
|
super_version->current->Get(options, lkey, value, &s, &merge_context,
|
||||||
&stats, *cfd->options());
|
&stats);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -106,19 +106,20 @@ Iterator* TableCache::NewIterator(const ReadOptions& options,
|
|||||||
if (table_reader_ptr != nullptr) {
|
if (table_reader_ptr != nullptr) {
|
||||||
*table_reader_ptr = nullptr;
|
*table_reader_ptr = nullptr;
|
||||||
}
|
}
|
||||||
Cache::Handle* handle = file_meta.table_reader_handle;
|
TableReader* table_reader = file_meta.table_reader;
|
||||||
|
Cache::Handle* handle = nullptr;
|
||||||
Status s;
|
Status s;
|
||||||
if (!handle) {
|
if (table_reader == nullptr) {
|
||||||
s = FindTable(toptions, icomparator, file_meta.number, file_meta.file_size,
|
s = FindTable(toptions, icomparator, file_meta.number, file_meta.file_size,
|
||||||
&handle, nullptr, options.read_tier == kBlockCacheTier);
|
&handle, nullptr, options.read_tier == kBlockCacheTier);
|
||||||
|
table_reader = GetTableReaderFromHandle(handle);
|
||||||
}
|
}
|
||||||
if (!s.ok()) {
|
if (!s.ok()) {
|
||||||
return NewErrorIterator(s);
|
return NewErrorIterator(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
TableReader* table_reader = GetTableReaderFromHandle(handle);
|
|
||||||
Iterator* result = table_reader->NewIterator(options);
|
Iterator* result = table_reader->NewIterator(options);
|
||||||
if (!file_meta.table_reader_handle) {
|
if (handle != nullptr) {
|
||||||
result->RegisterCleanup(&UnrefEntry, cache_, handle);
|
result->RegisterCleanup(&UnrefEntry, cache_, handle);
|
||||||
}
|
}
|
||||||
if (table_reader_ptr != nullptr) {
|
if (table_reader_ptr != nullptr) {
|
||||||
@ -138,17 +139,18 @@ Status TableCache::Get(const ReadOptions& options,
|
|||||||
bool (*saver)(void*, const ParsedInternalKey&,
|
bool (*saver)(void*, const ParsedInternalKey&,
|
||||||
const Slice&, bool),
|
const Slice&, bool),
|
||||||
bool* table_io, void (*mark_key_may_exist)(void*)) {
|
bool* table_io, void (*mark_key_may_exist)(void*)) {
|
||||||
Cache::Handle* handle = file_meta.table_reader_handle;
|
TableReader* t = file_meta.table_reader;
|
||||||
Status s;
|
Status s;
|
||||||
if (!handle) {
|
Cache::Handle* handle = nullptr;
|
||||||
|
if (!t) {
|
||||||
s = FindTable(storage_options_, internal_comparator, file_meta.number,
|
s = FindTable(storage_options_, internal_comparator, file_meta.number,
|
||||||
file_meta.file_size, &handle, table_io,
|
file_meta.file_size, &handle, table_io,
|
||||||
options.read_tier == kBlockCacheTier);
|
options.read_tier == kBlockCacheTier);
|
||||||
|
t = GetTableReaderFromHandle(handle);
|
||||||
}
|
}
|
||||||
if (s.ok()) {
|
if (s.ok()) {
|
||||||
TableReader* t = GetTableReaderFromHandle(handle);
|
|
||||||
s = t->Get(options, k, arg, saver, mark_key_may_exist);
|
s = t->Get(options, k, arg, saver, mark_key_may_exist);
|
||||||
if (!file_meta.table_reader_handle) {
|
if (handle != nullptr) {
|
||||||
ReleaseHandle(handle);
|
ReleaseHandle(handle);
|
||||||
}
|
}
|
||||||
} else if (options.read_tier && s.IsIncomplete()) {
|
} else if (options.read_tier && s.IsIncomplete()) {
|
||||||
@ -164,15 +166,16 @@ Status TableCache::GetTableProperties(
|
|||||||
const FileMetaData& file_meta,
|
const FileMetaData& file_meta,
|
||||||
std::shared_ptr<const TableProperties>* properties, bool no_io) {
|
std::shared_ptr<const TableProperties>* properties, bool no_io) {
|
||||||
Status s;
|
Status s;
|
||||||
auto table_handle = file_meta.table_reader_handle;
|
auto table_reader = file_meta.table_reader;
|
||||||
// table already been pre-loaded?
|
// table already been pre-loaded?
|
||||||
if (table_handle) {
|
if (table_reader) {
|
||||||
auto table = GetTableReaderFromHandle(table_handle);
|
*properties = table_reader->GetTableProperties();
|
||||||
*properties = table->GetTableProperties();
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool table_io;
|
bool table_io;
|
||||||
|
Cache::Handle* table_handle = nullptr;
|
||||||
s = FindTable(toptions, internal_comparator, file_meta.number,
|
s = FindTable(toptions, internal_comparator, file_meta.number,
|
||||||
file_meta.file_size, &table_handle, &table_io, no_io);
|
file_meta.file_size, &table_handle, &table_io, no_io);
|
||||||
if (!s.ok()) {
|
if (!s.ok()) {
|
||||||
@ -190,20 +193,21 @@ bool TableCache::PrefixMayMatch(const ReadOptions& options,
|
|||||||
const FileMetaData& file_meta,
|
const FileMetaData& file_meta,
|
||||||
const Slice& internal_prefix, bool* table_io) {
|
const Slice& internal_prefix, bool* table_io) {
|
||||||
bool may_match = true;
|
bool may_match = true;
|
||||||
auto table_handle = file_meta.table_reader_handle;
|
auto table_reader = file_meta.table_reader;
|
||||||
if (table_handle == nullptr) {
|
Cache::Handle* table_handle = nullptr;
|
||||||
|
if (table_reader == nullptr) {
|
||||||
// Need to get table handle from file number
|
// Need to get table handle from file number
|
||||||
Status s = FindTable(storage_options_, icomparator, file_meta.number,
|
Status s = FindTable(storage_options_, icomparator, file_meta.number,
|
||||||
file_meta.file_size, &table_handle, table_io);
|
file_meta.file_size, &table_handle, table_io);
|
||||||
if (!s.ok()) {
|
if (!s.ok()) {
|
||||||
return may_match;
|
return may_match;
|
||||||
}
|
}
|
||||||
|
table_reader = GetTableReaderFromHandle(table_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto table = GetTableReaderFromHandle(table_handle);
|
may_match = table_reader->PrefixMayMatch(internal_prefix);
|
||||||
may_match = table->PrefixMayMatch(internal_prefix);
|
|
||||||
|
|
||||||
if (file_meta.table_reader_handle == nullptr) {
|
if (table_handle != nullptr) {
|
||||||
// Need to release handle if it is generated from here.
|
// Need to release handle if it is generated from here.
|
||||||
ReleaseHandle(table_handle);
|
ReleaseHandle(table_handle);
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,8 @@ struct FileMetaData {
|
|||||||
|
|
||||||
// Needs to be disposed when refs becomes 0.
|
// Needs to be disposed when refs becomes 0.
|
||||||
Cache::Handle* table_reader_handle;
|
Cache::Handle* table_reader_handle;
|
||||||
|
// Table reader in table_reader_handle
|
||||||
|
TableReader* table_reader;
|
||||||
|
|
||||||
FileMetaData(uint64_t number, uint64_t file_size)
|
FileMetaData(uint64_t number, uint64_t file_size)
|
||||||
: refs(0),
|
: refs(0),
|
||||||
@ -39,7 +41,8 @@ struct FileMetaData {
|
|||||||
number(number),
|
number(number),
|
||||||
file_size(file_size),
|
file_size(file_size),
|
||||||
being_compacted(false),
|
being_compacted(false),
|
||||||
table_reader_handle(nullptr) {}
|
table_reader_handle(nullptr),
|
||||||
|
table_reader(nullptr) {}
|
||||||
FileMetaData() : FileMetaData(0, 0) {}
|
FileMetaData() : FileMetaData(0, 0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -483,6 +483,17 @@ bool BySmallestKey(FileMetaData* a, FileMetaData* b,
|
|||||||
Version::Version(ColumnFamilyData* cfd, VersionSet* vset,
|
Version::Version(ColumnFamilyData* cfd, VersionSet* vset,
|
||||||
uint64_t version_number)
|
uint64_t version_number)
|
||||||
: cfd_(cfd),
|
: cfd_(cfd),
|
||||||
|
internal_comparator_((cfd == nullptr) ? nullptr
|
||||||
|
: &cfd->internal_comparator()),
|
||||||
|
user_comparator_((cfd == nullptr)
|
||||||
|
? nullptr
|
||||||
|
: internal_comparator_->user_comparator()),
|
||||||
|
table_cache_((cfd == nullptr) ? nullptr : cfd->table_cache()),
|
||||||
|
merge_operator_((cfd == nullptr) ? nullptr
|
||||||
|
: cfd->options()->merge_operator.get()),
|
||||||
|
info_log_((cfd == nullptr) ? nullptr : cfd->options()->info_log.get()),
|
||||||
|
db_statistics_((cfd == nullptr) ? nullptr
|
||||||
|
: cfd->options()->statistics.get()),
|
||||||
vset_(vset),
|
vset_(vset),
|
||||||
next_(this),
|
next_(this),
|
||||||
prev_(this),
|
prev_(this),
|
||||||
@ -504,27 +515,22 @@ void Version::Get(const ReadOptions& options,
|
|||||||
Status* status,
|
Status* status,
|
||||||
MergeContext* merge_context,
|
MergeContext* merge_context,
|
||||||
GetStats* stats,
|
GetStats* stats,
|
||||||
const Options& db_options,
|
|
||||||
bool* value_found) {
|
bool* value_found) {
|
||||||
Slice ikey = k.internal_key();
|
Slice ikey = k.internal_key();
|
||||||
Slice user_key = k.user_key();
|
Slice user_key = k.user_key();
|
||||||
const Comparator* ucmp = cfd_->internal_comparator().user_comparator();
|
|
||||||
|
|
||||||
auto merge_operator = db_options.merge_operator.get();
|
|
||||||
auto logger = db_options.info_log.get();
|
|
||||||
|
|
||||||
assert(status->ok() || status->IsMergeInProgress());
|
assert(status->ok() || status->IsMergeInProgress());
|
||||||
Saver saver;
|
Saver saver;
|
||||||
saver.state = status->ok()? kNotFound : kMerge;
|
saver.state = status->ok()? kNotFound : kMerge;
|
||||||
saver.ucmp = ucmp;
|
saver.ucmp = user_comparator_;
|
||||||
saver.user_key = user_key;
|
saver.user_key = user_key;
|
||||||
saver.value_found = value_found;
|
saver.value_found = value_found;
|
||||||
saver.value = value;
|
saver.value = value;
|
||||||
saver.merge_operator = merge_operator;
|
saver.merge_operator = merge_operator_;
|
||||||
saver.merge_context = merge_context;
|
saver.merge_context = merge_context;
|
||||||
saver.logger = logger;
|
saver.logger = info_log_;
|
||||||
saver.didIO = false;
|
saver.didIO = false;
|
||||||
saver.statistics = db_options.statistics.get();
|
saver.statistics = db_statistics_;
|
||||||
|
|
||||||
stats->seek_file = nullptr;
|
stats->seek_file = nullptr;
|
||||||
stats->seek_file_level = -1;
|
stats->seek_file_level = -1;
|
||||||
@ -555,7 +561,7 @@ void Version::Get(const ReadOptions& options,
|
|||||||
// On Level-n (n>=1), files are sorted.
|
// On Level-n (n>=1), files are sorted.
|
||||||
// Binary search to find earliest index whose largest key >= ikey.
|
// Binary search to find earliest index whose largest key >= ikey.
|
||||||
// We will also stop when the file no longer overlaps ikey
|
// We will also stop when the file no longer overlaps ikey
|
||||||
start_index = FindFile(cfd_->internal_comparator(), files_[level], ikey);
|
start_index = FindFile(*internal_comparator_, files_[level], ikey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse each relevant file to find the desired key
|
// Traverse each relevant file to find the desired key
|
||||||
@ -564,8 +570,10 @@ void Version::Get(const ReadOptions& options,
|
|||||||
#endif
|
#endif
|
||||||
for (uint32_t i = start_index; i < num_files; ++i) {
|
for (uint32_t i = start_index; i < num_files; ++i) {
|
||||||
FileMetaData* f = files[i];
|
FileMetaData* f = files[i];
|
||||||
if (ucmp->Compare(user_key, f->smallest.user_key()) < 0 ||
|
// Skip key range filtering for levle 0 if there are few level 0 files.
|
||||||
ucmp->Compare(user_key, f->largest.user_key()) > 0) {
|
if ((level > 0 || num_files > 2) &&
|
||||||
|
(user_comparator_->Compare(user_key, f->smallest.user_key()) < 0 ||
|
||||||
|
user_comparator_->Compare(user_key, f->largest.user_key()) > 0)) {
|
||||||
// Only process overlapping files.
|
// Only process overlapping files.
|
||||||
if (level > 0) {
|
if (level > 0) {
|
||||||
// If on Level-n (n>=1) then the files are sorted.
|
// If on Level-n (n>=1) then the files are sorted.
|
||||||
@ -581,8 +589,8 @@ void Version::Get(const ReadOptions& options,
|
|||||||
// Sanity check to make sure that the files are correctly sorted
|
// Sanity check to make sure that the files are correctly sorted
|
||||||
if (prev_file) {
|
if (prev_file) {
|
||||||
if (level != 0) {
|
if (level != 0) {
|
||||||
int comp_sign = cfd_->internal_comparator().Compare(
|
int comp_sign =
|
||||||
prev_file->largest, f->smallest);
|
internal_comparator_->Compare(prev_file->largest, f->smallest);
|
||||||
assert(comp_sign < 0);
|
assert(comp_sign < 0);
|
||||||
} else {
|
} else {
|
||||||
// level == 0, the current file cannot be newer than the previous one.
|
// level == 0, the current file cannot be newer than the previous one.
|
||||||
@ -596,9 +604,8 @@ void Version::Get(const ReadOptions& options,
|
|||||||
prev_file = f;
|
prev_file = f;
|
||||||
#endif
|
#endif
|
||||||
bool tableIO = false;
|
bool tableIO = false;
|
||||||
*status = cfd_->table_cache()->Get(options, cfd_->internal_comparator(),
|
*status = table_cache_->Get(options, *internal_comparator_, *f, ikey,
|
||||||
*f, ikey, &saver, SaveValue, &tableIO,
|
&saver, SaveValue, &tableIO, MarkKeyMayExist);
|
||||||
MarkKeyMayExist);
|
|
||||||
// TODO: examine the behavior for corrupted key
|
// TODO: examine the behavior for corrupted key
|
||||||
if (!status->ok()) {
|
if (!status->ok()) {
|
||||||
return;
|
return;
|
||||||
@ -643,12 +650,12 @@ void Version::Get(const ReadOptions& options,
|
|||||||
if (kMerge == saver.state) {
|
if (kMerge == saver.state) {
|
||||||
// merge_operands are in saver and we hit the beginning of the key history
|
// merge_operands are in saver and we hit the beginning of the key history
|
||||||
// do a final merge of nullptr and operands;
|
// do a final merge of nullptr and operands;
|
||||||
if (merge_operator->FullMerge(user_key, nullptr,
|
if (merge_operator_->FullMerge(user_key, nullptr,
|
||||||
saver.merge_context->GetOperands(),
|
saver.merge_context->GetOperands(), value,
|
||||||
value, logger)) {
|
info_log_)) {
|
||||||
*status = Status::OK();
|
*status = Status::OK();
|
||||||
} else {
|
} else {
|
||||||
RecordTick(db_options.statistics.get(), NUMBER_MERGE_FAILURES);
|
RecordTick(db_statistics_, NUMBER_MERGE_FAILURES);
|
||||||
*status = Status::Corruption("could not perform end-of-key merge for ",
|
*status = Status::Corruption("could not perform end-of-key merge for ",
|
||||||
user_key);
|
user_key);
|
||||||
}
|
}
|
||||||
@ -1458,6 +1465,12 @@ class VersionSet::Builder {
|
|||||||
base_->vset_->storage_options_, cfd_->internal_comparator(),
|
base_->vset_->storage_options_, cfd_->internal_comparator(),
|
||||||
file_meta->number, file_meta->file_size,
|
file_meta->number, file_meta->file_size,
|
||||||
&file_meta->table_reader_handle, &table_io, false);
|
&file_meta->table_reader_handle, &table_io, false);
|
||||||
|
if (file_meta->table_reader_handle != nullptr) {
|
||||||
|
// Load table_reader
|
||||||
|
file_meta->table_reader =
|
||||||
|
cfd_->table_cache()->GetTableReaderFromHandle(
|
||||||
|
file_meta->table_reader_handle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,8 +88,7 @@ class Version {
|
|||||||
int seek_file_level;
|
int seek_file_level;
|
||||||
};
|
};
|
||||||
void Get(const ReadOptions&, const LookupKey& key, std::string* val,
|
void Get(const ReadOptions&, const LookupKey& key, std::string* val,
|
||||||
Status* status, MergeContext* merge_context,
|
Status* status, MergeContext* merge_context, GetStats* stats,
|
||||||
GetStats* stats, const Options& db_option,
|
|
||||||
bool* value_found = nullptr);
|
bool* value_found = nullptr);
|
||||||
|
|
||||||
// Adds "stats" into the current state. Returns true if a new
|
// Adds "stats" into the current state. Returns true if a new
|
||||||
@ -230,6 +229,12 @@ class Version {
|
|||||||
void UpdateFilesBySize();
|
void UpdateFilesBySize();
|
||||||
|
|
||||||
ColumnFamilyData* cfd_; // ColumnFamilyData to which this Version belongs
|
ColumnFamilyData* cfd_; // ColumnFamilyData to which this Version belongs
|
||||||
|
const InternalKeyComparator* internal_comparator_;
|
||||||
|
const Comparator* user_comparator_;
|
||||||
|
TableCache* table_cache_;
|
||||||
|
const MergeOperator* merge_operator_;
|
||||||
|
Logger* info_log_;
|
||||||
|
Statistics* db_statistics_;
|
||||||
VersionSet* vset_; // VersionSet to which this Version belongs
|
VersionSet* vset_; // VersionSet to which this Version belongs
|
||||||
Version* next_; // Next version in linked list
|
Version* next_; // Next version in linked list
|
||||||
Version* prev_; // Previous version in linked list
|
Version* prev_; // Previous version in linked list
|
||||||
|
Loading…
Reference in New Issue
Block a user