Skip to content

Commit 08be7f5

Browse files
committed
Implement Prepare method in CuckooTableReader
Summary: - Implement Prepare method - Rewrite performance tests in cuckoo_table_reader_test to write new file only if one doesn't already exist. - Add performance tests for batch lookup along with prefetching. Test Plan: ./cuckoo_table_reader_test --enable_perf Results (We get better results if we used int64 comparator instead of string comparator (TBD in future diffs)): With 100000000 items and hash table ratio 0.500000, number of hash functions used: 2. Time taken per op is 0.208us (4.8 Mqps) with batch size of 0 With 100000000 items and hash table ratio 0.500000, number of hash functions used: 2. Time taken per op is 0.182us (5.5 Mqps) with batch size of 10 With 100000000 items and hash table ratio 0.500000, number of hash functions used: 2. Time taken per op is 0.161us (6.2 Mqps) with batch size of 25 With 100000000 items and hash table ratio 0.500000, number of hash functions used: 2. Time taken per op is 0.161us (6.2 Mqps) with batch size of 50 With 100000000 items and hash table ratio 0.500000, number of hash functions used: 2. Time taken per op is 0.163us (6.1 Mqps) with batch size of 100 With 100000000 items and hash table ratio 0.600000, number of hash functions used: 3. Time taken per op is 0.252us (4.0 Mqps) with batch size of 0 With 100000000 items and hash table ratio 0.600000, number of hash functions used: 3. Time taken per op is 0.192us (5.2 Mqps) with batch size of 10 With 100000000 items and hash table ratio 0.600000, number of hash functions used: 3. Time taken per op is 0.195us (5.1 Mqps) with batch size of 25 With 100000000 items and hash table ratio 0.600000, number of hash functions used: 3. Time taken per op is 0.191us (5.2 Mqps) with batch size of 50 With 100000000 items and hash table ratio 0.600000, number of hash functions used: 3. Time taken per op is 0.194us (5.1 Mqps) with batch size of 100 With 100000000 items and hash table ratio 0.750000, number of hash functions used: 3. Time taken per op is 0.228us (4.4 Mqps) with batch size of 0 With 100000000 items and hash table ratio 0.750000, number of hash functions used: 3. Time taken per op is 0.185us (5.4 Mqps) with batch size of 10 With 100000000 items and hash table ratio 0.750000, number of hash functions used: 3. Time taken per op is 0.186us (5.4 Mqps) with batch size of 25 With 100000000 items and hash table ratio 0.750000, number of hash functions used: 3. Time taken per op is 0.189us (5.3 Mqps) with batch size of 50 With 100000000 items and hash table ratio 0.750000, number of hash functions used: 3. Time taken per op is 0.188us (5.3 Mqps) with batch size of 100 With 100000000 items and hash table ratio 0.900000, number of hash functions used: 3. Time taken per op is 0.325us (3.1 Mqps) with batch size of 0 With 100000000 items and hash table ratio 0.900000, number of hash functions used: 3. Time taken per op is 0.196us (5.1 Mqps) with batch size of 10 With 100000000 items and hash table ratio 0.900000, number of hash functions used: 3. Time taken per op is 0.199us (5.0 Mqps) with batch size of 25 With 100000000 items and hash table ratio 0.900000, number of hash functions used: 3. Time taken per op is 0.196us (5.1 Mqps) with batch size of 50 With 100000000 items and hash table ratio 0.900000, number of hash functions used: 3. Time taken per op is 0.209us (4.8 Mqps) with batch size of 100 Reviewers: sdong, yhchiang, igor, ljin Reviewed By: ljin Subscribers: leveldb Differential Revision: https://reviews.facebook.net/D22167
1 parent 47b452c commit 08be7f5

File tree

3 files changed

+115
-56
lines changed

3 files changed

+115
-56
lines changed

table/cuckoo_table_reader.cc

