Commit 64dabe32 authored by Xiao Shi's avatar Xiao Shi Committed by Facebook Github Bot

use F14NodeMap with heterogeneous lookup / mutation as StringKeyedUnorderedMap|Set

Summary:
`F14NodeMap` has proven to be a safe and more performant drop-in replacement
for `std::unordered_map`. With the ability to do heterogeneous lookup and
mutation, we no longer need `StringKeyedUnorderedMap`, whose main purpose was
to avoid unnecessary string copies during lookups and inserts.

Reviewed By: nbronson

Differential Revision: D9124737

fbshipit-source-id: 81d5be1df9096ac258a3dc1523a6a0f5d6b9e862
parent fa72983f
......@@ -13,226 +13,47 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2013-present Facebook. All Rights Reserved.
// @author: Pavlo Kushnir (pavlo)
#pragma once
#include <functional>
#include <initializer_list>
#include <memory>
#include <unordered_map>
#include <utility>
#include <folly/Range.h>
#include <folly/experimental/StringKeyedCommon.h>
#include <folly/hash/Hash.h>
#include <folly/container/F14Map.h>
namespace folly {
/**
* Wrapper class for unordered_map<string, Value> that can
* perform lookup operations with StringPiece, not only string.
*
* It uses kind of hack: string pointed by StringPiece is copied when
* StringPiece is inserted into map
*/
template <
class Value,
class Hash = Hash,
class Mapped,
class Hash = hasher<StringPiece>,
class Eq = std::equal_to<StringPiece>,
class Alloc = std::allocator<std::pair<const StringPiece, Value>>>
class StringKeyedUnorderedMap
: private std::unordered_map<StringPiece, Value, Hash, Eq, Alloc> {
private:
using Base = std::unordered_map<StringPiece, Value, Hash, Eq, Alloc>;
class Alloc = f14::DefaultAlloc<std::pair<std::string const, Mapped>>>
struct StringKeyedUnorderedMap : public F14NodeMap<
std::string,
Mapped,
transparent<Hash>,
transparent<Eq>,
Alloc> {
using Super = F14NodeMap<
std::string,
Mapped,
transparent<Hash>,
transparent<Eq>,
Alloc>;
public:
typedef typename Base::key_type key_type;
typedef typename Base::mapped_type mapped_type;
typedef typename Base::value_type value_type;
typedef typename Base::hasher hasher;
typedef typename Base::key_equal key_equal;
typedef typename Base::allocator_type allocator_type;
typedef typename Base::reference reference;
typedef typename Base::const_reference const_reference;
typedef typename Base::pointer pointer;
typedef typename Base::const_pointer const_pointer;
typedef typename Base::iterator iterator;
typedef typename Base::const_iterator const_iterator;
typedef typename Base::size_type size_type;
typedef typename Base::difference_type difference_type;
explicit StringKeyedUnorderedMap() = default;
explicit StringKeyedUnorderedMap(
size_type n,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type())
: Base(n, hf, eql, alloc) {
}
explicit StringKeyedUnorderedMap(const allocator_type& a)
: Base(a) {
}
template <class InputIterator>
StringKeyedUnorderedMap(InputIterator b, InputIterator e) {
for (; b != e; ++b) {
// insert() will carry the duplication
emplace(b->first, b->second);
}
}
template <class InputIterator>
StringKeyedUnorderedMap(
InputIterator b, InputIterator e,
size_type n,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type())
: Base(n, hf, eql, alloc) {
for (; b != e; ++b) {
// insert() will carry the duplication
emplace(b->first, b->second);
}
}
StringKeyedUnorderedMap(const StringKeyedUnorderedMap& rhs)
: StringKeyedUnorderedMap(rhs.begin(), rhs.end(),
rhs.bucket_count(),
rhs.hash_function(),
rhs.key_eq(),
rhs.get_allocator()) {
}
StringKeyedUnorderedMap(StringKeyedUnorderedMap&& rhs) noexcept
: StringKeyedUnorderedMap(std::move(rhs), rhs.get_allocator()) {
}
StringKeyedUnorderedMap(StringKeyedUnorderedMap&& other,
const allocator_type& /* a */) noexcept
: Base(std::move(other) /*, a*/ /* not supported by gcc */) {}
StringKeyedUnorderedMap(std::initializer_list<value_type> il)
: StringKeyedUnorderedMap(il.begin(), il.end()) {
}
StringKeyedUnorderedMap(
std::initializer_list<value_type> il,
size_type n,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type())
: StringKeyedUnorderedMap(il.begin(), il.end(), n, hf, eql, alloc) {
}
StringKeyedUnorderedMap& operator=(const StringKeyedUnorderedMap& other) & {
if (this == &other) {
return *this;
}
return *this = StringKeyedUnorderedMap(other);
}
StringKeyedUnorderedMap&
operator=(StringKeyedUnorderedMap&& other) & noexcept {
assert(this != &other);
clear();
Base::operator=(std::move(other));
return *this;
}
using Base::empty;
using Base::size;
using Base::max_size;
using Base::begin;
using Base::end;
using Base::cbegin;
using Base::cend;
bool operator==(StringKeyedUnorderedMap const& other) const {
Base const& lhs = *this;
Base const& rhs = static_cast<Base const&>(other);
return lhs == rhs;
}
void swap(StringKeyedUnorderedMap& other) & {
return Base::swap(other);
}
// No need for copy/move overload as StringPiece is small struct.
mapped_type& operator[](StringPiece key) {
auto it = find(key);
if (it != end()) {
return it->second;
}
// operator[] will create new (key, value) pair
// we need to allocate memory for key
return Base::operator[](stringPieceDup(key, get_allocator()));
}
using Base::at;
using Base::find;
using Base::count;
template <class... Args>
std::pair<iterator, bool> emplace(StringPiece key, Args&&... args) {
auto it = find(key);
if (it != end()) {
return {it, false};
}
return Base::emplace(stringPieceDup(key, get_allocator()),
std::forward<Args>(args)...);
}
std::pair<iterator, bool> insert(value_type val) {
auto it = find(val.first);
if (it != end()) {
return {it, false};
}
auto valCopy = std::make_pair(stringPieceDup(val.first, get_allocator()),
std::move(val.second));
return Base::insert(valCopy);
}
iterator erase(const_iterator position) {
auto key = position->first;
auto result = Base::erase(position);
stringPieceDel(key, get_allocator());
return result;
}
size_type erase(StringPiece key) {
auto it = find(key);
if (it == end()) {
return 0;
}
erase(it);
return 1;
}
void clear() noexcept {
for (auto& it : *this) {
stringPieceDel(it.first, get_allocator());
}
Base::clear();
}
using Base::reserve;
using Base::hash_function;
using Base::key_eq;
using Base::get_allocator;
using Base::bucket_count;
using Base::max_bucket_count;
using Base::bucket_size;
using Base::bucket;
~StringKeyedUnorderedMap() {
// Here we assume that unordered_map doesn't use keys in destructor
for (auto& it : *this) {
stringPieceDel(it.first, get_allocator());
}
using Super::Super;
StringKeyedUnorderedMap() : Super() {}
// TODO(T31574848): Work around libstdc++ versions (e.g., GCC < 6) with no
// implementation of N4387 ("perfect initialization" for pairs and tuples) to
// support existing callsites that list-initialize:
// m.insert({sp, x});
std::pair<typename Super::iterator, bool> insert(
std::pair<StringPiece, Mapped> const& p) {
return this->emplace(p.first, p.second);
}
std::pair<typename Super::iterator, bool> insert(
std::pair<StringPiece, Mapped>&& p) {
return this->emplace(std::move(p));
}
};
......
......@@ -13,221 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2013-present Facebook. All Rights Reserved.
// @author: Pavlo Kushnir (pavlo)
#pragma once
#include <functional>
#include <initializer_list>
#include <memory>
#include <unordered_set>
#include <utility>
#include <folly/Range.h>
#include <folly/experimental/StringKeyedCommon.h>
#include <folly/hash/Hash.h>
#include <folly/container/F14Set.h>
namespace folly {
/**
* Wrapper class for unordered_set<string> that can
* perform lookup operations with StringPiece, not only string.
*
* It uses kind of hack: string pointed by StringPiece is copied when
* StringPiece is inserted into set
*/
template <
class Hasher = Hash,
class Hash = hasher<StringPiece>,
class Eq = std::equal_to<StringPiece>,
class Alloc = std::allocator<folly::StringPiece>>
class BasicStringKeyedUnorderedSet
: private std::unordered_set<StringPiece, Hasher, Eq, Alloc> {
using Base = std::unordered_set<StringPiece, Hasher, Eq, Alloc>;
public:
typedef typename Base::key_type key_type;
typedef typename Base::value_type value_type;
typedef typename Base::hasher hasher;
typedef typename Base::key_equal key_equal;
typedef typename Base::allocator_type allocator_type;
typedef typename Base::reference reference;
typedef typename Base::const_reference const_reference;
typedef typename Base::pointer pointer;
typedef typename Base::const_pointer const_pointer;
typedef typename Base::iterator iterator;
typedef typename Base::const_iterator const_iterator;
typedef typename Base::size_type size_type;
typedef typename Base::difference_type difference_type;
// Constructors in the same order as in
// http://cplusplus.com/reference/unordered_set/unordered_set/unordered_set/
explicit BasicStringKeyedUnorderedSet() {
}
explicit BasicStringKeyedUnorderedSet(
size_type n,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type())
: Base(n, hf, eql, alloc) {
}
explicit BasicStringKeyedUnorderedSet(const allocator_type& alloc)
: Base(alloc) {
}
template <class InputIterator>
BasicStringKeyedUnorderedSet(InputIterator b, InputIterator e) {
for (; b != e; ++b) {
emplace(*b);
}
}
template <class InputIterator>
BasicStringKeyedUnorderedSet(
InputIterator b, InputIterator e,
size_type n,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type())
: Base(n, hf, eql, alloc) {
for (; b != e; ++b) {
emplace(*b);
}
}
BasicStringKeyedUnorderedSet(const BasicStringKeyedUnorderedSet& rhs)
: BasicStringKeyedUnorderedSet(rhs, rhs.get_allocator()) {
}
BasicStringKeyedUnorderedSet(const BasicStringKeyedUnorderedSet& rhs,
const allocator_type& a)
: BasicStringKeyedUnorderedSet(rhs.begin(),
rhs.end(),
rhs.bucket_count(),
rhs.hash_function(),
rhs.key_eq(),
a) {
}
BasicStringKeyedUnorderedSet(BasicStringKeyedUnorderedSet&& rhs) noexcept
: Base(std::move(rhs)) {
assert(rhs.empty());
}
BasicStringKeyedUnorderedSet(BasicStringKeyedUnorderedSet&& rhs,
const allocator_type& /* a */) noexcept
: Base(std::move(rhs) /* , a */ /* not supported by gcc */) {
assert(rhs.empty());
}
BasicStringKeyedUnorderedSet(std::initializer_list<value_type> il)
: BasicStringKeyedUnorderedSet(il.begin(), il.end()) {
}
BasicStringKeyedUnorderedSet(
std::initializer_list<value_type> il,
size_type n,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& alloc = allocator_type())
: BasicStringKeyedUnorderedSet(il.begin(), il.end(), n, hf, eql, alloc) {
}
BasicStringKeyedUnorderedSet&
operator=(const BasicStringKeyedUnorderedSet& rhs) & {
if (this == &rhs) {
return *this;
}
// Cost is as bad as a full copy, so to it via copy + move
return *this = BasicStringKeyedUnorderedSet(rhs);
}
BasicStringKeyedUnorderedSet&
operator=(BasicStringKeyedUnorderedSet&& rhs) & noexcept {
assert(this != &rhs);
clear();
Base::operator=(std::move(rhs));
return *this;
}
using Base::empty;
using Base::size;
using Base::max_size;
using Base::begin;
using Base::end;
using Base::cbegin;
using Base::cend;
using Base::find;
using Base::count;
bool operator==(const BasicStringKeyedUnorderedSet& other) const {
Base const& lhs = *this;
Base const& rhs = static_cast<Base const&>(other);
return lhs == rhs;
}
template <class... Args>
std::pair<iterator, bool> emplace(Args&&... args) {
auto key = StringPiece(std::forward<Args>(args)...);
auto it = find(key);
return it != end()
? std::make_pair(it, false)
: Base::emplace(stringPieceDup(key, get_allocator()));
}
std::pair<iterator, bool> insert(value_type val) {
auto it = find(val);
return it != end()
? std::make_pair(it, false)
: Base::insert(stringPieceDup(val, get_allocator()));
}
iterator erase(const_iterator position) {
auto key = *position;
auto result = Base::erase(position);
stringPieceDel(key, get_allocator());
return result;
}
size_type erase(folly::StringPiece key) {
auto it = find(key);
if (it == end()) {
return 0;
}
erase(it);
return 1;
}
void clear() noexcept {
for (auto& it : *this) {
stringPieceDel(it, get_allocator());
}
Base::clear();
}
using Base::reserve;
using Base::hash_function;
using Base::key_eq;
using Base::get_allocator;
using Base::bucket_count;
using Base::max_bucket_count;
using Base::bucket_size;
using Base::bucket;
void swap(BasicStringKeyedUnorderedSet& other) & {
return Base::swap(other);
}
~BasicStringKeyedUnorderedSet() {
// Here we assume that unordered_set doesn't use keys in destructor
for (auto& it : *this) {
stringPieceDel(it, get_allocator());
}
}
};
class Alloc = f14::DefaultAlloc<std::string>>
using BasicStringKeyedUnorderedSet =
F14NodeSet<std::string, transparent<Hash>, transparent<Eq>, Alloc>;
typedef BasicStringKeyedUnorderedSet<> StringKeyedUnorderedSet;
using StringKeyedUnorderedSet = BasicStringKeyedUnorderedSet<>;
} // namespace folly
......@@ -115,12 +115,12 @@ using KeyValuePairLeakChecker = MemoryLeakCheckerAllocator<
using ValueLeakChecker =
MemoryLeakCheckerAllocator<std::allocator<StringPiece>>;
typedef StringKeyedUnorderedMap<
using LeakCheckedUnorderedMap = StringKeyedUnorderedMap<
int,
folly::Hash,
folly::hasher<StringPiece>,
std::equal_to<StringPiece>,
KeyValuePairLeakChecker>
LeakCheckedUnorderedMap;
MemoryLeakCheckerAllocator<
std::allocator<std::pair<const std::string, int>>>>;
typedef StringKeyedSetBase<std::less<StringPiece>, ValueLeakChecker>
LeakCheckedSet;
......@@ -129,9 +129,9 @@ typedef StringKeyedMap<int, std::less<StringPiece>, KeyValuePairLeakChecker>
LeakCheckedMap;
using LeakCheckedUnorderedSet = BasicStringKeyedUnorderedSet<
folly::Hash,
folly::hasher<StringPiece>,
std::equal_to<folly::StringPiece>,
ValueLeakChecker>;
MemoryLeakCheckerAllocator<std::allocator<std::string>>>;
TEST(StringKeyedUnorderedMapTest, sanity) {
LeakCheckedUnorderedMap map;
......
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