mirror of
https://github.com/facebook/rocksdb.git
synced 2024-11-26 16:30:56 +00:00
31da5e34c1
Summary: Based on https://github.com/facebook/rocksdb/issues/6648 (CLA Signed), but heavily modified / extended: * Implicit capture of this via [=] deprecated in C++20, and [=,this] not standard before C++20 -> now using explicit capture lists * Implicit copy operator deprecated in gcc 9 -> add explicit '= default' definition * std::random_shuffle deprecated in C++17 and removed in C++20 -> migrated to a replacement in RocksDB random.h API * Add the ability to build with different std version though -DCMAKE_CXX_STANDARD=11/14/17/20 on the cmake command line * Minimal rebuild flag of MSVC is deprecated and is forbidden with /std:c++latest (C++20) * Added MSVC 2019 C++11 & MSVC 2019 C++20 in AppVeyor * Added GCC 9 C++11 & GCC9 C++20 in Travis Pull Request resolved: https://github.com/facebook/rocksdb/pull/6697 Test Plan: make check and CI Reviewed By: cheng-chang Differential Revision: D21020318 Pulled By: pdillinger fbshipit-source-id: 12311be5dbd8675a0e2c817f7ec50fa11c18ab91
219 lines
7.6 KiB
C++
219 lines
7.6 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) 2011 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 <atomic>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include "memory/allocator.h"
|
|
#include "memory/arena.h"
|
|
#include "port/lang.h"
|
|
#include "port/likely.h"
|
|
#include "util/core_local.h"
|
|
#include "util/mutexlock.h"
|
|
#include "util/thread_local.h"
|
|
|
|
// Only generate field unused warning for padding array, or build under
|
|
// GCC 4.8.1 will fail.
|
|
#ifdef __clang__
|
|
#define ROCKSDB_FIELD_UNUSED __attribute__((__unused__))
|
|
#else
|
|
#define ROCKSDB_FIELD_UNUSED
|
|
#endif // __clang__
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
class Logger;
|
|
|
|
// ConcurrentArena wraps an Arena. It makes it thread safe using a fast
|
|
// inlined spinlock, and adds small per-core allocation caches to avoid
|
|
// contention for small allocations. To avoid any memory waste from the
|
|
// per-core shards, they are kept small, they are lazily instantiated
|
|
// only if ConcurrentArena actually notices concurrent use, and they
|
|
// adjust their size so that there is no fragmentation waste when the
|
|
// shard blocks are allocated from the underlying main arena.
|
|
class ConcurrentArena : public Allocator {
|
|
public:
|
|
// block_size and huge_page_size are the same as for Arena (and are
|
|
// in fact just passed to the constructor of arena_. The core-local
|
|
// shards compute their shard_block_size as a fraction of block_size
|
|
// that varies according to the hardware concurrency level.
|
|
explicit ConcurrentArena(size_t block_size = Arena::kMinBlockSize,
|
|
AllocTracker* tracker = nullptr,
|
|
size_t huge_page_size = 0);
|
|
|
|
char* Allocate(size_t bytes) override {
|
|
return AllocateImpl(bytes, false /*force_arena*/,
|
|
[this, bytes]() { return arena_.Allocate(bytes); });
|
|
}
|
|
|
|
char* AllocateAligned(size_t bytes, size_t huge_page_size = 0,
|
|
Logger* logger = nullptr) override {
|
|
size_t rounded_up = ((bytes - 1) | (sizeof(void*) - 1)) + 1;
|
|
assert(rounded_up >= bytes && rounded_up < bytes + sizeof(void*) &&
|
|
(rounded_up % sizeof(void*)) == 0);
|
|
|
|
return AllocateImpl(rounded_up, huge_page_size != 0 /*force_arena*/,
|
|
[this, rounded_up, huge_page_size, logger]() {
|
|
return arena_.AllocateAligned(rounded_up,
|
|
huge_page_size, logger);
|
|
});
|
|
}
|
|
|
|
size_t ApproximateMemoryUsage() const {
|
|
std::unique_lock<SpinMutex> lock(arena_mutex_, std::defer_lock);
|
|
lock.lock();
|
|
return arena_.ApproximateMemoryUsage() - ShardAllocatedAndUnused();
|
|
}
|
|
|
|
size_t MemoryAllocatedBytes() const {
|
|
return memory_allocated_bytes_.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
size_t AllocatedAndUnused() const {
|
|
return arena_allocated_and_unused_.load(std::memory_order_relaxed) +
|
|
ShardAllocatedAndUnused();
|
|
}
|
|
|
|
size_t IrregularBlockNum() const {
|
|
return irregular_block_num_.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
size_t BlockSize() const override { return arena_.BlockSize(); }
|
|
|
|
private:
|
|
struct Shard {
|
|
char padding[40] ROCKSDB_FIELD_UNUSED;
|
|
mutable SpinMutex mutex;
|
|
char* free_begin_;
|
|
std::atomic<size_t> allocated_and_unused_;
|
|
|
|
Shard() : free_begin_(nullptr), allocated_and_unused_(0) {}
|
|
};
|
|
|
|
#ifdef ROCKSDB_SUPPORT_THREAD_LOCAL
|
|
static __thread size_t tls_cpuid;
|
|
#else
|
|
enum ZeroFirstEnum : size_t { tls_cpuid = 0 };
|
|
#endif
|
|
|
|
char padding0[56] ROCKSDB_FIELD_UNUSED;
|
|
|
|
size_t shard_block_size_;
|
|
|
|
CoreLocalArray<Shard> shards_;
|
|
|
|
Arena arena_;
|
|
mutable SpinMutex arena_mutex_;
|
|
std::atomic<size_t> arena_allocated_and_unused_;
|
|
std::atomic<size_t> memory_allocated_bytes_;
|
|
std::atomic<size_t> irregular_block_num_;
|
|
|
|
char padding1[56] ROCKSDB_FIELD_UNUSED;
|
|
|
|
Shard* Repick();
|
|
|
|
size_t ShardAllocatedAndUnused() const {
|
|
size_t total = 0;
|
|
for (size_t i = 0; i < shards_.Size(); ++i) {
|
|
total += shards_.AccessAtCore(i)->allocated_and_unused_.load(
|
|
std::memory_order_relaxed);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
template <typename Func>
|
|
char* AllocateImpl(size_t bytes, bool force_arena, const Func& func) {
|
|
size_t cpu;
|
|
|
|
// Go directly to the arena if the allocation is too large, or if
|
|
// we've never needed to Repick() and the arena mutex is available
|
|
// with no waiting. This keeps the fragmentation penalty of
|
|
// concurrency zero unless it might actually confer an advantage.
|
|
std::unique_lock<SpinMutex> arena_lock(arena_mutex_, std::defer_lock);
|
|
if (bytes > shard_block_size_ / 4 || force_arena ||
|
|
((cpu = tls_cpuid) == 0 &&
|
|
!shards_.AccessAtCore(0)->allocated_and_unused_.load(
|
|
std::memory_order_relaxed) &&
|
|
arena_lock.try_lock())) {
|
|
if (!arena_lock.owns_lock()) {
|
|
arena_lock.lock();
|
|
}
|
|
auto rv = func();
|
|
Fixup();
|
|
return rv;
|
|
}
|
|
|
|
// pick a shard from which to allocate
|
|
Shard* s = shards_.AccessAtCore(cpu & (shards_.Size() - 1));
|
|
if (!s->mutex.try_lock()) {
|
|
s = Repick();
|
|
s->mutex.lock();
|
|
}
|
|
std::unique_lock<SpinMutex> lock(s->mutex, std::adopt_lock);
|
|
|
|
size_t avail = s->allocated_and_unused_.load(std::memory_order_relaxed);
|
|
if (avail < bytes) {
|
|
// reload
|
|
std::lock_guard<SpinMutex> reload_lock(arena_mutex_);
|
|
|
|
// If the arena's current block is within a factor of 2 of the right
|
|
// size, we adjust our request to avoid arena waste.
|
|
auto exact = arena_allocated_and_unused_.load(std::memory_order_relaxed);
|
|
assert(exact == arena_.AllocatedAndUnused());
|
|
|
|
if (exact >= bytes && arena_.IsInInlineBlock()) {
|
|
// If we haven't exhausted arena's inline block yet, allocate from arena
|
|
// directly. This ensures that we'll do the first few small allocations
|
|
// without allocating any blocks.
|
|
// In particular this prevents empty memtables from using
|
|
// disproportionately large amount of memory: a memtable allocates on
|
|
// the order of 1 KB of memory when created; we wouldn't want to
|
|
// allocate a full arena block (typically a few megabytes) for that,
|
|
// especially if there are thousands of empty memtables.
|
|
auto rv = func();
|
|
Fixup();
|
|
return rv;
|
|
}
|
|
|
|
avail = exact >= shard_block_size_ / 2 && exact < shard_block_size_ * 2
|
|
? exact
|
|
: shard_block_size_;
|
|
s->free_begin_ = arena_.AllocateAligned(avail);
|
|
Fixup();
|
|
}
|
|
s->allocated_and_unused_.store(avail - bytes, std::memory_order_relaxed);
|
|
|
|
char* rv;
|
|
if ((bytes % sizeof(void*)) == 0) {
|
|
// aligned allocation from the beginning
|
|
rv = s->free_begin_;
|
|
s->free_begin_ += bytes;
|
|
} else {
|
|
// unaligned from the end
|
|
rv = s->free_begin_ + avail - bytes;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
void Fixup() {
|
|
arena_allocated_and_unused_.store(arena_.AllocatedAndUnused(),
|
|
std::memory_order_relaxed);
|
|
memory_allocated_bytes_.store(arena_.MemoryAllocatedBytes(),
|
|
std::memory_order_relaxed);
|
|
irregular_block_num_.store(arena_.IrregularBlockNum(),
|
|
std::memory_order_relaxed);
|
|
}
|
|
|
|
ConcurrentArena(const ConcurrentArena&) = delete;
|
|
ConcurrentArena& operator=(const ConcurrentArena&) = delete;
|
|
};
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|