+10-2
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ Status CuckooTableReader::Get(
9696
for (uint32_t hash_cnt = 0; hash_cnt < num_hash_fun_; ++hash_cnt) {
9797
uint64_t hash_val = get_slice_hash_(ikey.user_key, hash_cnt, num_buckets_);
9898
assert(hash_val < num_buckets_);
99-
uint64_t offset = hash_val * bucket_length_;
100-
const char* bucket = &file_data_.data()[offset];
99+
const char* bucket = &file_data_.data()[hash_val * bucket_length_];
101100
if (unused_key_.compare(0, key_length_, bucket, key_length_) == 0) {
102101
return Status::OK();
103102
}
@@ -121,6 +120,15 @@ Status CuckooTableReader::Get(
121120
return Status::OK();
122121
}
123122

123+
void CuckooTableReader::Prepare(const Slice& key) {
124+
// Prefetching first location also helps improve Get performance.
125+
for (uint32_t hash_cnt = 0; hash_cnt < num_hash_fun_; ++hash_cnt) {
126+
uint64_t hash_val = get_slice_hash_(ExtractUserKey(key),
127+
hash_cnt, num_buckets_);
128+
PREFETCH(&file_data_.data()[hash_val * bucket_length_], 0, 3);
129+
}
130+
}
131+
124132
class CuckooTableIterator : public Iterator {
125133
public:
126134
explicit CuckooTableIterator(CuckooTableReader* reader);

table/cuckoo_table_reader.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ class CuckooTableReader: public TableReader {
4646
override;
4747

4848
Iterator* NewIterator(const ReadOptions&, Arena* arena = nullptr) override;
49+
void Prepare(const Slice& target) override;
4950

5051
// Report an approximation of how much memory has been used.
5152
size_t ApproximateMemoryUsage() const override;
5253

5354
// Following methods are not implemented for Cuckoo Table Reader
5455
uint64_t ApproximateOffsetOf(const Slice& key) override { return 0; }
5556
void SetupForCompaction() override {}
56-
void Prepare(const Slice& target) override {}
5757
// End of methods not implemented.
5858

5959
private:

table/cuckoo_table_reader_test.cc

+104-53
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ using GFLAGS::SetUsageMessage;
3333
DEFINE_string(file_dir, "", "Directory where the files will be created"
3434
" for benchmark. Added for using tmpfs.");
3535
DEFINE_bool(enable_perf, false, "Run Benchmark Tests too.");
36+
DEFINE_bool(write, false,
37+
"Should write new values to file in performance tests?");
3638

3739
namespace rocksdb {
3840

@@ -103,7 +105,7 @@ class CuckooReaderTest {
103105
}
104106

105107
void CreateCuckooFileAndCheckReader() {
106-
unique_ptr<WritableFile> writable_file;
108+
std::unique_ptr<WritableFile> writable_file;
107109
ASSERT_OK(env->NewWritableFile(fname, &writable_file, env_options));
108110
CuckooTableBuilder builder(
109111
writable_file.get(), 0.9, kNumHashFunc, 100, GetSliceHash);
@@ -119,7 +121,7 @@ class CuckooReaderTest {
119121
ASSERT_OK(writable_file->Close());
120122

121123
// Check reader now.
122-
unique_ptr<RandomAccessFile> read_file;
124+
std::unique_ptr<RandomAccessFile> read_file;
123125
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
124126
CuckooTableReader reader(
125127
options,
@@ -144,7 +146,7 @@ class CuckooReaderTest {
144146
}
145147

146148
void CheckIterator() {
147-
unique_ptr<RandomAccessFile> read_file;
149+
std::unique_ptr<RandomAccessFile> read_file;
148150
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
149151
CuckooTableReader reader(
150152
options,
@@ -273,7 +275,7 @@ TEST(CuckooReaderTest, WhenKeyNotFound) {
273275
AddHashLookups(user_keys[i], 0, kNumHashFunc);
274276
}
275277
CreateCuckooFileAndCheckReader();
276-
unique_ptr<RandomAccessFile> read_file;
278+
std::unique_ptr<RandomAccessFile> read_file;
277279
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
278280
CuckooTableReader reader(
279281
options,
@@ -356,53 +358,88 @@ bool CheckValue(void* cnt_ptr, const ParsedInternalKey& k, const Slice& v) {
356358
return false;
357359
}
358360

361+
void GetKeys(uint64_t num, std::vector<std::string>* keys) {
362+
IterKey k;
363+
k.SetInternalKey("", 0, kTypeValue);
364+
std::string internal_key_suffix = k.GetKey().ToString();
365+
ASSERT_EQ(8, internal_key_suffix.size());
366+
for (uint64_t key_idx = 0; key_idx < num; ++key_idx) {
367+
std::string new_key(reinterpret_cast<char*>(&key_idx), sizeof(key_idx));
368+
new_key += internal_key_suffix;
369+
keys->push_back(new_key);
370+
}
371+
}
372+
373+
std::string GetFileName(uint64_t num, double hash_ratio) {
374+
if (FLAGS_file_dir.empty()) {
375+
FLAGS_file_dir = test::TmpDir();
376+
}
377+
return FLAGS_file_dir + "/cuckoo_read_benchmark" +
378+
std::to_string(num/1000000) + "Mratio" +
379+
std::to_string(static_cast<int>(100*hash_ratio));
380+
}
381+
359382
// Create last level file as we are interested in measuring performance of
360383
// last level file only.
361-
void BM_CuckooRead(uint64_t num, uint32_t key_length,
362-
uint32_t value_length, uint64_t num_reads, double hash_ratio) {
363-
assert(value_length <= key_length);
364-
assert(8 <= key_length);
365-
std::vector<std::string> keys;
384+
void WriteFile(const std::vector<std::string>& keys,
385+
const uint64_t num, double hash_ratio) {
366386
Options options;
367387
options.allow_mmap_reads = true;
368388
Env* env = options.env;
369389
EnvOptions env_options = EnvOptions(options);
370-
uint64_t file_size;
371-
if (FLAGS_file_dir.empty()) {
372-
FLAGS_file_dir = test::TmpDir();
373-
}
374-
std::string fname = FLAGS_file_dir + "/cuckoo_read_benchmark";
390+
std::string fname = GetFileName(num, hash_ratio);
375391

376-
unique_ptr<WritableFile> writable_file;
392+
std::unique_ptr<WritableFile> writable_file;
377393
ASSERT_OK(env->NewWritableFile(fname, &writable_file, env_options));
378394
CuckooTableBuilder builder(
379395
writable_file.get(), hash_ratio,
380396
kMaxNumHashTable, 1000, GetSliceMurmurHash);
381397
ASSERT_OK(builder.status());
382398
for (uint64_t key_idx = 0; key_idx < num; ++key_idx) {
383399
// Value is just a part of key.
384-
std::string new_key(reinterpret_cast<char*>(&key_idx), sizeof(key_idx));
385-
new_key = std::string(key_length - new_key.size(), 'k') + new_key;
386-
ParsedInternalKey ikey(new_key, 0, kTypeValue);
387-
std::string full_key;
388-
AppendInternalKey(&full_key, ikey);
389-
builder.Add(Slice(full_key), Slice(&full_key[0], value_length));
400+
builder.Add(Slice(keys[key_idx]), Slice(&keys[key_idx][0], 4));
390401
ASSERT_EQ(builder.NumEntries(), key_idx + 1);
391402
ASSERT_OK(builder.status());
392-
keys.push_back(full_key);
393403
}
394404
ASSERT_OK(builder.Finish());
395405
ASSERT_EQ(num, builder.NumEntries());
396-
file_size = builder.FileSize();
397406
ASSERT_OK(writable_file->Close());
398-
unique_ptr<RandomAccessFile> read_file;
407+
408+
uint64_t file_size;
409+
env->GetFileSize(fname, &file_size);
410+
std::unique_ptr<RandomAccessFile> read_file;
399411
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
400412

401413
CuckooTableReader reader(
402-
options,
403-
std::move(read_file),
404-
file_size,
405-
GetSliceMurmurHash);
414+
options, std::move(read_file), file_size, GetSliceMurmurHash);
415+
ASSERT_OK(reader.status());
416+
ReadOptions r_options;
417+
for (const auto& key : keys) {
418+
int cnt = 0;
419+
ASSERT_OK(reader.Get(r_options, Slice(key), &cnt, CheckValue, nullptr));
420+
if (cnt != 1) {
421+
fprintf(stderr, "%lu not found.\n",
422+
*reinterpret_cast<const uint64_t*>(key.data()));
423+
ASSERT_EQ(1, cnt);
424+
}
425+
}
426+
}
427+
428+
void ReadKeys(const std::vector<std::string>& keys, uint64_t num,
429+
double hash_ratio, uint32_t batch_size) {
430+
Options options;
431+
options.allow_mmap_reads = true;
432+
Env* env = options.env;
433+
EnvOptions env_options = EnvOptions(options);
434+
std::string fname = GetFileName(num, hash_ratio);
435+
436+
uint64_t file_size;
437+
env->GetFileSize(fname, &file_size);
438+
std::unique_ptr<RandomAccessFile> read_file;
439+
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
440+
441+
CuckooTableReader reader(
442+
options, std::move(read_file), file_size, GetSliceMurmurHash);
406443
ASSERT_OK(reader.status());
407444
const UserCollectedProperties user_props =
408445
reader.GetTableProperties()->user_collected_properties;
@@ -411,39 +448,53 @@ void BM_CuckooRead(uint64_t num, uint32_t key_length,
411448
fprintf(stderr, "With %" PRIu64 " items and hash table ratio %f, number of"
412449
" hash functions used: %u.\n", num, hash_ratio, num_hash_fun);
413450
ReadOptions r_options;
414-
for (auto& key : keys) {
415-
int cnt = 0;
416-
ASSERT_OK(reader.Get(r_options, Slice(key), &cnt, CheckValue, nullptr));
417-
ASSERT_EQ(1, cnt);
418-
}
419-
// Shuffle Keys.
420-
std::random_shuffle(keys.begin(), keys.end());
421-
422-
uint64_t time_now = env->NowMicros();
423-
reader.NewIterator(ReadOptions(), nullptr);
424-
fprintf(stderr, "Time taken for preparing iterator for %" PRIu64 " items: %" PRIu64 " ms.\n",
425-
num, (env->NowMicros() - time_now)/1000);
426-
time_now = env->NowMicros();
427-
for (uint64_t i = 0; i < num_reads; ++i) {
428-
reader.Get(r_options, Slice(keys[i % num]), nullptr, DoNothing, nullptr);
451+
452+
uint64_t start_time = env->NowMicros();
453+
if (batch_size > 0) {
454+
for (uint64_t i = 0; i < num; i += batch_size) {
455+
for (uint64_t j = i; j < i+batch_size && j < num; ++j) {
456+
reader.Prepare(Slice(keys[j]));
457+
}
458+
for (uint64_t j = i; j < i+batch_size && j < num; ++j) {
459+
reader.Get(r_options, Slice(keys[j]), nullptr, DoNothing, nullptr);
460+
}
461+
}
462+
} else {
463+
for (uint64_t i = 0; i < num; i++) {
464+
reader.Get(r_options, Slice(keys[i]), nullptr, DoNothing, nullptr);
465+
}
429466
}
430-
fprintf(stderr, "Time taken per op is %.3fus\n",
431-
(env->NowMicros() - time_now)*1.0/num_reads);
467+
float time_per_op = (env->NowMicros() - start_time) * 1.0 / num;
468+
fprintf(stderr,
469+
"Time taken per op is %.3fus (%.1f Mqps) with batch size of %u\n",
470+
time_per_op, 1.0 / time_per_op, batch_size);
432471
}
433472
} // namespace.
434473

435-
TEST(CuckooReaderTest, Performance) {
436-
// In all these tests, num_reads = 10*num_items.
474+
TEST(CuckooReaderTest, TestReadPerformance) {
475+
uint64_t num = 1000*1000*100;
437476
if (!FLAGS_enable_perf) {
438477
return;
439478
}
440-
BM_CuckooRead(100000, 8, 4, 1000000, 0.9);
441-
BM_CuckooRead(1000000, 8, 4, 10000000, 0.9);
442-
BM_CuckooRead(1000000, 8, 4, 10000000, 0.7);
443-
BM_CuckooRead(10000000, 8, 4, 100000000, 0.9);
444-
BM_CuckooRead(10000000, 8, 4, 100000000, 0.7);
479+
#ifndef NDEBUG
480+
fprintf(stdout,
481+
"WARNING: Not compiled with DNDEBUG. Performance tests may be slow.\n");
482+
#endif
483+
std::vector<std::string> keys;
484+
GetKeys(num, &keys);
485+
for (double hash_ratio : std::vector<double>({0.5, 0.6, 0.75, 0.9})) {
486+
if (FLAGS_write || !Env::Default()->FileExists(
487+
GetFileName(num, hash_ratio))) {
488+
WriteFile(keys, num, hash_ratio);
489+
}
490+
ReadKeys(keys, num, hash_ratio, 0);
491+
ReadKeys(keys, num, hash_ratio, 10);
492+
ReadKeys(keys, num, hash_ratio, 25);
493+
ReadKeys(keys, num, hash_ratio, 50);
494+
ReadKeys(keys, num, hash_ratio, 100);
495+
fprintf(stderr, "\n");
496+
}
445497
}
446-
447498
} // namespace rocksdb
448499

449500
int main(int argc, char** argv) {

0 commit comments

Comments
 (0)