Commit cf17895f authored by Nathan Bronson's avatar Nathan Bronson Committed by Facebook Github Bot

optimize first insertion into F14 hash table

Summary:
This diff performs several micro-optimizations that improve
the lifecycle of a single-element F14FastMap or F14FastSet by about 10%
(in a single-threaded microbenchmark).  Lifecycle here means construction,
insertion of one entry, and then destruction, so it includes one malloc
and one free.

Reviewed By: yfeldblum

Differential Revision: D8771446

fbshipit-source-id: 95d59f32de5a450b16ecdcb0e39b7f566ce797da
parent 5c438fac
......@@ -529,6 +529,9 @@ class ValueContainerPolicy : public BasePolicy<
void
constructValueAtItem(std::size_t /*size*/, Item* itemAddr, Args&&... args) {
Alloc& a = this->alloc();
// This assume helps GCC and MSVC avoid a null-check in the subsequent
// placement new. GCC >= 6 and clang seem to figure it out themselves.
// MSVC as of 19 2017 still has the issue.
assume(itemAddr != nullptr);
AllocTraits::construct(a, itemAddr, std::forward<Args>(args)...);
}
......@@ -1106,8 +1109,9 @@ class VectorContainerPolicy : public BasePolicy<
void constructValueAtItem(std::size_t size, Item* itemAddr, Args&&... args) {
Alloc& a = this->alloc();
*itemAddr = size;
AllocTraits::construct(
a, std::addressof(values_[size]), std::forward<Args>(args)...);
auto dst = std::addressof(values_[size]);
assume(dst != nullptr);
AllocTraits::construct(a, dst, std::forward<Args>(args)...);
}
void moveItemDuringRehash(Item* itemAddr, Item& src) {
......
......@@ -1766,13 +1766,41 @@ class F14Table : public Policy {
}
FOLLY_NOINLINE void rehashImpl(
std::size_t newChunkCount,
std::size_t newMaxSizeWithoutRehash) {
FOLLY_SAFE_DCHECK(newMaxSizeWithoutRehash > 0, "");
std::size_t capacity,
std::size_t origChunkCount,
std::size_t origMaxSizeWithoutRehash) {
FOLLY_SAFE_DCHECK(capacity >= size(), "");
auto origChunks = chunks_;
const auto origChunkCount = chunkMask_ + 1;
const auto origMaxSizeWithoutRehash = bucket_count();
// compute new size
std::size_t const kInitialCapacity = 2;
std::size_t const kHalfChunkCapacity =
(Chunk::kDesiredCapacity / 2) & ~std::size_t{1};
std::size_t newMaxSizeWithoutRehash;
std::size_t newChunkCount;
if (capacity <= kHalfChunkCapacity) {
newChunkCount = 1;
newMaxSizeWithoutRehash =
(capacity < kInitialCapacity) ? kInitialCapacity : kHalfChunkCapacity;
} else {
newChunkCount = nextPowTwo(
(capacity + Chunk::kDesiredCapacity - 1) / Chunk::kDesiredCapacity);
newMaxSizeWithoutRehash = newChunkCount * Chunk::kDesiredCapacity;
constexpr std::size_t kMaxChunksWithoutCapacityOverflow =
(std::numeric_limits<std::size_t>::max)() / Chunk::kDesiredCapacity;
if (newChunkCount > kMaxChunksWithoutCapacityOverflow ||
newMaxSizeWithoutRehash > max_size()) {
throw_exception<std::bad_alloc>();
}
}
// is rehash needed?
if (origMaxSizeWithoutRehash == newMaxSizeWithoutRehash) {
return;
}
BytePtr rawAllocation;
auto undoState = this->beforeRehash(
......@@ -1900,38 +1928,10 @@ class F14Table : public Policy {
// user has no control over max_load_factor
void rehash(std::size_t capacity) {
if (capacity < size()) {
capacity = size();
}
std::size_t const kInitialCapacity = 2;
std::size_t const kHalfChunkCapacity =
(Chunk::kDesiredCapacity / 2) & ~std::size_t{1};
std::size_t maxSizeWithoutRehash;
std::size_t chunkCount;
if (capacity <= kInitialCapacity) {
chunkCount = 1;
maxSizeWithoutRehash = kInitialCapacity;
} else if (capacity <= kHalfChunkCapacity) {
chunkCount = 1;
maxSizeWithoutRehash = kHalfChunkCapacity;
} else {
chunkCount = folly::nextPowTwo(
(capacity + Chunk::kDesiredCapacity - 1) / Chunk::kDesiredCapacity);
maxSizeWithoutRehash = chunkCount * Chunk::kDesiredCapacity;
constexpr std::size_t kMaxChunksWithoutCapacityOverflow =
(std::numeric_limits<std::size_t>::max)() / Chunk::kDesiredCapacity;
if (UNLIKELY(
chunkCount > kMaxChunksWithoutCapacityOverflow ||
maxSizeWithoutRehash > max_size())) {
throw_exception<std::bad_alloc>();
}
}
if (bucket_count() != maxSizeWithoutRehash) {
rehashImpl(chunkCount, maxSizeWithoutRehash);
}
rehashImpl(
std::max<std::size_t>(capacity, size()),
chunkMask_ + 1,
bucket_count());
}
void reserve(std::size_t capacity) {
......@@ -1940,15 +1940,13 @@ class F14Table : public Policy {
// Returns true iff a rehash was performed
void reserveForInsert(size_t incoming = 1) {
if (size() + incoming - 1 >= bucket_count()) {
reserveForInsertImpl(incoming);
auto capacity = size() + incoming;
auto bc = bucket_count();
if (capacity - 1 >= bc) {
rehashImpl(capacity, chunkMask_ + 1, bc);
}
}
FOLLY_NOINLINE void reserveForInsertImpl(size_t incoming) {
rehash(size() + incoming);
}
// Returns pos,true if construct, pos,false if found. key is only used
// during the search; all constructor args for an inserted value come
// from args... key won't be accessed after args are touched.
......@@ -1956,9 +1954,11 @@ class F14Table : public Policy {
std::pair<ItemIter, bool> tryEmplaceValue(K const& key, Args&&... args) {
const auto hp = splitHash(this->computeKeyHash(key));
auto existing = findImpl(hp, key);
if (!existing.atEnd()) {
return std::make_pair(existing, false);
if (size() > 0) {
auto existing = findImpl(hp, key);
if (!existing.atEnd()) {
return std::make_pair(existing, false);
}
}
reserveForInsert();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment