Add pipelined & parallel compression optimization (#6262)
Summary: This PR adds support for pipelined & parallel compression optimization for `BlockBasedTableBuilder`. This optimization makes block building, block compression and block appending a pipeline, and uses multiple threads to accelerate block compression. Users can set `CompressionOptions::parallel_threads` greater than 1 to enable compression parallelism. Pull Request resolved: https://github.com/facebook/rocksdb/pull/6262 Reviewed By: ajkr Differential Revision: D20651306 fbshipit-source-id: 62125590a9c15b6d9071def9dc72589c1696a4cbmain
parent
719c0f91bf
commit
03a781a90c
@ -0,0 +1,149 @@ |
|||||||
|
// 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) 2016-present, Facebook, Inc. |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* This source code is licensed under both the BSD-style license (found in the |
||||||
|
* LICENSE file in the root directory of this source tree) and the GPLv2 (found |
||||||
|
* in the COPYING file in the root directory of this source tree). |
||||||
|
*/ |
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <atomic> |
||||||
|
#include <cassert> |
||||||
|
#include <condition_variable> |
||||||
|
#include <cstddef> |
||||||
|
#include <cstddef> |
||||||
|
#include <functional> |
||||||
|
#include <mutex> |
||||||
|
#include <queue> |
||||||
|
|
||||||
|
namespace ROCKSDB_NAMESPACE { |
||||||
|
|
||||||
|
/// Unbounded thread-safe work queue.
|
||||||
|
//
|
||||||
|
// This file is an excerpt from Facebook's zstd repo at
|
||||||
|
// https://github.com/facebook/zstd/. The relevant file is
|
||||||
|
// contrib/pzstd/utils/WorkQueue.h.
|
||||||
|
|
||||||
|
template <typename T> |
||||||
|
class WorkQueue { |
||||||
|
// Protects all member variable access
|
||||||
|
std::mutex mutex_; |
||||||
|
std::condition_variable readerCv_; |
||||||
|
std::condition_variable writerCv_; |
||||||
|
std::condition_variable finishCv_; |
||||||
|
|
||||||
|
std::queue<T> queue_; |
||||||
|
bool done_; |
||||||
|
std::size_t maxSize_; |
||||||
|
|
||||||
|
// Must have lock to call this function
|
||||||
|
bool full() const { |
||||||
|
if (maxSize_ == 0) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return queue_.size() >= maxSize_; |
||||||
|
} |
||||||
|
|
||||||
|
public: |
||||||
|
/**
|
||||||
|
* Constructs an empty work queue with an optional max size. |
||||||
|
* If `maxSize == 0` the queue size is unbounded. |
||||||
|
* |
||||||
|
* @param maxSize The maximum allowed size of the work queue. |
||||||
|
*/ |
||||||
|
WorkQueue(std::size_t maxSize = 0) : done_(false), maxSize_(maxSize) {} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Push an item onto the work queue. Notify a single thread that work is |
||||||
|
* available. If `finish()` has been called, do nothing and return false. |
||||||
|
* If `push()` returns false, then `item` has not been copied from. |
||||||
|
* |
||||||
|
* @param item Item to push onto the queue. |
||||||
|
* @returns True upon success, false if `finish()` has been called. An |
||||||
|
* item was pushed iff `push()` returns true. |
||||||
|
*/ |
||||||
|
template <typename U> |
||||||
|
bool push(U&& item) { |
||||||
|
{ |
||||||
|
std::unique_lock<std::mutex> lock(mutex_); |
||||||
|
while (full() && !done_) { |
||||||
|
writerCv_.wait(lock); |
||||||
|
} |
||||||
|
if (done_) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
queue_.push(std::forward<U>(item)); |
||||||
|
} |
||||||
|
readerCv_.notify_one(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to pop an item off the work queue. It will block until data is |
||||||
|
* available or `finish()` has been called. |
||||||
|
* |
||||||
|
* @param[out] item If `pop` returns `true`, it contains the popped item. |
||||||
|
* If `pop` returns `false`, it is unmodified. |
||||||
|
* @returns True upon success. False if the queue is empty and |
||||||
|
* `finish()` has been called. |
||||||
|
*/ |
||||||
|
bool pop(T& item) { |
||||||
|
{ |
||||||
|
std::unique_lock<std::mutex> lock(mutex_); |
||||||
|
while (queue_.empty() && !done_) { |
||||||
|
readerCv_.wait(lock); |
||||||
|
} |
||||||
|
if (queue_.empty()) { |
||||||
|
assert(done_); |
||||||
|
return false; |
||||||
|
} |
||||||
|
item = queue_.front(); |
||||||
|
queue_.pop(); |
||||||
|
} |
||||||
|
writerCv_.notify_one(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum queue size. If `maxSize == 0` then it is unbounded. |
||||||
|
* |
||||||
|
* @param maxSize The new maximum queue size. |
||||||
|
*/ |
||||||
|
void setMaxSize(std::size_t maxSize) { |
||||||
|
{ |
||||||
|
std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
maxSize_ = maxSize; |
||||||
|
} |
||||||
|
writerCv_.notify_all(); |
||||||
|
} |
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise that `push()` won't be called again, so once the queue is empty |
||||||
|
* there will never any more work. |
||||||
|
*/ |
||||||
|
void finish() { |
||||||
|
{ |
||||||
|
std::lock_guard<std::mutex> lock(mutex_); |
||||||
|
assert(!done_); |
||||||
|
done_ = true; |
||||||
|
} |
||||||
|
readerCv_.notify_all(); |
||||||
|
writerCv_.notify_all(); |
||||||
|
finishCv_.notify_all(); |
||||||
|
} |
||||||
|
|
||||||
|
/// Blocks until `finish()` has been called (but the queue may not be empty).
|
||||||
|
void waitUntilFinished() { |
||||||
|
std::unique_lock<std::mutex> lock(mutex_); |
||||||
|
while (!done_) { |
||||||
|
finishCv_.wait(lock); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,268 @@ |
|||||||
|
// 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) 2016-present, Facebook, Inc. |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* This source code is licensed under both the BSD-style license (found in the |
||||||
|
* LICENSE file in the root directory of this source tree) and the GPLv2 (found |
||||||
|
* in the COPYING file in the root directory of this source tree). |
||||||
|
*/ |
||||||
|
#include "util/work_queue.h" |
||||||
|
|
||||||
|
#include <gtest/gtest.h> |
||||||
|
#include <iostream> |
||||||
|
#include <memory> |
||||||
|
#include <mutex> |
||||||
|
#include <thread> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
namespace ROCKSDB_NAMESPACE { |
||||||
|
|
||||||
|
// Unit test for work_queue.h.
|
||||||
|
//
|
||||||
|
// This file is an excerpt from Facebook's zstd repo at
|
||||||
|
// https://github.com/facebook/zstd/. The relevant file is
|
||||||
|
// contrib/pzstd/utils/test/WorkQueueTest.cpp.
|
||||||
|
|
||||||
|
struct Popper { |
||||||
|
WorkQueue<int>* queue; |
||||||
|
int* results; |
||||||
|
std::mutex* mutex; |
||||||
|
|
||||||
|
void operator()() { |
||||||
|
int result; |
||||||
|
while (queue->pop(result)) { |
||||||
|
std::lock_guard<std::mutex> lock(*mutex); |
||||||
|
results[result] = result; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(WorkQueue, SingleThreaded) { |
||||||
|
WorkQueue<int> queue; |
||||||
|
int result; |
||||||
|
|
||||||
|
queue.push(5); |
||||||
|
EXPECT_TRUE(queue.pop(result)); |
||||||
|
EXPECT_EQ(5, result); |
||||||
|
|
||||||
|
queue.push(1); |
||||||
|
queue.push(2); |
||||||
|
EXPECT_TRUE(queue.pop(result)); |
||||||
|
EXPECT_EQ(1, result); |
||||||
|
EXPECT_TRUE(queue.pop(result)); |
||||||
|
EXPECT_EQ(2, result); |
||||||
|
|
||||||
|
queue.push(1); |
||||||
|
queue.push(2); |
||||||
|
queue.finish(); |
||||||
|
EXPECT_TRUE(queue.pop(result)); |
||||||
|
EXPECT_EQ(1, result); |
||||||
|
EXPECT_TRUE(queue.pop(result)); |
||||||
|
EXPECT_EQ(2, result); |
||||||
|
EXPECT_FALSE(queue.pop(result)); |
||||||
|
|
||||||
|
queue.waitUntilFinished(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WorkQueue, SPSC) { |
||||||
|
WorkQueue<int> queue; |
||||||
|
const int max = 100; |
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) { |
||||||
|
queue.push(i); |
||||||
|
} |
||||||
|
|
||||||
|
std::thread thread([&queue, max] { |
||||||
|
int result; |
||||||
|
for (int i = 0;; ++i) { |
||||||
|
if (!queue.pop(result)) { |
||||||
|
EXPECT_EQ(i, max); |
||||||
|
break; |
||||||
|
} |
||||||
|
EXPECT_EQ(i, result); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
std::this_thread::yield(); |
||||||
|
for (int i = 10; i < max; ++i) { |
||||||
|
queue.push(i); |
||||||
|
} |
||||||
|
queue.finish(); |
||||||
|
|
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WorkQueue, SPMC) { |
||||||
|
WorkQueue<int> queue; |
||||||
|
std::vector<int> results(50, -1); |
||||||
|
std::mutex mutex; |
||||||
|
std::vector<std::thread> threads; |
||||||
|
for (int i = 0; i < 5; ++i) { |
||||||
|
threads.emplace_back(Popper{&queue, results.data(), &mutex}); |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < 50; ++i) { |
||||||
|
queue.push(i); |
||||||
|
} |
||||||
|
queue.finish(); |
||||||
|
|
||||||
|
for (auto& thread : threads) { |
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < 50; ++i) { |
||||||
|
EXPECT_EQ(i, results[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WorkQueue, MPMC) { |
||||||
|
WorkQueue<int> queue; |
||||||
|
std::vector<int> results(100, -1); |
||||||
|
std::mutex mutex; |
||||||
|
std::vector<std::thread> popperThreads; |
||||||
|
for (int i = 0; i < 4; ++i) { |
||||||
|
popperThreads.emplace_back(Popper{&queue, results.data(), &mutex}); |
||||||
|
} |
||||||
|
|
||||||
|
std::vector<std::thread> pusherThreads; |
||||||
|
for (int i = 0; i < 2; ++i) { |
||||||
|
auto min = i * 50; |
||||||
|
auto max = (i + 1) * 50; |
||||||
|
pusherThreads.emplace_back([&queue, min, max] { |
||||||
|
for (int j = min; j < max; ++j) { |
||||||
|
queue.push(j); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
for (auto& thread : pusherThreads) { |
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
queue.finish(); |
||||||
|
|
||||||
|
for (auto& thread : popperThreads) { |
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < 100; ++i) { |
||||||
|
EXPECT_EQ(i, results[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WorkQueue, BoundedSizeWorks) { |
||||||
|
WorkQueue<int> queue(1); |
||||||
|
int result; |
||||||
|
queue.push(5); |
||||||
|
queue.pop(result); |
||||||
|
queue.push(5); |
||||||
|
queue.pop(result); |
||||||
|
queue.push(5); |
||||||
|
queue.finish(); |
||||||
|
queue.pop(result); |
||||||
|
EXPECT_EQ(5, result); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WorkQueue, BoundedSizePushAfterFinish) { |
||||||
|
WorkQueue<int> queue(1); |
||||||
|
int result; |
||||||
|
queue.push(5); |
||||||
|
std::thread pusher([&queue] { queue.push(6); }); |
||||||
|
// Dirtily try and make sure that pusher has run.
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1)); |
||||||
|
queue.finish(); |
||||||
|
EXPECT_TRUE(queue.pop(result)); |
||||||
|
EXPECT_EQ(5, result); |
||||||
|
EXPECT_FALSE(queue.pop(result)); |
||||||
|
|
||||||
|
pusher.join(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WorkQueue, SetMaxSize) { |
||||||
|
WorkQueue<int> queue(2); |
||||||
|
int result; |
||||||
|
queue.push(5); |
||||||
|
queue.push(6); |
||||||
|
queue.setMaxSize(1); |
||||||
|
std::thread pusher([&queue] { queue.push(7); }); |
||||||
|
// Dirtily try and make sure that pusher has run.
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1)); |
||||||
|
queue.finish(); |
||||||
|
EXPECT_TRUE(queue.pop(result)); |
||||||
|
EXPECT_EQ(5, result); |
||||||
|
EXPECT_TRUE(queue.pop(result)); |
||||||
|
EXPECT_EQ(6, result); |
||||||
|
EXPECT_FALSE(queue.pop(result)); |
||||||
|
|
||||||
|
pusher.join(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WorkQueue, BoundedSizeMPMC) { |
||||||
|
WorkQueue<int> queue(10); |
||||||
|
std::vector<int> results(200, -1); |
||||||
|
std::mutex mutex; |
||||||
|
std::cerr << "Creating popperThreads" << std::endl; |
||||||
|
std::vector<std::thread> popperThreads; |
||||||
|
for (int i = 0; i < 4; ++i) { |
||||||
|
popperThreads.emplace_back(Popper{&queue, results.data(), &mutex}); |
||||||
|
} |
||||||
|
|
||||||
|
std::cerr << "Creating pusherThreads" << std::endl; |
||||||
|
std::vector<std::thread> pusherThreads; |
||||||
|
for (int i = 0; i < 2; ++i) { |
||||||
|
auto min = i * 100; |
||||||
|
auto max = (i + 1) * 100; |
||||||
|
pusherThreads.emplace_back([&queue, min, max] { |
||||||
|
for (int j = min; j < max; ++j) { |
||||||
|
queue.push(j); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
std::cerr << "Joining pusherThreads" << std::endl; |
||||||
|
for (auto& thread : pusherThreads) { |
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
std::cerr << "Finishing queue" << std::endl; |
||||||
|
queue.finish(); |
||||||
|
|
||||||
|
std::cerr << "Joining popperThreads" << std::endl; |
||||||
|
for (auto& thread : popperThreads) { |
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
|
||||||
|
std::cerr << "Inspecting results" << std::endl; |
||||||
|
for (int i = 0; i < 200; ++i) { |
||||||
|
EXPECT_EQ(i, results[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WorkQueue, FailedPush) { |
||||||
|
WorkQueue<int> queue; |
||||||
|
EXPECT_TRUE(queue.push(1)); |
||||||
|
queue.finish(); |
||||||
|
EXPECT_FALSE(queue.push(1)); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(WorkQueue, FailedPop) { |
||||||
|
WorkQueue<int> queue; |
||||||
|
int x = 5; |
||||||
|
EXPECT_TRUE(queue.push(x)); |
||||||
|
queue.finish(); |
||||||
|
x = 0; |
||||||
|
EXPECT_TRUE(queue.pop(x)); |
||||||
|
EXPECT_EQ(5, x); |
||||||
|
EXPECT_FALSE(queue.pop(x)); |
||||||
|
EXPECT_EQ(5, x); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace ROCKSDB_NAMESPACE
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
::testing::InitGoogleTest(&argc, argv); |
||||||
|
return RUN_ALL_TESTS(); |
||||||
|
} |
Loading…
Reference in new issue