Switch folly to using a submodule instead of a fork.
To sync the folly submodule the first time, you'll need to issue: git submodule init git submodule update From then on, you'll also want to make sure folly is up to date by issuing `git submodule update` after a `git pull`.
Esse commit está contido em:
@@ -0,0 +1,3 @@
|
||||
[submodule "hphp/submodules/folly"]
|
||||
path = hphp/submodules/folly
|
||||
url = git://github.com/facebook/folly.git
|
||||
@@ -0,0 +1,31 @@
|
||||
# folly-config.h is a generated file from autotools
|
||||
# We need to do the equivalent checks here and use
|
||||
# add_definitions as needed
|
||||
add_definitions(-DFOLLY_NO_CONFIG=1)
|
||||
|
||||
INCLUDE(CheckCXXSourceCompiles)
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
extern \"C\" void (*test_ifunc(void))() { return 0; }
|
||||
void func() __attribute__((ifunc(\"test_ifunc\")));
|
||||
" FOLLY_IFUNC)
|
||||
if (FOLLY_IFUNC)
|
||||
add_definitions("-DHAVE_IFUNC=1")
|
||||
endif()
|
||||
|
||||
set(CMAKE_REQUIRED_LIBRARIES rt)
|
||||
CHECK_CXX_SOURCE_COMPILES("#include <time.h>
|
||||
int main() {
|
||||
clock_gettime((clockid_t)0, NULL);
|
||||
return 0;
|
||||
}" HAVE_CLOCK_GETTIME)
|
||||
|
||||
if (HAVE_CLOCK_GETTIME)
|
||||
add_definitions("-DFOLLY_HAVE_CLOCK_GETTIME=1")
|
||||
endif()
|
||||
set(CMAKE_REQUIRED_LIBRARIES)
|
||||
|
||||
find_path(FEATURES_H_INCLUDE_DIR NAMES features.h)
|
||||
if (FEATURES_H_INCLUDE_DIR)
|
||||
include_directories("${FEATURES_H_INCLUDE_DIR}")
|
||||
add_definitions("-DFOLLY_HAVE_FEATURES_H=1")
|
||||
endif()
|
||||
@@ -16,6 +16,7 @@
|
||||
#
|
||||
|
||||
include(HPHPSetup)
|
||||
include(FollySetup)
|
||||
|
||||
# HHVM Build
|
||||
SET(USE_HHVM TRUE)
|
||||
|
||||
Submódulo
+1
Submodule hphp/submodules/folly added at 78d1c04a40
+13
-14
@@ -1,10 +1,9 @@
|
||||
# folly-config.h is a generated file from autotools
|
||||
# We need to do the equivalent checks here and use
|
||||
# add_definitions as needed
|
||||
add_definitions(-DFOLLY_NO_CONFIG=1)
|
||||
|
||||
set(FOLLY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/folly")
|
||||
|
||||
if (NOT EXISTS "${FOLLY_DIR}/Portability.h")
|
||||
message(FATAL_ERROR "${FOLLY_DIR}/Portability.h missing, did you forget to run `git submodule init && git submodule update`?")
|
||||
endif()
|
||||
|
||||
# Generated files from folly/build/generate_*.py
|
||||
auto_sources(genfiles "*.cpp" "RECURSE" "${CMAKE_CURRENT_SOURCE_DIR}/gen")
|
||||
|
||||
@@ -20,11 +19,15 @@ foreach (file ${files})
|
||||
endforeach()
|
||||
list(REMOVE_ITEM files
|
||||
${FOLLY_DIR}/Benchmark.cpp
|
||||
${FOLLY_DIR}/build/GenerateFingerprintTables.cpp
|
||||
${FOLLY_DIR}/detail/Clock.cpp
|
||||
${FOLLY_DIR}/experimental/File.cpp
|
||||
${FOLLY_DIR}/experimental/exception_tracer/ExceptionTracer.cpp
|
||||
${FOLLY_DIR}/experimental/exception_tracer/ExceptionTracerBenchmark.cpp
|
||||
${FOLLY_DIR}/experimental/exception_tracer/ExceptionTracerLib.cpp
|
||||
${FOLLY_DIR}/experimental/exception_tracer/ExceptionTracerTest.cpp
|
||||
${FOLLY_DIR}/experimental/io/AsyncIO.cpp
|
||||
${FOLLY_DIR}/experimental/io/HugePageUtil.cpp
|
||||
${FOLLY_DIR}/experimental/symbolizer/SymbolizerTest.cpp
|
||||
${FOLLY_DIR}/experimental/symbolizer/ElfUtil.cpp
|
||||
)
|
||||
@@ -40,6 +43,11 @@ endif()
|
||||
# and some other folly pieces we're not including yet
|
||||
# For now, that's not actually a requirement, so skip it
|
||||
list(REMOVE_ITEM files ${FOLLY_DIR}/Subprocess.cpp)
|
||||
|
||||
# io/Compression requires snappy library
|
||||
# Don't add dep until we need it
|
||||
list(REMOVE_ITEM files ${FOLLY_DIR}/io/Compression.cpp)
|
||||
|
||||
add_library(folly STATIC ${files} ${genfiles} ${cfiles} )
|
||||
|
||||
find_package(Boost 1.48.0 COMPONENTS system program_options filesystem regex REQUIRED)
|
||||
@@ -52,15 +60,6 @@ include_directories(${LIBGLOG_INCLUDE_DIR})
|
||||
find_package(PThread REQUIRED)
|
||||
include_directories(${LIBPTHREAD_INCLUDE_DIRS})
|
||||
|
||||
INCLUDE(CheckCXXSourceCompiles)
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
extern \"C\" void (*test_ifunc(void))() { return 0; }
|
||||
void func() __attribute__((ifunc(\"test_ifunc\")));
|
||||
" FOLLY_IFUNC)
|
||||
if (FOLLY_IFUNC)
|
||||
add_definitions("-DHAVE_IFUNC=1")
|
||||
endif()
|
||||
|
||||
target_link_libraries(folly ${Boost_LIBRARIES}
|
||||
${LIBGLOG_LIBRARY}
|
||||
${LIBPTHREAD_LIBRARIES})
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../submodules/folly/folly
|
||||
-111
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Defines a function folly::applyTuple, which takes a function and a
|
||||
* std::tuple of arguments and calls the function with those
|
||||
* arguments.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* int x = folly::applyTuple(std::plus<int>(), std::make_tuple(12, 12));
|
||||
* ASSERT(x == 24);
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_APPLYTUPLE_H_
|
||||
#define FOLLY_APPLYTUPLE_H_
|
||||
|
||||
#include <tuple>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
namespace folly {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
// This is to allow using this with pointers to member functions,
|
||||
// where the first argument in the tuple will be the this pointer.
|
||||
template<class F> F& makeCallable(F& f) { return f; }
|
||||
template<class R, class C, class ...A>
|
||||
auto makeCallable(R (C::*d)(A...)) -> decltype(std::mem_fn(d)) {
|
||||
return std::mem_fn(d);
|
||||
}
|
||||
|
||||
template<class Tuple>
|
||||
struct DerefSize
|
||||
: std::tuple_size<typename std::remove_reference<Tuple>::type>
|
||||
{};
|
||||
|
||||
// CallTuple recursively unpacks tuple arguments so we can forward
|
||||
// them into the function.
|
||||
template<class Ret>
|
||||
struct CallTuple {
|
||||
template<class F, class Tuple, class ...Unpacked>
|
||||
static typename std::enable_if<
|
||||
(sizeof...(Unpacked) < DerefSize<Tuple>::value),
|
||||
Ret
|
||||
>::type call(const F& f, Tuple&& t, Unpacked&&... unp) {
|
||||
typedef typename std::tuple_element<
|
||||
sizeof...(Unpacked),
|
||||
typename std::remove_reference<Tuple>::type
|
||||
>::type ElementType;
|
||||
return CallTuple<Ret>::call(f, std::forward<Tuple>(t),
|
||||
std::forward<Unpacked>(unp)...,
|
||||
std::forward<ElementType>(std::get<sizeof...(Unpacked)>(t))
|
||||
);
|
||||
}
|
||||
|
||||
template<class F, class Tuple, class ...Unpacked>
|
||||
static typename std::enable_if<
|
||||
(sizeof...(Unpacked) == DerefSize<Tuple>::value),
|
||||
Ret
|
||||
>::type call(const F& f, Tuple&& t, Unpacked&&... unp) {
|
||||
return makeCallable(f)(std::forward<Unpacked>(unp)...);
|
||||
}
|
||||
};
|
||||
|
||||
// The point of this meta function is to extract the contents of the
|
||||
// tuple as a parameter pack so we can pass it into std::result_of<>.
|
||||
template<class F, class Args> struct ReturnValue {};
|
||||
template<class F, class ...Args>
|
||||
struct ReturnValue<F,std::tuple<Args...>> {
|
||||
typedef typename std::result_of<F (Args...)>::type type;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<class Callable, class Tuple>
|
||||
typename detail::ReturnValue<
|
||||
typename std::decay<Callable>::type,
|
||||
typename std::remove_reference<Tuple>::type
|
||||
>::type
|
||||
applyTuple(const Callable& c, Tuple&& t) {
|
||||
typedef typename detail::ReturnValue<
|
||||
typename std::decay<Callable>::type,
|
||||
typename std::remove_reference<Tuple>::type
|
||||
>::type RetT;
|
||||
return detail::CallTuple<RetT>::call(c, std::forward<Tuple>(t));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_ARENA_H_
|
||||
#error This file may only be included from Arena.h
|
||||
#endif
|
||||
|
||||
// Implementation of Arena.h functions
|
||||
|
||||
namespace folly {
|
||||
|
||||
template <class Alloc>
|
||||
std::pair<typename Arena<Alloc>::Block*, size_t>
|
||||
Arena<Alloc>::Block::allocate(Alloc& alloc, size_t size, bool allowSlack) {
|
||||
size_t allocSize = sizeof(Block) + size;
|
||||
if (allowSlack) {
|
||||
allocSize = ArenaAllocatorTraits<Alloc>::goodSize(alloc, allocSize);
|
||||
}
|
||||
|
||||
void* mem = alloc.allocate(allocSize);
|
||||
assert(isAligned(mem));
|
||||
return std::make_pair(new (mem) Block(), allocSize - sizeof(Block));
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
void Arena<Alloc>::Block::deallocate(Alloc& alloc) {
|
||||
this->~Block();
|
||||
alloc.deallocate(this);
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
void* Arena<Alloc>::allocateSlow(size_t size) {
|
||||
std::pair<Block*, size_t> p;
|
||||
char* start;
|
||||
|
||||
|
||||
size_t allocSize = std::max(size, minBlockSize()) + sizeof(Block);
|
||||
if(sizeLimit_ && allocSize > sizeLimit_ - totalAllocatedSize_) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
if (size > minBlockSize()) {
|
||||
// Allocate a large block for this chunk only, put it at the back of the
|
||||
// list so it doesn't get used for small allocations; don't change ptr_
|
||||
// and end_, let them point into a normal block (or none, if they're
|
||||
// null)
|
||||
p = Block::allocate(alloc(), size, false);
|
||||
start = p.first->start();
|
||||
blocks_.push_back(*p.first);
|
||||
} else {
|
||||
// Allocate a normal sized block and carve out size bytes from it
|
||||
p = Block::allocate(alloc(), minBlockSize(), true);
|
||||
start = p.first->start();
|
||||
blocks_.push_front(*p.first);
|
||||
ptr_ = start + size;
|
||||
end_ = start + p.second;
|
||||
}
|
||||
|
||||
assert(p.second >= size);
|
||||
totalAllocatedSize_ += p.second + sizeof(Block);
|
||||
return start;
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
void Arena<Alloc>::merge(Arena<Alloc>&& other) {
|
||||
blocks_.splice_after(blocks_.before_begin(), other.blocks_);
|
||||
other.blocks_.clear();
|
||||
other.ptr_ = other.end_ = nullptr;
|
||||
totalAllocatedSize_ += other.totalAllocatedSize_;
|
||||
other.totalAllocatedSize_ = 0;
|
||||
}
|
||||
|
||||
template <class Alloc>
|
||||
Arena<Alloc>::~Arena() {
|
||||
auto disposer = [this] (Block* b) { b->deallocate(this->alloc()); };
|
||||
while (!blocks_.empty()) {
|
||||
blocks_.pop_front_and_dispose(disposer);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
-248
@@ -1,248 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_ARENA_H_
|
||||
#define FOLLY_ARENA_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include <limits>
|
||||
#include <boost/intrusive/slist.hpp>
|
||||
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/Malloc.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Simple arena: allocate memory which gets freed when the arena gets
|
||||
* destroyed.
|
||||
*
|
||||
* The arena itself allocates memory using a custom allocator which provides
|
||||
* the following interface (same as required by StlAllocator in StlAllocator.h)
|
||||
*
|
||||
* void* allocate(size_t size);
|
||||
* Allocate a block of size bytes, properly aligned to the maximum
|
||||
* alignment required on your system; throw std::bad_alloc if the
|
||||
* allocation can't be satisfied.
|
||||
*
|
||||
* void deallocate(void* ptr);
|
||||
* Deallocate a previously allocated block.
|
||||
*
|
||||
* You may also specialize ArenaAllocatorTraits for your allocator type to
|
||||
* provide:
|
||||
*
|
||||
* size_t goodSize(const Allocator& alloc, size_t size) const;
|
||||
* Return a size (>= the provided size) that is considered "good" for your
|
||||
* allocator (for example, if your allocator allocates memory in 4MB
|
||||
* chunks, size should be rounded up to 4MB). The provided value is
|
||||
* guaranteed to be rounded up to a multiple of the maximum alignment
|
||||
* required on your system; the returned value must be also.
|
||||
*
|
||||
* An implementation that uses malloc() / free() is defined below, see
|
||||
* SysAlloc / SysArena.
|
||||
*/
|
||||
template <class Alloc> struct ArenaAllocatorTraits;
|
||||
template <class Alloc>
|
||||
class Arena {
|
||||
public:
|
||||
explicit Arena(const Alloc& alloc,
|
||||
size_t minBlockSize = kDefaultMinBlockSize,
|
||||
size_t sizeLimit = 0)
|
||||
: allocAndSize_(alloc, minBlockSize)
|
||||
, ptr_(nullptr)
|
||||
, end_(nullptr)
|
||||
, totalAllocatedSize_(0)
|
||||
, bytesUsed_(0)
|
||||
, sizeLimit_(sizeLimit) {
|
||||
}
|
||||
|
||||
~Arena();
|
||||
|
||||
void* allocate(size_t size) {
|
||||
size = roundUp(size);
|
||||
bytesUsed_ += size;
|
||||
|
||||
if (LIKELY(end_ - ptr_ >= size)) {
|
||||
// Fast path: there's enough room in the current block
|
||||
char* r = ptr_;
|
||||
ptr_ += size;
|
||||
assert(isAligned(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
// Not enough room in the current block
|
||||
void* r = allocateSlow(size);
|
||||
assert(isAligned(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
void deallocate(void* p) {
|
||||
// Deallocate? Never!
|
||||
}
|
||||
|
||||
// Transfer ownership of all memory allocated from "other" to "this".
|
||||
void merge(Arena&& other);
|
||||
|
||||
// Gets the total memory used by the arena
|
||||
size_t totalSize() const {
|
||||
return totalAllocatedSize_ + sizeof(Arena);
|
||||
}
|
||||
|
||||
// Gets the total number of "used" bytes, i.e. bytes that the arena users
|
||||
// allocated via the calls to `allocate`. Doesn't include fragmentation, e.g.
|
||||
// if block size is 4KB and you allocate 2 objects of 3KB in size,
|
||||
// `bytesUsed()` will be 6KB, while `totalSize()` will be 8KB+.
|
||||
size_t bytesUsed() const {
|
||||
return bytesUsed_;
|
||||
}
|
||||
|
||||
private:
|
||||
// not copyable
|
||||
Arena(const Arena&) = delete;
|
||||
Arena& operator=(const Arena&) = delete;
|
||||
|
||||
// movable
|
||||
Arena(Arena&&) = default;
|
||||
Arena& operator=(Arena&&) = default;
|
||||
|
||||
struct Block;
|
||||
typedef boost::intrusive::slist_member_hook<
|
||||
boost::intrusive::tag<Arena>> BlockLink;
|
||||
|
||||
struct Block {
|
||||
BlockLink link;
|
||||
|
||||
// Allocate a block with at least size bytes of storage.
|
||||
// If allowSlack is true, allocate more than size bytes if convenient
|
||||
// (via ArenaAllocatorTraits::goodSize()) as we'll try to pack small
|
||||
// allocations in this block.
|
||||
static std::pair<Block*, size_t> allocate(
|
||||
Alloc& alloc, size_t size, bool allowSlack);
|
||||
void deallocate(Alloc& alloc);
|
||||
|
||||
char* start() {
|
||||
return reinterpret_cast<char*>(this + 1);
|
||||
}
|
||||
|
||||
private:
|
||||
Block() { }
|
||||
~Block() { }
|
||||
} __attribute__((aligned));
|
||||
// This should be alignas(std::max_align_t) but neither alignas nor
|
||||
// max_align_t are supported by gcc 4.6.2.
|
||||
|
||||
public:
|
||||
static constexpr size_t kDefaultMinBlockSize = 4096 - sizeof(Block);
|
||||
|
||||
private:
|
||||
static constexpr size_t maxAlign = alignof(Block);
|
||||
static constexpr bool isAligned(uintptr_t address) {
|
||||
return (address & (maxAlign - 1)) == 0;
|
||||
}
|
||||
static bool isAligned(void* p) {
|
||||
return isAligned(reinterpret_cast<uintptr_t>(p));
|
||||
}
|
||||
|
||||
// Round up size so it's properly aligned
|
||||
static constexpr size_t roundUp(size_t size) {
|
||||
return (size + maxAlign - 1) & ~(maxAlign - 1);
|
||||
}
|
||||
|
||||
// cache_last<true> makes the list keep a pointer to the last element, so we
|
||||
// have push_back() and constant time splice_after()
|
||||
typedef boost::intrusive::slist<
|
||||
Block,
|
||||
boost::intrusive::member_hook<Block, BlockLink, &Block::link>,
|
||||
boost::intrusive::constant_time_size<false>,
|
||||
boost::intrusive::cache_last<true>> BlockList;
|
||||
|
||||
void* allocateSlow(size_t size);
|
||||
|
||||
// Empty member optimization: package Alloc with a non-empty member
|
||||
// in case Alloc is empty (as it is in the case of SysAlloc).
|
||||
struct AllocAndSize : public Alloc {
|
||||
explicit AllocAndSize(const Alloc& a, size_t s)
|
||||
: Alloc(a), minBlockSize(s) {
|
||||
}
|
||||
|
||||
size_t minBlockSize;
|
||||
};
|
||||
|
||||
size_t minBlockSize() const {
|
||||
return allocAndSize_.minBlockSize;
|
||||
}
|
||||
Alloc& alloc() { return allocAndSize_; }
|
||||
const Alloc& alloc() const { return allocAndSize_; }
|
||||
|
||||
AllocAndSize allocAndSize_;
|
||||
BlockList blocks_;
|
||||
char* ptr_;
|
||||
char* end_;
|
||||
size_t totalAllocatedSize_;
|
||||
size_t bytesUsed_;
|
||||
size_t sizeLimit_;
|
||||
};
|
||||
|
||||
/**
|
||||
* By default, don't pad the given size.
|
||||
*/
|
||||
template <class Alloc>
|
||||
struct ArenaAllocatorTraits {
|
||||
static size_t goodSize(const Alloc& alloc, size_t size) {
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Arena-compatible allocator that calls malloc() and free(); see
|
||||
* goodMallocSize() in Malloc.h for goodSize().
|
||||
*/
|
||||
class SysAlloc {
|
||||
public:
|
||||
void* allocate(size_t size) {
|
||||
return checkedMalloc(size);
|
||||
}
|
||||
|
||||
void deallocate(void* p) {
|
||||
free(p);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ArenaAllocatorTraits<SysAlloc> {
|
||||
static size_t goodSize(const SysAlloc& alloc, size_t size) {
|
||||
return goodMallocSize(size);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Arena that uses the system allocator (malloc / free)
|
||||
*/
|
||||
class SysArena : public Arena<SysAlloc> {
|
||||
public:
|
||||
explicit SysArena(
|
||||
size_t minBlockSize = kDefaultMinBlockSize,
|
||||
size_t sizeLimit = 0)
|
||||
: Arena<SysAlloc>(SysAlloc(), minBlockSize, sizeLimit) {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#include "folly/Arena-inl.h"
|
||||
|
||||
#endif /* FOLLY_ARENA_H_ */
|
||||
@@ -1,379 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_ATOMICHASHARRAY_H_
|
||||
#error "This should only be included by AtomicHashArray.h"
|
||||
#endif
|
||||
|
||||
#include "folly/Bits.h"
|
||||
#include "folly/detail/AtomicHashUtils.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
// AtomicHashArray private constructor --
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
AtomicHashArray(size_t capacity, KeyT emptyKey, KeyT lockedKey,
|
||||
KeyT erasedKey, double maxLoadFactor, size_t cacheSize)
|
||||
: capacity_(capacity), maxEntries_(size_t(maxLoadFactor * capacity_ + 0.5)),
|
||||
kEmptyKey_(emptyKey), kLockedKey_(lockedKey), kErasedKey_(erasedKey),
|
||||
kAnchorMask_(nextPowTwo(capacity_) - 1), numEntries_(0, cacheSize),
|
||||
numPendingEntries_(0, cacheSize), isFull_(0), numErases_(0) {
|
||||
}
|
||||
|
||||
/*
|
||||
* findInternal --
|
||||
*
|
||||
* Sets ret.second to value found and ret.index to index
|
||||
* of key and returns true, or if key does not exist returns false and
|
||||
* ret.index is set to capacity_.
|
||||
*/
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::SimpleRetT
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
findInternal(const KeyT key_in) {
|
||||
DCHECK_NE(key_in, kEmptyKey_);
|
||||
DCHECK_NE(key_in, kLockedKey_);
|
||||
DCHECK_NE(key_in, kErasedKey_);
|
||||
for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0;
|
||||
;
|
||||
idx = probeNext(idx, numProbes)) {
|
||||
const KeyT key = acquireLoadKey(cells_[idx]);
|
||||
if (LIKELY(EqualFcn()(key, key_in))) {
|
||||
return SimpleRetT(idx, true);
|
||||
}
|
||||
if (UNLIKELY(key == kEmptyKey_)) {
|
||||
// if we hit an empty element, this key does not exist
|
||||
return SimpleRetT(capacity_, false);
|
||||
}
|
||||
++numProbes;
|
||||
if (UNLIKELY(numProbes >= capacity_)) {
|
||||
// probed every cell...fail
|
||||
return SimpleRetT(capacity_, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* insertInternal --
|
||||
*
|
||||
* Returns false on failure due to key collision or full.
|
||||
* Also sets ret.index to the index of the key. If the map is full, sets
|
||||
* ret.index = capacity_. Also sets ret.second to cell value, thus if insert
|
||||
* successful this will be what we just inserted, if there is a key collision
|
||||
* this will be the previously inserted value, and if the map is full it is
|
||||
* default.
|
||||
*/
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
template <class T>
|
||||
typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::SimpleRetT
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
insertInternal(KeyT key_in, T&& value) {
|
||||
const short NO_NEW_INSERTS = 1;
|
||||
const short NO_PENDING_INSERTS = 2;
|
||||
CHECK_NE(key_in, kEmptyKey_);
|
||||
CHECK_NE(key_in, kLockedKey_);
|
||||
CHECK_NE(key_in, kErasedKey_);
|
||||
|
||||
size_t idx = keyToAnchorIdx(key_in);
|
||||
size_t numProbes = 0;
|
||||
for (;;) {
|
||||
DCHECK_LT(idx, capacity_);
|
||||
value_type* cell = &cells_[idx];
|
||||
if (relaxedLoadKey(*cell) == kEmptyKey_) {
|
||||
// NOTE: isFull_ is set based on numEntries_.readFast(), so it's
|
||||
// possible to insert more than maxEntries_ entries. However, it's not
|
||||
// possible to insert past capacity_.
|
||||
++numPendingEntries_;
|
||||
if (isFull_.load(std::memory_order_acquire)) {
|
||||
--numPendingEntries_;
|
||||
|
||||
// Before deciding whether this insert succeeded, this thread needs to
|
||||
// wait until no other thread can add a new entry.
|
||||
|
||||
// Correctness assumes isFull_ is true at this point. If
|
||||
// another thread now does ++numPendingEntries_, we expect it
|
||||
// to pass the isFull_.load() test above. (It shouldn't insert
|
||||
// a new entry.)
|
||||
FOLLY_SPIN_WAIT(
|
||||
isFull_.load(std::memory_order_acquire) != NO_PENDING_INSERTS
|
||||
&& numPendingEntries_.readFull() != 0
|
||||
);
|
||||
isFull_.store(NO_PENDING_INSERTS, std::memory_order_release);
|
||||
|
||||
if (relaxedLoadKey(*cell) == kEmptyKey_) {
|
||||
// Don't insert past max load factor
|
||||
return SimpleRetT(capacity_, false);
|
||||
}
|
||||
} else {
|
||||
// An unallocated cell. Try once to lock it. If we succeed, insert here.
|
||||
// If we fail, fall through to comparison below; maybe the insert that
|
||||
// just beat us was for this very key....
|
||||
if (tryLockCell(cell)) {
|
||||
// Write the value - done before unlocking
|
||||
try {
|
||||
DCHECK(relaxedLoadKey(*cell) == kLockedKey_);
|
||||
/*
|
||||
* This happens using the copy constructor because we won't have
|
||||
* constructed a lhs to use an assignment operator on when
|
||||
* values are being set.
|
||||
*/
|
||||
new (&cell->second) ValueT(std::forward<T>(value));
|
||||
unlockCell(cell, key_in); // Sets the new key
|
||||
} catch (...) {
|
||||
// Transition back to empty key---requires handling
|
||||
// locked->empty below.
|
||||
unlockCell(cell, kEmptyKey_);
|
||||
--numPendingEntries_;
|
||||
throw;
|
||||
}
|
||||
// Direct comparison rather than EqualFcn ok here
|
||||
// (we just inserted it)
|
||||
DCHECK(relaxedLoadKey(*cell) == key_in);
|
||||
--numPendingEntries_;
|
||||
++numEntries_; // This is a thread cached atomic increment :)
|
||||
if (numEntries_.readFast() >= maxEntries_) {
|
||||
isFull_.store(NO_NEW_INSERTS, std::memory_order_relaxed);
|
||||
}
|
||||
return SimpleRetT(idx, true);
|
||||
}
|
||||
--numPendingEntries_;
|
||||
}
|
||||
}
|
||||
DCHECK(relaxedLoadKey(*cell) != kEmptyKey_);
|
||||
if (kLockedKey_ == acquireLoadKey(*cell)) {
|
||||
FOLLY_SPIN_WAIT(
|
||||
kLockedKey_ == acquireLoadKey(*cell)
|
||||
);
|
||||
}
|
||||
|
||||
const KeyT thisKey = acquireLoadKey(*cell);
|
||||
if (EqualFcn()(thisKey, key_in)) {
|
||||
// Found an existing entry for our key, but we don't overwrite the
|
||||
// previous value.
|
||||
return SimpleRetT(idx, false);
|
||||
} else if (thisKey == kEmptyKey_ || thisKey == kLockedKey_) {
|
||||
// We need to try again (i.e., don't increment numProbes or
|
||||
// advance idx): this case can happen if the constructor for
|
||||
// ValueT threw for this very cell (the rethrow block above).
|
||||
continue;
|
||||
}
|
||||
|
||||
++numProbes;
|
||||
if (UNLIKELY(numProbes >= capacity_)) {
|
||||
// probed every cell...fail
|
||||
return SimpleRetT(capacity_, false);
|
||||
}
|
||||
|
||||
idx = probeNext(idx, numProbes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* erase --
|
||||
*
|
||||
* This will attempt to erase the given key key_in if the key is found. It
|
||||
* returns 1 iff the key was located and marked as erased, and 0 otherwise.
|
||||
*
|
||||
* Memory is not freed or reclaimed by erase, i.e. the cell containing the
|
||||
* erased key will never be reused. If there's an associated value, we won't
|
||||
* touch it either.
|
||||
*/
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
size_t AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
erase(KeyT key_in) {
|
||||
CHECK_NE(key_in, kEmptyKey_);
|
||||
CHECK_NE(key_in, kLockedKey_);
|
||||
CHECK_NE(key_in, kErasedKey_);
|
||||
for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0;
|
||||
;
|
||||
idx = probeNext(idx, numProbes)) {
|
||||
DCHECK_LT(idx, capacity_);
|
||||
value_type* cell = &cells_[idx];
|
||||
KeyT currentKey = acquireLoadKey(*cell);
|
||||
if (currentKey == kEmptyKey_ || currentKey == kLockedKey_) {
|
||||
// If we hit an empty (or locked) element, this key does not exist. This
|
||||
// is similar to how it's handled in find().
|
||||
return 0;
|
||||
}
|
||||
if (EqualFcn()(currentKey, key_in)) {
|
||||
// Found an existing entry for our key, attempt to mark it erased.
|
||||
// Some other thread may have erased our key, but this is ok.
|
||||
KeyT expect = currentKey;
|
||||
if (cellKeyPtr(*cell)->compare_exchange_strong(expect, kErasedKey_)) {
|
||||
numErases_.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// Even if there's a value in the cell, we won't delete (or even
|
||||
// default construct) it because some other thread may be accessing it.
|
||||
// Locking it meanwhile won't work either since another thread may be
|
||||
// holding a pointer to it.
|
||||
|
||||
// We found the key and successfully erased it.
|
||||
return 1;
|
||||
}
|
||||
// If another thread succeeds in erasing our key, we'll stop our search.
|
||||
return 0;
|
||||
}
|
||||
++numProbes;
|
||||
if (UNLIKELY(numProbes >= capacity_)) {
|
||||
// probed every cell...fail
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
const typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::Config
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::defaultConfig;
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::SmartPtr
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
create(size_t maxSize, const Config& c) {
|
||||
CHECK_LE(c.maxLoadFactor, 1.0);
|
||||
CHECK_GT(c.maxLoadFactor, 0.0);
|
||||
CHECK_NE(c.emptyKey, c.lockedKey);
|
||||
size_t capacity = size_t(maxSize / c.maxLoadFactor);
|
||||
size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * capacity;
|
||||
|
||||
std::unique_ptr<void, void(*)(void*)> mem(malloc(sz), free);
|
||||
new(mem.get()) AtomicHashArray(capacity, c.emptyKey, c.lockedKey, c.erasedKey,
|
||||
c.maxLoadFactor, c.entryCountThreadCacheSize);
|
||||
SmartPtr map(static_cast<AtomicHashArray*>(mem.release()));
|
||||
|
||||
/*
|
||||
* Mark all cells as empty.
|
||||
*
|
||||
* Note: we're bending the rules a little here accessing the key
|
||||
* element in our cells even though the cell object has not been
|
||||
* constructed, and casting them to atomic objects (see cellKeyPtr).
|
||||
* (Also, in fact we never actually invoke the value_type
|
||||
* constructor.) This is in order to avoid needing to default
|
||||
* construct a bunch of value_type when we first start up: if you
|
||||
* have an expensive default constructor for the value type this can
|
||||
* noticeably speed construction time for an AHA.
|
||||
*/
|
||||
FOR_EACH_RANGE(i, 0, map->capacity_) {
|
||||
cellKeyPtr(map->cells_[i])->store(map->kEmptyKey_,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
destroy(AtomicHashArray* p) {
|
||||
assert(p);
|
||||
FOR_EACH_RANGE(i, 0, p->capacity_) {
|
||||
if (p->cells_[i].first != p->kEmptyKey_) {
|
||||
p->cells_[i].~value_type();
|
||||
}
|
||||
}
|
||||
p->~AtomicHashArray();
|
||||
free(p);
|
||||
}
|
||||
|
||||
// clear -- clears all keys and values in the map and resets all counters
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
clear() {
|
||||
FOR_EACH_RANGE(i, 0, capacity_) {
|
||||
if (cells_[i].first != kEmptyKey_) {
|
||||
cells_[i].~value_type();
|
||||
*const_cast<KeyT*>(&cells_[i].first) = kEmptyKey_;
|
||||
}
|
||||
CHECK(cells_[i].first == kEmptyKey_);
|
||||
}
|
||||
numEntries_.set(0);
|
||||
numPendingEntries_.set(0);
|
||||
isFull_.store(0, std::memory_order_relaxed);
|
||||
numErases_.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
|
||||
// Iterator implementation
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
template <class ContT, class IterVal>
|
||||
struct AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::aha_iterator
|
||||
: boost::iterator_facade<aha_iterator<ContT,IterVal>,
|
||||
IterVal,
|
||||
boost::forward_traversal_tag>
|
||||
{
|
||||
explicit aha_iterator() : aha_(0) {}
|
||||
|
||||
// Conversion ctor for interoperability between const_iterator and
|
||||
// iterator. The enable_if<> magic keeps us well-behaved for
|
||||
// is_convertible<> (v. the iterator_facade documentation).
|
||||
template<class OtherContT, class OtherVal>
|
||||
aha_iterator(const aha_iterator<OtherContT,OtherVal>& o,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<OtherVal*,IterVal*>::value >::type* = 0)
|
||||
: aha_(o.aha_)
|
||||
, offset_(o.offset_)
|
||||
{}
|
||||
|
||||
explicit aha_iterator(ContT* array, size_t offset)
|
||||
: aha_(array)
|
||||
, offset_(offset)
|
||||
{
|
||||
advancePastEmpty();
|
||||
}
|
||||
|
||||
// Returns unique index that can be used with findAt().
|
||||
// WARNING: The following function will fail silently for hashtable
|
||||
// with capacity > 2^32
|
||||
uint32_t getIndex() const { return offset_; }
|
||||
|
||||
private:
|
||||
friend class AtomicHashArray;
|
||||
friend class boost::iterator_core_access;
|
||||
|
||||
void increment() {
|
||||
++offset_;
|
||||
advancePastEmpty();
|
||||
}
|
||||
|
||||
bool equal(const aha_iterator& o) const {
|
||||
return aha_ == o.aha_ && offset_ == o.offset_;
|
||||
}
|
||||
|
||||
IterVal& dereference() const {
|
||||
return aha_->cells_[offset_];
|
||||
}
|
||||
|
||||
void advancePastEmpty() {
|
||||
while (offset_ < aha_->capacity_ && !isValid()) {
|
||||
++offset_;
|
||||
}
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
KeyT key = acquireLoadKey(aha_->cells_[offset_]);
|
||||
return key != aha_->kEmptyKey_ &&
|
||||
key != aha_->kLockedKey_ &&
|
||||
key != aha_->kErasedKey_;
|
||||
}
|
||||
|
||||
private:
|
||||
ContT* aha_;
|
||||
size_t offset_;
|
||||
}; // aha_iterator
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#undef FOLLY_SPIN_WAIT
|
||||
@@ -1,297 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* AtomicHashArray is the building block for AtomicHashMap. It provides the
|
||||
* core lock-free functionality, but is limitted by the fact that it cannot
|
||||
* grow past it's initialization size and is a little more awkward (no public
|
||||
* constructor, for example). If you're confident that you won't run out of
|
||||
* space, don't mind the awkardness, and really need bare-metal performance,
|
||||
* feel free to use AHA directly.
|
||||
*
|
||||
* Check out AtomicHashMap.h for more thorough documentation on perf and
|
||||
* general pros and cons relative to other hash maps.
|
||||
*
|
||||
* @author Spencer Ahrens <sahrens@fb.com>
|
||||
* @author Jordan DeLong <delong.j@fb.com>
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_ATOMICHASHARRAY_H_
|
||||
#define FOLLY_ATOMICHASHARRAY_H_
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include "folly/Hash.h"
|
||||
#include "folly/ThreadCachedInt.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
template <class KeyT, class ValueT,
|
||||
class HashFcn = std::hash<KeyT>, class EqualFcn = std::equal_to<KeyT>>
|
||||
class AtomicHashMap;
|
||||
|
||||
template <class KeyT, class ValueT,
|
||||
class HashFcn = std::hash<KeyT>, class EqualFcn = std::equal_to<KeyT>>
|
||||
class AtomicHashArray : boost::noncopyable {
|
||||
static_assert((std::is_convertible<KeyT,int32_t>::value ||
|
||||
std::is_convertible<KeyT,int64_t>::value ||
|
||||
std::is_convertible<KeyT,const void*>::value),
|
||||
"You are trying to use AtomicHashArray with disallowed key "
|
||||
"types. You must use atomically compare-and-swappable integer "
|
||||
"keys, or a different container class.");
|
||||
public:
|
||||
typedef KeyT key_type;
|
||||
typedef ValueT mapped_type;
|
||||
typedef std::pair<const KeyT, ValueT> value_type;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef value_type& reference;
|
||||
typedef const value_type& const_reference;
|
||||
typedef value_type* pointer;
|
||||
typedef const value_type* const_pointer;
|
||||
|
||||
const size_t capacity_;
|
||||
const size_t maxEntries_;
|
||||
const KeyT kEmptyKey_;
|
||||
const KeyT kLockedKey_;
|
||||
const KeyT kErasedKey_;
|
||||
|
||||
template<class ContT, class IterVal>
|
||||
struct aha_iterator;
|
||||
|
||||
typedef aha_iterator<const AtomicHashArray,const value_type> const_iterator;
|
||||
typedef aha_iterator<AtomicHashArray,value_type> iterator;
|
||||
|
||||
// You really shouldn't need this if you use the SmartPtr provided by create,
|
||||
// but if you really want to do something crazy like stick the released
|
||||
// pointer into a DescriminatedPtr or something, you'll need this to clean up
|
||||
// after yourself.
|
||||
static void destroy(AtomicHashArray*);
|
||||
|
||||
private:
|
||||
const size_t kAnchorMask_;
|
||||
|
||||
struct Deleter {
|
||||
void operator()(AtomicHashArray* ptr) {
|
||||
AtomicHashArray::destroy(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
typedef std::unique_ptr<AtomicHashArray, Deleter> SmartPtr;
|
||||
|
||||
/*
|
||||
* create --
|
||||
*
|
||||
* Creates AtomicHashArray objects. Use instead of constructor/destructor.
|
||||
*
|
||||
* We do things this way in order to avoid the perf penalty of a second
|
||||
* pointer indirection when composing these into AtomicHashMap, which needs
|
||||
* to store an array of pointers so that it can perform atomic operations on
|
||||
* them when growing.
|
||||
*
|
||||
* Instead of a mess of arguments, we take a max size and a Config struct to
|
||||
* simulate named ctor parameters. The Config struct has sensible defaults
|
||||
* for everything, but is overloaded - if you specify a positive capacity,
|
||||
* that will be used directly instead of computing it based on
|
||||
* maxLoadFactor.
|
||||
*
|
||||
* Create returns an AHA::SmartPtr which is a unique_ptr with a custom
|
||||
* deleter to make sure everything is cleaned up properly.
|
||||
*/
|
||||
struct Config {
|
||||
KeyT emptyKey;
|
||||
KeyT lockedKey;
|
||||
KeyT erasedKey;
|
||||
double maxLoadFactor;
|
||||
double growthFactor;
|
||||
int entryCountThreadCacheSize;
|
||||
size_t capacity; // if positive, overrides maxLoadFactor
|
||||
|
||||
constexpr Config() : emptyKey((KeyT)-1),
|
||||
lockedKey((KeyT)-2),
|
||||
erasedKey((KeyT)-3),
|
||||
maxLoadFactor(0.8),
|
||||
growthFactor(-1),
|
||||
entryCountThreadCacheSize(1000),
|
||||
capacity(0) {}
|
||||
};
|
||||
|
||||
static const Config defaultConfig;
|
||||
static SmartPtr create(size_t maxSize, const Config& = defaultConfig);
|
||||
|
||||
iterator find(KeyT k) {
|
||||
return iterator(this, findInternal(k).idx);
|
||||
}
|
||||
const_iterator find(KeyT k) const {
|
||||
return const_cast<AtomicHashArray*>(this)->find(k);
|
||||
}
|
||||
|
||||
/*
|
||||
* insert --
|
||||
*
|
||||
* Returns a pair with iterator to the element at r.first and bool success.
|
||||
* Retrieve the index with ret.first.getIndex().
|
||||
*
|
||||
* Fails on key collision (does not overwrite) or if map becomes
|
||||
* full, at which point no element is inserted, iterator is set to end(),
|
||||
* and success is set false. On collisions, success is set false, but the
|
||||
* iterator is set to the existing entry.
|
||||
*/
|
||||
std::pair<iterator,bool> insert(const value_type& r) {
|
||||
SimpleRetT ret = insertInternal(r.first, r.second);
|
||||
return std::make_pair(iterator(this, ret.idx), ret.success);
|
||||
}
|
||||
std::pair<iterator,bool> insert(value_type&& r) {
|
||||
SimpleRetT ret = insertInternal(r.first, std::move(r.second));
|
||||
return std::make_pair(iterator(this, ret.idx), ret.success);
|
||||
}
|
||||
|
||||
// returns the number of elements erased - should never exceed 1
|
||||
size_t erase(KeyT k);
|
||||
|
||||
// clears all keys and values in the map and resets all counters. Not thread
|
||||
// safe.
|
||||
void clear();
|
||||
|
||||
// Exact number of elements in the map - note that readFull() acquires a
|
||||
// mutex. See folly/ThreadCachedInt.h for more details.
|
||||
size_t size() const {
|
||||
return numEntries_.readFull() -
|
||||
numErases_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool empty() const { return size() == 0; }
|
||||
|
||||
iterator begin() { return iterator(this, 0); }
|
||||
iterator end() { return iterator(this, capacity_); }
|
||||
const_iterator begin() const { return const_iterator(this, 0); }
|
||||
const_iterator end() const { return const_iterator(this, capacity_); }
|
||||
|
||||
// See AtomicHashMap::findAt - access elements directly
|
||||
// WARNING: The following 2 functions will fail silently for hashtable
|
||||
// with capacity > 2^32
|
||||
iterator findAt(uint32_t idx) {
|
||||
DCHECK_LT(idx, capacity_);
|
||||
return iterator(this, idx);
|
||||
}
|
||||
const_iterator findAt(uint32_t idx) const {
|
||||
return const_cast<AtomicHashArray*>(this)->findAt(idx);
|
||||
}
|
||||
|
||||
iterator makeIter(size_t idx) { return iterator(this, idx); }
|
||||
const_iterator makeIter(size_t idx) const {
|
||||
return const_iterator(this, idx);
|
||||
}
|
||||
|
||||
// The max load factor allowed for this map
|
||||
double maxLoadFactor() const { return ((double) maxEntries_) / capacity_; }
|
||||
|
||||
void setEntryCountThreadCacheSize(uint32_t newSize) {
|
||||
numEntries_.setCacheSize(newSize);
|
||||
numPendingEntries_.setCacheSize(newSize);
|
||||
}
|
||||
|
||||
int getEntryCountThreadCacheSize() const {
|
||||
return numEntries_.getCacheSize();
|
||||
}
|
||||
|
||||
/* Private data and helper functions... */
|
||||
|
||||
private:
|
||||
friend class AtomicHashMap<KeyT,ValueT,HashFcn,EqualFcn>;
|
||||
|
||||
struct SimpleRetT { size_t idx; bool success;
|
||||
SimpleRetT(size_t i, bool s) : idx(i), success(s) {}
|
||||
SimpleRetT() {}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
SimpleRetT insertInternal(KeyT key, T&& value);
|
||||
|
||||
SimpleRetT findInternal(const KeyT key);
|
||||
|
||||
static std::atomic<KeyT>* cellKeyPtr(const value_type& r) {
|
||||
// We need some illegal casting here in order to actually store
|
||||
// our value_type as a std::pair<const,>. But a little bit of
|
||||
// undefined behavior never hurt anyone ...
|
||||
static_assert(sizeof(std::atomic<KeyT>) == sizeof(KeyT),
|
||||
"std::atomic is implemented in an unexpected way for AHM");
|
||||
return
|
||||
const_cast<std::atomic<KeyT>*>(
|
||||
reinterpret_cast<std::atomic<KeyT> const*>(&r.first));
|
||||
}
|
||||
|
||||
static KeyT relaxedLoadKey(const value_type& r) {
|
||||
return cellKeyPtr(r)->load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static KeyT acquireLoadKey(const value_type& r) {
|
||||
return cellKeyPtr(r)->load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
// Fun with thread local storage - atomic increment is expensive
|
||||
// (relatively), so we accumulate in the thread cache and periodically
|
||||
// flush to the actual variable, and walk through the unflushed counts when
|
||||
// reading the value, so be careful of calling size() too frequently. This
|
||||
// increases insertion throughput several times over while keeping the count
|
||||
// accurate.
|
||||
ThreadCachedInt<int64_t> numEntries_; // Successful key inserts
|
||||
ThreadCachedInt<int64_t> numPendingEntries_; // Used by insertInternal
|
||||
std::atomic<int64_t> isFull_; // Used by insertInternal
|
||||
std::atomic<int64_t> numErases_; // Successful key erases
|
||||
|
||||
value_type cells_[0]; // This must be the last field of this class
|
||||
|
||||
// Force constructor/destructor private since create/destroy should be
|
||||
// used externally instead
|
||||
AtomicHashArray(size_t capacity, KeyT emptyKey, KeyT lockedKey,
|
||||
KeyT erasedKey, double maxLoadFactor, size_t cacheSize);
|
||||
|
||||
~AtomicHashArray() {}
|
||||
|
||||
inline void unlockCell(value_type* const cell, KeyT newKey) {
|
||||
cellKeyPtr(*cell)->store(newKey, std::memory_order_release);
|
||||
}
|
||||
|
||||
inline bool tryLockCell(value_type* const cell) {
|
||||
KeyT expect = kEmptyKey_;
|
||||
return cellKeyPtr(*cell)->compare_exchange_strong(expect, kLockedKey_,
|
||||
std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
inline size_t keyToAnchorIdx(const KeyT k) const {
|
||||
const size_t hashVal = HashFcn()(k);
|
||||
const size_t probe = hashVal & kAnchorMask_;
|
||||
return LIKELY(probe < capacity_) ? probe : hashVal % capacity_;
|
||||
}
|
||||
|
||||
inline size_t probeNext(size_t idx, size_t numProbes) {
|
||||
//idx += numProbes; // quadratic probing
|
||||
idx += 1; // linear probing
|
||||
// Avoid modulus because it's slow
|
||||
return LIKELY(idx < capacity_) ? idx : (idx - capacity_);
|
||||
}
|
||||
}; // AtomicHashArray
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#include "AtomicHashArray-inl.h"
|
||||
|
||||
#endif // FOLLY_ATOMICHASHARRAY_H_
|
||||
@@ -1,413 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_ATOMICHASHMAP_H_
|
||||
#error "This should only be included by AtomicHashMap.h"
|
||||
#endif
|
||||
|
||||
#include "folly/detail/AtomicHashUtils.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
const typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::Config
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::defaultConfig;
|
||||
|
||||
// AtomicHashMap constructor -- Atomic wrapper that allows growth
|
||||
// This class has a lot of overhead (184 Bytes) so only use for big maps
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
AtomicHashMap(size_t size, const Config& config)
|
||||
: kGrowthFrac_(config.growthFactor < 0 ?
|
||||
1.0 - config.maxLoadFactor : config.growthFactor) {
|
||||
CHECK(config.maxLoadFactor > 0.0 && config.maxLoadFactor < 1.0);
|
||||
subMaps_[0].store(SubMap::create(size, config).release(),
|
||||
std::memory_order_relaxed);
|
||||
auto numSubMaps = kNumSubMaps_;
|
||||
FOR_EACH_RANGE(i, 1, numSubMaps) {
|
||||
subMaps_[i].store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
numMapsAllocated_.store(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// insert --
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
std::pair<typename AtomicHashMap<KeyT,ValueT,HashFcn,EqualFcn>::iterator,bool>
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
insert(key_type k, const mapped_type& v) {
|
||||
SimpleRetT ret = insertInternal(k,v);
|
||||
SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed);
|
||||
return std::make_pair(iterator(this, ret.i, subMap->makeIter(ret.j)),
|
||||
ret.success);
|
||||
}
|
||||
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
std::pair<typename AtomicHashMap<KeyT,ValueT,HashFcn,EqualFcn>::iterator,bool>
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
insert(key_type k, mapped_type&& v) {
|
||||
SimpleRetT ret = insertInternal(k, std::move(v));
|
||||
SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed);
|
||||
return std::make_pair(iterator(this, ret.i, subMap->makeIter(ret.j)),
|
||||
ret.success);
|
||||
}
|
||||
|
||||
// insertInternal -- Allocates new sub maps as existing ones fill up.
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
template <class T>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::SimpleRetT
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
insertInternal(key_type key, T&& value) {
|
||||
beginInsertInternal:
|
||||
int nextMapIdx = // this maintains our state
|
||||
numMapsAllocated_.load(std::memory_order_acquire);
|
||||
typename SubMap::SimpleRetT ret;
|
||||
FOR_EACH_RANGE(i, 0, nextMapIdx) {
|
||||
// insert in each map successively. If one succeeds, we're done!
|
||||
SubMap* subMap = subMaps_[i].load(std::memory_order_relaxed);
|
||||
ret = subMap->insertInternal(key, std::forward<T>(value));
|
||||
if (ret.idx == subMap->capacity_) {
|
||||
continue; //map is full, so try the next one
|
||||
}
|
||||
// Either collision or success - insert in either case
|
||||
return SimpleRetT(i, ret.idx, ret.success);
|
||||
}
|
||||
|
||||
// If we made it this far, all maps are full and we need to try to allocate
|
||||
// the next one.
|
||||
|
||||
SubMap* primarySubMap = subMaps_[0].load(std::memory_order_relaxed);
|
||||
if (nextMapIdx >= kNumSubMaps_ ||
|
||||
primarySubMap->capacity_ * kGrowthFrac_ < 1.0) {
|
||||
// Can't allocate any more sub maps.
|
||||
throw AtomicHashMapFullError();
|
||||
}
|
||||
|
||||
if (tryLockMap(nextMapIdx)) {
|
||||
// Alloc a new map and shove it in. We can change whatever
|
||||
// we want because other threads are waiting on us...
|
||||
size_t numCellsAllocated = (size_t)
|
||||
(primarySubMap->capacity_ *
|
||||
std::pow(1.0 + kGrowthFrac_, nextMapIdx - 1));
|
||||
size_t newSize = (int) (numCellsAllocated * kGrowthFrac_);
|
||||
DCHECK(subMaps_[nextMapIdx].load(std::memory_order_relaxed) ==
|
||||
(SubMap*)kLockedPtr_);
|
||||
// create a new map using the settings stored in the first map
|
||||
|
||||
Config config;
|
||||
config.emptyKey = primarySubMap->kEmptyKey_;
|
||||
config.lockedKey = primarySubMap->kLockedKey_;
|
||||
config.erasedKey = primarySubMap->kErasedKey_;
|
||||
config.maxLoadFactor = primarySubMap->maxLoadFactor();
|
||||
config.entryCountThreadCacheSize =
|
||||
primarySubMap->getEntryCountThreadCacheSize();
|
||||
subMaps_[nextMapIdx].store(SubMap::create(newSize, config).release(),
|
||||
std::memory_order_relaxed);
|
||||
|
||||
// Publish the map to other threads.
|
||||
numMapsAllocated_.fetch_add(1, std::memory_order_release);
|
||||
DCHECK_EQ(nextMapIdx + 1,
|
||||
numMapsAllocated_.load(std::memory_order_relaxed));
|
||||
} else {
|
||||
// If we lost the race, we'll have to wait for the next map to get
|
||||
// allocated before doing any insertion here.
|
||||
FOLLY_SPIN_WAIT(
|
||||
nextMapIdx >= numMapsAllocated_.load(std::memory_order_acquire)
|
||||
);
|
||||
}
|
||||
|
||||
// Relaxed is ok here because either we just created this map, or we
|
||||
// just did a spin wait with an acquire load on numMapsAllocated_.
|
||||
SubMap* loadedMap = subMaps_[nextMapIdx].load(std::memory_order_relaxed);
|
||||
DCHECK(loadedMap && loadedMap != (SubMap*)kLockedPtr_);
|
||||
ret = loadedMap->insertInternal(key, std::forward<T>(value));
|
||||
if (ret.idx != loadedMap->capacity_) {
|
||||
return SimpleRetT(nextMapIdx, ret.idx, ret.success);
|
||||
}
|
||||
// We took way too long and the new map is already full...try again from
|
||||
// the top (this should pretty much never happen).
|
||||
goto beginInsertInternal;
|
||||
}
|
||||
|
||||
// find --
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::iterator
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
find(KeyT k) {
|
||||
SimpleRetT ret = findInternal(k);
|
||||
if (ret.i >= numMapsAllocated_.load(std::memory_order_acquire)) {
|
||||
return end();
|
||||
}
|
||||
SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed);
|
||||
return iterator(this, ret.i, subMap->makeIter(ret.j));
|
||||
}
|
||||
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::const_iterator
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
find(KeyT k) const {
|
||||
return const_cast<AtomicHashMap*>(this)->find(k);
|
||||
}
|
||||
|
||||
// findInternal --
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::SimpleRetT
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
findInternal(const KeyT k) const {
|
||||
SubMap* const primaryMap = subMaps_[0].load(std::memory_order_relaxed);
|
||||
typename SubMap::SimpleRetT ret = primaryMap->findInternal(k);
|
||||
if (LIKELY(ret.idx != primaryMap->capacity_)) {
|
||||
return SimpleRetT(0, ret.idx, ret.success);
|
||||
}
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE(i, 1, numMaps) {
|
||||
// Check each map successively. If one succeeds, we're done!
|
||||
SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
|
||||
ret = thisMap->findInternal(k);
|
||||
if (LIKELY(ret.idx != thisMap->capacity_)) {
|
||||
return SimpleRetT(i, ret.idx, ret.success);
|
||||
}
|
||||
}
|
||||
// Didn't find our key...
|
||||
return SimpleRetT(numMaps, 0, false);
|
||||
}
|
||||
|
||||
// findAtInternal -- see encodeIndex() for details.
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::SimpleRetT
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
findAtInternal(uint32_t idx) const {
|
||||
uint32_t subMapIdx, subMapOffset;
|
||||
if (idx & kSecondaryMapBit_) {
|
||||
// idx falls in a secondary map
|
||||
idx &= ~kSecondaryMapBit_; // unset secondary bit
|
||||
subMapIdx = idx >> kSubMapIndexShift_;
|
||||
DCHECK_LT(subMapIdx, numMapsAllocated_.load(std::memory_order_relaxed));
|
||||
subMapOffset = idx & kSubMapIndexMask_;
|
||||
} else {
|
||||
// idx falls in primary map
|
||||
subMapIdx = 0;
|
||||
subMapOffset = idx;
|
||||
}
|
||||
return SimpleRetT(subMapIdx, subMapOffset, true);
|
||||
}
|
||||
|
||||
// erase --
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::size_type
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
erase(const KeyT k) {
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE(i, 0, numMaps) {
|
||||
// Check each map successively. If one succeeds, we're done!
|
||||
if (subMaps_[i].load(std::memory_order_relaxed)->erase(k)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// Didn't find our key...
|
||||
return 0;
|
||||
}
|
||||
|
||||
// capacity -- summation of capacities of all submaps
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
capacity() const {
|
||||
size_t totalCap(0);
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE(i, 0, numMaps) {
|
||||
totalCap += subMaps_[i].load(std::memory_order_relaxed)->capacity_;
|
||||
}
|
||||
return totalCap;
|
||||
}
|
||||
|
||||
// spaceRemaining --
|
||||
// number of new insertions until current submaps are all at max load
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
spaceRemaining() const {
|
||||
size_t spaceRem(0);
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE(i, 0, numMaps) {
|
||||
SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
|
||||
spaceRem += std::max(
|
||||
0,
|
||||
thisMap->maxEntries_ - &thisMap->numEntries_.readFull()
|
||||
);
|
||||
}
|
||||
return spaceRem;
|
||||
}
|
||||
|
||||
// clear -- Wipes all keys and values from primary map and destroys
|
||||
// all secondary maps. Not thread safe.
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
void AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
clear() {
|
||||
subMaps_[0].load(std::memory_order_relaxed)->clear();
|
||||
int const numMaps = numMapsAllocated_
|
||||
.load(std::memory_order_relaxed);
|
||||
FOR_EACH_RANGE(i, 1, numMaps) {
|
||||
SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
|
||||
DCHECK(thisMap);
|
||||
SubMap::destroy(thisMap);
|
||||
subMaps_[i].store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
numMapsAllocated_.store(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// size --
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
size() const {
|
||||
size_t totalSize(0);
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE(i, 0, numMaps) {
|
||||
totalSize += subMaps_[i].load(std::memory_order_relaxed)->size();
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
// encodeIndex -- Encode the submap index and offset into return.
|
||||
// index_ret must be pre-populated with the submap offset.
|
||||
//
|
||||
// We leave index_ret untouched when referring to the primary map
|
||||
// so it can be as large as possible (31 data bits). Max size of
|
||||
// secondary maps is limited by what can fit in the low 27 bits.
|
||||
//
|
||||
// Returns the following bit-encoded data in index_ret:
|
||||
// if subMap == 0 (primary map) =>
|
||||
// bit(s) value
|
||||
// 31 0
|
||||
// 0-30 submap offset (index_ret input)
|
||||
//
|
||||
// if subMap > 0 (secondary maps) =>
|
||||
// bit(s) value
|
||||
// 31 1
|
||||
// 27-30 which subMap
|
||||
// 0-26 subMap offset (index_ret input)
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
inline uint32_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
encodeIndex(uint32_t subMap, uint32_t offset) {
|
||||
DCHECK_EQ(offset & kSecondaryMapBit_, 0); // offset can't be too big
|
||||
if (subMap == 0) return offset;
|
||||
// Make sure subMap isn't too big
|
||||
DCHECK_EQ(subMap >> kNumSubMapBits_, 0);
|
||||
// Make sure subMap bits of offset are clear
|
||||
DCHECK_EQ(offset & (~kSubMapIndexMask_ | kSecondaryMapBit_), 0);
|
||||
|
||||
// Set high-order bits to encode which submap this index belongs to
|
||||
return offset | (subMap << kSubMapIndexShift_) | kSecondaryMapBit_;
|
||||
}
|
||||
|
||||
|
||||
// Iterator implementation
|
||||
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
template<class ContT, class IterVal, class SubIt>
|
||||
struct AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::ahm_iterator
|
||||
: boost::iterator_facade<ahm_iterator<ContT,IterVal,SubIt>,
|
||||
IterVal,
|
||||
boost::forward_traversal_tag>
|
||||
{
|
||||
explicit ahm_iterator() : ahm_(0) {}
|
||||
|
||||
// Conversion ctor for interoperability between const_iterator and
|
||||
// iterator. The enable_if<> magic keeps us well-behaved for
|
||||
// is_convertible<> (v. the iterator_facade documentation).
|
||||
template<class OtherContT, class OtherVal, class OtherSubIt>
|
||||
ahm_iterator(const ahm_iterator<OtherContT,OtherVal,OtherSubIt>& o,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<OtherSubIt,SubIt>::value >::type* = 0)
|
||||
: ahm_(o.ahm_)
|
||||
, subMap_(o.subMap_)
|
||||
, subIt_(o.subIt_)
|
||||
{}
|
||||
|
||||
/*
|
||||
* Returns the unique index that can be used for access directly
|
||||
* into the data storage.
|
||||
*/
|
||||
uint32_t getIndex() const {
|
||||
CHECK(!isEnd());
|
||||
return ahm_->encodeIndex(subMap_, subIt_.getIndex());
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AtomicHashMap;
|
||||
explicit ahm_iterator(ContT* ahm,
|
||||
uint32_t subMap,
|
||||
const SubIt& subIt)
|
||||
: ahm_(ahm)
|
||||
, subMap_(subMap)
|
||||
, subIt_(subIt)
|
||||
{
|
||||
checkAdvanceToNextSubmap();
|
||||
}
|
||||
|
||||
friend class boost::iterator_core_access;
|
||||
|
||||
void increment() {
|
||||
CHECK(!isEnd());
|
||||
++subIt_;
|
||||
checkAdvanceToNextSubmap();
|
||||
}
|
||||
|
||||
bool equal(const ahm_iterator& other) const {
|
||||
if (ahm_ != other.ahm_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isEnd() || other.isEnd()) {
|
||||
return isEnd() == other.isEnd();
|
||||
}
|
||||
|
||||
return subMap_ == other.subMap_ &&
|
||||
subIt_ == other.subIt_;
|
||||
}
|
||||
|
||||
IterVal& dereference() const {
|
||||
return *subIt_;
|
||||
}
|
||||
|
||||
bool isEnd() const { return ahm_ == nullptr; }
|
||||
|
||||
void checkAdvanceToNextSubmap() {
|
||||
if (isEnd()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SubMap* thisMap = ahm_->subMaps_[subMap_].
|
||||
load(std::memory_order_relaxed);
|
||||
if (subIt_ == thisMap->end()) {
|
||||
// This sub iterator is done, advance to next one
|
||||
if (subMap_ + 1 <
|
||||
ahm_->numMapsAllocated_.load(std::memory_order_acquire)) {
|
||||
++subMap_;
|
||||
thisMap = ahm_->subMaps_[subMap_].load(std::memory_order_relaxed);
|
||||
subIt_ = thisMap->begin();
|
||||
} else {
|
||||
ahm_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ContT* ahm_;
|
||||
uint32_t subMap_;
|
||||
SubIt subIt_;
|
||||
}; // ahm_iterator
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#undef FOLLY_SPIN_WAIT
|
||||
-412
@@ -1,412 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* AtomicHashMap --
|
||||
*
|
||||
* A high performance concurrent hash map with int32 or int64 keys. Supports
|
||||
* insert, find(key), findAt(index), erase(key), size, and more. Memory cannot
|
||||
* be freed or reclaimed by erase. Can grow to a maximum of about 18 times the
|
||||
* initial capacity, but performance degrades linearly with growth. Can also be
|
||||
* used as an object store with unique 32-bit references directly into the
|
||||
* internal storage (retrieved with iterator::getIndex()).
|
||||
*
|
||||
* Advantages:
|
||||
* - High performance (~2-4x tbb::concurrent_hash_map in heavily
|
||||
* multi-threaded environments).
|
||||
* - Efficient memory usage if initial capacity is not over estimated
|
||||
* (especially for small keys and values).
|
||||
* - Good fragmentation properties (only allocates in large slabs which can
|
||||
* be reused with clear() and never move).
|
||||
* - Can generate unique, long-lived 32-bit references for efficient lookup
|
||||
* (see findAt()).
|
||||
*
|
||||
* Disadvantages:
|
||||
* - Keys must be native int32 or int64, or explicitly converted.
|
||||
* - Must be able to specify unique empty, locked, and erased keys
|
||||
* - Performance degrades linearly as size grows beyond initialization
|
||||
* capacity.
|
||||
* - Max size limit of ~18x initial size (dependent on max load factor).
|
||||
* - Memory is not freed or reclaimed by erase.
|
||||
*
|
||||
* Usage and Operation Details:
|
||||
* Simple performance/memory tradeoff with maxLoadFactor. Higher load factors
|
||||
* give better memory utilization but probe lengths increase, reducing
|
||||
* performance.
|
||||
*
|
||||
* Implementation and Performance Details:
|
||||
* AHArray is a fixed size contiguous block of value_type cells. When
|
||||
* writing a cell, the key is locked while the rest of the record is
|
||||
* written. Once done, the cell is unlocked by setting the key. find()
|
||||
* is completely wait-free and doesn't require any non-relaxed atomic
|
||||
* operations. AHA cannot grow beyond initialization capacity, but is
|
||||
* faster because of reduced data indirection.
|
||||
*
|
||||
* AHMap is a wrapper around AHArray sub-maps that allows growth and provides
|
||||
* an interface closer to the stl UnorderedAssociativeContainer concept. These
|
||||
* sub-maps are allocated on the fly and are processed in series, so the more
|
||||
* there are (from growing past initial capacity), the worse the performance.
|
||||
*
|
||||
* Insert returns false if there is a key collision and throws if the max size
|
||||
* of the map is exceeded.
|
||||
*
|
||||
* Benchmark performance with 8 simultaneous threads processing 1 million
|
||||
* unique <int64, int64> entries on a 4-core, 2.5 GHz machine:
|
||||
*
|
||||
* Load Factor Mem Efficiency usec/Insert usec/Find
|
||||
* 50% 50% 0.19 0.05
|
||||
* 85% 85% 0.20 0.06
|
||||
* 90% 90% 0.23 0.08
|
||||
* 95% 95% 0.27 0.10
|
||||
*
|
||||
* See folly/tests/AtomicHashMapTest.cpp for more benchmarks.
|
||||
*
|
||||
* @author Spencer Ahrens <sahrens@fb.com>
|
||||
* @author Jordan DeLong <delong.j@fb.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_ATOMICHASHMAP_H_
|
||||
#define FOLLY_ATOMICHASHMAP_H_
|
||||
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/type_traits/is_convertible.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
#include "folly/AtomicHashArray.h"
|
||||
#include "folly/Foreach.h"
|
||||
#include "folly/Hash.h"
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/ThreadCachedInt.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
/*
|
||||
* AtomicHashMap provides an interface somewhat similar to the
|
||||
* UnorderedAssociativeContainer concept in C++. This does not
|
||||
* exactly match this concept (or even the basic Container concept),
|
||||
* because of some restrictions imposed by our datastructure.
|
||||
*
|
||||
* Specific differences (there are quite a few):
|
||||
*
|
||||
* - Efficiently thread safe for inserts (main point of this stuff),
|
||||
* wait-free for lookups.
|
||||
*
|
||||
* - You can erase from this container, but the cell containing the key will
|
||||
* not be free or reclaimed.
|
||||
*
|
||||
* - You can erase everything by calling clear() (and you must guarantee only
|
||||
* one thread can be using the container to do that).
|
||||
*
|
||||
* - We aren't DefaultConstructible, CopyConstructible, Assignable, or
|
||||
* EqualityComparable. (Most of these are probably not something
|
||||
* you actually want to do with this anyway.)
|
||||
*
|
||||
* - We don't support the various bucket functions, rehash(),
|
||||
* reserve(), or equal_range(). Also no constructors taking
|
||||
* iterators, although this could change.
|
||||
*
|
||||
* - Several insertion functions, notably operator[], are not
|
||||
* implemented. It is a little too easy to misuse these functions
|
||||
* with this container, where part of the point is that when an
|
||||
* insertion happens for a new key, it will atomically have the
|
||||
* desired value.
|
||||
*
|
||||
* - The map has no templated insert() taking an iterator range, but
|
||||
* we do provide an insert(key, value). The latter seems more
|
||||
* frequently useful for this container (to avoid sprinkling
|
||||
* make_pair everywhere), and providing both can lead to some gross
|
||||
* template error messages.
|
||||
*
|
||||
* - Not Allocator-aware.
|
||||
*
|
||||
* - KeyT must be a 32 bit or 64 bit atomic integer type, and you must
|
||||
* define special 'locked' and 'empty' key values in the ctor
|
||||
*
|
||||
* - We don't take the Hash function object as an instance in the
|
||||
* constructor.
|
||||
*
|
||||
*/
|
||||
|
||||
// Thrown when insertion fails due to running out of space for
|
||||
// submaps.
|
||||
struct AtomicHashMapFullError : std::runtime_error {
|
||||
explicit AtomicHashMapFullError()
|
||||
: std::runtime_error("AtomicHashMap is full")
|
||||
{}
|
||||
};
|
||||
|
||||
template<class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
class AtomicHashMap : boost::noncopyable {
|
||||
typedef AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn> SubMap;
|
||||
|
||||
public:
|
||||
typedef KeyT key_type;
|
||||
typedef ValueT mapped_type;
|
||||
typedef std::pair<const KeyT, ValueT> value_type;
|
||||
typedef HashFcn hasher;
|
||||
typedef EqualFcn key_equal;
|
||||
typedef value_type* pointer;
|
||||
typedef value_type& reference;
|
||||
typedef const value_type& const_reference;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef std::size_t size_type;
|
||||
typedef typename SubMap::Config Config;
|
||||
|
||||
template<class ContT, class IterVal, class SubIt>
|
||||
struct ahm_iterator;
|
||||
|
||||
typedef ahm_iterator<const AtomicHashMap,
|
||||
const value_type,
|
||||
typename SubMap::const_iterator>
|
||||
const_iterator;
|
||||
typedef ahm_iterator<AtomicHashMap,
|
||||
value_type,
|
||||
typename SubMap::iterator>
|
||||
iterator;
|
||||
|
||||
public:
|
||||
const float kGrowthFrac_; // How much to grow when we run out of capacity.
|
||||
|
||||
// The constructor takes a finalSizeEst which is the optimal
|
||||
// number of elements to maximize space utilization and performance,
|
||||
// and a Config object to specify more advanced options.
|
||||
static const Config defaultConfig;
|
||||
explicit AtomicHashMap(size_t finalSizeEst, const Config& = defaultConfig);
|
||||
|
||||
~AtomicHashMap() {
|
||||
const int numMaps = numMapsAllocated_.load(std::memory_order_relaxed);
|
||||
FOR_EACH_RANGE (i, 0, numMaps) {
|
||||
SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
|
||||
DCHECK(thisMap);
|
||||
SubMap::destroy(thisMap);
|
||||
}
|
||||
}
|
||||
|
||||
key_equal key_eq() const { return key_equal(); }
|
||||
hasher hash_function() const { return hasher(); }
|
||||
|
||||
// TODO: emplace() support would be nice.
|
||||
|
||||
/*
|
||||
* insert --
|
||||
*
|
||||
* Returns a pair with iterator to the element at r.first and
|
||||
* success. Retrieve the index with ret.first.getIndex().
|
||||
*
|
||||
* Does not overwrite on key collision, but returns an iterator to
|
||||
* the existing element (since this could due to a race with
|
||||
* another thread, it is often important to check this return
|
||||
* value).
|
||||
*
|
||||
* Allocates new sub maps as the existing ones become full. If
|
||||
* all sub maps are full, no element is inserted, and
|
||||
* AtomicHashMapFullError is thrown.
|
||||
*/
|
||||
std::pair<iterator,bool> insert(const value_type& r) {
|
||||
return insert(r.first, r.second);
|
||||
}
|
||||
std::pair<iterator,bool> insert(key_type k, const mapped_type& v);
|
||||
std::pair<iterator,bool> insert(value_type&& r) {
|
||||
return insert(r.first, std::move(r.second));
|
||||
}
|
||||
std::pair<iterator,bool> insert(key_type k, mapped_type&& v);
|
||||
|
||||
/*
|
||||
* find --
|
||||
*
|
||||
* Returns an iterator into the map.
|
||||
*
|
||||
* If the key is not found, returns end().
|
||||
*/
|
||||
iterator find(key_type k);
|
||||
const_iterator find(key_type k) const;
|
||||
|
||||
/*
|
||||
* erase --
|
||||
*
|
||||
* Erases key k from the map
|
||||
*
|
||||
* Returns 1 iff the key is found and erased, and 0 otherwise.
|
||||
*/
|
||||
size_type erase(key_type k);
|
||||
|
||||
/*
|
||||
* clear --
|
||||
*
|
||||
* Wipes all keys and values from primary map and destroys all secondary
|
||||
* maps. Primary map remains allocated and thus the memory can be reused
|
||||
* in place. Not thread safe.
|
||||
*
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/*
|
||||
* size --
|
||||
*
|
||||
* Returns the exact size of the map. Note this is not as cheap as typical
|
||||
* size() implementations because, for each AtomicHashArray in this AHM, we
|
||||
* need to grab a lock and accumulate the values from all the thread local
|
||||
* counters. See folly/ThreadCachedInt.h for more details.
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
bool empty() const { return size() == 0; }
|
||||
|
||||
size_type count(key_type k) const {
|
||||
return find(k) == end() ? 0 : 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* findAt --
|
||||
*
|
||||
* Returns an iterator into the map.
|
||||
*
|
||||
* idx should only be an unmodified value returned by calling getIndex() on
|
||||
* a valid iterator returned by find() or insert(). If idx is invalid you
|
||||
* have a bug and the process aborts.
|
||||
*/
|
||||
iterator findAt(uint32_t idx) {
|
||||
SimpleRetT ret = findAtInternal(idx);
|
||||
DCHECK_LT(ret.i, numSubMaps());
|
||||
return iterator(this, ret.i,
|
||||
subMaps_[ret.i].load(std::memory_order_relaxed)->makeIter(ret.j));
|
||||
}
|
||||
const_iterator findAt(uint32_t idx) const {
|
||||
return const_cast<AtomicHashMap*>(this)->findAt(idx);
|
||||
}
|
||||
|
||||
// Total capacity - summation of capacities of all submaps.
|
||||
size_t capacity() const;
|
||||
|
||||
// Number of new insertions until current submaps are all at max load factor.
|
||||
size_t spaceRemaining() const;
|
||||
|
||||
void setEntryCountThreadCacheSize(int32_t newSize) {
|
||||
const int numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
for (int i = 0; i < numMaps; ++i) {
|
||||
SubMap* map = subMaps_[i].load(std::memory_order_relaxed);
|
||||
map->setEntryCountThreadCacheSize(newSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Number of sub maps allocated so far to implement this map. The more there
|
||||
// are, the worse the performance.
|
||||
int numSubMaps() const {
|
||||
return numMapsAllocated_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
iterator begin() {
|
||||
return iterator(this, 0,
|
||||
subMaps_[0].load(std::memory_order_relaxed)->begin());
|
||||
}
|
||||
|
||||
iterator end() {
|
||||
return iterator();
|
||||
}
|
||||
|
||||
const_iterator begin() const {
|
||||
return const_iterator(this, 0,
|
||||
subMaps_[0].load(std::memory_order_relaxed)->begin());
|
||||
}
|
||||
|
||||
const_iterator end() const {
|
||||
return const_iterator();
|
||||
}
|
||||
|
||||
/* Advanced functions for direct access: */
|
||||
|
||||
inline uint32_t recToIdx(const value_type& r, bool mayInsert = true) {
|
||||
SimpleRetT ret = mayInsert ?
|
||||
insertInternal(r.first, r.second) : findInternal(r.first);
|
||||
return encodeIndex(ret.i, ret.j);
|
||||
}
|
||||
|
||||
inline uint32_t recToIdx(value_type&& r, bool mayInsert = true) {
|
||||
SimpleRetT ret = mayInsert ?
|
||||
insertInternal(r.first, std::move(r.second)) : findInternal(r.first);
|
||||
return encodeIndex(ret.i, ret.j);
|
||||
}
|
||||
|
||||
inline uint32_t recToIdx(key_type k, const mapped_type& v,
|
||||
bool mayInsert = true) {
|
||||
SimpleRetT ret = mayInsert ? insertInternal(k, v) : findInternal(k);
|
||||
return encodeIndex(ret.i, ret.j);
|
||||
}
|
||||
|
||||
inline uint32_t recToIdx(key_type k, mapped_type&& v, bool mayInsert = true) {
|
||||
SimpleRetT ret = mayInsert ?
|
||||
insertInternal(k, std::move(v)) : findInternal(k);
|
||||
return encodeIndex(ret.i, ret.j);
|
||||
}
|
||||
|
||||
inline uint32_t keyToIdx(const KeyT k, bool mayInsert = false) {
|
||||
return recToIdx(value_type(k), mayInsert);
|
||||
}
|
||||
|
||||
inline const value_type& idxToRec(uint32_t idx) const {
|
||||
SimpleRetT ret = findAtInternal(idx);
|
||||
return subMaps_[ret.i].load(std::memory_order_relaxed)->idxToRec(ret.j);
|
||||
}
|
||||
|
||||
/* Private data and helper functions... */
|
||||
|
||||
private:
|
||||
// This limits primary submap size to 2^31 ~= 2 billion, secondary submap
|
||||
// size to 2^(32 - kNumSubMapBits_ - 1) = 2^27 ~= 130 million, and num subMaps
|
||||
// to 2^kNumSubMapBits_ = 16.
|
||||
static const uint32_t kNumSubMapBits_ = 4;
|
||||
static const uint32_t kSecondaryMapBit_ = 1u << 31; // Highest bit
|
||||
static const uint32_t kSubMapIndexShift_ = 32 - kNumSubMapBits_ - 1;
|
||||
static const uint32_t kSubMapIndexMask_ = (1 << kSubMapIndexShift_) - 1;
|
||||
static const uint32_t kNumSubMaps_ = 1 << kNumSubMapBits_;
|
||||
static const uintptr_t kLockedPtr_ = 0x88ul << 48; // invalid pointer
|
||||
|
||||
struct SimpleRetT { uint32_t i; size_t j; bool success;
|
||||
SimpleRetT(uint32_t ii, size_t jj, bool s) : i(ii), j(jj), success(s) {}
|
||||
SimpleRetT() {}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
SimpleRetT insertInternal(KeyT key, T&& value);
|
||||
|
||||
SimpleRetT findInternal(const KeyT k) const;
|
||||
|
||||
SimpleRetT findAtInternal(uint32_t idx) const;
|
||||
|
||||
std::atomic<SubMap*> subMaps_[kNumSubMaps_];
|
||||
std::atomic<uint32_t> numMapsAllocated_;
|
||||
|
||||
inline bool tryLockMap(int idx) {
|
||||
SubMap* val = nullptr;
|
||||
return subMaps_[idx].compare_exchange_strong(val, (SubMap*)kLockedPtr_,
|
||||
std::memory_order_acquire);
|
||||
}
|
||||
|
||||
static inline uint32_t encodeIndex(uint32_t subMap, uint32_t subMapIdx);
|
||||
|
||||
}; // AtomicHashMap
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#include "AtomicHashMap-inl.h"
|
||||
|
||||
#endif // FOLLY_ATOMICHASHMAP_H_
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/Bits.h"
|
||||
|
||||
#include "folly/CpuId.h"
|
||||
|
||||
// None of this is necessary if we're compiling for a target that supports
|
||||
// popcnt
|
||||
#ifndef __POPCNT__
|
||||
|
||||
namespace {
|
||||
|
||||
int popcount_builtin(unsigned int x) {
|
||||
return __builtin_popcount(x);
|
||||
}
|
||||
|
||||
int popcountll_builtin(unsigned long long x) {
|
||||
return __builtin_popcountll(x);
|
||||
}
|
||||
|
||||
|
||||
#if FOLLY_HAVE_IFUNC
|
||||
|
||||
// Strictly speaking, these versions of popcount are usable without ifunc
|
||||
// support. However, we would have to check, via CpuId, if the processor
|
||||
// implements the popcnt instruction first, which is what we use ifunc for.
|
||||
int popcount_inst(unsigned int x) {
|
||||
int n;
|
||||
asm ("popcntl %1, %0" : "=r" (n) : "r" (x));
|
||||
return n;
|
||||
}
|
||||
|
||||
int popcountll_inst(unsigned long long x) {
|
||||
unsigned long long n;
|
||||
asm ("popcntq %1, %0" : "=r" (n) : "r" (x));
|
||||
return n;
|
||||
}
|
||||
|
||||
typedef decltype(popcount_builtin) Type_popcount;
|
||||
typedef decltype(popcountll_builtin) Type_popcountll;
|
||||
|
||||
// This function is called on startup to resolve folly::detail::popcount
|
||||
extern "C" Type_popcount* folly_popcount_ifunc() {
|
||||
return folly::CpuId().popcnt() ? popcount_inst : popcount_builtin;
|
||||
}
|
||||
|
||||
// This function is called on startup to resolve folly::detail::popcountll
|
||||
extern "C" Type_popcountll* folly_popcountll_ifunc() {
|
||||
return folly::CpuId().popcnt() ? popcountll_inst : popcountll_builtin;
|
||||
}
|
||||
|
||||
#endif // FOLLY_HAVE_IFUNC
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace folly {
|
||||
namespace detail {
|
||||
|
||||
// Call folly_popcount_ifunc on startup to resolve to either popcount_inst
|
||||
// or popcount_builtin
|
||||
int popcount(unsigned int x)
|
||||
#if FOLLY_HAVE_IFUNC
|
||||
__attribute__((ifunc("folly_popcount_ifunc")));
|
||||
#else
|
||||
{ return popcount_builtin(x); }
|
||||
#endif
|
||||
|
||||
// Call folly_popcount_ifunc on startup to resolve to either popcountll_inst
|
||||
// or popcountll_builtin
|
||||
int popcountll(unsigned long long x)
|
||||
#if FOLLY_HAVE_IFUNC
|
||||
__attribute__((ifunc("folly_popcountll_ifunc")));
|
||||
#else
|
||||
{ return popcountll_builtin(x); }
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
} // namespace folly
|
||||
|
||||
#endif /* !__POPCNT__ */
|
||||
-558
@@ -1,558 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Various low-level, bit-manipulation routines.
|
||||
*
|
||||
* findFirstSet(x) [constexpr]
|
||||
* find first (least significant) bit set in a value of an integral type,
|
||||
* 1-based (like ffs()). 0 = no bits are set (x == 0)
|
||||
*
|
||||
* findLastSet(x) [constexpr]
|
||||
* find last (most significant) bit set in a value of an integral type,
|
||||
* 1-based. 0 = no bits are set (x == 0)
|
||||
* for x != 0, findLastSet(x) == 1 + floor(log2(x))
|
||||
*
|
||||
* nextPowTwo(x) [constexpr]
|
||||
* Finds the next power of two >= x.
|
||||
*
|
||||
* isPowTwo(x) [constexpr]
|
||||
* return true iff x is a power of two
|
||||
*
|
||||
* popcount(x)
|
||||
* return the number of 1 bits in x
|
||||
*
|
||||
* Endian
|
||||
* convert between native, big, and little endian representation
|
||||
* Endian::big(x) big <-> native
|
||||
* Endian::little(x) little <-> native
|
||||
* Endian::swap(x) big <-> little
|
||||
*
|
||||
* BitIterator
|
||||
* Wrapper around an iterator over an integral type that iterates
|
||||
* over its underlying bits in MSb to LSb order
|
||||
*
|
||||
* findFirstSet(BitIterator begin, BitIterator end)
|
||||
* return a BitIterator pointing to the first 1 bit in [begin, end), or
|
||||
* end if all bits in [begin, end) are 0
|
||||
*
|
||||
* @author Tudor Bosman (tudorb@fb.com)
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_BITS_H_
|
||||
#define FOLLY_BITS_H_
|
||||
|
||||
#include "folly/Portability.h"
|
||||
|
||||
#ifndef __GNUC__
|
||||
#error GCC required
|
||||
#endif
|
||||
|
||||
#ifndef __clang__
|
||||
#define FOLLY_INTRINSIC_CONSTEXPR constexpr
|
||||
#else
|
||||
// Unlike GCC, in Clang (as of 3.2) intrinsics aren't constexpr.
|
||||
#define FOLLY_INTRINSIC_CONSTEXPR const
|
||||
#endif
|
||||
|
||||
#ifndef FOLLY_NO_CONFIG
|
||||
#include "folly/folly-config.h"
|
||||
#endif
|
||||
|
||||
#include "folly/detail/BitsDetail.h"
|
||||
#include "folly/detail/BitIteratorDetail.h"
|
||||
#include "folly/Likely.h"
|
||||
|
||||
#if FOLLY_HAVE_BYTESWAP_H
|
||||
# include <byteswap.h>
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <boost/iterator/iterator_adaptor.hpp>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
// Generate overloads for findFirstSet as wrappers around
|
||||
// appropriate ffs, ffsl, ffsll gcc builtins
|
||||
template <class T>
|
||||
inline FOLLY_INTRINSIC_CONSTEXPR
|
||||
typename std::enable_if<
|
||||
(std::is_integral<T>::value &&
|
||||
std::is_unsigned<T>::value &&
|
||||
sizeof(T) <= sizeof(unsigned int)),
|
||||
unsigned int>::type
|
||||
findFirstSet(T x) {
|
||||
return __builtin_ffs(x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline FOLLY_INTRINSIC_CONSTEXPR
|
||||
typename std::enable_if<
|
||||
(std::is_integral<T>::value &&
|
||||
std::is_unsigned<T>::value &&
|
||||
sizeof(T) > sizeof(unsigned int) &&
|
||||
sizeof(T) <= sizeof(unsigned long)),
|
||||
unsigned int>::type
|
||||
findFirstSet(T x) {
|
||||
return __builtin_ffsl(x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline FOLLY_INTRINSIC_CONSTEXPR
|
||||
typename std::enable_if<
|
||||
(std::is_integral<T>::value &&
|
||||
std::is_unsigned<T>::value &&
|
||||
sizeof(T) > sizeof(unsigned long) &&
|
||||
sizeof(T) <= sizeof(unsigned long long)),
|
||||
unsigned int>::type
|
||||
findFirstSet(T x) {
|
||||
return __builtin_ffsll(x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline FOLLY_INTRINSIC_CONSTEXPR
|
||||
typename std::enable_if<
|
||||
(std::is_integral<T>::value && std::is_signed<T>::value),
|
||||
unsigned int>::type
|
||||
findFirstSet(T x) {
|
||||
// Note that conversion from a signed type to the corresponding unsigned
|
||||
// type is technically implementation-defined, but will likely work
|
||||
// on any impementation that uses two's complement.
|
||||
return findFirstSet(static_cast<typename std::make_unsigned<T>::type>(x));
|
||||
}
|
||||
|
||||
// findLastSet: return the 1-based index of the highest bit set
|
||||
// for x > 0, findLastSet(x) == 1 + floor(log2(x))
|
||||
template <class T>
|
||||
inline FOLLY_INTRINSIC_CONSTEXPR
|
||||
typename std::enable_if<
|
||||
(std::is_integral<T>::value &&
|
||||
std::is_unsigned<T>::value &&
|
||||
sizeof(T) <= sizeof(unsigned int)),
|
||||
unsigned int>::type
|
||||
findLastSet(T x) {
|
||||
return x ? 8 * sizeof(unsigned int) - __builtin_clz(x) : 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline FOLLY_INTRINSIC_CONSTEXPR
|
||||
typename std::enable_if<
|
||||
(std::is_integral<T>::value &&
|
||||
std::is_unsigned<T>::value &&
|
||||
sizeof(T) > sizeof(unsigned int) &&
|
||||
sizeof(T) <= sizeof(unsigned long)),
|
||||
unsigned int>::type
|
||||
findLastSet(T x) {
|
||||
return x ? 8 * sizeof(unsigned long) - __builtin_clzl(x) : 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline FOLLY_INTRINSIC_CONSTEXPR
|
||||
typename std::enable_if<
|
||||
(std::is_integral<T>::value &&
|
||||
std::is_unsigned<T>::value &&
|
||||
sizeof(T) > sizeof(unsigned long) &&
|
||||
sizeof(T) <= sizeof(unsigned long long)),
|
||||
unsigned int>::type
|
||||
findLastSet(T x) {
|
||||
return x ? 8 * sizeof(unsigned long long) - __builtin_clzll(x) : 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline FOLLY_INTRINSIC_CONSTEXPR
|
||||
typename std::enable_if<
|
||||
(std::is_integral<T>::value &&
|
||||
std::is_signed<T>::value),
|
||||
unsigned int>::type
|
||||
findLastSet(T x) {
|
||||
return findLastSet(static_cast<typename std::make_unsigned<T>::type>(x));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline FOLLY_INTRINSIC_CONSTEXPR
|
||||
typename std::enable_if<
|
||||
std::is_integral<T>::value && std::is_unsigned<T>::value,
|
||||
T>::type
|
||||
nextPowTwo(T v) {
|
||||
return v ? (1ul << findLastSet(v - 1)) : 1;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline constexpr
|
||||
typename std::enable_if<
|
||||
std::is_integral<T>::value && std::is_unsigned<T>::value,
|
||||
bool>::type
|
||||
isPowTwo(T v) {
|
||||
return (v != 0) && !(v & (v - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Population count
|
||||
*/
|
||||
template <class T>
|
||||
inline typename std::enable_if<
|
||||
(std::is_integral<T>::value &&
|
||||
std::is_unsigned<T>::value &&
|
||||
sizeof(T) <= sizeof(unsigned int)),
|
||||
size_t>::type
|
||||
popcount(T x) {
|
||||
return detail::popcount(x);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline typename std::enable_if<
|
||||
(std::is_integral<T>::value &&
|
||||
std::is_unsigned<T>::value &&
|
||||
sizeof(T) > sizeof(unsigned int) &&
|
||||
sizeof(T) <= sizeof(unsigned long long)),
|
||||
size_t>::type
|
||||
popcount(T x) {
|
||||
return detail::popcountll(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endianness detection and manipulation primitives.
|
||||
*/
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
struct EndianIntBase {
|
||||
public:
|
||||
static T swap(T x);
|
||||
};
|
||||
|
||||
/**
|
||||
* If we have the bswap_16 macro from byteswap.h, use it; otherwise, provide our
|
||||
* own definition.
|
||||
*/
|
||||
#ifdef bswap_16
|
||||
# define our_bswap16 bswap_16
|
||||
#else
|
||||
|
||||
template<class Int16>
|
||||
inline constexpr typename std::enable_if<
|
||||
sizeof(Int16) == 2,
|
||||
Int16>::type
|
||||
our_bswap16(Int16 x) {
|
||||
return ((x >> 8) & 0xff) | ((x & 0xff) << 8);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define FB_GEN(t, fn) \
|
||||
template<> inline t EndianIntBase<t>::swap(t x) { return fn(x); }
|
||||
|
||||
// fn(x) expands to (x) if the second argument is empty, which is exactly
|
||||
// what we want for [u]int8_t. Also, gcc 4.7 on Intel doesn't have
|
||||
// __builtin_bswap16 for some reason, so we have to provide our own.
|
||||
FB_GEN( int8_t,)
|
||||
FB_GEN(uint8_t,)
|
||||
FB_GEN( int64_t, __builtin_bswap64)
|
||||
FB_GEN(uint64_t, __builtin_bswap64)
|
||||
FB_GEN( int32_t, __builtin_bswap32)
|
||||
FB_GEN(uint32_t, __builtin_bswap32)
|
||||
FB_GEN( int16_t, our_bswap16)
|
||||
FB_GEN(uint16_t, our_bswap16)
|
||||
|
||||
#undef FB_GEN
|
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
|
||||
template <class T>
|
||||
struct EndianInt : public detail::EndianIntBase<T> {
|
||||
public:
|
||||
static T big(T x) { return EndianInt::swap(x); }
|
||||
static T little(T x) { return x; }
|
||||
};
|
||||
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
|
||||
template <class T>
|
||||
struct EndianInt : public detail::EndianIntBase<T> {
|
||||
public:
|
||||
static T big(T x) { return x; }
|
||||
static T little(T x) { return EndianInt::swap(x); }
|
||||
};
|
||||
|
||||
#else
|
||||
# error Your machine uses a weird endianness!
|
||||
#endif /* __BYTE_ORDER__ */
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// big* convert between native and big-endian representations
|
||||
// little* convert between native and little-endian representations
|
||||
// swap* convert between big-endian and little-endian representations
|
||||
//
|
||||
// ntohs, htons == big16
|
||||
// ntohl, htonl == big32
|
||||
#define FB_GEN1(fn, t, sz) \
|
||||
static t fn##sz(t x) { return fn<t>(x); } \
|
||||
|
||||
#define FB_GEN2(t, sz) \
|
||||
FB_GEN1(swap, t, sz) \
|
||||
FB_GEN1(big, t, sz) \
|
||||
FB_GEN1(little, t, sz)
|
||||
|
||||
#define FB_GEN(sz) \
|
||||
FB_GEN2(uint##sz##_t, sz) \
|
||||
FB_GEN2(int##sz##_t, sz)
|
||||
|
||||
class Endian {
|
||||
public:
|
||||
enum class Order : uint8_t {
|
||||
LITTLE,
|
||||
BIG
|
||||
};
|
||||
|
||||
static constexpr Order order =
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
Order::LITTLE;
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
Order::BIG;
|
||||
#else
|
||||
# error Your machine uses a weird endianness!
|
||||
#endif /* __BYTE_ORDER__ */
|
||||
|
||||
template <class T> static T swap(T x) {
|
||||
return detail::EndianInt<T>::swap(x);
|
||||
}
|
||||
template <class T> static T big(T x) {
|
||||
return detail::EndianInt<T>::big(x);
|
||||
}
|
||||
template <class T> static T little(T x) {
|
||||
return detail::EndianInt<T>::little(x);
|
||||
}
|
||||
|
||||
FB_GEN(64)
|
||||
FB_GEN(32)
|
||||
FB_GEN(16)
|
||||
FB_GEN(8)
|
||||
};
|
||||
|
||||
#undef FB_GEN
|
||||
#undef FB_GEN2
|
||||
#undef FB_GEN1
|
||||
|
||||
/**
|
||||
* Fast bit iteration facility.
|
||||
*/
|
||||
|
||||
|
||||
template <class BaseIter> class BitIterator;
|
||||
template <class BaseIter>
|
||||
BitIterator<BaseIter> findFirstSet(BitIterator<BaseIter>,
|
||||
BitIterator<BaseIter>);
|
||||
/**
|
||||
* Wrapper around an iterator over an integer type that iterates
|
||||
* over its underlying bits in LSb to MSb order.
|
||||
*
|
||||
* BitIterator models the same iterator concepts as the base iterator.
|
||||
*/
|
||||
template <class BaseIter>
|
||||
class BitIterator
|
||||
: public bititerator_detail::BitIteratorBase<BaseIter>::type {
|
||||
public:
|
||||
/**
|
||||
* Return the number of bits in an element of the underlying iterator.
|
||||
*/
|
||||
static size_t bitsPerBlock() {
|
||||
return std::numeric_limits<
|
||||
typename std::make_unsigned<
|
||||
typename std::iterator_traits<BaseIter>::value_type
|
||||
>::type
|
||||
>::digits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a BitIterator that points at a given bit offset (default 0)
|
||||
* in iter.
|
||||
*/
|
||||
#pragma GCC diagnostic push // bitOffset shadows a member
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
explicit BitIterator(const BaseIter& iter, size_t bitOffset=0)
|
||||
: bititerator_detail::BitIteratorBase<BaseIter>::type(iter),
|
||||
bitOffset_(bitOffset) {
|
||||
assert(bitOffset_ < bitsPerBlock());
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
size_t bitOffset() const {
|
||||
return bitOffset_;
|
||||
}
|
||||
|
||||
void advanceToNextBlock() {
|
||||
bitOffset_ = 0;
|
||||
++this->base_reference();
|
||||
}
|
||||
|
||||
BitIterator& operator=(const BaseIter& other) {
|
||||
this->~BitIterator();
|
||||
new (this) BitIterator(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class boost::iterator_core_access;
|
||||
friend BitIterator findFirstSet<>(BitIterator, BitIterator);
|
||||
|
||||
typedef bititerator_detail::BitReference<
|
||||
typename std::iterator_traits<BaseIter>::reference,
|
||||
typename std::iterator_traits<BaseIter>::value_type
|
||||
> BitRef;
|
||||
|
||||
void advanceInBlock(size_t n) {
|
||||
bitOffset_ += n;
|
||||
assert(bitOffset_ < bitsPerBlock());
|
||||
}
|
||||
|
||||
BitRef dereference() const {
|
||||
return BitRef(*this->base_reference(), bitOffset_);
|
||||
}
|
||||
|
||||
void advance(ssize_t n) {
|
||||
size_t bpb = bitsPerBlock();
|
||||
ssize_t blocks = n / bpb;
|
||||
bitOffset_ += n % bpb;
|
||||
if (bitOffset_ >= bpb) {
|
||||
bitOffset_ -= bpb;
|
||||
++blocks;
|
||||
}
|
||||
this->base_reference() += blocks;
|
||||
}
|
||||
|
||||
void increment() {
|
||||
if (++bitOffset_ == bitsPerBlock()) {
|
||||
advanceToNextBlock();
|
||||
}
|
||||
}
|
||||
|
||||
void decrement() {
|
||||
if (bitOffset_-- == 0) {
|
||||
bitOffset_ = bitsPerBlock() - 1;
|
||||
--this->base_reference();
|
||||
}
|
||||
}
|
||||
|
||||
bool equal(const BitIterator& other) const {
|
||||
return (bitOffset_ == other.bitOffset_ &&
|
||||
this->base_reference() == other.base_reference());
|
||||
}
|
||||
|
||||
ssize_t distance_to(const BitIterator& other) const {
|
||||
return
|
||||
(other.base_reference() - this->base_reference()) * bitsPerBlock() +
|
||||
(other.bitOffset_ - bitOffset_);
|
||||
}
|
||||
|
||||
ssize_t bitOffset_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function, so you can write
|
||||
* auto bi = makeBitIterator(container.begin());
|
||||
*/
|
||||
template <class BaseIter>
|
||||
BitIterator<BaseIter> makeBitIterator(const BaseIter& iter) {
|
||||
return BitIterator<BaseIter>(iter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find first bit set in a range of bit iterators.
|
||||
* 4.5x faster than the obvious std::find(begin, end, true);
|
||||
*/
|
||||
template <class BaseIter>
|
||||
BitIterator<BaseIter> findFirstSet(BitIterator<BaseIter> begin,
|
||||
BitIterator<BaseIter> end) {
|
||||
// shortcut to avoid ugly static_cast<>
|
||||
static const typename BaseIter::value_type one = 1;
|
||||
|
||||
while (begin.base() != end.base()) {
|
||||
typename BaseIter::value_type v = *begin.base();
|
||||
// mask out the bits that don't matter (< begin.bitOffset)
|
||||
v &= ~((one << begin.bitOffset()) - 1);
|
||||
size_t firstSet = findFirstSet(v);
|
||||
if (firstSet) {
|
||||
--firstSet; // now it's 0-based
|
||||
assert(firstSet >= begin.bitOffset());
|
||||
begin.advanceInBlock(firstSet - begin.bitOffset());
|
||||
return begin;
|
||||
}
|
||||
begin.advanceToNextBlock();
|
||||
}
|
||||
|
||||
// now begin points to the same block as end
|
||||
if (end.bitOffset() != 0) { // assume end is dereferenceable
|
||||
typename BaseIter::value_type v = *begin.base();
|
||||
// mask out the bits that don't matter (< begin.bitOffset)
|
||||
v &= ~((one << begin.bitOffset()) - 1);
|
||||
// mask out the bits that don't matter (>= end.bitOffset)
|
||||
v &= (one << end.bitOffset()) - 1;
|
||||
size_t firstSet = findFirstSet(v);
|
||||
if (firstSet) {
|
||||
--firstSet; // now it's 0-based
|
||||
assert(firstSet >= begin.bitOffset());
|
||||
begin.advanceInBlock(firstSet - begin.bitOffset());
|
||||
return begin;
|
||||
}
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
|
||||
template <class T, class Enable=void> struct Unaligned;
|
||||
|
||||
/**
|
||||
* Representation of an unaligned value of a POD type.
|
||||
*/
|
||||
template <class T>
|
||||
struct Unaligned<
|
||||
T,
|
||||
typename std::enable_if<std::is_pod<T>::value>::type> {
|
||||
Unaligned() = default; // uninitialized
|
||||
/* implicit */ Unaligned(T v) : value(v) { }
|
||||
T value;
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* Read an unaligned value of type T and return it.
|
||||
*/
|
||||
template <class T>
|
||||
inline T loadUnaligned(const void* p) {
|
||||
static_assert(sizeof(Unaligned<T>) == sizeof(T), "Invalid unaligned size");
|
||||
static_assert(alignof(Unaligned<T>) == 1, "Invalid alignment");
|
||||
return static_cast<const Unaligned<T>*>(p)->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an unaligned value of type T.
|
||||
*/
|
||||
template <class T>
|
||||
inline void storeUnaligned(void* p, T value) {
|
||||
static_assert(sizeof(Unaligned<T>) == sizeof(T), "Invalid unaligned size");
|
||||
static_assert(alignof(Unaligned<T>) == 1, "Invalid alignment");
|
||||
new (p) Unaligned<T>(value);
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_BITS_H_ */
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Wrapper around <chrono> that hides away some gcc 4.6 issues
|
||||
#ifndef FOLLY_CHRONO_H_
|
||||
#define FOLLY_CHRONO_H_
|
||||
|
||||
#include <chrono>
|
||||
#include "folly/Portability.h"
|
||||
|
||||
// gcc 4.6 uses an obsolete name for steady_clock, although the implementation
|
||||
// is the same
|
||||
#if __GNUC_PREREQ(4, 6) && !__GNUC_PREREQ(4, 7)
|
||||
namespace std { namespace chrono {
|
||||
typedef monotonic_clock steady_clock;
|
||||
}} // namespaces
|
||||
#endif
|
||||
|
||||
#endif /* FOLLY_CHRONO_H_ */
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @author: Xin Liu <xliux@fb.com>
|
||||
|
||||
#ifndef FOLLY_CONCURRENTSKIPLIST_INL_H_
|
||||
#define FOLLY_CONCURRENTSKIPLIST_INL_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <boost/random.hpp>
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include "folly/SmallLocks.h"
|
||||
#include "folly/ThreadLocal.h"
|
||||
|
||||
namespace folly { namespace detail {
|
||||
|
||||
template<typename ValT, typename NodeT> class csl_iterator;
|
||||
|
||||
template<typename T>
|
||||
class SkipListNode : boost::noncopyable {
|
||||
enum {
|
||||
IS_HEAD_NODE = 1,
|
||||
MARKED_FOR_REMOVAL = (1 << 1),
|
||||
FULLY_LINKED = (1 << 2),
|
||||
};
|
||||
public:
|
||||
typedef T value_type;
|
||||
|
||||
template<typename U,
|
||||
typename=typename std::enable_if<std::is_convertible<U, T>::value>::type>
|
||||
static SkipListNode* create(int height, U&& data, bool isHead = false) {
|
||||
DCHECK(height >= 1 && height < 64) << height;
|
||||
|
||||
size_t size = sizeof(SkipListNode) +
|
||||
height * sizeof(std::atomic<SkipListNode*>);
|
||||
auto* node = static_cast<SkipListNode*>(malloc(size));
|
||||
// do placement new
|
||||
new (node) SkipListNode(height, std::forward<U>(data), isHead);
|
||||
return node;
|
||||
}
|
||||
|
||||
static void destroy(SkipListNode* node) {
|
||||
node->~SkipListNode();
|
||||
free(node);
|
||||
}
|
||||
|
||||
// copy the head node to a new head node assuming lock acquired
|
||||
SkipListNode* copyHead(SkipListNode* node) {
|
||||
DCHECK(node != nullptr && height_ > node->height_);
|
||||
setFlags(node->getFlags());
|
||||
for (int i = 0; i < node->height_; ++i) {
|
||||
setSkip(i, node->skip(i));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
inline SkipListNode* skip(int layer) const {
|
||||
DCHECK_LT(layer, height_);
|
||||
return skip_[layer].load(std::memory_order_consume);
|
||||
}
|
||||
|
||||
// next valid node as in the linked list
|
||||
SkipListNode* next() {
|
||||
SkipListNode* node;
|
||||
for (node = skip(0);
|
||||
(node != nullptr && node->markedForRemoval());
|
||||
node = node->skip(0)) {}
|
||||
return node;
|
||||
}
|
||||
|
||||
void setSkip(uint8_t h, SkipListNode* next) {
|
||||
DCHECK_LT(h, height_);
|
||||
skip_[h].store(next, std::memory_order_release);
|
||||
}
|
||||
|
||||
value_type& data() { return data_; }
|
||||
const value_type& data() const { return data_; }
|
||||
int maxLayer() const { return height_ - 1; }
|
||||
int height() const { return height_; }
|
||||
|
||||
std::unique_lock<MicroSpinLock> acquireGuard() {
|
||||
return std::unique_lock<MicroSpinLock>(spinLock_);
|
||||
}
|
||||
|
||||
bool fullyLinked() const { return getFlags() & FULLY_LINKED; }
|
||||
bool markedForRemoval() const { return getFlags() & MARKED_FOR_REMOVAL; }
|
||||
bool isHeadNode() const { return getFlags() & IS_HEAD_NODE; }
|
||||
|
||||
void setIsHeadNode() {
|
||||
setFlags(getFlags() | IS_HEAD_NODE);
|
||||
}
|
||||
void setFullyLinked() {
|
||||
setFlags(getFlags() | FULLY_LINKED);
|
||||
}
|
||||
void setMarkedForRemoval() {
|
||||
setFlags(getFlags() | MARKED_FOR_REMOVAL);
|
||||
}
|
||||
|
||||
private:
|
||||
// Note! this can only be called from create() as a placement new.
|
||||
template<typename U>
|
||||
SkipListNode(uint8_t height, U&& data, bool isHead) :
|
||||
height_(height), data_(std::forward<U>(data)) {
|
||||
spinLock_.init();
|
||||
setFlags(0);
|
||||
if (isHead) setIsHeadNode();
|
||||
// need to explicitly init the dynamic atomic pointer array
|
||||
for (uint8_t i = 0; i < height_; ++i) {
|
||||
new (&skip_[i]) std::atomic<SkipListNode*>(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
~SkipListNode() {
|
||||
for (uint8_t i = 0; i < height_; ++i) {
|
||||
skip_[i].~atomic();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t getFlags() const {
|
||||
return flags_.load(std::memory_order_consume);
|
||||
}
|
||||
void setFlags(uint16_t flags) {
|
||||
flags_.store(flags, std::memory_order_release);
|
||||
}
|
||||
|
||||
// TODO(xliu): on x86_64, it's possible to squeeze these into
|
||||
// skip_[0] to maybe save 8 bytes depending on the data alignments.
|
||||
// NOTE: currently this is x86_64 only anyway, due to the
|
||||
// MicroSpinLock.
|
||||
std::atomic<uint16_t> flags_;
|
||||
const uint8_t height_;
|
||||
MicroSpinLock spinLock_;
|
||||
|
||||
value_type data_;
|
||||
|
||||
std::atomic<SkipListNode*> skip_[0];
|
||||
};
|
||||
|
||||
class SkipListRandomHeight {
|
||||
enum { kMaxHeight = 64 };
|
||||
public:
|
||||
// make it a singleton.
|
||||
static SkipListRandomHeight *instance() {
|
||||
static SkipListRandomHeight instance_;
|
||||
return &instance_;
|
||||
}
|
||||
|
||||
int getHeight(int maxHeight) const {
|
||||
DCHECK_LE(maxHeight, kMaxHeight) << "max height too big!";
|
||||
double p = randomProb();
|
||||
for (int i = 0; i < maxHeight; ++i) {
|
||||
if (p < lookupTable_[i]) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
size_t getSizeLimit(int height) const {
|
||||
DCHECK_LT(height, kMaxHeight);
|
||||
return sizeLimitTable_[height];
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
SkipListRandomHeight() { initLookupTable(); }
|
||||
|
||||
void initLookupTable() {
|
||||
// set skip prob = 1/E
|
||||
static const double kProbInv = exp(1);
|
||||
static const double kProb = 1.0 / kProbInv;
|
||||
static const size_t kMaxSizeLimit = std::numeric_limits<size_t>::max();
|
||||
|
||||
double sizeLimit = 1;
|
||||
double p = lookupTable_[0] = (1 - kProb);
|
||||
sizeLimitTable_[0] = 1;
|
||||
for (int i = 1; i < kMaxHeight - 1; ++i) {
|
||||
p *= kProb;
|
||||
sizeLimit *= kProbInv;
|
||||
lookupTable_[i] = lookupTable_[i - 1] + p;
|
||||
sizeLimitTable_[i] = sizeLimit > kMaxSizeLimit ?
|
||||
kMaxSizeLimit :
|
||||
static_cast<size_t>(sizeLimit);
|
||||
}
|
||||
lookupTable_[kMaxHeight - 1] = 1;
|
||||
sizeLimitTable_[kMaxHeight - 1] = kMaxSizeLimit;
|
||||
}
|
||||
|
||||
static double randomProb() {
|
||||
static ThreadLocal<boost::lagged_fibonacci2281> rng_;
|
||||
return (*rng_)();
|
||||
}
|
||||
|
||||
double lookupTable_[kMaxHeight];
|
||||
size_t sizeLimitTable_[kMaxHeight];
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif // FOLLY_CONCURRENTSKIPLIST_INL_H_
|
||||
@@ -1,859 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @author: Xin Liu <xliux@fb.com>
|
||||
//
|
||||
// A concurrent skip list (CSL) implementation.
|
||||
// Ref: http://www.cs.tau.ac.il/~shanir/nir-pubs-web/Papers/OPODIS2006-BA.pdf
|
||||
|
||||
/*
|
||||
|
||||
This implements a sorted associative container that supports only
|
||||
unique keys. (Similar to std::set.)
|
||||
|
||||
Features:
|
||||
|
||||
1. Small memory overhead: ~40% less memory overhead compared with
|
||||
std::set (1.6 words per node versus 3). It has an minimum of 4
|
||||
words (7 words if there nodes got deleted) per-list overhead
|
||||
though.
|
||||
|
||||
2. Read accesses (count, find iterator, skipper) are lock-free and
|
||||
mostly wait-free (the only wait a reader may need to do is when
|
||||
the node it is visiting is in a pending stage, i.e. deleting,
|
||||
adding and not fully linked). Write accesses (remove, add) need
|
||||
to acquire locks, but locks are local to the predecessor nodes
|
||||
and/or successor nodes.
|
||||
|
||||
3. Good high contention performance, comparable single-thread
|
||||
performance. In the multithreaded case (12 workers), CSL tested
|
||||
10x faster than a RWSpinLocked std::set for an averaged sized
|
||||
list (1K - 1M nodes).
|
||||
|
||||
Comparable read performance to std::set when single threaded,
|
||||
especially when the list size is large, and scales better to
|
||||
larger lists: when the size is small, CSL can be 20-50% slower on
|
||||
find()/contains(). As the size gets large (> 1M elements),
|
||||
find()/contains() can be 30% faster.
|
||||
|
||||
Iterating through a skiplist is similar to iterating through a
|
||||
linked list, thus is much (2-6x) faster than on a std::set
|
||||
(tree-based). This is especially true for short lists due to
|
||||
better cache locality. Based on that, it's also faster to
|
||||
intersect two skiplists.
|
||||
|
||||
4. Lazy removal with GC support. The removed nodes get deleted when
|
||||
the last Accessor to the skiplist is destroyed.
|
||||
|
||||
Caveats:
|
||||
|
||||
1. Write operations are usually 30% slower than std::set in a single
|
||||
threaded environment.
|
||||
|
||||
2. Need to have a head node for each list, which has a 4 word
|
||||
overhead.
|
||||
|
||||
3. When the list is quite small (< 1000 elements), single threaded
|
||||
benchmarks show CSL can be 10x slower than std:set.
|
||||
|
||||
4. The interface requires using an Accessor to access the skiplist.
|
||||
(See below.)
|
||||
|
||||
5. Currently x64 only, due to use of MicroSpinLock.
|
||||
|
||||
6. Freed nodes will not be reclaimed as long as there are ongoing
|
||||
uses of the list.
|
||||
|
||||
Sample usage:
|
||||
|
||||
typedef ConcurrentSkipList<int> SkipListT;
|
||||
shared_ptr<SkipListT> sl(SkipListT::createInstance(init_head_height);
|
||||
{
|
||||
// It's usually good practice to hold an accessor only during
|
||||
// its necessary life cycle (but not in a tight loop as
|
||||
// Accessor creation incurs ref-counting overhead).
|
||||
//
|
||||
// Holding it longer delays garbage-collecting the deleted
|
||||
// nodes in the list.
|
||||
SkipListT::Accessor accessor(sl);
|
||||
accessor.insert(23);
|
||||
accessor.erase(2);
|
||||
for (auto &elem : accessor) {
|
||||
// use elem to access data
|
||||
}
|
||||
... ...
|
||||
}
|
||||
|
||||
Another useful type is the Skipper accessor. This is useful if you
|
||||
want to skip to locations in the way std::lower_bound() works,
|
||||
i.e. it can be used for going through the list by skipping to the
|
||||
node no less than a specified key. The Skipper keeps its location as
|
||||
state, which makes it convenient for things like implementing
|
||||
intersection of two sets efficiently, as it can start from the last
|
||||
visited position.
|
||||
|
||||
{
|
||||
SkipListT::Accessor accessor(sl);
|
||||
SkipListT::Skipper skipper(accessor);
|
||||
skipper.to(30);
|
||||
if (skipper) {
|
||||
CHECK_LE(30, *skipper);
|
||||
}
|
||||
... ...
|
||||
// GC may happen when the accessor gets destructed.
|
||||
}
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_CONCURRENT_SKIP_LIST_H_
|
||||
#define FOLLY_CONCURRENT_SKIP_LIST_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include "folly/ConcurrentSkipList-inl.h"
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/SmallLocks.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
template<typename T, typename Comp=std::less<T>, int MAX_HEIGHT=24>
|
||||
class ConcurrentSkipList {
|
||||
// MAX_HEIGHT needs to be at least 2 to suppress compiler
|
||||
// warnings/errors (Werror=uninitialized tiggered due to preds_[1]
|
||||
// being treated as a scalar in the compiler).
|
||||
static_assert(MAX_HEIGHT >= 2 && MAX_HEIGHT < 64,
|
||||
"MAX_HEIGHT can only be in the range of [2, 64)");
|
||||
typedef std::unique_lock<folly::MicroSpinLock> ScopedLocker;
|
||||
typedef ConcurrentSkipList<T, Comp, MAX_HEIGHT> SkipListType;
|
||||
|
||||
public:
|
||||
typedef detail::SkipListNode<T> NodeType;
|
||||
typedef T value_type;
|
||||
typedef T key_type;
|
||||
|
||||
|
||||
typedef detail::csl_iterator<value_type, NodeType> iterator;
|
||||
typedef detail::csl_iterator<const value_type, const NodeType> const_iterator;
|
||||
|
||||
class Accessor;
|
||||
class Skipper;
|
||||
|
||||
// convenient function to get an Accessor to a new instance.
|
||||
static Accessor create(int height=1) {
|
||||
return Accessor(createInstance(height));
|
||||
}
|
||||
|
||||
// create a shared_ptr skiplist object with initial head height.
|
||||
static boost::shared_ptr<SkipListType> createInstance(int height=1) {
|
||||
return boost::shared_ptr<SkipListType>(new SkipListType(height));
|
||||
}
|
||||
|
||||
// create a unique_ptr skiplist object with initial head height.
|
||||
static std::unique_ptr<SkipListType> createRawInstance(int height=1) {
|
||||
return std::unique_ptr<SkipListType>(new SkipListType(height));
|
||||
}
|
||||
|
||||
|
||||
//===================================================================
|
||||
// Below are implementation details.
|
||||
// Please see ConcurrentSkipList::Accessor for stdlib-like APIs.
|
||||
//===================================================================
|
||||
|
||||
~ConcurrentSkipList() {
|
||||
CHECK_EQ(recycler_.refs(), 0);
|
||||
while (NodeType* current = head_.load(std::memory_order_relaxed)) {
|
||||
NodeType* tmp = current->skip(0);
|
||||
NodeType::destroy(current);
|
||||
head_.store(tmp, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static bool greater(const value_type &data, const NodeType *node) {
|
||||
return node && Comp()(node->data(), data);
|
||||
}
|
||||
|
||||
static bool less(const value_type &data, const NodeType *node) {
|
||||
return (node == nullptr) || Comp()(data, node->data());
|
||||
}
|
||||
|
||||
static int findInsertionPoint(NodeType *cur, int cur_layer,
|
||||
const value_type &data,
|
||||
NodeType *preds[], NodeType *succs[]) {
|
||||
int foundLayer = -1;
|
||||
NodeType *pred = cur;
|
||||
NodeType *foundNode = nullptr;
|
||||
for (int layer = cur_layer; layer >= 0; --layer) {
|
||||
NodeType *node = pred->skip(layer);
|
||||
while (greater(data, node)) {
|
||||
pred = node;
|
||||
node = node->skip(layer);
|
||||
}
|
||||
if (foundLayer == -1 && !less(data, node)) { // the two keys equal
|
||||
foundLayer = layer;
|
||||
foundNode = node;
|
||||
}
|
||||
preds[layer] = pred;
|
||||
|
||||
// if found, succs[0..foundLayer] need to point to the cached foundNode,
|
||||
// as foundNode might be deleted at the same time thus pred->skip() can
|
||||
// return NULL or another node.
|
||||
succs[layer] = foundNode ? foundNode : node;
|
||||
}
|
||||
return foundLayer;
|
||||
}
|
||||
|
||||
struct Recycler : private boost::noncopyable {
|
||||
Recycler() : refs_(0), dirty_(false) { lock_.init(); }
|
||||
|
||||
~Recycler() {
|
||||
if (nodes_) {
|
||||
for (auto& node : *nodes_) {
|
||||
NodeType::destroy(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add(NodeType* node) {
|
||||
std::lock_guard<MicroSpinLock> g(lock_);
|
||||
if (nodes_.get() == nullptr) {
|
||||
nodes_.reset(new std::vector<NodeType*>(1, node));
|
||||
} else {
|
||||
nodes_->push_back(node);
|
||||
}
|
||||
DCHECK_GT(refs(), 0);
|
||||
dirty_.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int refs() const {
|
||||
return refs_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int addRef() {
|
||||
return refs_.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int release() {
|
||||
// We don't expect to clean the recycler immediately everytime it is OK
|
||||
// to do so. Here, it is possible that multiple accessors all release at
|
||||
// the same time but nobody would clean the recycler here. If this
|
||||
// happens, the recycler will usually still get cleaned when
|
||||
// such a race doesn't happen. The worst case is the recycler will
|
||||
// eventually get deleted along with the skiplist.
|
||||
if (LIKELY(!dirty_.load(std::memory_order_relaxed) || refs() > 1)) {
|
||||
return refs_.fetch_add(-1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
boost::scoped_ptr<std::vector<NodeType*> > newNodes;
|
||||
{
|
||||
std::lock_guard<MicroSpinLock> g(lock_);
|
||||
if (nodes_.get() == nullptr || refs() > 1) {
|
||||
return refs_.fetch_add(-1, std::memory_order_relaxed);
|
||||
}
|
||||
// once refs_ reaches 1 and there is no other accessor, it is safe to
|
||||
// remove all the current nodes in the recycler, as we already acquired
|
||||
// the lock here so no more new nodes can be added, even though new
|
||||
// accessors may be added after that.
|
||||
newNodes.swap(nodes_);
|
||||
dirty_.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// TODO(xliu) should we spawn a thread to do this when there are large
|
||||
// number of nodes in the recycler?
|
||||
for (auto& node : *newNodes) {
|
||||
NodeType::destroy(node);
|
||||
}
|
||||
|
||||
// decrease the ref count at the very end, to minimize the
|
||||
// chance of other threads acquiring lock_ to clear the deleted
|
||||
// nodes again.
|
||||
return refs_.fetch_add(-1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
boost::scoped_ptr<std::vector<NodeType*>> nodes_;
|
||||
std::atomic<int32_t> refs_; // current number of visitors to the list
|
||||
std::atomic<bool> dirty_; // whether *nodes_ is non-empty
|
||||
MicroSpinLock lock_; // protects access to *nodes_
|
||||
}; // class ConcurrentSkipList::Recycler
|
||||
|
||||
explicit ConcurrentSkipList(int height) :
|
||||
head_(NodeType::create(height, value_type(), true)), size_(0) {}
|
||||
|
||||
size_t size() const { return size_.load(std::memory_order_relaxed); }
|
||||
int height() const {
|
||||
return head_.load(std::memory_order_consume)->height();
|
||||
}
|
||||
int maxLayer() const { return height() - 1; }
|
||||
|
||||
size_t incrementSize(int delta) {
|
||||
return size_.fetch_add(delta, std::memory_order_relaxed) + delta;
|
||||
}
|
||||
|
||||
// Returns the node if found, nullptr otherwise.
|
||||
NodeType* find(const value_type &data) {
|
||||
auto ret = findNode(data);
|
||||
if (ret.second && !ret.first->markedForRemoval()) return ret.first;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// lock all the necessary nodes for changing (adding or removing) the list.
|
||||
// returns true if all the lock acquried successfully and the related nodes
|
||||
// are all validate (not in certain pending states), false otherwise.
|
||||
bool lockNodesForChange(int nodeHeight,
|
||||
ScopedLocker guards[MAX_HEIGHT],
|
||||
NodeType *preds[MAX_HEIGHT],
|
||||
NodeType *succs[MAX_HEIGHT],
|
||||
bool adding=true) {
|
||||
NodeType *pred, *succ, *prevPred = nullptr;
|
||||
bool valid = true;
|
||||
for (int layer = 0; valid && layer < nodeHeight; ++layer) {
|
||||
pred = preds[layer];
|
||||
DCHECK(pred != nullptr) << "layer=" << layer << " height=" << height()
|
||||
<< " nodeheight=" << nodeHeight;
|
||||
succ = succs[layer];
|
||||
if (pred != prevPred) {
|
||||
guards[layer] = pred->acquireGuard();
|
||||
prevPred = pred;
|
||||
}
|
||||
valid = !pred->markedForRemoval() &&
|
||||
pred->skip(layer) == succ; // check again after locking
|
||||
|
||||
if (adding) { // when adding a node, the succ shouldn't be going away
|
||||
valid = valid && (succ == nullptr || !succ->markedForRemoval());
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
// Returns a paired value:
|
||||
// pair.first always stores the pointer to the node with the same input key.
|
||||
// It could be either the newly added data, or the existed data in the
|
||||
// list with the same key.
|
||||
// pair.second stores whether the data is added successfully:
|
||||
// 0 means not added, otherwise reutrns the new size.
|
||||
template<typename U>
|
||||
std::pair<NodeType*, size_t> addOrGetData(U &&data) {
|
||||
NodeType *preds[MAX_HEIGHT], *succs[MAX_HEIGHT];
|
||||
NodeType *newNode;
|
||||
size_t newSize;
|
||||
while (true) {
|
||||
int max_layer = 0;
|
||||
int layer = findInsertionPointGetMaxLayer(data, preds, succs, &max_layer);
|
||||
|
||||
if (layer >= 0) {
|
||||
NodeType *nodeFound = succs[layer];
|
||||
DCHECK(nodeFound != nullptr);
|
||||
if (nodeFound->markedForRemoval()) {
|
||||
continue; // if it's getting deleted retry finding node.
|
||||
}
|
||||
// wait until fully linked.
|
||||
while (UNLIKELY(!nodeFound->fullyLinked())) {}
|
||||
return std::make_pair(nodeFound, 0);
|
||||
}
|
||||
|
||||
// need to capped at the original height -- the real height may have grown
|
||||
int nodeHeight = detail::SkipListRandomHeight::instance()->
|
||||
getHeight(max_layer + 1);
|
||||
|
||||
ScopedLocker guards[MAX_HEIGHT];
|
||||
if (!lockNodesForChange(nodeHeight, guards, preds, succs)) {
|
||||
continue; // give up the locks and retry until all valid
|
||||
}
|
||||
|
||||
// locks acquired and all valid, need to modify the links under the locks.
|
||||
newNode = NodeType::create(nodeHeight, std::forward<U>(data));
|
||||
for (int layer = 0; layer < nodeHeight; ++layer) {
|
||||
newNode->setSkip(layer, succs[layer]);
|
||||
preds[layer]->setSkip(layer, newNode);
|
||||
}
|
||||
|
||||
newNode->setFullyLinked();
|
||||
newSize = incrementSize(1);
|
||||
break;
|
||||
}
|
||||
|
||||
int hgt = height();
|
||||
size_t sizeLimit =
|
||||
detail::SkipListRandomHeight::instance()->getSizeLimit(hgt);
|
||||
|
||||
if (hgt < MAX_HEIGHT && newSize > sizeLimit) {
|
||||
growHeight(hgt + 1);
|
||||
}
|
||||
CHECK_GT(newSize, 0);
|
||||
return std::make_pair(newNode, newSize);
|
||||
}
|
||||
|
||||
bool remove(const value_type &data) {
|
||||
NodeType *nodeToDelete = nullptr;
|
||||
ScopedLocker nodeGuard;
|
||||
bool isMarked = false;
|
||||
int nodeHeight = 0;
|
||||
NodeType* preds[MAX_HEIGHT], *succs[MAX_HEIGHT];
|
||||
|
||||
while (true) {
|
||||
int max_layer = 0;
|
||||
int layer = findInsertionPointGetMaxLayer(data, preds, succs, &max_layer);
|
||||
if (!isMarked && (layer < 0 || !okToDelete(succs[layer], layer))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isMarked) {
|
||||
nodeToDelete = succs[layer];
|
||||
nodeHeight = nodeToDelete->height();
|
||||
nodeGuard = nodeToDelete->acquireGuard();
|
||||
if (nodeToDelete->markedForRemoval()) return false;
|
||||
nodeToDelete->setMarkedForRemoval();
|
||||
isMarked = true;
|
||||
}
|
||||
|
||||
// acquire pred locks from bottom layer up
|
||||
ScopedLocker guards[MAX_HEIGHT];
|
||||
if (!lockNodesForChange(nodeHeight, guards, preds, succs, false)) {
|
||||
continue; // this will unlock all the locks
|
||||
}
|
||||
|
||||
for (int layer = nodeHeight - 1; layer >= 0; --layer) {
|
||||
preds[layer]->setSkip(layer, nodeToDelete->skip(layer));
|
||||
}
|
||||
|
||||
incrementSize(-1);
|
||||
break;
|
||||
}
|
||||
recycle(nodeToDelete);
|
||||
return true;
|
||||
}
|
||||
|
||||
const value_type *first() const {
|
||||
auto node = head_.load(std::memory_order_consume)->skip(0);
|
||||
return node ? &node->data() : nullptr;
|
||||
}
|
||||
|
||||
const value_type *last() const {
|
||||
NodeType *pred = head_.load(std::memory_order_consume);
|
||||
NodeType *node = nullptr;
|
||||
for (int layer = maxLayer(); layer >= 0; --layer) {
|
||||
do {
|
||||
node = pred->skip(layer);
|
||||
if (node) pred = node;
|
||||
} while (node != nullptr);
|
||||
}
|
||||
return pred == head_.load(std::memory_order_relaxed)
|
||||
? nullptr : &pred->data();
|
||||
}
|
||||
|
||||
static bool okToDelete(NodeType *candidate, int layer) {
|
||||
DCHECK(candidate != nullptr);
|
||||
return candidate->fullyLinked() &&
|
||||
candidate->maxLayer() == layer &&
|
||||
!candidate->markedForRemoval();
|
||||
}
|
||||
|
||||
// find node for insertion/deleting
|
||||
int findInsertionPointGetMaxLayer(const value_type &data,
|
||||
NodeType *preds[], NodeType *succs[], int *max_layer) const {
|
||||
*max_layer = maxLayer();
|
||||
return findInsertionPoint(head_.load(std::memory_order_consume),
|
||||
*max_layer, data, preds, succs);
|
||||
}
|
||||
|
||||
// Find node for access. Returns a paired values:
|
||||
// pair.first = the first node that no-less than data value
|
||||
// pair.second = 1 when the data value is founded, or 0 otherwise.
|
||||
// This is like lower_bound, but not exact: we could have the node marked for
|
||||
// removal so still need to check that.
|
||||
std::pair<NodeType*, int> findNode(const value_type &data) const {
|
||||
return findNodeDownRight(data);
|
||||
}
|
||||
|
||||
// Find node by first stepping down then stepping right. Based on benchmark
|
||||
// results, this is slightly faster than findNodeRightDown for better
|
||||
// localality on the skipping pointers.
|
||||
std::pair<NodeType*, int> findNodeDownRight(const value_type &data) const {
|
||||
NodeType *pred = head_.load(std::memory_order_consume);
|
||||
int ht = pred->height();
|
||||
NodeType *node = nullptr;
|
||||
|
||||
bool found = false;
|
||||
while (!found) {
|
||||
// stepping down
|
||||
for (; ht > 0 && less(data, pred->skip(ht - 1)); --ht) {}
|
||||
if (ht == 0) return std::make_pair(pred->skip(0), 0); // not found
|
||||
|
||||
node = pred->skip(--ht); // node <= data now
|
||||
// stepping right
|
||||
while (greater(data, node)) {
|
||||
pred = node;
|
||||
node = node->skip(ht);
|
||||
}
|
||||
found = !less(data, node);
|
||||
}
|
||||
return std::make_pair(node, found);
|
||||
}
|
||||
|
||||
// find node by first stepping right then stepping down.
|
||||
// We still keep this for reference purposes.
|
||||
std::pair<NodeType*, int> findNodeRightDown(const value_type &data) const {
|
||||
NodeType *pred = head_.load(std::memory_order_consume);
|
||||
NodeType *node = nullptr;
|
||||
auto top = maxLayer();
|
||||
int found = 0;
|
||||
for (int layer = top; !found && layer >= 0; --layer) {
|
||||
node = pred->skip(layer);
|
||||
while (greater(data, node)) {
|
||||
pred = node;
|
||||
node = node->skip(layer);
|
||||
}
|
||||
found = !less(data, node);
|
||||
}
|
||||
return std::make_pair(node, found);
|
||||
}
|
||||
|
||||
NodeType* lower_bound(const value_type &data) const {
|
||||
auto node = findNode(data).first;
|
||||
while (node != nullptr && node->markedForRemoval()) {
|
||||
node = node->skip(0);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
void growHeight(int height) {
|
||||
NodeType* oldHead = head_.load(std::memory_order_consume);
|
||||
if (oldHead->height() >= height) { // someone else already did this
|
||||
return;
|
||||
}
|
||||
|
||||
NodeType* newHead = NodeType::create(height, value_type(), true);
|
||||
|
||||
{ // need to guard the head node in case others are adding/removing
|
||||
// nodes linked to the head.
|
||||
ScopedLocker g = oldHead->acquireGuard();
|
||||
newHead->copyHead(oldHead);
|
||||
NodeType* expected = oldHead;
|
||||
if (!head_.compare_exchange_strong(expected, newHead,
|
||||
std::memory_order_release)) {
|
||||
// if someone has already done the swap, just return.
|
||||
NodeType::destroy(newHead);
|
||||
return;
|
||||
}
|
||||
oldHead->setMarkedForRemoval();
|
||||
}
|
||||
recycle(oldHead);
|
||||
}
|
||||
|
||||
void recycle(NodeType *node) {
|
||||
recycler_.add(node);
|
||||
}
|
||||
|
||||
std::atomic<NodeType*> head_;
|
||||
Recycler recycler_;
|
||||
std::atomic<size_t> size_;
|
||||
};
|
||||
|
||||
template<typename T, typename Comp, int MAX_HEIGHT>
|
||||
class ConcurrentSkipList<T, Comp, MAX_HEIGHT>::Accessor {
|
||||
typedef detail::SkipListNode<T> NodeType;
|
||||
typedef ConcurrentSkipList<T, Comp, MAX_HEIGHT> SkipListType;
|
||||
public:
|
||||
typedef T value_type;
|
||||
typedef T key_type;
|
||||
typedef T& reference;
|
||||
typedef T* pointer;
|
||||
typedef const T& const_reference;
|
||||
typedef const T* const_pointer;
|
||||
typedef size_t size_type;
|
||||
typedef Comp key_compare;
|
||||
typedef Comp value_compare;
|
||||
|
||||
typedef typename SkipListType::iterator iterator;
|
||||
typedef typename SkipListType::const_iterator const_iterator;
|
||||
typedef typename SkipListType::Skipper Skipper;
|
||||
|
||||
explicit Accessor(boost::shared_ptr<ConcurrentSkipList> skip_list)
|
||||
: slHolder_(std::move(skip_list))
|
||||
{
|
||||
sl_ = slHolder_.get();
|
||||
DCHECK(sl_ != nullptr);
|
||||
sl_->recycler_.addRef();
|
||||
}
|
||||
|
||||
// Unsafe initializer: the caller assumes the responsibility to keep
|
||||
// skip_list valid during the whole life cycle of the Acessor.
|
||||
explicit Accessor(ConcurrentSkipList *skip_list) : sl_(skip_list) {
|
||||
DCHECK(sl_ != nullptr);
|
||||
sl_->recycler_.addRef();
|
||||
}
|
||||
|
||||
Accessor(const Accessor &accessor) :
|
||||
sl_(accessor.sl_),
|
||||
slHolder_(accessor.slHolder_) {
|
||||
sl_->recycler_.addRef();
|
||||
}
|
||||
|
||||
Accessor& operator=(const Accessor &accessor) {
|
||||
if (this != &accessor) {
|
||||
slHolder_ = accessor.slHolder_;
|
||||
sl_->recycler_.release();
|
||||
sl_ = accessor.sl_;
|
||||
sl_->recycler_.addRef();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Accessor() {
|
||||
sl_->recycler_.release();
|
||||
}
|
||||
|
||||
bool empty() const { return sl_->size() == 0; }
|
||||
size_t size() const { return sl_->size(); }
|
||||
size_type max_size() const { return std::numeric_limits<size_type>::max(); }
|
||||
|
||||
// returns end() if the value is not in the list, otherwise returns an
|
||||
// iterator pointing to the data, and it's guaranteed that the data is valid
|
||||
// as far as the Accessor is hold.
|
||||
iterator find(const key_type &value) { return iterator(sl_->find(value)); }
|
||||
const_iterator find(const key_type &value) const {
|
||||
return iterator(sl_->find(value));
|
||||
}
|
||||
size_type count(const key_type &data) const { return contains(data); }
|
||||
|
||||
iterator begin() const {
|
||||
NodeType* head = sl_->head_.load(std::memory_order_consume);
|
||||
return iterator(head->next());
|
||||
}
|
||||
iterator end() const { return iterator(nullptr); }
|
||||
const_iterator cbegin() const { return begin(); }
|
||||
const_iterator cend() const { return end(); }
|
||||
|
||||
template<typename U,
|
||||
typename=typename std::enable_if<std::is_convertible<U, T>::value>::type>
|
||||
std::pair<iterator, bool> insert(U&& data) {
|
||||
auto ret = sl_->addOrGetData(std::forward<U>(data));
|
||||
return std::make_pair(iterator(ret.first), ret.second);
|
||||
}
|
||||
size_t erase(const key_type &data) { return remove(data); }
|
||||
|
||||
iterator lower_bound(const key_type &data) const {
|
||||
return iterator(sl_->lower_bound(data));
|
||||
}
|
||||
|
||||
size_t height() const { return sl_->height(); }
|
||||
|
||||
// first() returns pointer to the first element in the skiplist, or
|
||||
// nullptr if empty.
|
||||
//
|
||||
// last() returns the pointer to the last element in the skiplist,
|
||||
// nullptr if list is empty.
|
||||
//
|
||||
// Note: As concurrent writing can happen, first() is not
|
||||
// guaranteed to be the min_element() in the list. Similarly
|
||||
// last() is not guaranteed to be the max_element(), and both of them can
|
||||
// be invalid (i.e. nullptr), so we name them differently from front() and
|
||||
// tail() here.
|
||||
const key_type *first() const { return sl_->first(); }
|
||||
const key_type *last() const { return sl_->last(); }
|
||||
|
||||
// Try to remove the last element in the skip list.
|
||||
//
|
||||
// Returns true if we removed it, false if either the list is empty
|
||||
// or a race condition happened (i.e. the used-to-be last element
|
||||
// was already removed by another thread).
|
||||
bool pop_back() {
|
||||
auto last = sl_->last();
|
||||
return last ? sl_->remove(*last) : false;
|
||||
}
|
||||
|
||||
std::pair<key_type*, bool> addOrGetData(const key_type &data) {
|
||||
auto ret = sl_->addOrGetData(data);
|
||||
return std::make_pair(&ret.first->data(), ret.second);
|
||||
}
|
||||
|
||||
SkipListType* skiplist() const { return sl_; }
|
||||
|
||||
// legacy interfaces
|
||||
// TODO:(xliu) remove these.
|
||||
// Returns true if the node is added successfully, false if not, i.e. the
|
||||
// node with the same key already existed in the list.
|
||||
bool contains(const key_type &data) const { return sl_->find(data); }
|
||||
bool add(const key_type &data) { return sl_->addOrGetData(data).second; }
|
||||
bool remove(const key_type &data) { return sl_->remove(data); }
|
||||
|
||||
private:
|
||||
SkipListType *sl_;
|
||||
boost::shared_ptr<SkipListType> slHolder_;
|
||||
};
|
||||
|
||||
// implements forward iterator concept.
|
||||
template<typename ValT, typename NodeT>
|
||||
class detail::csl_iterator :
|
||||
public boost::iterator_facade<csl_iterator<ValT, NodeT>,
|
||||
ValT, boost::forward_traversal_tag> {
|
||||
public:
|
||||
typedef ValT value_type;
|
||||
typedef value_type& reference;
|
||||
typedef value_type* pointer;
|
||||
typedef ptrdiff_t difference_type;
|
||||
|
||||
explicit csl_iterator(NodeT* node = nullptr) : node_(node) {}
|
||||
|
||||
template<typename OtherVal, typename OtherNode>
|
||||
csl_iterator(const csl_iterator<OtherVal, OtherNode> &other,
|
||||
typename std::enable_if<std::is_convertible<OtherVal, ValT>::value>::type*
|
||||
= 0) : node_(other.node_) {}
|
||||
|
||||
size_t nodeSize() const {
|
||||
return node_ == nullptr ? 0 :
|
||||
node_->height() * sizeof(NodeT*) + sizeof(*this);
|
||||
}
|
||||
|
||||
bool good() const { return node_ != nullptr; }
|
||||
|
||||
private:
|
||||
friend class boost::iterator_core_access;
|
||||
template<class,class> friend class csl_iterator;
|
||||
|
||||
void increment() { node_ = node_->next(); };
|
||||
bool equal(const csl_iterator& other) const { return node_ == other.node_; }
|
||||
value_type& dereference() const { return node_->data(); }
|
||||
|
||||
NodeT* node_;
|
||||
};
|
||||
|
||||
// Skipper interface
|
||||
template<typename T, typename Comp, int MAX_HEIGHT>
|
||||
class ConcurrentSkipList<T, Comp, MAX_HEIGHT>::Skipper {
|
||||
typedef detail::SkipListNode<T> NodeType;
|
||||
typedef ConcurrentSkipList<T, Comp, MAX_HEIGHT> SkipListType;
|
||||
typedef typename SkipListType::Accessor Accessor;
|
||||
|
||||
public:
|
||||
typedef T value_type;
|
||||
typedef T& reference;
|
||||
typedef T* pointer;
|
||||
typedef ptrdiff_t difference_type;
|
||||
|
||||
Skipper(const boost::shared_ptr<SkipListType>& skipList) :
|
||||
accessor_(skipList) {
|
||||
init();
|
||||
}
|
||||
|
||||
Skipper(const Accessor& accessor) : accessor_(accessor) {
|
||||
init();
|
||||
}
|
||||
|
||||
void init() {
|
||||
// need to cache the head node
|
||||
NodeType* head_node = head();
|
||||
headHeight_ = head_node->height();
|
||||
for (int i = 0; i < headHeight_; ++i) {
|
||||
preds_[i] = head_node;
|
||||
succs_[i] = head_node->skip(i);
|
||||
}
|
||||
int max_layer = maxLayer();
|
||||
for (int i = 0; i < max_layer; ++i) {
|
||||
hints_[i] = i + 1;
|
||||
}
|
||||
hints_[max_layer] = max_layer;
|
||||
}
|
||||
|
||||
// advance to the next node in the list.
|
||||
Skipper& operator ++() {
|
||||
preds_[0] = succs_[0];
|
||||
succs_[0] = preds_[0]->skip(0);
|
||||
int height = curHeight();
|
||||
for (int i = 1; i < height && preds_[0] == succs_[i]; ++i) {
|
||||
preds_[i] = succs_[i];
|
||||
succs_[i] = preds_[i]->skip(i);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool good() const { return succs_[0] != nullptr; }
|
||||
|
||||
int maxLayer() const { return headHeight_ - 1; }
|
||||
|
||||
int curHeight() const {
|
||||
// need to cap the height to the cached head height, as the current node
|
||||
// might be some newly inserted node and also during the time period the
|
||||
// head height may have grown.
|
||||
return succs_[0] ? std::min(headHeight_, succs_[0]->height()) : 0;
|
||||
}
|
||||
|
||||
const value_type &data() const {
|
||||
DCHECK(succs_[0] != NULL);
|
||||
return succs_[0]->data();
|
||||
}
|
||||
|
||||
value_type &operator *() const {
|
||||
DCHECK(succs_[0] != NULL);
|
||||
return succs_[0]->data();
|
||||
}
|
||||
|
||||
value_type *operator->() {
|
||||
DCHECK(succs_[0] != NULL);
|
||||
return &succs_[0]->data();
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip to the position whose data is no less than the parameter.
|
||||
* (I.e. the lower_bound).
|
||||
*
|
||||
* Returns true if the data is found, false otherwise.
|
||||
*/
|
||||
bool to(const value_type &data) {
|
||||
int layer = curHeight() - 1;
|
||||
if (layer < 0) return false; // reaches the end of the list
|
||||
|
||||
int lyr = hints_[layer];
|
||||
int max_layer = maxLayer();
|
||||
while (SkipListType::greater(data, succs_[lyr]) && lyr < max_layer) {
|
||||
++lyr;
|
||||
}
|
||||
hints_[layer] = lyr; // update the hint
|
||||
|
||||
int foundLayer = SkipListType::
|
||||
findInsertionPoint(preds_[lyr], lyr, data, preds_, succs_);
|
||||
if (foundLayer < 0) return false;
|
||||
|
||||
DCHECK(succs_[0] != NULL) << "lyr=" << lyr << "; max_layer=" << max_layer;
|
||||
return !succs_[0]->markedForRemoval();
|
||||
}
|
||||
|
||||
private:
|
||||
NodeType* head() const {
|
||||
return accessor_.skiplist()->head_.load(std::memory_order_consume);
|
||||
}
|
||||
|
||||
Accessor accessor_;
|
||||
int headHeight_;
|
||||
NodeType *succs_[MAX_HEIGHT], *preds_[MAX_HEIGHT];
|
||||
uint8_t hints_[MAX_HEIGHT];
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif // FOLLY_CONCURRENT_SKIP_LIST_H_
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#define FOLLY_CONV_INTERNAL
|
||||
#include "folly/Conv.h"
|
||||
|
||||
namespace folly {
|
||||
namespace detail {
|
||||
|
||||
extern const char digit1[101] =
|
||||
"00000000001111111111222222222233333333334444444444"
|
||||
"55555555556666666666777777777788888888889999999999";
|
||||
extern const char digit2[101] =
|
||||
"01234567890123456789012345678901234567890123456789"
|
||||
"01234567890123456789012345678901234567890123456789";
|
||||
|
||||
template <> const char *const MaxString<bool>::value = "true";
|
||||
template <> const char *const MaxString<uint8_t>::value = "255";
|
||||
template <> const char *const MaxString<uint16_t>::value = "65535";
|
||||
template <> const char *const MaxString<uint32_t>::value = "4294967295";
|
||||
#if __SIZEOF_LONG__ == 4
|
||||
template <> const char *const MaxString<unsigned long>::value =
|
||||
"4294967295";
|
||||
#else
|
||||
template <> const char *const MaxString<unsigned long>::value =
|
||||
"18446744073709551615";
|
||||
#endif
|
||||
static_assert(sizeof(unsigned long) >= 4,
|
||||
"Wrong value for MaxString<unsigned long>::value,"
|
||||
" please update.");
|
||||
template <> const char *const MaxString<unsigned long long>::value =
|
||||
"18446744073709551615";
|
||||
static_assert(sizeof(unsigned long long) >= 8,
|
||||
"Wrong value for MaxString<unsigned long long>::value"
|
||||
", please update.");
|
||||
|
||||
inline bool bool_str_cmp(const char** b, size_t len, const char* value) {
|
||||
// Can't use strncasecmp, since we want to ensure that the full value matches
|
||||
const char* p = *b;
|
||||
const char* e = *b + len;
|
||||
const char* v = value;
|
||||
while (*v != '\0') {
|
||||
if (p == e || tolower(*p) != *v) { // value is already lowercase
|
||||
return false;
|
||||
}
|
||||
++p;
|
||||
++v;
|
||||
}
|
||||
|
||||
*b = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool str_to_bool(StringPiece* src) {
|
||||
auto b = src->begin(), e = src->end();
|
||||
for (;; ++b) {
|
||||
FOLLY_RANGE_CHECK(b < e,
|
||||
"No non-whitespace characters found in input string");
|
||||
if (!isspace(*b)) break;
|
||||
}
|
||||
|
||||
bool result;
|
||||
size_t len = e - b;
|
||||
switch (*b) {
|
||||
case '0':
|
||||
case '1': {
|
||||
// Attempt to parse the value as an integer
|
||||
StringPiece tmp(*src);
|
||||
uint8_t value = to<uint8_t>(&tmp);
|
||||
// Only accept 0 or 1
|
||||
FOLLY_RANGE_CHECK(value <= 1,
|
||||
"Integer overflow when parsing bool: must be 0 or 1");
|
||||
b = tmp.begin();
|
||||
result = (value == 1);
|
||||
break;
|
||||
}
|
||||
case 'y':
|
||||
case 'Y':
|
||||
result = true;
|
||||
if (!bool_str_cmp(&b, len, "yes")) {
|
||||
++b; // accept the single 'y' character
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
case 'N':
|
||||
result = false;
|
||||
if (!bool_str_cmp(&b, len, "no")) {
|
||||
++b;
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
case 'T':
|
||||
result = true;
|
||||
if (!bool_str_cmp(&b, len, "true")) {
|
||||
++b;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
case 'F':
|
||||
result = false;
|
||||
if (!bool_str_cmp(&b, len, "false")) {
|
||||
++b;
|
||||
}
|
||||
break;
|
||||
case 'o':
|
||||
case 'O':
|
||||
if (bool_str_cmp(&b, len, "on")) {
|
||||
result = true;
|
||||
} else if (bool_str_cmp(&b, len, "off")) {
|
||||
result = false;
|
||||
} else {
|
||||
FOLLY_RANGE_CHECK(false, "Invalid value for bool");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
FOLLY_RANGE_CHECK(false, "Invalid value for bool");
|
||||
}
|
||||
|
||||
src->assign(b, e);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace folly
|
||||
-1120
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
-119
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_CPUID_H_
|
||||
#define FOLLY_CPUID_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Identification of an Intel CPU.
|
||||
* Supports CPUID (EAX=1) feature flags.
|
||||
* Values from http://www.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html
|
||||
*/
|
||||
class CpuId {
|
||||
public:
|
||||
CpuId() {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
__asm__("cpuid" : "=c"(c_), "=d"(d_) : "a"(1) : "ebx");
|
||||
#else
|
||||
// On non-Intel, none of these features exist; at least not in the same form
|
||||
// as they do on Intel
|
||||
c_ = 0;
|
||||
d_ = 0;
|
||||
#endif
|
||||
}
|
||||
#define X(name, r, bit) bool name() const { return r & (1U << bit); }
|
||||
#define C(name, bit) X(name, c_, bit)
|
||||
#define D(name, bit) X(name, d_, bit)
|
||||
C(sse3, 0)
|
||||
C(pclmuldq, 1)
|
||||
C(dtes64, 2)
|
||||
C(monitor, 3)
|
||||
C(dscpl, 4)
|
||||
C(vmx, 5)
|
||||
C(smx, 6)
|
||||
C(eist, 7)
|
||||
C(tm2, 8)
|
||||
C(ssse3, 9)
|
||||
C(cnxtid, 10)
|
||||
// 11 is reserved
|
||||
C(fma, 12)
|
||||
C(cx16, 13)
|
||||
C(xtpr, 14)
|
||||
C(pdcm, 15)
|
||||
// 16 is reserved
|
||||
C(pcid, 17)
|
||||
C(dca, 18)
|
||||
C(sse41, 19)
|
||||
C(sse42, 20)
|
||||
C(x2apic, 21)
|
||||
C(movbe, 22)
|
||||
C(popcnt, 23)
|
||||
C(tscdeadline, 24)
|
||||
C(aes, 25)
|
||||
C(xsave, 26)
|
||||
C(osxsave, 27)
|
||||
C(avx, 28)
|
||||
C(f16c, 29)
|
||||
C(rdrand, 30)
|
||||
// 31 is not used
|
||||
D(fpu, 0)
|
||||
D(vme, 1)
|
||||
D(de, 2)
|
||||
D(pse, 3)
|
||||
D(tsc, 4)
|
||||
D(msr, 5)
|
||||
D(pae, 6)
|
||||
D(mce, 7)
|
||||
D(cx8, 8)
|
||||
D(apic, 9)
|
||||
// 10 is reserved
|
||||
D(sep, 11)
|
||||
D(mtrr, 12)
|
||||
D(pge, 13)
|
||||
D(mca, 14)
|
||||
D(cmov, 15)
|
||||
D(pat, 16)
|
||||
D(pse36, 17)
|
||||
D(psn, 18)
|
||||
D(clfsh, 19)
|
||||
// 20 is reserved
|
||||
D(ds, 21)
|
||||
D(acpi, 22)
|
||||
D(mmx, 23)
|
||||
D(fxsr, 24)
|
||||
D(sse, 25)
|
||||
D(sse2, 26)
|
||||
D(ss, 27)
|
||||
D(htt, 28)
|
||||
D(tm, 29)
|
||||
// 30 is reserved
|
||||
D(pbe, 31)
|
||||
#undef D
|
||||
#undef C
|
||||
#undef X
|
||||
private:
|
||||
uint32_t c_; // ECX
|
||||
uint32_t d_; // EDX
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_CPUID_H_ */
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Discriminated pointer: Type-safe pointer to one of several types.
|
||||
*
|
||||
* Similar to boost::variant, but has no space overhead over a raw pointer, as
|
||||
* it relies on the fact that (on x86_64) there are 16 unused bits in a
|
||||
* pointer.
|
||||
*
|
||||
* @author Tudor Bosman (tudorb@fb.com)
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_DISCRIMINATEDPTR_H_
|
||||
#define FOLLY_DISCRIMINATEDPTR_H_
|
||||
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <glog/logging.h>
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/detail/DiscriminatedPtrDetail.h"
|
||||
|
||||
#ifndef __x86_64__
|
||||
# error "DiscriminatedPtr is x64-specific code."
|
||||
#endif
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Discriminated pointer.
|
||||
*
|
||||
* Given a list of types, a DiscriminatedPtr<Types...> may point to an object
|
||||
* of one of the given types, or may be empty. DiscriminatedPtr is type-safe:
|
||||
* you may only get a pointer to the type that you put in, otherwise get
|
||||
* throws an exception (and get_nothrow returns nullptr)
|
||||
*
|
||||
* This pointer does not do any kind of lifetime management -- it's not a
|
||||
* "smart" pointer. You are responsible for deallocating any memory used
|
||||
* to hold pointees, if necessary.
|
||||
*/
|
||||
template <typename... Types>
|
||||
class DiscriminatedPtr {
|
||||
// <, not <=, as our indexes are 1-based (0 means "empty")
|
||||
static_assert(sizeof...(Types) < std::numeric_limits<uint16_t>::max(),
|
||||
"too many types");
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create an empty DiscriminatedPtr.
|
||||
*/
|
||||
DiscriminatedPtr() : data_(0) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DiscriminatedPtr that points to an object of type T.
|
||||
* Fails at compile time if T is not a valid type (listed in Types)
|
||||
*/
|
||||
template <typename T>
|
||||
explicit DiscriminatedPtr(T* ptr) {
|
||||
set(ptr, typeIndex<T>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this DiscriminatedPtr to point to an object of type T.
|
||||
* Fails at compile time if T is not a valid type (listed in Types)
|
||||
*/
|
||||
template <typename T>
|
||||
void set(T* ptr) {
|
||||
set(ptr, typeIndex<T>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a pointer to the object that this DiscriminatedPtr points to, if it is
|
||||
* of type T. Fails at compile time if T is not a valid type (listed in
|
||||
* Types), and returns nullptr if this DiscriminatedPtr is empty or points to
|
||||
* an object of a different type.
|
||||
*/
|
||||
template <typename T>
|
||||
T* get_nothrow() noexcept {
|
||||
void* p = LIKELY(hasType<T>()) ? ptr() : nullptr;
|
||||
return static_cast<T*>(p);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* get_nothrow() const noexcept {
|
||||
const void* p = LIKELY(hasType<T>()) ? ptr() : nullptr;
|
||||
return static_cast<const T*>(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a pointer to the object that this DiscriminatedPtr points to, if it is
|
||||
* of type T. Fails at compile time if T is not a valid type (listed in
|
||||
* Types), and throws std::invalid_argument if this DiscriminatedPtr is empty
|
||||
* or points to an object of a different type.
|
||||
*/
|
||||
template <typename T>
|
||||
T* get() {
|
||||
if (UNLIKELY(!hasType<T>())) {
|
||||
throw std::invalid_argument("Invalid type");
|
||||
}
|
||||
return static_cast<T*>(ptr());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* get() const {
|
||||
if (UNLIKELY(!hasType<T>())) {
|
||||
throw std::invalid_argument("Invalid type");
|
||||
}
|
||||
return static_cast<const T*>(ptr());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true iff this DiscriminatedPtr is empty.
|
||||
*/
|
||||
bool empty() const {
|
||||
return index() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true iff the object pointed by this DiscriminatedPtr has type T,
|
||||
* false otherwise. Fails at compile time if T is not a valid type (listed
|
||||
* in Types...)
|
||||
*/
|
||||
template <typename T>
|
||||
bool hasType() const {
|
||||
return index() == typeIndex<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear this DiscriminatedPtr, making it empty.
|
||||
*/
|
||||
void clear() {
|
||||
data_ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assignment operator from a pointer of type T.
|
||||
*/
|
||||
template <typename T>
|
||||
DiscriminatedPtr& operator=(T* ptr) {
|
||||
set(ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a visitor to this object, calling the appropriate overload for
|
||||
* the type currently stored in DiscriminatedPtr. Throws invalid_argument
|
||||
* if the DiscriminatedPtr is empty.
|
||||
*
|
||||
* The visitor must meet the following requirements:
|
||||
*
|
||||
* - The visitor must allow invocation as a function by overloading
|
||||
* operator(), unambiguously accepting all values of type T* (or const T*)
|
||||
* for all T in Types...
|
||||
* - All operations of the function object on T* (or const T*) must
|
||||
* return the same type (or a static_assert will fire).
|
||||
*/
|
||||
template <typename V>
|
||||
typename dptr_detail::VisitorResult<V, Types...>::type apply(V&& visitor) {
|
||||
size_t n = index();
|
||||
if (n == 0) throw std::invalid_argument("Empty DiscriminatedPtr");
|
||||
return dptr_detail::ApplyVisitor<V, Types...>()(
|
||||
n, std::forward<V>(visitor), ptr());
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
typename dptr_detail::ConstVisitorResult<V, Types...>::type apply(V&& visitor)
|
||||
const {
|
||||
size_t n = index();
|
||||
if (n == 0) throw std::invalid_argument("Empty DiscriminatedPtr");
|
||||
return dptr_detail::ApplyConstVisitor<V, Types...>()(
|
||||
n, std::forward<V>(visitor), ptr());
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Get the 1-based type index of T in Types.
|
||||
*/
|
||||
template <typename T>
|
||||
size_t typeIndex() const {
|
||||
return dptr_detail::GetTypeIndex<T, Types...>::value;
|
||||
}
|
||||
|
||||
uint16_t index() const { return data_ >> 48; }
|
||||
void* ptr() const {
|
||||
return reinterpret_cast<void*>(data_ & ((1ULL << 48) - 1));
|
||||
}
|
||||
|
||||
void set(void* p, uint16_t v) {
|
||||
uintptr_t ip = reinterpret_cast<uintptr_t>(p);
|
||||
CHECK(!(ip >> 48));
|
||||
ip |= static_cast<uintptr_t>(v) << 48;
|
||||
data_ = ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* We store a pointer in the least significant 48 bits of data_, and a type
|
||||
* index (0 = empty, or 1-based index in Types) in the most significant 16
|
||||
* bits. We rely on the fact that pointers have their most significant 16
|
||||
* bits clear on x86_64.
|
||||
*/
|
||||
uintptr_t data_;
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_DISCRIMINATEDPTR_H_ */
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @author Nicholas Ormrod <njormrod@fb.com>
|
||||
|
||||
#ifndef DYNAMIC_CONVERTER_H
|
||||
#define DYNAMIC_CONVERTER_H
|
||||
|
||||
#include "folly/dynamic.h"
|
||||
namespace folly {
|
||||
template <typename T> T convertTo(const dynamic&);
|
||||
template <typename T> dynamic toDynamic(const T&);
|
||||
}
|
||||
|
||||
/**
|
||||
* convertTo returns a well-typed representation of the input dynamic.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* dynamic d = { { 1, 2, 3 }, { 4, 5 } }; // a vector of vector of int
|
||||
* auto vvi = convertTo<fbvector<fbvector<int>>>(d);
|
||||
*
|
||||
* See docs/DynamicConverter.md for supported types and customization
|
||||
*/
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <iterator>
|
||||
#include <boost/iterator/iterator_adaptor.hpp>
|
||||
#include <boost/mpl/has_xxx.hpp>
|
||||
#include "folly/Likely.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// traits
|
||||
|
||||
namespace dynamicconverter_detail {
|
||||
|
||||
BOOST_MPL_HAS_XXX_TRAIT_DEF(value_type);
|
||||
BOOST_MPL_HAS_XXX_TRAIT_DEF(iterator);
|
||||
BOOST_MPL_HAS_XXX_TRAIT_DEF(mapped_type);
|
||||
|
||||
template <typename T> struct class_is_container {
|
||||
typedef std::reverse_iterator<T*> some_iterator;
|
||||
enum { value = has_value_type<T>::value &&
|
||||
has_iterator<T>::value &&
|
||||
std::is_constructible<T, some_iterator, some_iterator>::value };
|
||||
};
|
||||
|
||||
template <typename T> struct class_is_range {
|
||||
enum { value = has_value_type<T>::value &&
|
||||
has_iterator<T>::value };
|
||||
};
|
||||
|
||||
|
||||
template <typename T> struct is_container
|
||||
: std::conditional<
|
||||
std::is_class<T>::value,
|
||||
class_is_container<T>,
|
||||
std::false_type
|
||||
>::type {};
|
||||
|
||||
template <typename T> struct is_range
|
||||
: std::conditional<
|
||||
std::is_class<T>::value,
|
||||
class_is_range<T>,
|
||||
std::false_type
|
||||
>::type {};
|
||||
|
||||
template <typename T> struct is_associative_container
|
||||
: std::integral_constant<
|
||||
bool,
|
||||
is_range<T>::value && has_mapped_type<T>::value
|
||||
> {};
|
||||
|
||||
} // namespace dynamicconverter_detail
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// custom iterators
|
||||
|
||||
/**
|
||||
* We have iterators that dereference to dynamics, but need iterators
|
||||
* that dereference to typename T.
|
||||
*
|
||||
* Implementation details:
|
||||
* 1. We cache the value of the dereference operator. This is necessary
|
||||
* because boost::iterator_adaptor requires *it to return a
|
||||
* reference.
|
||||
* 2. For const reasons, we cannot call operator= to refresh the
|
||||
* cache: we must call the destructor then placement new.
|
||||
*/
|
||||
|
||||
namespace dynamicconverter_detail {
|
||||
|
||||
template<typename T>
|
||||
struct Dereferencer {
|
||||
static inline void
|
||||
derefToCache(T* mem, const dynamic::const_item_iterator& it) {
|
||||
throw TypeError("array", dynamic::Type::OBJECT);
|
||||
}
|
||||
|
||||
static inline void derefToCache(T* mem, const dynamic::const_iterator& it) {
|
||||
new (mem) T(convertTo<T>(*it));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename F, typename S>
|
||||
struct Dereferencer<std::pair<F, S>> {
|
||||
static inline void
|
||||
derefToCache(std::pair<F, S>* mem, const dynamic::const_item_iterator& it) {
|
||||
new (mem) std::pair<F, S>(
|
||||
convertTo<F>(it->first), convertTo<S>(it->second)
|
||||
);
|
||||
}
|
||||
|
||||
// Intentional duplication of the code in Dereferencer
|
||||
template <typename T>
|
||||
static inline void derefToCache(T* mem, const dynamic::const_iterator& it) {
|
||||
new (mem) T(convertTo<T>(*it));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename It>
|
||||
class Transformer : public boost::iterator_adaptor<
|
||||
Transformer<T, It>,
|
||||
It,
|
||||
typename T::value_type
|
||||
> {
|
||||
friend class boost::iterator_core_access;
|
||||
|
||||
typedef typename T::value_type ttype;
|
||||
|
||||
mutable ttype cache_;
|
||||
mutable bool valid_;
|
||||
|
||||
void increment() {
|
||||
++this->base_reference();
|
||||
valid_ = false;
|
||||
}
|
||||
|
||||
ttype& dereference() const {
|
||||
if (LIKELY(!valid_)) {
|
||||
cache_.~ttype();
|
||||
Dereferencer<ttype>::derefToCache(&cache_, this->base_reference());
|
||||
valid_ = true;
|
||||
}
|
||||
return cache_;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit Transformer(const It& it)
|
||||
: Transformer::iterator_adaptor_(it), valid_(false) {}
|
||||
};
|
||||
|
||||
// conversion factory
|
||||
template <typename T, typename It>
|
||||
static inline std::move_iterator<Transformer<T, It>>
|
||||
conversionIterator(const It& it) {
|
||||
return std::make_move_iterator(Transformer<T, It>(it));
|
||||
}
|
||||
|
||||
} // namespace dynamicconverter_detail
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// DynamicConverter specializations
|
||||
|
||||
template <typename T, typename Enable = void> struct DynamicConverter;
|
||||
|
||||
/**
|
||||
* Each specialization of DynamicConverter has the function
|
||||
* 'static T convert(const dynamic& d);'
|
||||
*/
|
||||
|
||||
// boolean
|
||||
template <>
|
||||
struct DynamicConverter<bool> {
|
||||
static bool convert(const dynamic& d) {
|
||||
return d.asBool();
|
||||
}
|
||||
};
|
||||
|
||||
// integrals
|
||||
template <typename T>
|
||||
struct DynamicConverter<T,
|
||||
typename std::enable_if<std::is_integral<T>::value &&
|
||||
!std::is_same<T, bool>::value>::type> {
|
||||
static T convert(const dynamic& d) {
|
||||
return static_cast<T>(d.asInt());
|
||||
}
|
||||
};
|
||||
|
||||
// floating point
|
||||
template <typename T>
|
||||
struct DynamicConverter<T,
|
||||
typename std::enable_if<std::is_floating_point<T>::value>::type> {
|
||||
static T convert(const dynamic& d) {
|
||||
return static_cast<T>(d.asDouble());
|
||||
}
|
||||
};
|
||||
|
||||
// fbstring
|
||||
template <>
|
||||
struct DynamicConverter<folly::fbstring> {
|
||||
static folly::fbstring convert(const dynamic& d) {
|
||||
return d.asString();
|
||||
}
|
||||
};
|
||||
|
||||
// std::string
|
||||
template <>
|
||||
struct DynamicConverter<std::string> {
|
||||
static std::string convert(const dynamic& d) {
|
||||
return d.asString().toStdString();
|
||||
}
|
||||
};
|
||||
|
||||
// std::pair
|
||||
template <typename F, typename S>
|
||||
struct DynamicConverter<std::pair<F,S>> {
|
||||
static std::pair<F, S> convert(const dynamic& d) {
|
||||
if (d.isArray() && d.size() == 2) {
|
||||
return std::make_pair(convertTo<F>(d[0]), convertTo<S>(d[1]));
|
||||
} else if (d.isObject() && d.size() == 1) {
|
||||
auto it = d.items().begin();
|
||||
return std::make_pair(convertTo<F>(it->first), convertTo<S>(it->second));
|
||||
} else {
|
||||
throw TypeError("array (size 2) or object (size 1)", d.type());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// containers
|
||||
template <typename C>
|
||||
struct DynamicConverter<C,
|
||||
typename std::enable_if<
|
||||
dynamicconverter_detail::is_container<C>::value>::type> {
|
||||
static C convert(const dynamic& d) {
|
||||
if (d.isArray()) {
|
||||
return C(dynamicconverter_detail::conversionIterator<C>(d.begin()),
|
||||
dynamicconverter_detail::conversionIterator<C>(d.end()));
|
||||
} else if (d.isObject()) {
|
||||
return C(dynamicconverter_detail::conversionIterator<C>
|
||||
(d.items().begin()),
|
||||
dynamicconverter_detail::conversionIterator<C>
|
||||
(d.items().end()));
|
||||
} else {
|
||||
throw TypeError("object or array", d.type());
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <typename C, typename Enable = void>
|
||||
struct DynamicConstructor {
|
||||
static dynamic construct(const C& x) {
|
||||
return dynamic(x);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename C>
|
||||
struct DynamicConstructor<C,
|
||||
typename std::enable_if<
|
||||
dynamicconverter_detail::is_associative_container<C>::value>::type> {
|
||||
static dynamic construct(const C& x) {
|
||||
dynamic d = dynamic::object;
|
||||
for (auto& pair : x) {
|
||||
d.insert(toDynamic(pair.first), toDynamic(pair.second));
|
||||
}
|
||||
return d;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename C>
|
||||
struct DynamicConstructor<C,
|
||||
typename std::enable_if<
|
||||
!dynamicconverter_detail::is_associative_container<C>::value &&
|
||||
!std::is_constructible<StringPiece, const C&>::value &&
|
||||
dynamicconverter_detail::is_range<C>::value>::type> {
|
||||
static dynamic construct(const C& x) {
|
||||
dynamic d = {};
|
||||
for (auto& item : x) {
|
||||
d.push_back(toDynamic(item));
|
||||
}
|
||||
return d;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename A, typename B>
|
||||
struct DynamicConstructor<std::pair<A, B>, void> {
|
||||
static dynamic construct(const std::pair<A, B>& x) {
|
||||
dynamic d = {};
|
||||
d.push_back(toDynamic(x.first));
|
||||
d.push_back(toDynamic(x.second));
|
||||
return d;
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// convertTo implementation
|
||||
|
||||
template <typename T>
|
||||
T convertTo(const dynamic& d) {
|
||||
return DynamicConverter<typename std::remove_cv<T>::type>::convert(d);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
dynamic toDynamic(const T& x) {
|
||||
return DynamicConstructor<typename std::remove_cv<T>::type>::construct(x);
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif // DYNAMIC_CONVERTER_H
|
||||
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_EXCEPTION_H_
|
||||
#define FOLLY_EXCEPTION_H_
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
|
||||
#include "folly/Conv.h"
|
||||
#include "folly/FBString.h"
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/Portability.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
// Various helpers to throw appropriate std::system_error exceptions from C
|
||||
// library errors (returned in errno, as positive return values (many POSIX
|
||||
// functions), or as negative return values (Linux syscalls))
|
||||
//
|
||||
// The *Explicit functions take an explicit value for errno.
|
||||
|
||||
// Helper to throw std::system_error
|
||||
void throwSystemErrorExplicit(int err, const char*) FOLLY_NORETURN;
|
||||
inline void throwSystemErrorExplicit(int err, const char* msg) {
|
||||
throw std::system_error(err, std::system_category(), msg);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void throwSystemErrorExplicit(int, Args&&... args) FOLLY_NORETURN;
|
||||
template <class... Args>
|
||||
void throwSystemErrorExplicit(int err, Args&&... args) {
|
||||
throwSystemErrorExplicit(
|
||||
err, to<fbstring>(std::forward<Args>(args)...).c_str());
|
||||
}
|
||||
|
||||
// Helper to throw std::system_error from errno and components of a string
|
||||
template <class... Args>
|
||||
void throwSystemError(Args&&... args) FOLLY_NORETURN;
|
||||
template <class... Args>
|
||||
void throwSystemError(Args&&... args) {
|
||||
throwSystemErrorExplicit(errno, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Check a Posix return code (0 on success, error number on error), throw
|
||||
// on error.
|
||||
template <class... Args>
|
||||
void checkPosixError(int err, Args&&... args) {
|
||||
if (UNLIKELY(err != 0)) {
|
||||
throwSystemErrorExplicit(err, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
// Check a Linux kernel-style return code (>= 0 on success, negative error
|
||||
// number on error), throw on error.
|
||||
template <class... Args>
|
||||
void checkKernelError(ssize_t ret, Args&&... args) {
|
||||
if (UNLIKELY(ret < 0)) {
|
||||
throwSystemErrorExplicit(-ret, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
// Check a traditional Unix return code (-1 and sets errno on error), throw
|
||||
// on error.
|
||||
template <class... Args>
|
||||
void checkUnixError(ssize_t ret, Args&&... args) {
|
||||
if (UNLIKELY(ret == -1)) {
|
||||
throwSystemError(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void checkUnixErrorExplicit(ssize_t ret, int savedErrno, Args&&... args) {
|
||||
if (UNLIKELY(ret == -1)) {
|
||||
throwSystemErrorExplicit(savedErrno, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the return code from a fopen-style function (returns a non-nullptr
|
||||
// FILE* on success, nullptr on error, sets errno). Works with fopen, fdopen,
|
||||
// freopen, tmpfile, etc.
|
||||
template <class... Args>
|
||||
void checkFopenError(FILE* fp, Args&&... args) {
|
||||
if (UNLIKELY(!fp)) {
|
||||
throwSystemError(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void checkFopenErrorExplicit(FILE* fp, int savedErrno, Args&&... args) {
|
||||
if (UNLIKELY(!fp)) {
|
||||
throwSystemErrorExplicit(savedErrno, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_EXCEPTION_H_ */
|
||||
|
||||
-2398
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
-1763
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
-131
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/File.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "folly/Exception.h"
|
||||
#include "folly/FileUtil.h"
|
||||
#include "folly/Format.h"
|
||||
#include "folly/ScopeGuard.h"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
File::File()
|
||||
: fd_(-1)
|
||||
, ownsFd_(false)
|
||||
{}
|
||||
|
||||
File::File(int fd, bool ownsFd)
|
||||
: fd_(fd)
|
||||
, ownsFd_(ownsFd)
|
||||
{}
|
||||
|
||||
File::File(const char* name, int flags, mode_t mode)
|
||||
: fd_(::open(name, flags, mode))
|
||||
, ownsFd_(false) {
|
||||
if (fd_ == -1) {
|
||||
throwSystemError(folly::format("open(\"{}\", {:#o}, 0{:#o}) failed",
|
||||
name, flags, mode).fbstr());
|
||||
}
|
||||
ownsFd_ = true;
|
||||
}
|
||||
|
||||
File::File(File&& other)
|
||||
: fd_(other.fd_)
|
||||
, ownsFd_(other.ownsFd_) {
|
||||
|
||||
other.release();
|
||||
}
|
||||
|
||||
File& File::operator=(File&& other) {
|
||||
closeNoThrow();
|
||||
swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
closeNoThrow(); // ignore error
|
||||
}
|
||||
|
||||
/* static */ File File::temporary() {
|
||||
// make a temp file with tmpfile(), dup the fd, then return it in a File.
|
||||
FILE* tmpFile = tmpfile();
|
||||
checkFopenError(tmpFile, "tmpfile() failed");
|
||||
SCOPE_EXIT { fclose(tmpFile); };
|
||||
|
||||
int fd = dup(fileno(tmpFile));
|
||||
checkUnixError(fd, "dup() failed");
|
||||
|
||||
return File(fd, true);
|
||||
}
|
||||
|
||||
void File::release() {
|
||||
fd_ = -1;
|
||||
ownsFd_ = false;
|
||||
}
|
||||
|
||||
void File::swap(File& other) {
|
||||
using std::swap;
|
||||
swap(fd_, other.fd_);
|
||||
swap(ownsFd_, other.ownsFd_);
|
||||
}
|
||||
|
||||
void swap(File& a, File& b) {
|
||||
a.swap(b);
|
||||
}
|
||||
|
||||
void File::close() {
|
||||
if (!closeNoThrow()) {
|
||||
throwSystemError("close() failed");
|
||||
}
|
||||
}
|
||||
|
||||
bool File::closeNoThrow() {
|
||||
int r = ownsFd_ ? ::close(fd_) : 0;
|
||||
release();
|
||||
return r == 0;
|
||||
}
|
||||
|
||||
void File::lock() { doLock(LOCK_EX); }
|
||||
bool File::try_lock() { return doTryLock(LOCK_EX); }
|
||||
void File::lock_shared() { doLock(LOCK_SH); }
|
||||
bool File::try_lock_shared() { return doTryLock(LOCK_SH); }
|
||||
|
||||
void File::doLock(int op) {
|
||||
checkUnixError(flockNoInt(fd_, op), "flock() failed (lock)");
|
||||
}
|
||||
|
||||
bool File::doTryLock(int op) {
|
||||
int r = flockNoInt(fd_, op | LOCK_NB);
|
||||
// flock returns EWOULDBLOCK if already locked
|
||||
if (r == -1 && errno == EWOULDBLOCK) return false;
|
||||
checkUnixError(r, "flock() failed (try_lock)");
|
||||
return true;
|
||||
}
|
||||
|
||||
void File::unlock() {
|
||||
checkUnixError(flockNoInt(fd_, LOCK_UN), "flock() failed (unlock)");
|
||||
}
|
||||
void File::unlock_shared() { unlock(); }
|
||||
|
||||
} // namespace folly
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_FILE_H_
|
||||
#define FOLLY_FILE_H_
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* A File represents an open file.
|
||||
*/
|
||||
class File {
|
||||
public:
|
||||
/**
|
||||
* Creates an empty File object, for late initialization.
|
||||
*/
|
||||
File();
|
||||
|
||||
/**
|
||||
* Create a File object from an existing file descriptor.
|
||||
* Takes ownership of the file descriptor if ownsFd is true.
|
||||
*/
|
||||
/* implicit */ File(int fd,
|
||||
bool ownsFd = false);
|
||||
|
||||
/**
|
||||
* Open and create a file object. Throws on error.
|
||||
*/
|
||||
/* implicit */ File(const char* name,
|
||||
int flags = O_RDONLY,
|
||||
mode_t mode = 0644);
|
||||
|
||||
~File();
|
||||
|
||||
/**
|
||||
* Create and return a temporary, owned file (uses tmpfile()).
|
||||
*/
|
||||
static File temporary();
|
||||
|
||||
/**
|
||||
* Return the file descriptor, or -1 if the file was closed.
|
||||
*/
|
||||
int fd() const { return fd_; }
|
||||
|
||||
/**
|
||||
* Returns 'true' iff the file was successfully opened.
|
||||
*/
|
||||
explicit operator bool() const {
|
||||
return fd_ >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we own the file descriptor, close the file and throw on error.
|
||||
* Otherwise, do nothing.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Closes the file (if owned). Returns true on success, false (and sets
|
||||
* errno) on error.
|
||||
*/
|
||||
bool closeNoThrow();
|
||||
|
||||
/**
|
||||
* Releases the file descriptor; no longer owned by this File.
|
||||
*/
|
||||
void release();
|
||||
|
||||
/**
|
||||
* Swap this File with another.
|
||||
*/
|
||||
void swap(File& other);
|
||||
|
||||
// movable
|
||||
File(File&&);
|
||||
File& operator=(File&&);
|
||||
|
||||
// FLOCK (INTERPROCESS) LOCKS
|
||||
//
|
||||
// NOTE THAT THESE LOCKS ARE flock() LOCKS. That is, they may only be used
|
||||
// for inter-process synchronization -- an attempt to acquire a second lock
|
||||
// on the same file descriptor from the same process may succeed. Attempting
|
||||
// to acquire a second lock on a different file descriptor for the same file
|
||||
// should fail, but some systems might implement flock() using fcntl() locks,
|
||||
// in which case it will succeed.
|
||||
void lock();
|
||||
bool try_lock();
|
||||
void unlock();
|
||||
|
||||
void lock_shared();
|
||||
bool try_lock_shared();
|
||||
void unlock_shared();
|
||||
|
||||
private:
|
||||
void doLock(int op);
|
||||
bool doTryLock(int op);
|
||||
|
||||
// unique
|
||||
File(const File&) = delete;
|
||||
File& operator=(const File&) = delete;
|
||||
|
||||
int fd_;
|
||||
bool ownsFd_;
|
||||
};
|
||||
|
||||
void swap(File& a, File& b);
|
||||
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_FILE_H_ */
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/FileUtil.h"
|
||||
|
||||
#include <cerrno>
|
||||
#ifdef __APPLE__
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
#include <sys/file.h>
|
||||
|
||||
#include "folly/detail/FileUtilDetail.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
using namespace fileutil_detail;
|
||||
|
||||
int openNoInt(const char* name, int flags, mode_t mode) {
|
||||
return wrapNoInt(open, name, flags, mode);
|
||||
}
|
||||
|
||||
int closeNoInt(int fd) {
|
||||
int r = close(fd);
|
||||
// Ignore EINTR. On Linux, close() may only return EINTR after the file
|
||||
// descriptor has been closed, so you must not retry close() on EINTR --
|
||||
// in the best case, you'll get EBADF, and in the worst case, you'll end up
|
||||
// closing a different file (one opened from another thread).
|
||||
//
|
||||
// Interestingly enough, the Single Unix Specification says that the state
|
||||
// of the file descriptor is unspecified if close returns EINTR. In that
|
||||
// case, the safe thing to do is also not to retry close() -- leaking a file
|
||||
// descriptor is probably better than closing the wrong file.
|
||||
if (r == -1 && errno == EINTR) {
|
||||
r = 0;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int fsyncNoInt(int fd) {
|
||||
return wrapNoInt(fsync, fd);
|
||||
}
|
||||
|
||||
int fdatasyncNoInt(int fd) {
|
||||
#if defined(__APPLE__)
|
||||
return wrapNoInt(fcntl, fd, F_FULLFSYNC);
|
||||
#elif defined(__FreeBSD__)
|
||||
return wrapNoInt(fsync, fd);
|
||||
#else
|
||||
return wrapNoInt(fdatasync, fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
int ftruncateNoInt(int fd, off_t len) {
|
||||
return wrapNoInt(ftruncate, fd, len);
|
||||
}
|
||||
|
||||
int truncateNoInt(const char* path, off_t len) {
|
||||
return wrapNoInt(truncate, path, len);
|
||||
}
|
||||
|
||||
int flockNoInt(int fd, int operation) {
|
||||
return wrapNoInt(flock, fd, operation);
|
||||
}
|
||||
|
||||
ssize_t readNoInt(int fd, void* buf, size_t count) {
|
||||
return wrapNoInt(read, fd, buf, count);
|
||||
}
|
||||
|
||||
ssize_t preadNoInt(int fd, void* buf, size_t count, off_t offset) {
|
||||
return wrapNoInt(pread, fd, buf, count, offset);
|
||||
}
|
||||
|
||||
ssize_t readvNoInt(int fd, const iovec* iov, int count) {
|
||||
return wrapNoInt(writev, fd, iov, count);
|
||||
}
|
||||
|
||||
ssize_t writeNoInt(int fd, const void* buf, size_t count) {
|
||||
return wrapNoInt(write, fd, buf, count);
|
||||
}
|
||||
|
||||
ssize_t pwriteNoInt(int fd, const void* buf, size_t count, off_t offset) {
|
||||
return wrapNoInt(pwrite, fd, buf, count, offset);
|
||||
}
|
||||
|
||||
ssize_t writevNoInt(int fd, const iovec* iov, int count) {
|
||||
return wrapNoInt(writev, fd, iov, count);
|
||||
}
|
||||
|
||||
ssize_t readFull(int fd, void* buf, size_t count) {
|
||||
return wrapFull(read, fd, buf, count);
|
||||
}
|
||||
|
||||
ssize_t preadFull(int fd, void* buf, size_t count, off_t offset) {
|
||||
return wrapFull(pread, fd, buf, count, offset);
|
||||
}
|
||||
|
||||
ssize_t writeFull(int fd, const void* buf, size_t count) {
|
||||
return wrapFull(write, fd, const_cast<void*>(buf), count);
|
||||
}
|
||||
|
||||
ssize_t pwriteFull(int fd, const void* buf, size_t count, off_t offset) {
|
||||
return wrapFull(pwrite, fd, const_cast<void*>(buf), count, offset);
|
||||
}
|
||||
|
||||
ssize_t readvFull(int fd, iovec* iov, int count) {
|
||||
return wrapvFull(readv, fd, iov, count);
|
||||
}
|
||||
|
||||
#ifdef FOLLY_HAVE_PREADV
|
||||
ssize_t preadvFull(int fd, iovec* iov, int count, off_t offset) {
|
||||
return wrapvFull(preadv, fd, iov, count, offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
ssize_t writevFull(int fd, iovec* iov, int count) {
|
||||
return wrapvFull(writev, fd, iov, count);
|
||||
}
|
||||
|
||||
#ifdef FOLLY_HAVE_PWRITEV
|
||||
ssize_t pwritevFull(int fd, iovec* iov, int count, off_t offset) {
|
||||
return wrapvFull(pwritev, fd, iov, count, offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespaces
|
||||
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_FILEUTIL_H_
|
||||
#define FOLLY_FILEUTIL_H_
|
||||
|
||||
#include "folly/Portability.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Convenience wrappers around some commonly used system calls. The *NoInt
|
||||
* wrappers retry on EINTR. The *Full wrappers retry on EINTR and also loop
|
||||
* until all data is written. Note that *Full wrappers weaken the thread
|
||||
* semantics of underlying system calls.
|
||||
*/
|
||||
int openNoInt(const char* name, int flags, mode_t mode=0644);
|
||||
int closeNoInt(int fd);
|
||||
int fsyncNoInt(int fd);
|
||||
int fdatasyncNoInt(int fd);
|
||||
int ftruncateNoInt(int fd, off_t len);
|
||||
int truncateNoInt(const char* path, off_t len);
|
||||
int flockNoInt(int fd, int operation);
|
||||
|
||||
ssize_t readNoInt(int fd, void* buf, size_t n);
|
||||
ssize_t preadNoInt(int fd, void* buf, size_t n, off_t offset);
|
||||
ssize_t readvNoInt(int fd, const iovec* iov, int count);
|
||||
|
||||
ssize_t writeNoInt(int fd, const void* buf, size_t n);
|
||||
ssize_t pwriteNoInt(int fd, const void* buf, size_t n, off_t offset);
|
||||
ssize_t writevNoInt(int fd, const iovec* iov, int count);
|
||||
|
||||
/**
|
||||
* Wrapper around read() (and pread()) that, in addition to retrying on
|
||||
* EINTR, will loop until all data is read.
|
||||
*
|
||||
* This wrapper is only useful for blocking file descriptors (for non-blocking
|
||||
* file descriptors, you have to be prepared to deal with incomplete reads
|
||||
* anyway), and only exists because POSIX allows read() to return an incomplete
|
||||
* read if interrupted by a signal (instead of returning -1 and setting errno
|
||||
* to EINTR).
|
||||
*
|
||||
* Note that this wrapper weakens the thread safety of read(): the file pointer
|
||||
* is shared between threads, but the system call is atomic. If multiple
|
||||
* threads are reading from a file at the same time, you don't know where your
|
||||
* data came from in the file, but you do know that the returned bytes were
|
||||
* contiguous. You can no longer make this assumption if using readFull().
|
||||
* You should probably use pread() when reading from the same file descriptor
|
||||
* from multiple threads simultaneously, anyway.
|
||||
*
|
||||
* Note that readvFull and preadvFull require iov to be non-const, unlike
|
||||
* readv and preadv. The contents of iov after these functions return
|
||||
* is unspecified.
|
||||
*/
|
||||
ssize_t readFull(int fd, void* buf, size_t n);
|
||||
ssize_t preadFull(int fd, void* buf, size_t n, off_t offset);
|
||||
ssize_t readvFull(int fd, iovec* iov, int count);
|
||||
#ifdef FOLLY_HAVE_PREADV
|
||||
ssize_t preadvFull(int fd, iovec* iov, int count, off_t offset);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Similar to readFull and preadFull above, wrappers around write() and
|
||||
* pwrite() that loop until all data is written.
|
||||
*
|
||||
* Generally, the write() / pwrite() system call may always write fewer bytes
|
||||
* than requested, just like read(). In certain cases (such as when writing to
|
||||
* a pipe), POSIX provides stronger guarantees, but not in the general case.
|
||||
* For example, Linux (even on a 64-bit platform) won't write more than 2GB in
|
||||
* one write() system call.
|
||||
*
|
||||
* Note that writevFull and pwritevFull require iov to be non-const, unlike
|
||||
* writev and pwritev. The contents of iov after these functions return
|
||||
* is unspecified.
|
||||
*/
|
||||
ssize_t writeFull(int fd, const void* buf, size_t n);
|
||||
ssize_t pwriteFull(int fd, const void* buf, size_t n, off_t offset);
|
||||
ssize_t writevFull(int fd, iovec* iov, int count);
|
||||
#ifdef FOLLY_HAVE_PWRITEV
|
||||
ssize_t pwritevFull(int fd, iovec* iov, int count, off_t offset);
|
||||
#endif
|
||||
|
||||
} // namespaces
|
||||
|
||||
#endif /* FOLLY_FILEUTIL_H_ */
|
||||
|
||||
-266
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compute 64-, 96-, and 128-bit Rabin fingerprints, as described in
|
||||
* Michael O. Rabin (1981)
|
||||
* Fingerprinting by Random Polynomials
|
||||
* Center for Research in Computing Technology, Harvard University
|
||||
* Tech Report TR-CSE-03-01
|
||||
*
|
||||
* The implementation follows the optimization described in
|
||||
* Andrei Z. Broder (1993)
|
||||
* Some applications of Rabin's fingerprinting method
|
||||
*
|
||||
* extended for fingerprints larger than 64 bits, and modified to use
|
||||
* 64-bit instead of 32-bit integers for computation.
|
||||
*
|
||||
* The precomputed tables are in FingerprintTable.cpp, which is automatically
|
||||
* generated by ComputeFingerprintTable.cpp.
|
||||
*
|
||||
* Benchmarked on 10/13/2009 on a 2.5GHz quad-core Xeon L5420,
|
||||
* - Fingerprint<64>::update64() takes about 12ns
|
||||
* - Fingerprint<96>::update64() takes about 30ns
|
||||
* - Fingerprint<128>::update128() takes about 30ns
|
||||
* (unsurprisingly, Fingerprint<96> and Fingerprint<128> take the
|
||||
* same amount of time, as they both use 128-bit operations; the least
|
||||
* significant 32 bits of Fingerprint<96> will always be 0)
|
||||
*
|
||||
* @author Tudor Bosman (tudorb@facebook.com)
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_FINGERPRINT_H_
|
||||
#define FOLLY_FINGERPRINT_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "folly/Range.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace detail {
|
||||
template <int BITS>
|
||||
struct FingerprintTable {
|
||||
static const uint64_t poly[1 + (BITS-1)/64];
|
||||
static const uint64_t table[8][256][1 + (BITS-1)/64];
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Compute the Rabin fingerprint.
|
||||
*
|
||||
* TODO(tudorb): Extend this to allow removing values from the computed
|
||||
* fingerprint (so we can fingerprint a sliding window, as in the Rabin-Karp
|
||||
* string matching algorithm)
|
||||
*
|
||||
* update* methods return *this, so you can chain them together:
|
||||
* Fingerprint<96>().update8(x).update(str).update64(val).write(output);
|
||||
*/
|
||||
template <int BITS>
|
||||
class Fingerprint {
|
||||
public:
|
||||
Fingerprint() {
|
||||
// Use a non-zero starting value. We'll use (1 << (BITS-1))
|
||||
fp_[0] = 1UL << 63;
|
||||
for (int i = 1; i < size(); i++)
|
||||
fp_[i] = 0;
|
||||
}
|
||||
|
||||
Fingerprint& update8(uint8_t v) {
|
||||
uint8_t out = shlor8(v);
|
||||
xortab(detail::FingerprintTable<BITS>::table[0][out]);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// update32 and update64 are convenience functions to update the fingerprint
|
||||
// with 4 and 8 bytes at a time. They are faster than calling update8
|
||||
// in a loop. They process the bytes in big-endian order.
|
||||
Fingerprint& update32(uint32_t v) {
|
||||
uint32_t out = shlor32(v);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
xortab(detail::FingerprintTable<BITS>::table[i][out&0xff]);
|
||||
out >>= 8;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Fingerprint& update64(uint64_t v) {
|
||||
uint64_t out = shlor64(v);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
xortab(detail::FingerprintTable<BITS>::table[i][out&0xff]);
|
||||
out >>= 8;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Fingerprint& update(StringPiece str) {
|
||||
// TODO(tudorb): We could be smart and do update64 or update32 if aligned
|
||||
for (auto c : str) {
|
||||
update8(uint8_t(c));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of uint64s needed to hold the fingerprint value.
|
||||
*/
|
||||
static int size() {
|
||||
return 1 + (BITS-1)/64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the computed fingeprint to an array of size() uint64_t's.
|
||||
* For Fingerprint<64>, size()==1; we write 64 bits in out[0]
|
||||
* For Fingerprint<96>, size()==2; we write 64 bits in out[0] and
|
||||
* the most significant 32 bits of out[1]
|
||||
* For Fingerprint<128>, size()==2; we write 64 bits in out[0] and
|
||||
* 64 bits in out[1].
|
||||
*/
|
||||
void write(uint64_t* out) const {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
out[i] = fp_[i];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// XOR the fingerprint with a value from one of the tables.
|
||||
void xortab(const uint64_t* tab) {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
fp_[i] ^= tab[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions: shift the fingerprint value left by 8/32/64 bits,
|
||||
// return the "out" value (the bits that were shifted out), and add "v"
|
||||
// in the bits on the right.
|
||||
uint8_t shlor8(uint8_t v);
|
||||
uint32_t shlor32(uint32_t v);
|
||||
uint64_t shlor64(uint64_t v);
|
||||
|
||||
uint64_t fp_[1 + (BITS-1)/64];
|
||||
};
|
||||
|
||||
// Convenience functions
|
||||
|
||||
/**
|
||||
* Return the 64-bit Rabin fingerprint of a string.
|
||||
*/
|
||||
inline uint64_t fingerprint64(StringPiece str) {
|
||||
uint64_t fp;
|
||||
Fingerprint<64>().update(str).write(&fp);
|
||||
return fp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the 96-bit Rabin fingerprint of a string.
|
||||
* Return the 64 most significant bits in *msb, and the 32 least significant
|
||||
* bits in *lsb.
|
||||
*/
|
||||
inline void fingerprint96(StringPiece str,
|
||||
uint64_t* msb, uint32_t* lsb) {
|
||||
uint64_t fp[2];
|
||||
Fingerprint<96>().update(str).write(fp);
|
||||
*msb = fp[0];
|
||||
*lsb = (uint32_t)(fp[1] >> 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the 128-bit Rabin fingerprint of a string.
|
||||
* Return the 64 most significant bits in *msb, and the 64 least significant
|
||||
* bits in *lsb.
|
||||
*/
|
||||
inline void fingerprint128(StringPiece str,
|
||||
uint64_t* msb, uint64_t* lsb) {
|
||||
uint64_t fp[2];
|
||||
Fingerprint<128>().update(str).write(fp);
|
||||
*msb = fp[0];
|
||||
*lsb = fp[1];
|
||||
}
|
||||
|
||||
|
||||
template <>
|
||||
inline uint8_t Fingerprint<64>::shlor8(uint8_t v) {
|
||||
uint8_t out = (uint8_t)(fp_[0] >> 56);
|
||||
fp_[0] = (fp_[0] << 8) | ((uint64_t)v);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint32_t Fingerprint<64>::shlor32(uint32_t v) {
|
||||
uint32_t out = (uint32_t)(fp_[0] >> 32);
|
||||
fp_[0] = (fp_[0] << 32) | ((uint64_t)v);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint64_t Fingerprint<64>::shlor64(uint64_t v) {
|
||||
uint64_t out = fp_[0];
|
||||
fp_[0] = v;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint8_t Fingerprint<96>::shlor8(uint8_t v) {
|
||||
uint8_t out = (uint8_t)(fp_[0] >> 56);
|
||||
fp_[0] = (fp_[0] << 8) | (fp_[1] >> 56);
|
||||
fp_[1] = (fp_[1] << 8) | ((uint64_t)v << 32);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint32_t Fingerprint<96>::shlor32(uint32_t v) {
|
||||
uint32_t out = (uint32_t)(fp_[0] >> 32);
|
||||
fp_[0] = (fp_[0] << 32) | (fp_[1] >> 32);
|
||||
fp_[1] = ((uint64_t)v << 32);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint64_t Fingerprint<96>::shlor64(uint64_t v) {
|
||||
uint64_t out = fp_[0];
|
||||
fp_[0] = fp_[1] | (v >> 32);
|
||||
fp_[1] = v << 32;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint8_t Fingerprint<128>::shlor8(uint8_t v) {
|
||||
uint8_t out = (uint8_t)(fp_[0] >> 56);
|
||||
fp_[0] = (fp_[0] << 8) | (fp_[1] >> 56);
|
||||
fp_[1] = (fp_[1] << 8) | ((uint64_t)v);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint32_t Fingerprint<128>::shlor32(uint32_t v) {
|
||||
uint32_t out = (uint32_t)(fp_[0] >> 32);
|
||||
fp_[0] = (fp_[0] << 32) | (fp_[1] >> 32);
|
||||
fp_[1] = (fp_[1] << 32) | ((uint64_t)v);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint64_t Fingerprint<128>::shlor64(uint64_t v) {
|
||||
uint64_t out = fp_[0];
|
||||
fp_[0] = fp_[1];
|
||||
fp_[1] = v;
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_FINGERPRINT_H_ */
|
||||
|
||||
-260
@@ -1,260 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_BASE_FOREACH_H_
|
||||
#define FOLLY_BASE_FOREACH_H_
|
||||
|
||||
/*
|
||||
* Iterim macros (until we have C++0x range-based for) that simplify
|
||||
* writing loops of the form
|
||||
*
|
||||
* for (Container<data>::iterator i = c.begin(); i != c.end(); ++i) statement
|
||||
*
|
||||
* Just replace the above with:
|
||||
*
|
||||
* FOR_EACH (i, c) statement
|
||||
*
|
||||
* and everything is taken care of.
|
||||
*
|
||||
* The implementation is a bit convoluted to make sure the container is
|
||||
* only evaluated once (however, keep in mind that c.end() is evaluated
|
||||
* at every pass through the loop). To ensure the container is not
|
||||
* evaluated multiple times, the macro defines one do-nothing if
|
||||
* statement to inject the Boolean variable FOR_EACH_state1, and then a
|
||||
* for statement that is executed only once, which defines the variable
|
||||
* FOR_EACH_state2 holding a reference to the container being
|
||||
* iterated. The workhorse is the last loop, which uses the just defined
|
||||
* reference FOR_EACH_state2.
|
||||
*
|
||||
* The state variables are nested so they don't interfere; you can use
|
||||
* FOR_EACH multiple times in the same scope, either at the same level or
|
||||
* nested.
|
||||
*
|
||||
* In optimized builds g++ eliminates the extra gymnastics entirely and
|
||||
* generates code 100% identical to the handwritten loop.
|
||||
*
|
||||
* This will not work with temporary containers. Consider BOOST_FOREACH
|
||||
* if you need that.
|
||||
*/
|
||||
|
||||
#include <boost/type_traits/remove_cv.hpp>
|
||||
|
||||
namespace folly { namespace detail {
|
||||
|
||||
/*
|
||||
* Simple template for obtaining the unqualified type given a generic
|
||||
* type T. For example, if T is const int,
|
||||
* typeof(remove_cv_from_expression(T())) yields int. Due to a bug in
|
||||
* g++, you need to actually use
|
||||
* typeof(remove_cv_from_expression(T())) instead of typename
|
||||
* boost::remove_cv<T>::type. Note that the function
|
||||
* remove_cv_from_expression is never defined - use it only inside
|
||||
* typeof.
|
||||
*/
|
||||
template <class T> typename boost::remove_cv<T>::type
|
||||
remove_cv_from_expression(T value);
|
||||
|
||||
}}
|
||||
|
||||
/*
|
||||
* Use a "reference reference" (auto&&) to take advantage of reference
|
||||
* collapsing rules, if available. In this case, FOR_EACH* will work with
|
||||
* temporary containers.
|
||||
*/
|
||||
#define FB_AUTO_RR(x, y) auto&& x = y
|
||||
|
||||
/*
|
||||
* The first AUTO should be replaced by decltype((c)) &
|
||||
* FOR_EACH_state2, but bugs in gcc prevent that from functioning
|
||||
* properly. The second pair of parens in decltype is actually
|
||||
* required, see
|
||||
* cpp-next.com/archive/2011/04/appearing-and-disappearing-consts-in-c/
|
||||
*/
|
||||
#define FOR_EACH(i, c) \
|
||||
if (bool FOR_EACH_state1 = false) {} else \
|
||||
for (auto & FOR_EACH_state2 = (c); \
|
||||
!FOR_EACH_state1; FOR_EACH_state1 = true) \
|
||||
for (auto i = FOR_EACH_state2.begin(); \
|
||||
i != FOR_EACH_state2.end(); ++i)
|
||||
|
||||
/*
|
||||
* Similar to FOR_EACH, but iterates the container backwards by
|
||||
* using rbegin() and rend().
|
||||
*/
|
||||
#define FOR_EACH_R(i, c) \
|
||||
if (bool FOR_EACH_R_state1 = false) {} else \
|
||||
for (auto & FOR_EACH_R_state2 = (c); \
|
||||
!FOR_EACH_R_state1; FOR_EACH_R_state1 = true) \
|
||||
for (auto i = FOR_EACH_R_state2.rbegin(); \
|
||||
i != FOR_EACH_R_state2.rend(); ++i)
|
||||
|
||||
/*
|
||||
* Similar to FOR_EACH but also allows client to specify a 'count' variable
|
||||
* to track the current iteration in the loop (starting at zero).
|
||||
* Similar to python's enumerate() function. For example:
|
||||
* string commaSeparatedValues = "VALUES: ";
|
||||
* FOR_EACH_ENUMERATE(ii, value, columns) { // don't want comma at the end!
|
||||
* commaSeparatedValues += (ii == 0) ? *value : string(",") + *value;
|
||||
* }
|
||||
*/
|
||||
#define FOR_EACH_ENUMERATE(count, i, c) \
|
||||
if (bool FOR_EACH_state1 = false) {} else \
|
||||
for (auto & FOR_EACH_state2 = (c); \
|
||||
!FOR_EACH_state1; FOR_EACH_state1 = true) \
|
||||
if (size_t FOR_EACH_privateCount = 0) {} else \
|
||||
if (const size_t& count = FOR_EACH_privateCount) {} else \
|
||||
for (auto i = FOR_EACH_state2.begin(); \
|
||||
i != FOR_EACH_state2.end(); ++FOR_EACH_privateCount, ++i)
|
||||
|
||||
/**
|
||||
* Similar to FOR_EACH, but gives the user the key and value for each entry in
|
||||
* the container, instead of just the iterator to the entry. For example:
|
||||
* map<string, string> testMap;
|
||||
* FOR_EACH_KV(key, value, testMap) {
|
||||
* cout << key << " " << value;
|
||||
* }
|
||||
*/
|
||||
#define FOR_EACH_KV(k, v, c) \
|
||||
if (unsigned int FOR_EACH_state1 = 0) {} else \
|
||||
for (FB_AUTO_RR(FOR_EACH_state2, (c)); \
|
||||
!FOR_EACH_state1; FOR_EACH_state1 = 1) \
|
||||
for (auto FOR_EACH_state3 = FOR_EACH_state2.begin(); \
|
||||
FOR_EACH_state3 != FOR_EACH_state2.end(); \
|
||||
FOR_EACH_state1 == 2 \
|
||||
? ((FOR_EACH_state1 = 0), ++FOR_EACH_state3) \
|
||||
: (FOR_EACH_state3 = FOR_EACH_state2.end())) \
|
||||
for (auto &k = FOR_EACH_state3->first; \
|
||||
!FOR_EACH_state1; ++FOR_EACH_state1) \
|
||||
for (auto &v = FOR_EACH_state3->second; \
|
||||
!FOR_EACH_state1; ++FOR_EACH_state1)
|
||||
|
||||
namespace folly { namespace detail {
|
||||
|
||||
// Boost 1.48 lacks has_less, we emulate a subset of it here.
|
||||
template <typename T, typename U>
|
||||
class HasLess {
|
||||
struct BiggerThanChar { char unused[2]; };
|
||||
template <typename C, typename D> static char test(decltype(C() < D())*);
|
||||
template <typename, typename> static BiggerThanChar test(...);
|
||||
public:
|
||||
enum { value = sizeof(test<T, U>(0)) == 1 };
|
||||
};
|
||||
|
||||
/**
|
||||
* notThereYet helps the FOR_EACH_RANGE macro by opportunistically
|
||||
* using "<" instead of "!=" whenever available when checking for loop
|
||||
* termination. This makes e.g. examples such as FOR_EACH_RANGE (i,
|
||||
* 10, 5) execute zero iterations instead of looping virtually
|
||||
* forever. At the same time, some iterator types define "!=" but not
|
||||
* "<". The notThereYet function will dispatch differently for those.
|
||||
*
|
||||
* Below is the correct implementation of notThereYet. It is disabled
|
||||
* because of a bug in Boost 1.46: The filesystem::path::iterator
|
||||
* defines operator< (via boost::iterator_facade), but that in turn
|
||||
* uses distance_to which is undefined for that particular
|
||||
* iterator. So HasLess (defined above) identifies
|
||||
* boost::filesystem::path as properly comparable with <, but in fact
|
||||
* attempting to do so will yield a compile-time error.
|
||||
*
|
||||
* The else branch (active) contains a conservative
|
||||
* implementation.
|
||||
*/
|
||||
|
||||
#if 0
|
||||
|
||||
template <class T, class U>
|
||||
typename std::enable_if<HasLess<T, U>::value, bool>::type
|
||||
notThereYet(T& iter, const U& end) {
|
||||
return iter < end;
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
typename std::enable_if<!HasLess<T, U>::value, bool>::type
|
||||
notThereYet(T& iter, const U& end) {
|
||||
return iter != end;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template <class T, class U>
|
||||
typename std::enable_if<
|
||||
(std::is_arithmetic<T>::value && std::is_arithmetic<U>::value) ||
|
||||
(std::is_pointer<T>::value && std::is_pointer<U>::value),
|
||||
bool>::type
|
||||
notThereYet(T& iter, const U& end) {
|
||||
return iter < end;
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
typename std::enable_if<
|
||||
!(
|
||||
(std::is_arithmetic<T>::value && std::is_arithmetic<U>::value) ||
|
||||
(std::is_pointer<T>::value && std::is_pointer<U>::value)
|
||||
),
|
||||
bool>::type
|
||||
notThereYet(T& iter, const U& end) {
|
||||
return iter != end;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* downTo is similar to notThereYet, but in reverse - it helps the
|
||||
* FOR_EACH_RANGE_R macro.
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename std::enable_if<HasLess<U, T>::value, bool>::type
|
||||
downTo(T& iter, const U& begin) {
|
||||
return begin < iter--;
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
typename std::enable_if<!HasLess<U, T>::value, bool>::type
|
||||
downTo(T& iter, const U& begin) {
|
||||
if (iter == begin) return false;
|
||||
--iter;
|
||||
return true;
|
||||
}
|
||||
|
||||
} }
|
||||
|
||||
/*
|
||||
* Iteration with given limits. end is assumed to be reachable from
|
||||
* begin. end is evaluated every pass through the loop.
|
||||
*
|
||||
* NOTE: The type of the loop variable should be the common type of "begin"
|
||||
* and "end". e.g. If "begin" is "int" but "end" is "long", we want "i"
|
||||
* to be "long". This is done by getting the type of (true ? begin : end)
|
||||
*/
|
||||
#define FOR_EACH_RANGE(i, begin, end) \
|
||||
for (auto i = (true ? (begin) : (end)); \
|
||||
::folly::detail::notThereYet(i, (end)); \
|
||||
++i)
|
||||
|
||||
/*
|
||||
* Iteration with given limits. begin is assumed to be reachable from
|
||||
* end by successive decrements. begin is evaluated every pass through
|
||||
* the loop.
|
||||
*
|
||||
* NOTE: The type of the loop variable should be the common type of "begin"
|
||||
* and "end". e.g. If "begin" is "int" but "end" is "long", we want "i"
|
||||
* to be "long". This is done by getting the type of (false ? begin : end)
|
||||
*/
|
||||
#define FOR_EACH_RANGE_R(i, begin, end) \
|
||||
for (auto i = (false ? (begin) : (end)); ::folly::detail::downTo(i, (begin));)
|
||||
|
||||
#endif
|
||||
-1117
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
-137
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/Format.h"
|
||||
|
||||
namespace folly {
|
||||
namespace detail {
|
||||
|
||||
extern const FormatArg::Align formatAlignTable[];
|
||||
extern const FormatArg::Sign formatSignTable[];
|
||||
|
||||
} // namespace detail
|
||||
|
||||
using namespace folly::detail;
|
||||
|
||||
void FormatArg::initSlow() {
|
||||
auto b = fullArgString.begin();
|
||||
auto end = fullArgString.end();
|
||||
|
||||
// Parse key
|
||||
auto p = static_cast<const char*>(memchr(b, ':', end - b));
|
||||
if (!p) {
|
||||
key_ = StringPiece(b, end);
|
||||
return;
|
||||
}
|
||||
key_ = StringPiece(b, p);
|
||||
|
||||
if (*p == ':') {
|
||||
// parse format spec
|
||||
if (++p == end) return;
|
||||
|
||||
// fill/align, or just align
|
||||
Align a;
|
||||
if (p + 1 != end &&
|
||||
(a = formatAlignTable[static_cast<unsigned char>(p[1])]) !=
|
||||
Align::INVALID) {
|
||||
fill = *p;
|
||||
align = a;
|
||||
p += 2;
|
||||
if (p == end) return;
|
||||
} else if ((a = formatAlignTable[static_cast<unsigned char>(*p)]) !=
|
||||
Align::INVALID) {
|
||||
align = a;
|
||||
if (++p == end) return;
|
||||
}
|
||||
|
||||
Sign s;
|
||||
unsigned char uSign = static_cast<unsigned char>(*p);
|
||||
if ((s = formatSignTable[uSign]) != Sign::INVALID) {
|
||||
sign = s;
|
||||
if (++p == end) return;
|
||||
}
|
||||
|
||||
if (*p == '#') {
|
||||
basePrefix = true;
|
||||
if (++p == end) return;
|
||||
}
|
||||
|
||||
if (*p == '0') {
|
||||
enforce(align == Align::DEFAULT, "alignment specified twice");
|
||||
fill = '0';
|
||||
align = Align::PAD_AFTER_SIGN;
|
||||
if (++p == end) return;
|
||||
}
|
||||
|
||||
if (*p >= '0' && *p <= '9') {
|
||||
auto b = p;
|
||||
do {
|
||||
++p;
|
||||
} while (p != end && *p >= '0' && *p <= '9');
|
||||
width = to<int>(StringPiece(b, p));
|
||||
|
||||
if (p == end) return;
|
||||
}
|
||||
|
||||
if (*p == ',') {
|
||||
thousandsSeparator = true;
|
||||
if (++p == end) return;
|
||||
}
|
||||
|
||||
if (*p == '.') {
|
||||
auto b = ++p;
|
||||
while (p != end && *p >= '0' && *p <= '9') {
|
||||
++p;
|
||||
}
|
||||
precision = to<int>(StringPiece(b, p));
|
||||
|
||||
if (p == end) return;
|
||||
}
|
||||
|
||||
presentation = *p;
|
||||
if (++p == end) return;
|
||||
}
|
||||
|
||||
error("extra characters in format string");
|
||||
}
|
||||
|
||||
void FormatArg::validate(Type type) const {
|
||||
enforce(keyEmpty(), "index not allowed");
|
||||
switch (type) {
|
||||
case Type::INTEGER:
|
||||
enforce(precision == kDefaultPrecision,
|
||||
"precision not allowed on integers");
|
||||
break;
|
||||
case Type::FLOAT:
|
||||
enforce(!basePrefix,
|
||||
"base prefix ('#') specifier only allowed on integers");
|
||||
enforce(!thousandsSeparator,
|
||||
"thousands separator (',') only allowed on integers");
|
||||
break;
|
||||
case Type::OTHER:
|
||||
enforce(align != Align::PAD_AFTER_SIGN,
|
||||
"'='alignment only allowed on numbers");
|
||||
enforce(sign == Sign::DEFAULT,
|
||||
"sign specifier only allowed on numbers");
|
||||
enforce(!basePrefix,
|
||||
"base prefix ('#') specifier only allowed on integers");
|
||||
enforce(!thousandsSeparator,
|
||||
"thousands separator (',') only allowed on integers");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
-277
@@ -1,277 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_FORMAT_H_
|
||||
#define FOLLY_FORMAT_H_
|
||||
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <double-conversion.h>
|
||||
|
||||
#include "folly/FBVector.h"
|
||||
#include "folly/Conv.h"
|
||||
#include "folly/Range.h"
|
||||
#include "folly/Traits.h"
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/String.h"
|
||||
#include "folly/small_vector.h"
|
||||
#include "folly/FormatArg.h"
|
||||
|
||||
// Ignore shadowing warnings within this file, so includers can use -Wshadow.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
|
||||
namespace folly {
|
||||
|
||||
// forward declarations
|
||||
template <bool containerMode, class... Args> class Formatter;
|
||||
template <class... Args>
|
||||
Formatter<false, Args...> format(StringPiece fmt, Args&&... args);
|
||||
template <class C>
|
||||
Formatter<true, C> vformat(StringPiece fmt, C&& container);
|
||||
template <class T, class Enable=void> class FormatValue;
|
||||
|
||||
/**
|
||||
* Formatter class.
|
||||
*
|
||||
* Note that this class is tricky, as it keeps *references* to its arguments
|
||||
* (and doesn't copy the passed-in format string). Thankfully, you can't use
|
||||
* this directly, you have to use format(...) below.
|
||||
*/
|
||||
|
||||
template <bool containerMode, class... Args>
|
||||
class Formatter {
|
||||
template <class... A>
|
||||
friend Formatter<false, A...> format(StringPiece fmt, A&&... arg);
|
||||
template <class C>
|
||||
friend Formatter<true, C> vformat(StringPiece fmt, C&& container);
|
||||
public:
|
||||
/**
|
||||
* Append to output. out(StringPiece sp) may be called (more than once)
|
||||
*/
|
||||
template <class Output>
|
||||
void operator()(Output& out) const;
|
||||
|
||||
/**
|
||||
* Append to a string.
|
||||
*/
|
||||
template <class Str>
|
||||
typename std::enable_if<detail::IsSomeString<Str>::value>::type
|
||||
appendTo(Str& str) const {
|
||||
auto appender = [&str] (StringPiece s) { str.append(s.data(), s.size()); };
|
||||
(*this)(appender);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversion to string
|
||||
*/
|
||||
std::string str() const {
|
||||
std::string s;
|
||||
appendTo(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversion to fbstring
|
||||
*/
|
||||
fbstring fbstr() const {
|
||||
fbstring s;
|
||||
appendTo(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Formatter(StringPiece str, Args&&... args);
|
||||
|
||||
// Not copyable
|
||||
Formatter(const Formatter&) = delete;
|
||||
Formatter& operator=(const Formatter&) = delete;
|
||||
|
||||
// Movable, but the move constructor and assignment operator are private,
|
||||
// for the exclusive use of format() (below). This way, you can't create
|
||||
// a Formatter object, but can handle references to it (for streaming,
|
||||
// conversion to string, etc) -- which is good, as Formatter objects are
|
||||
// dangerous (they hold references, possibly to temporaries)
|
||||
Formatter(Formatter&&) = default;
|
||||
Formatter& operator=(Formatter&&) = default;
|
||||
|
||||
typedef std::tuple<FormatValue<
|
||||
typename std::decay<Args>::type>...> ValueTuple;
|
||||
static constexpr size_t valueCount = std::tuple_size<ValueTuple>::value;
|
||||
|
||||
template <size_t K, class Callback>
|
||||
typename std::enable_if<K == valueCount>::type
|
||||
doFormatFrom(size_t i, FormatArg& arg, Callback& cb) const {
|
||||
arg.error("argument index out of range, max=", i);
|
||||
}
|
||||
|
||||
template <size_t K, class Callback>
|
||||
typename std::enable_if<(K < valueCount)>::type
|
||||
doFormatFrom(size_t i, FormatArg& arg, Callback& cb) const {
|
||||
if (i == K) {
|
||||
std::get<K>(values_).format(arg, cb);
|
||||
} else {
|
||||
doFormatFrom<K+1>(i, arg, cb);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Callback>
|
||||
void doFormat(size_t i, FormatArg& arg, Callback& cb) const {
|
||||
return doFormatFrom<0>(i, arg, cb);
|
||||
}
|
||||
|
||||
StringPiece str_;
|
||||
ValueTuple values_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatter objects can be written to streams.
|
||||
*/
|
||||
template<bool containerMode, class... Args>
|
||||
std::ostream& operator<<(std::ostream& out,
|
||||
const Formatter<containerMode, Args...>& formatter) {
|
||||
auto writer = [&out] (StringPiece sp) { out.write(sp.data(), sp.size()); };
|
||||
formatter(writer);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a formatter object.
|
||||
*
|
||||
* std::string formatted = format("{} {}", 23, 42).str();
|
||||
* LOG(INFO) << format("{} {}", 23, 42);
|
||||
*/
|
||||
template <class... Args>
|
||||
Formatter<false, Args...> format(StringPiece fmt, Args&&... args) {
|
||||
return Formatter<false, Args...>(
|
||||
fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a formatter object that takes one argument (of container type)
|
||||
* and uses that container to get argument values from.
|
||||
*
|
||||
* std::map<string, string> map { {"hello", "world"}, {"answer", "42"} };
|
||||
*
|
||||
* The following are equivalent:
|
||||
* format("{0[hello]} {0[answer]}", map);
|
||||
*
|
||||
* vformat("{hello} {answer}", map);
|
||||
*
|
||||
* but the latter is cleaner.
|
||||
*/
|
||||
template <class Container>
|
||||
Formatter<true, Container> vformat(StringPiece fmt, Container&& container) {
|
||||
return Formatter<true, Container>(
|
||||
fmt, std::forward<Container>(container));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append formatted output to a string.
|
||||
*
|
||||
* std::string foo;
|
||||
* format(&foo, "{} {}", 42, 23);
|
||||
*
|
||||
* Shortcut for toAppend(format(...), &foo);
|
||||
*/
|
||||
template <class Str, class... Args>
|
||||
typename std::enable_if<detail::IsSomeString<Str>::value>::type
|
||||
format(Str* out, StringPiece fmt, Args&&... args) {
|
||||
format(fmt, std::forward<Args>(args)...).appendTo(*out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append vformatted output to a string.
|
||||
*/
|
||||
template <class Str, class Container>
|
||||
typename std::enable_if<detail::IsSomeString<Str>::value>::type
|
||||
vformat(Str* out, StringPiece fmt, Container&& container) {
|
||||
vformat(fmt, std::forward<Container>(container)).appendTo(*out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilities for all format value specializations.
|
||||
*/
|
||||
namespace format_value {
|
||||
|
||||
/**
|
||||
* Format a string in "val", obeying appropriate alignment, padding, width,
|
||||
* and precision. Treats Align::DEFAULT as Align::LEFT, and
|
||||
* Align::PAD_AFTER_SIGN as Align::RIGHT; use formatNumber for
|
||||
* number-specific formatting.
|
||||
*/
|
||||
template <class FormatCallback>
|
||||
void formatString(StringPiece val, FormatArg& arg, FormatCallback& cb);
|
||||
|
||||
/**
|
||||
* Format a number in "val"; the first prefixLen characters form the prefix
|
||||
* (sign, "0x" base prefix, etc) which must be left-aligned if the alignment
|
||||
* is Align::PAD_AFTER_SIGN. Treats Align::DEFAULT as Align::LEFT. Ignores
|
||||
* arg.precision, as that has a different meaning for numbers (not "maximum
|
||||
* field width")
|
||||
*/
|
||||
template <class FormatCallback>
|
||||
void formatNumber(StringPiece val, int prefixLen, FormatArg& arg,
|
||||
FormatCallback& cb);
|
||||
|
||||
|
||||
/**
|
||||
* Format a Formatter object recursively. Behaves just like
|
||||
* formatString(fmt.str(), arg, cb); but avoids creating a temporary
|
||||
* string if possible.
|
||||
*/
|
||||
template <class FormatCallback, bool containerMode, class... Args>
|
||||
void formatFormatter(const Formatter<containerMode, Args...>& formatter,
|
||||
FormatArg& arg,
|
||||
FormatCallback& cb);
|
||||
|
||||
} // namespace format_value
|
||||
|
||||
/*
|
||||
* Specialize folly::FormatValue for your type.
|
||||
*
|
||||
* FormatValue<T> is constructed with a (reference-collapsed) T&&, which is
|
||||
* guaranteed to stay alive until the FormatValue object is destroyed, so you
|
||||
* may keep a reference (or pointer) to it instead of making a copy.
|
||||
*
|
||||
* You must define
|
||||
* template <class Callback>
|
||||
* void format(FormatArg& arg, Callback& cb) const;
|
||||
* with the following semantics: format the value using the given argument.
|
||||
*
|
||||
* arg is given by non-const reference for convenience -- it won't be reused,
|
||||
* so feel free to modify it in place if necessary. (For example, wrap an
|
||||
* existing conversion but change the default, or remove the "key" when
|
||||
* extracting an element from a container)
|
||||
*
|
||||
* Call the callback to append data to the output. You may call the callback
|
||||
* as many times as you'd like (or not at all, if you want to output an
|
||||
* empty string)
|
||||
*/
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#include "folly/Format-inl.h"
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif /* FOLLY_FORMAT_H_ */
|
||||
-256
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_FORMATARG_H_
|
||||
#define FOLLY_FORMATARG_H_
|
||||
|
||||
#include <stdexcept>
|
||||
#include "folly/Conv.h"
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/Portability.h"
|
||||
#include "folly/Range.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Parsed format argument.
|
||||
*/
|
||||
struct FormatArg {
|
||||
/**
|
||||
* Parse a format argument from a string. Keeps a reference to the
|
||||
* passed-in string -- does not copy the given characters.
|
||||
*/
|
||||
explicit FormatArg(StringPiece sp)
|
||||
: fullArgString(sp),
|
||||
fill(kDefaultFill),
|
||||
align(Align::DEFAULT),
|
||||
sign(Sign::DEFAULT),
|
||||
basePrefix(false),
|
||||
thousandsSeparator(false),
|
||||
width(kDefaultWidth),
|
||||
precision(kDefaultPrecision),
|
||||
presentation(kDefaultPresentation),
|
||||
nextKeyMode_(NextKeyMode::NONE) {
|
||||
if (!sp.empty()) {
|
||||
initSlow();
|
||||
}
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
INTEGER,
|
||||
FLOAT,
|
||||
OTHER
|
||||
};
|
||||
/**
|
||||
* Validate the argument for the given type; throws on error.
|
||||
*/
|
||||
void validate(Type type) const;
|
||||
|
||||
/**
|
||||
* Throw an exception if the first argument is false. The exception
|
||||
* message will contain the argument string as well as any passed-in
|
||||
* arguments to enforce, formatted using folly::to<std::string>.
|
||||
*/
|
||||
template <typename... Args>
|
||||
void enforce(bool v, Args&&... args) const {
|
||||
if (UNLIKELY(!v)) {
|
||||
error(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void error(Args&&... args) const FOLLY_NORETURN;
|
||||
/**
|
||||
* Full argument string, as passed in to the constructor.
|
||||
*/
|
||||
StringPiece fullArgString;
|
||||
|
||||
/**
|
||||
* Fill
|
||||
*/
|
||||
static constexpr char kDefaultFill = '\0';
|
||||
char fill;
|
||||
|
||||
/**
|
||||
* Alignment
|
||||
*/
|
||||
enum class Align : uint8_t {
|
||||
DEFAULT,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
PAD_AFTER_SIGN,
|
||||
CENTER,
|
||||
INVALID
|
||||
};
|
||||
Align align;
|
||||
|
||||
/**
|
||||
* Sign
|
||||
*/
|
||||
enum class Sign : uint8_t {
|
||||
DEFAULT,
|
||||
PLUS_OR_MINUS,
|
||||
MINUS,
|
||||
SPACE_OR_MINUS,
|
||||
INVALID
|
||||
};
|
||||
Sign sign;
|
||||
|
||||
/**
|
||||
* Output base prefix (0 for octal, 0x for hex)
|
||||
*/
|
||||
bool basePrefix;
|
||||
|
||||
/**
|
||||
* Output thousands separator (comma)
|
||||
*/
|
||||
bool thousandsSeparator;
|
||||
|
||||
/**
|
||||
* Field width
|
||||
*/
|
||||
static constexpr int kDefaultWidth = -1;
|
||||
int width;
|
||||
|
||||
/**
|
||||
* Precision
|
||||
*/
|
||||
static constexpr int kDefaultPrecision = -1;
|
||||
int precision;
|
||||
|
||||
/**
|
||||
* Presentation
|
||||
*/
|
||||
static constexpr char kDefaultPresentation = '\0';
|
||||
char presentation;
|
||||
|
||||
/**
|
||||
* Split a key component from "key", which must be non-empty (an exception
|
||||
* is thrown otherwise).
|
||||
*/
|
||||
template <bool emptyOk=false>
|
||||
StringPiece splitKey();
|
||||
|
||||
/**
|
||||
* Is the entire key empty?
|
||||
*/
|
||||
bool keyEmpty() const {
|
||||
return nextKeyMode_ == NextKeyMode::NONE && key_.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Split an key component from "key", which must be non-empty and a valid
|
||||
* integer (an exception is thrown otherwise).
|
||||
*/
|
||||
int splitIntKey();
|
||||
|
||||
void setNextIntKey(int val) {
|
||||
assert(nextKeyMode_ == NextKeyMode::NONE);
|
||||
nextKeyMode_ = NextKeyMode::INT;
|
||||
nextIntKey_ = val;
|
||||
}
|
||||
|
||||
void setNextKey(StringPiece val) {
|
||||
assert(nextKeyMode_ == NextKeyMode::NONE);
|
||||
nextKeyMode_ = NextKeyMode::STRING;
|
||||
nextKey_ = val;
|
||||
}
|
||||
|
||||
private:
|
||||
void initSlow();
|
||||
template <bool emptyOk>
|
||||
StringPiece doSplitKey();
|
||||
|
||||
StringPiece key_;
|
||||
int nextIntKey_;
|
||||
StringPiece nextKey_;
|
||||
enum class NextKeyMode {
|
||||
NONE,
|
||||
INT,
|
||||
STRING,
|
||||
};
|
||||
NextKeyMode nextKeyMode_;
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
inline void FormatArg::error(Args&&... args) const {
|
||||
throw std::invalid_argument(to<std::string>(
|
||||
"folly::format: invalid format argument {", fullArgString, "}: ",
|
||||
std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <bool emptyOk>
|
||||
inline StringPiece FormatArg::splitKey() {
|
||||
enforce(nextKeyMode_ != NextKeyMode::INT, "integer key expected");
|
||||
return doSplitKey<emptyOk>();
|
||||
}
|
||||
|
||||
template <bool emptyOk>
|
||||
inline StringPiece FormatArg::doSplitKey() {
|
||||
if (nextKeyMode_ == NextKeyMode::STRING) {
|
||||
nextKeyMode_ = NextKeyMode::NONE;
|
||||
if (!emptyOk) { // static
|
||||
enforce(!nextKey_.empty(), "non-empty key required");
|
||||
}
|
||||
return nextKey_;
|
||||
}
|
||||
|
||||
if (key_.empty()) {
|
||||
if (!emptyOk) { // static
|
||||
error("non-empty key required");
|
||||
}
|
||||
return StringPiece();
|
||||
}
|
||||
|
||||
const char* b = key_.begin();
|
||||
const char* e = key_.end();
|
||||
const char* p;
|
||||
if (e[-1] == ']') {
|
||||
--e;
|
||||
p = static_cast<const char*>(memchr(b, '[', e - b));
|
||||
enforce(p, "unmatched ']'");
|
||||
} else {
|
||||
p = static_cast<const char*>(memchr(b, '.', e - b));
|
||||
}
|
||||
if (p) {
|
||||
key_.assign(p + 1, e);
|
||||
} else {
|
||||
p = e;
|
||||
key_.clear();
|
||||
}
|
||||
if (!emptyOk) { // static
|
||||
enforce(b != p, "non-empty key required");
|
||||
}
|
||||
return StringPiece(b, p);
|
||||
}
|
||||
|
||||
inline int FormatArg::splitIntKey() {
|
||||
if (nextKeyMode_ == NextKeyMode::INT) {
|
||||
nextKeyMode_ = NextKeyMode::NONE;
|
||||
return nextIntKey_;
|
||||
}
|
||||
try {
|
||||
return to<int>(doSplitKey<true>());
|
||||
} catch (const std::out_of_range& e) {
|
||||
error("integer key required");
|
||||
return 0; // unreached
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_FORMATARG_H_ */
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/GroupVarint.h"
|
||||
|
||||
#if HAVE_GROUP_VARINT
|
||||
namespace folly {
|
||||
|
||||
const uint32_t GroupVarint32::kMask[] = {
|
||||
0xff, 0xffff, 0xffffff, 0xffffffff
|
||||
};
|
||||
|
||||
const uint64_t GroupVarint64::kMask[] = {
|
||||
0xff, 0xffff, 0xffffff, 0xffffffff,
|
||||
0xffffffffffULL, 0xffffffffffffULL, 0xffffffffffffffULL,
|
||||
0xffffffffffffffffULL
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
#endif
|
||||
-608
@@ -1,608 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_GROUPVARINT_H_
|
||||
#define FOLLY_GROUPVARINT_H_
|
||||
|
||||
#ifndef __GNUC__
|
||||
#error GroupVarint.h requires GCC
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
#define HAVE_GROUP_VARINT 1
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include "folly/detail/GroupVarintDetail.h"
|
||||
#include "folly/Bits.h"
|
||||
#include "folly/Range.h"
|
||||
#include <glog/logging.h>
|
||||
|
||||
#ifdef __SSSE3__
|
||||
#include <x86intrin.h>
|
||||
namespace folly {
|
||||
namespace detail {
|
||||
extern const __m128i groupVarintSSEMasks[];
|
||||
} // namespace detail
|
||||
} // namespace folly
|
||||
#endif
|
||||
|
||||
namespace folly {
|
||||
namespace detail {
|
||||
extern const uint8_t groupVarintLengths[];
|
||||
} // namespace detail
|
||||
} // namespace folly
|
||||
|
||||
namespace folly {
|
||||
|
||||
template <typename T>
|
||||
class GroupVarint;
|
||||
|
||||
/**
|
||||
* GroupVarint encoding for 32-bit values.
|
||||
*
|
||||
* Encodes 4 32-bit integers at once, each using 1-4 bytes depending on size.
|
||||
* There is one byte of overhead. (The first byte contains the lengths of
|
||||
* the four integers encoded as two bits each; 00=1 byte .. 11=4 bytes)
|
||||
*
|
||||
* This implementation assumes little-endian and does unaligned 32-bit
|
||||
* accesses, so it's basically not portable outside of the x86[_64] world.
|
||||
*/
|
||||
template <>
|
||||
class GroupVarint<uint32_t> : public detail::GroupVarintBase<uint32_t> {
|
||||
public:
|
||||
|
||||
/**
|
||||
* Return the number of bytes used to encode these four values.
|
||||
*/
|
||||
static size_t size(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
|
||||
return kHeaderSize + kGroupSize + key(a) + key(b) + key(c) + key(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of bytes used to encode four uint32_t values stored
|
||||
* at consecutive positions in an array.
|
||||
*/
|
||||
static size_t size(const uint32_t* p) {
|
||||
return size(p[0], p[1], p[2], p[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of bytes used to encode count (<= 4) values.
|
||||
* If you clip a buffer after these many bytes, you can still decode
|
||||
* the first "count" values correctly (if the remaining size() -
|
||||
* partialSize() bytes are filled with garbage).
|
||||
*/
|
||||
static size_t partialSize(const type* p, size_t count) {
|
||||
DCHECK_LE(count, kGroupSize);
|
||||
size_t s = kHeaderSize + count;
|
||||
for (; count; --count, ++p) {
|
||||
s += key(*p);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of values from *p that are valid from an encoded
|
||||
* buffer of size bytes.
|
||||
*/
|
||||
static size_t partialCount(const char* p, size_t size) {
|
||||
char v = *p;
|
||||
size_t s = kHeaderSize;
|
||||
s += 1 + b0key(v);
|
||||
if (s > size) return 0;
|
||||
s += 1 + b1key(v);
|
||||
if (s > size) return 1;
|
||||
s += 1 + b2key(v);
|
||||
if (s > size) return 2;
|
||||
s += 1 + b3key(v);
|
||||
if (s > size) return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a pointer to the beginning of an GroupVarint32-encoded block,
|
||||
* return the number of bytes used by the encoding.
|
||||
*/
|
||||
static size_t encodedSize(const char* p) {
|
||||
return (kHeaderSize + kGroupSize +
|
||||
b0key(*p) + b1key(*p) + b2key(*p) + b3key(*p));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode four uint32_t values into the buffer pointed-to by p, and return
|
||||
* the next position in the buffer (that is, one character past the last
|
||||
* encoded byte). p needs to have at least size()+4 bytes available.
|
||||
*/
|
||||
static char* encode(char* p, uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
|
||||
uint8_t b0key = key(a);
|
||||
uint8_t b1key = key(b);
|
||||
uint8_t b2key = key(c);
|
||||
uint8_t b3key = key(d);
|
||||
*p++ = (b3key << 6) | (b2key << 4) | (b1key << 2) | b0key;
|
||||
storeUnaligned(p, a);
|
||||
p += b0key+1;
|
||||
storeUnaligned(p, b);
|
||||
p += b1key+1;
|
||||
storeUnaligned(p, c);
|
||||
p += b2key+1;
|
||||
storeUnaligned(p, d);
|
||||
p += b3key+1;
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode four uint32_t values from the array pointed-to by src into the
|
||||
* buffer pointed-to by p, similar to encode(p,a,b,c,d) above.
|
||||
*/
|
||||
static char* encode(char* p, const uint32_t* src) {
|
||||
return encode(p, src[0], src[1], src[2], src[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode four uint32_t values from a buffer, and return the next position
|
||||
* in the buffer (that is, one character past the last encoded byte).
|
||||
* The buffer needs to have at least 3 extra bytes available (they
|
||||
* may be read but ignored).
|
||||
*/
|
||||
static const char* decode_simple(const char* p, uint32_t* a, uint32_t* b,
|
||||
uint32_t* c, uint32_t* d) {
|
||||
size_t k = loadUnaligned<uint8_t>(p);
|
||||
const char* end = p + detail::groupVarintLengths[k];
|
||||
++p;
|
||||
size_t k0 = b0key(k);
|
||||
*a = loadUnaligned<uint32_t>(p) & kMask[k0];
|
||||
p += k0+1;
|
||||
size_t k1 = b1key(k);
|
||||
*b = loadUnaligned<uint32_t>(p) & kMask[k1];
|
||||
p += k1+1;
|
||||
size_t k2 = b2key(k);
|
||||
*c = loadUnaligned<uint32_t>(p) & kMask[k2];
|
||||
p += k2+1;
|
||||
size_t k3 = b3key(k);
|
||||
*d = loadUnaligned<uint32_t>(p) & kMask[k3];
|
||||
p += k3+1;
|
||||
return end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode four uint32_t values from a buffer and store them in the array
|
||||
* pointed-to by dest, similar to decode(p,a,b,c,d) above.
|
||||
*/
|
||||
static const char* decode_simple(const char* p, uint32_t* dest) {
|
||||
return decode_simple(p, dest, dest+1, dest+2, dest+3);
|
||||
}
|
||||
|
||||
#ifdef __SSSE3__
|
||||
static const char* decode(const char* p, uint32_t* dest) {
|
||||
uint8_t key = p[0];
|
||||
__m128i val = _mm_loadu_si128((const __m128i*)(p+1));
|
||||
__m128i mask = detail::groupVarintSSEMasks[key];
|
||||
__m128i r = _mm_shuffle_epi8(val, mask);
|
||||
_mm_storeu_si128((__m128i*)dest, r);
|
||||
return p + detail::groupVarintLengths[key];
|
||||
}
|
||||
|
||||
static const char* decode(const char* p, uint32_t* a, uint32_t* b,
|
||||
uint32_t* c, uint32_t* d) {
|
||||
uint8_t key = p[0];
|
||||
__m128i val = _mm_loadu_si128((const __m128i*)(p+1));
|
||||
__m128i mask = detail::groupVarintSSEMasks[key];
|
||||
__m128i r = _mm_shuffle_epi8(val, mask);
|
||||
|
||||
// Extracting 32 bits at a time out of an XMM register is a SSE4 feature
|
||||
#ifdef __SSE4__
|
||||
*a = _mm_extract_epi32(r, 0);
|
||||
*b = _mm_extract_epi32(r, 1);
|
||||
*c = _mm_extract_epi32(r, 2);
|
||||
*d = _mm_extract_epi32(r, 3);
|
||||
#else /* !__SSE4__ */
|
||||
*a = _mm_extract_epi16(r, 0) + (_mm_extract_epi16(r, 1) << 16);
|
||||
*b = _mm_extract_epi16(r, 2) + (_mm_extract_epi16(r, 3) << 16);
|
||||
*c = _mm_extract_epi16(r, 4) + (_mm_extract_epi16(r, 5) << 16);
|
||||
*d = _mm_extract_epi16(r, 6) + (_mm_extract_epi16(r, 7) << 16);
|
||||
#endif /* __SSE4__ */
|
||||
|
||||
return p + detail::groupVarintLengths[key];
|
||||
}
|
||||
|
||||
#else /* !__SSSE3__ */
|
||||
static const char* decode(const char* p, uint32_t* a, uint32_t* b,
|
||||
uint32_t* c, uint32_t* d) {
|
||||
return decode_simple(p, a, b, c, d);
|
||||
}
|
||||
|
||||
static const char* decode(const char* p, uint32_t* dest) {
|
||||
return decode_simple(p, dest);
|
||||
}
|
||||
#endif /* __SSSE3__ */
|
||||
|
||||
private:
|
||||
static uint8_t key(uint32_t x) {
|
||||
// __builtin_clz is undefined for the x==0 case
|
||||
return 3 - (__builtin_clz(x|1) / 8);
|
||||
}
|
||||
static size_t b0key(size_t x) { return x & 3; }
|
||||
static size_t b1key(size_t x) { return (x >> 2) & 3; }
|
||||
static size_t b2key(size_t x) { return (x >> 4) & 3; }
|
||||
static size_t b3key(size_t x) { return (x >> 6) & 3; }
|
||||
|
||||
static const uint32_t kMask[];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* GroupVarint encoding for 64-bit values.
|
||||
*
|
||||
* Encodes 5 64-bit integers at once, each using 1-8 bytes depending on size.
|
||||
* There are two bytes of overhead. (The first two bytes contain the lengths
|
||||
* of the five integers encoded as three bits each; 000=1 byte .. 111 = 8 bytes)
|
||||
*
|
||||
* This implementation assumes little-endian and does unaligned 64-bit
|
||||
* accesses, so it's basically not portable outside of the x86[_64] world.
|
||||
*/
|
||||
template <>
|
||||
class GroupVarint<uint64_t> : public detail::GroupVarintBase<uint64_t> {
|
||||
public:
|
||||
/**
|
||||
* Return the number of bytes used to encode these five values.
|
||||
*/
|
||||
static size_t size(uint64_t a, uint64_t b, uint64_t c, uint64_t d,
|
||||
uint64_t e) {
|
||||
return (kHeaderSize + kGroupSize +
|
||||
key(a) + key(b) + key(c) + key(d) + key(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of bytes used to encode five uint64_t values stored
|
||||
* at consecutive positions in an array.
|
||||
*/
|
||||
static size_t size(const uint64_t* p) {
|
||||
return size(p[0], p[1], p[2], p[3], p[4]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of bytes used to encode count (<= 4) values.
|
||||
* If you clip a buffer after these many bytes, you can still decode
|
||||
* the first "count" values correctly (if the remaining size() -
|
||||
* partialSize() bytes are filled with garbage).
|
||||
*/
|
||||
static size_t partialSize(const type* p, size_t count) {
|
||||
DCHECK_LE(count, kGroupSize);
|
||||
size_t s = kHeaderSize + count;
|
||||
for (; count; --count, ++p) {
|
||||
s += key(*p);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of values from *p that are valid from an encoded
|
||||
* buffer of size bytes.
|
||||
*/
|
||||
static size_t partialCount(const char* p, size_t size) {
|
||||
uint16_t v = loadUnaligned<uint16_t>(p);
|
||||
size_t s = kHeaderSize;
|
||||
s += 1 + b0key(v);
|
||||
if (s > size) return 0;
|
||||
s += 1 + b1key(v);
|
||||
if (s > size) return 1;
|
||||
s += 1 + b2key(v);
|
||||
if (s > size) return 2;
|
||||
s += 1 + b3key(v);
|
||||
if (s > size) return 3;
|
||||
s += 1 + b4key(v);
|
||||
if (s > size) return 4;
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a pointer to the beginning of an GroupVarint64-encoded block,
|
||||
* return the number of bytes used by the encoding.
|
||||
*/
|
||||
static size_t encodedSize(const char* p) {
|
||||
uint16_t n = loadUnaligned<uint16_t>(p);
|
||||
return (kHeaderSize + kGroupSize +
|
||||
b0key(n) + b1key(n) + b2key(n) + b3key(n) + b4key(n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode five uint64_t values into the buffer pointed-to by p, and return
|
||||
* the next position in the buffer (that is, one character past the last
|
||||
* encoded byte). p needs to have at least size()+8 bytes available.
|
||||
*/
|
||||
static char* encode(char* p, uint64_t a, uint64_t b, uint64_t c,
|
||||
uint64_t d, uint64_t e) {
|
||||
uint8_t b0key = key(a);
|
||||
uint8_t b1key = key(b);
|
||||
uint8_t b2key = key(c);
|
||||
uint8_t b3key = key(d);
|
||||
uint8_t b4key = key(e);
|
||||
storeUnaligned<uint16_t>(
|
||||
p,
|
||||
(b4key << 12) | (b3key << 9) | (b2key << 6) | (b1key << 3) | b0key);
|
||||
p += 2;
|
||||
storeUnaligned(p, a);
|
||||
p += b0key+1;
|
||||
storeUnaligned(p, b);
|
||||
p += b1key+1;
|
||||
storeUnaligned(p, c);
|
||||
p += b2key+1;
|
||||
storeUnaligned(p, d);
|
||||
p += b3key+1;
|
||||
storeUnaligned(p, e);
|
||||
p += b4key+1;
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode five uint64_t values from the array pointed-to by src into the
|
||||
* buffer pointed-to by p, similar to encode(p,a,b,c,d,e) above.
|
||||
*/
|
||||
static char* encode(char* p, const uint64_t* src) {
|
||||
return encode(p, src[0], src[1], src[2], src[3], src[4]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode five uint64_t values from a buffer, and return the next position
|
||||
* in the buffer (that is, one character past the last encoded byte).
|
||||
* The buffer needs to have at least 7 bytes available (they may be read
|
||||
* but ignored).
|
||||
*/
|
||||
static const char* decode(const char* p, uint64_t* a, uint64_t* b,
|
||||
uint64_t* c, uint64_t* d, uint64_t* e) {
|
||||
uint16_t k = loadUnaligned<uint16_t>(p);
|
||||
p += 2;
|
||||
uint8_t k0 = b0key(k);
|
||||
*a = loadUnaligned<uint64_t>(p) & kMask[k0];
|
||||
p += k0+1;
|
||||
uint8_t k1 = b1key(k);
|
||||
*b = loadUnaligned<uint64_t>(p) & kMask[k1];
|
||||
p += k1+1;
|
||||
uint8_t k2 = b2key(k);
|
||||
*c = loadUnaligned<uint64_t>(p) & kMask[k2];
|
||||
p += k2+1;
|
||||
uint8_t k3 = b3key(k);
|
||||
*d = loadUnaligned<uint64_t>(p) & kMask[k3];
|
||||
p += k3+1;
|
||||
uint8_t k4 = b4key(k);
|
||||
*e = loadUnaligned<uint64_t>(p) & kMask[k4];
|
||||
p += k4+1;
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode five uint64_t values from a buffer and store them in the array
|
||||
* pointed-to by dest, similar to decode(p,a,b,c,d,e) above.
|
||||
*/
|
||||
static const char* decode(const char* p, uint64_t* dest) {
|
||||
return decode(p, dest, dest+1, dest+2, dest+3, dest+4);
|
||||
}
|
||||
|
||||
private:
|
||||
enum { kHeaderBytes = 2 };
|
||||
|
||||
static uint8_t key(uint64_t x) {
|
||||
// __builtin_clzll is undefined for the x==0 case
|
||||
return 7 - (__builtin_clzll(x|1) / 8);
|
||||
}
|
||||
|
||||
static uint8_t b0key(uint16_t x) { return x & 7; }
|
||||
static uint8_t b1key(uint16_t x) { return (x >> 3) & 7; }
|
||||
static uint8_t b2key(uint16_t x) { return (x >> 6) & 7; }
|
||||
static uint8_t b3key(uint16_t x) { return (x >> 9) & 7; }
|
||||
static uint8_t b4key(uint16_t x) { return (x >> 12) & 7; }
|
||||
|
||||
static const uint64_t kMask[];
|
||||
};
|
||||
|
||||
typedef GroupVarint<uint32_t> GroupVarint32;
|
||||
typedef GroupVarint<uint64_t> GroupVarint64;
|
||||
|
||||
/**
|
||||
* Simplify use of GroupVarint* for the case where data is available one
|
||||
* entry at a time (instead of one group at a time). Handles buffering
|
||||
* and an incomplete last chunk.
|
||||
*
|
||||
* Output is a function object that accepts character ranges:
|
||||
* out(StringPiece) appends the given character range to the output.
|
||||
*/
|
||||
template <class T, class Output>
|
||||
class GroupVarintEncoder {
|
||||
public:
|
||||
typedef GroupVarint<T> Base;
|
||||
typedef T type;
|
||||
|
||||
explicit GroupVarintEncoder(Output out)
|
||||
: out_(out),
|
||||
count_(0) {
|
||||
}
|
||||
|
||||
~GroupVarintEncoder() {
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value to the encoder.
|
||||
*/
|
||||
void add(type val) {
|
||||
buf_[count_++] = val;
|
||||
if (count_ == Base::kGroupSize) {
|
||||
char* p = Base::encode(tmp_, buf_);
|
||||
out_(StringPiece(tmp_, p));
|
||||
count_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish encoding, flushing any buffered values if necessary.
|
||||
* After finish(), the encoder is immediately ready to encode more data
|
||||
* to the same output.
|
||||
*/
|
||||
void finish() {
|
||||
if (count_) {
|
||||
// This is not strictly necessary, but it makes testing easy;
|
||||
// uninitialized bytes are guaranteed to be recorded as taking one byte
|
||||
// (not more).
|
||||
for (size_t i = count_; i < Base::kGroupSize; i++) {
|
||||
buf_[i] = 0;
|
||||
}
|
||||
Base::encode(tmp_, buf_);
|
||||
out_(StringPiece(tmp_, Base::partialSize(buf_, count_)));
|
||||
count_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the appender that was used.
|
||||
*/
|
||||
Output& output() {
|
||||
return out_;
|
||||
}
|
||||
const Output& output() const {
|
||||
return out_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the encoder, disregarding any state (except what was already
|
||||
* flushed to the output, of course).
|
||||
*/
|
||||
void clear() {
|
||||
count_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
Output out_;
|
||||
char tmp_[Base::kMaxSize];
|
||||
type buf_[Base::kGroupSize];
|
||||
size_t count_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Simplify use of GroupVarint* for the case where the last group in the
|
||||
* input may be incomplete (but the exact size of the input is known).
|
||||
* Allows for extracting values one at a time.
|
||||
*/
|
||||
template <typename T>
|
||||
class GroupVarintDecoder {
|
||||
public:
|
||||
typedef GroupVarint<T> Base;
|
||||
typedef T type;
|
||||
|
||||
GroupVarintDecoder() { }
|
||||
|
||||
explicit GroupVarintDecoder(StringPiece data,
|
||||
size_t maxCount = (size_t)-1)
|
||||
: rrest_(data.end()),
|
||||
p_(data.data()),
|
||||
end_(data.end()),
|
||||
pos_(0),
|
||||
count_(0),
|
||||
remaining_(maxCount) {
|
||||
}
|
||||
|
||||
void reset(StringPiece data, size_t maxCount=(size_t)-1) {
|
||||
rrest_ = data.end();
|
||||
p_ = data.data();
|
||||
end_ = data.end();
|
||||
pos_ = 0;
|
||||
count_ = 0;
|
||||
remaining_ = maxCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the next value.
|
||||
*/
|
||||
bool next(type* val) {
|
||||
if (pos_ == count_) {
|
||||
// refill
|
||||
size_t rem = end_ - p_;
|
||||
if (rem == 0 || remaining_ == 0) {
|
||||
return false;
|
||||
}
|
||||
// next() attempts to read one full group at a time, and so we must have
|
||||
// at least enough bytes readable after its end to handle the case if the
|
||||
// last group is full.
|
||||
//
|
||||
// The best way to ensure this is to ensure that data has at least
|
||||
// Base::kMaxSize - 1 bytes readable *after* the end, otherwise we'll copy
|
||||
// into a temporary buffer.
|
||||
if (rem < Base::kMaxSize) {
|
||||
memcpy(tmp_, p_, rem);
|
||||
p_ = tmp_;
|
||||
end_ = p_ + rem;
|
||||
}
|
||||
pos_ = 0;
|
||||
const char* n = Base::decode(p_, buf_);
|
||||
if (n <= end_) {
|
||||
// Full group could be decoded
|
||||
if (remaining_ >= Base::kGroupSize) {
|
||||
remaining_ -= Base::kGroupSize;
|
||||
count_ = Base::kGroupSize;
|
||||
p_ = n;
|
||||
} else {
|
||||
count_ = remaining_;
|
||||
remaining_ = 0;
|
||||
p_ += Base::partialSize(buf_, count_);
|
||||
}
|
||||
} else {
|
||||
// Can't decode a full group
|
||||
count_ = Base::partialCount(p_, end_ - p_);
|
||||
if (remaining_ >= count_) {
|
||||
remaining_ -= count_;
|
||||
p_ = end_;
|
||||
} else {
|
||||
count_ = remaining_;
|
||||
remaining_ = 0;
|
||||
p_ += Base::partialSize(buf_, count_);
|
||||
}
|
||||
if (count_ == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
*val = buf_[pos_++];
|
||||
return true;
|
||||
}
|
||||
|
||||
StringPiece rest() const {
|
||||
// This is only valid after next() returned false
|
||||
CHECK(pos_ == count_ && (p_ == end_ || remaining_ == 0));
|
||||
// p_ may point to the internal buffer (tmp_), but we want
|
||||
// to return subpiece of the original data
|
||||
size_t size = end_ - p_;
|
||||
return StringPiece(rrest_ - size, rrest_);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* rrest_;
|
||||
const char* p_;
|
||||
const char* end_;
|
||||
char tmp_[Base::kMaxSize];
|
||||
type buf_[Base::kGroupSize];
|
||||
size_t pos_;
|
||||
size_t count_;
|
||||
size_t remaining_;
|
||||
};
|
||||
|
||||
typedef GroupVarintDecoder<uint32_t> GroupVarint32Decoder;
|
||||
typedef GroupVarintDecoder<uint64_t> GroupVarint64Decoder;
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* defined(__x86_64__) || defined(__i386__) */
|
||||
#endif /* FOLLY_GROUPVARINT_H_ */
|
||||
|
||||
-399
@@ -1,399 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_BASE_HASH_H_
|
||||
#define FOLLY_BASE_HASH_H_
|
||||
|
||||
#include <cstring>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
|
||||
#include "folly/SpookyHashV1.h"
|
||||
#include "folly/SpookyHashV2.h"
|
||||
|
||||
/*
|
||||
* Various hashing functions.
|
||||
*/
|
||||
|
||||
namespace folly { namespace hash {
|
||||
|
||||
// This is a general-purpose way to create a single hash from multiple
|
||||
// hashable objects. hash_combine_generic takes a class Hasher implementing
|
||||
// hash<T>; hash_combine uses a default hasher StdHasher that uses std::hash.
|
||||
// hash_combine_generic hashes each argument and combines those hashes in
|
||||
// an order-dependent way to yield a new hash.
|
||||
|
||||
|
||||
// This is the Hash128to64 function from Google's cityhash (available
|
||||
// under the MIT License). We use it to reduce multiple 64 bit hashes
|
||||
// into a single hash.
|
||||
inline size_t hash_128_to_64(const size_t upper, const size_t lower) {
|
||||
// Murmur-inspired hashing.
|
||||
const size_t kMul = 0x9ddfea08eb382d69ULL;
|
||||
size_t a = (lower ^ upper) * kMul;
|
||||
a ^= (a >> 47);
|
||||
size_t b = (upper ^ a) * kMul;
|
||||
b ^= (b >> 47);
|
||||
b *= kMul;
|
||||
return b;
|
||||
}
|
||||
|
||||
// Never used, but gcc demands it.
|
||||
template <class Hasher>
|
||||
inline size_t hash_combine_generic() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <class Hasher, typename T, typename... Ts>
|
||||
size_t hash_combine_generic(const T& t, const Ts&... ts) {
|
||||
size_t seed = Hasher::hash(t);
|
||||
if (sizeof...(ts) == 0) {
|
||||
return seed;
|
||||
}
|
||||
size_t remainder = hash_combine_generic<Hasher>(ts...);
|
||||
return hash_128_to_64(seed, remainder);
|
||||
}
|
||||
|
||||
// Simply uses std::hash to hash. Note that std::hash is not guaranteed
|
||||
// to be a very good hash function; provided std::hash doesn't collide on
|
||||
// the individual inputs, you are fine, but that won't be true for, say,
|
||||
// strings or pairs
|
||||
class StdHasher {
|
||||
public:
|
||||
template <typename T>
|
||||
static size_t hash(const T& t) {
|
||||
return std::hash<T>()(t);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
size_t hash_combine(const T& t, const Ts&... ts) {
|
||||
return hash_combine_generic<StdHasher>(t, ts...);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* Thomas Wang 64 bit mix hash function
|
||||
*/
|
||||
|
||||
inline uint64_t twang_mix64(uint64_t key) {
|
||||
key = (~key) + (key << 21); // key *= (1 << 21) - 1; key -= 1;
|
||||
key = key ^ (key >> 24);
|
||||
key = key + (key << 3) + (key << 8); // key *= 1 + (1 << 3) + (1 << 8)
|
||||
key = key ^ (key >> 14);
|
||||
key = key + (key << 2) + (key << 4); // key *= 1 + (1 << 2) + (1 << 4)
|
||||
key = key ^ (key >> 28);
|
||||
key = key + (key << 31); // key *= 1 + (1 << 31)
|
||||
return key;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inverse of twang_mix64
|
||||
*
|
||||
* Note that twang_unmix64 is significantly slower than twang_mix64.
|
||||
*/
|
||||
|
||||
inline uint64_t twang_unmix64(uint64_t key) {
|
||||
// See the comments in jenkins_rev_unmix32 for an explanation as to how this
|
||||
// was generated
|
||||
key *= 4611686016279904257U;
|
||||
key ^= (key >> 28) ^ (key >> 56);
|
||||
key *= 14933078535860113213U;
|
||||
key ^= (key >> 14) ^ (key >> 28) ^ (key >> 42) ^ (key >> 56);
|
||||
key *= 15244667743933553977U;
|
||||
key ^= (key >> 24) ^ (key >> 48);
|
||||
key = (key + 1) * 9223367638806167551U;
|
||||
return key;
|
||||
}
|
||||
|
||||
/*
|
||||
* Thomas Wang downscaling hash function
|
||||
*/
|
||||
|
||||
inline uint32_t twang_32from64(uint64_t key) {
|
||||
key = (~key) + (key << 18);
|
||||
key = key ^ (key >> 31);
|
||||
key = key * 21;
|
||||
key = key ^ (key >> 11);
|
||||
key = key + (key << 6);
|
||||
key = key ^ (key >> 22);
|
||||
return (uint32_t) key;
|
||||
}
|
||||
|
||||
/*
|
||||
* Robert Jenkins' reversible 32 bit mix hash function
|
||||
*/
|
||||
|
||||
inline uint32_t jenkins_rev_mix32(uint32_t key) {
|
||||
key += (key << 12); // key *= (1 + (1 << 12))
|
||||
key ^= (key >> 22);
|
||||
key += (key << 4); // key *= (1 + (1 << 4))
|
||||
key ^= (key >> 9);
|
||||
key += (key << 10); // key *= (1 + (1 << 10))
|
||||
key ^= (key >> 2);
|
||||
// key *= (1 + (1 << 7)) * (1 + (1 << 12))
|
||||
key += (key << 7);
|
||||
key += (key << 12);
|
||||
return key;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inverse of jenkins_rev_mix32
|
||||
*
|
||||
* Note that jenkinks_rev_unmix32 is significantly slower than
|
||||
* jenkins_rev_mix32.
|
||||
*/
|
||||
|
||||
inline uint32_t jenkins_rev_unmix32(uint32_t key) {
|
||||
// These are the modular multiplicative inverses (in Z_2^32) of the
|
||||
// multiplication factors in jenkins_rev_mix32, in reverse order. They were
|
||||
// computed using the Extended Euclidean algorithm, see
|
||||
// http://en.wikipedia.org/wiki/Modular_multiplicative_inverse
|
||||
key *= 2364026753U;
|
||||
|
||||
// The inverse of a ^= (a >> n) is
|
||||
// b = a
|
||||
// for (int i = n; i < 32; i += n) {
|
||||
// b ^= (a >> i);
|
||||
// }
|
||||
key ^=
|
||||
(key >> 2) ^ (key >> 4) ^ (key >> 6) ^ (key >> 8) ^
|
||||
(key >> 10) ^ (key >> 12) ^ (key >> 14) ^ (key >> 16) ^
|
||||
(key >> 18) ^ (key >> 20) ^ (key >> 22) ^ (key >> 24) ^
|
||||
(key >> 26) ^ (key >> 28) ^ (key >> 30);
|
||||
key *= 3222273025U;
|
||||
key ^= (key >> 9) ^ (key >> 18) ^ (key >> 27);
|
||||
key *= 4042322161U;
|
||||
key ^= (key >> 22);
|
||||
key *= 16773121U;
|
||||
return key;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fowler / Noll / Vo (FNV) Hash
|
||||
* http://www.isthe.com/chongo/tech/comp/fnv/
|
||||
*/
|
||||
|
||||
const uint32_t FNV_32_HASH_START = 216613626UL;
|
||||
const uint64_t FNV_64_HASH_START = 14695981039346656037ULL;
|
||||
|
||||
inline uint32_t fnv32(const char* s,
|
||||
uint32_t hash = FNV_32_HASH_START) {
|
||||
for (; *s; ++s) {
|
||||
hash += (hash << 1) + (hash << 4) + (hash << 7) +
|
||||
(hash << 8) + (hash << 24);
|
||||
hash ^= *s;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t fnv32_buf(const void* buf,
|
||||
int n,
|
||||
uint32_t hash = FNV_32_HASH_START) {
|
||||
const char* char_buf = reinterpret_cast<const char*>(buf);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
hash += (hash << 1) + (hash << 4) + (hash << 7) +
|
||||
(hash << 8) + (hash << 24);
|
||||
hash ^= char_buf[i];
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t fnv32(const std::string& str,
|
||||
uint64_t hash = FNV_32_HASH_START) {
|
||||
return fnv32_buf(str.data(), str.size(), hash);
|
||||
}
|
||||
|
||||
inline uint64_t fnv64(const char* s,
|
||||
uint64_t hash = FNV_64_HASH_START) {
|
||||
for (; *s; ++s) {
|
||||
hash += (hash << 1) + (hash << 4) + (hash << 5) + (hash << 7) +
|
||||
(hash << 8) + (hash << 40);
|
||||
hash ^= *s;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint64_t fnv64_buf(const void* buf,
|
||||
int n,
|
||||
uint64_t hash = FNV_64_HASH_START) {
|
||||
const char* char_buf = reinterpret_cast<const char*>(buf);
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
hash += (hash << 1) + (hash << 4) + (hash << 5) + (hash << 7) +
|
||||
(hash << 8) + (hash << 40);
|
||||
hash ^= char_buf[i];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint64_t fnv64(const std::string& str,
|
||||
uint64_t hash = FNV_64_HASH_START) {
|
||||
return fnv64_buf(str.data(), str.size(), hash);
|
||||
}
|
||||
|
||||
/*
|
||||
* Paul Hsieh: http://www.azillionmonkeys.com/qed/hash.html
|
||||
*/
|
||||
|
||||
#define get16bits(d) (*((const uint16_t*) (d)))
|
||||
|
||||
inline uint32_t hsieh_hash32_buf(const void* buf, int len) {
|
||||
const char* s = reinterpret_cast<const char*>(buf);
|
||||
uint32_t hash = len;
|
||||
uint32_t tmp;
|
||||
int rem;
|
||||
|
||||
if (len <= 0 || buf == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
rem = len & 3;
|
||||
len >>= 2;
|
||||
|
||||
/* Main loop */
|
||||
for (;len > 0; len--) {
|
||||
hash += get16bits (s);
|
||||
tmp = (get16bits (s+2) << 11) ^ hash;
|
||||
hash = (hash << 16) ^ tmp;
|
||||
s += 2*sizeof (uint16_t);
|
||||
hash += hash >> 11;
|
||||
}
|
||||
|
||||
/* Handle end cases */
|
||||
switch (rem) {
|
||||
case 3:
|
||||
hash += get16bits(s);
|
||||
hash ^= hash << 16;
|
||||
hash ^= s[sizeof (uint16_t)] << 18;
|
||||
hash += hash >> 11;
|
||||
break;
|
||||
case 2:
|
||||
hash += get16bits(s);
|
||||
hash ^= hash << 11;
|
||||
hash += hash >> 17;
|
||||
break;
|
||||
case 1:
|
||||
hash += *s;
|
||||
hash ^= hash << 10;
|
||||
hash += hash >> 1;
|
||||
}
|
||||
|
||||
/* Force "avalanching" of final 127 bits */
|
||||
hash ^= hash << 3;
|
||||
hash += hash >> 5;
|
||||
hash ^= hash << 4;
|
||||
hash += hash >> 17;
|
||||
hash ^= hash << 25;
|
||||
hash += hash >> 6;
|
||||
|
||||
return hash;
|
||||
};
|
||||
|
||||
#undef get16bits
|
||||
|
||||
inline uint32_t hsieh_hash32(const char* s) {
|
||||
return hsieh_hash32_buf(s, std::strlen(s));
|
||||
}
|
||||
|
||||
inline uint32_t hsieh_hash32_str(const std::string& str) {
|
||||
return hsieh_hash32_buf(str.data(), str.size());
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // namespace hash
|
||||
|
||||
template<class Key>
|
||||
struct hasher;
|
||||
|
||||
template<> struct hasher<int32_t> {
|
||||
size_t operator()(int32_t key) const {
|
||||
return hash::jenkins_rev_mix32(uint32_t(key));
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct hasher<uint32_t> {
|
||||
size_t operator()(uint32_t key) const {
|
||||
return hash::jenkins_rev_mix32(key);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct hasher<int64_t> {
|
||||
size_t operator()(int64_t key) const {
|
||||
return hash::twang_mix64(uint64_t(key));
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct hasher<uint64_t> {
|
||||
size_t operator()(uint64_t key) const {
|
||||
return hash::twang_mix64(key);
|
||||
}
|
||||
};
|
||||
|
||||
// recursion
|
||||
template <size_t index, typename... Ts>
|
||||
struct TupleHasher {
|
||||
size_t operator()(std::tuple<Ts...> const& key) const {
|
||||
return hash::hash_combine(
|
||||
TupleHasher<index - 1, Ts...>()(key),
|
||||
std::get<index>(key));
|
||||
}
|
||||
};
|
||||
|
||||
// base
|
||||
template <typename... Ts>
|
||||
struct TupleHasher<0, Ts...> {
|
||||
size_t operator()(std::tuple<Ts...> const& key) const {
|
||||
// we could do std::hash here directly, but hash_combine hides all the
|
||||
// ugly templating implicitly
|
||||
return hash::hash_combine(std::get<0>(key));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
// Custom hash functions.
|
||||
namespace std {
|
||||
// Hash function for pairs. Requires default hash functions for both
|
||||
// items in the pair.
|
||||
template <typename T1, typename T2>
|
||||
class hash<std::pair<T1, T2> > {
|
||||
public:
|
||||
size_t operator()(const std::pair<T1, T2>& x) const {
|
||||
return folly::hash::hash_combine(x.first, x.second);
|
||||
}
|
||||
};
|
||||
|
||||
// Hash function for tuples. Requires default hash functions for all types.
|
||||
template <typename... Ts>
|
||||
struct hash<std::tuple<Ts...>> {
|
||||
size_t operator()(std::tuple<Ts...> const& key) const {
|
||||
folly::TupleHasher<
|
||||
std::tuple_size<std::tuple<Ts...>>::value - 1, // start index
|
||||
Ts...> hasher;
|
||||
|
||||
return hasher(key);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
#endif
|
||||
-269
@@ -1,269 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_HISTOGRAM_INL_H_
|
||||
#define FOLLY_HISTOGRAM_INL_H_
|
||||
|
||||
#include "folly/Conv.h"
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename BucketT>
|
||||
HistogramBuckets<T, BucketT>::HistogramBuckets(ValueType bucketSize,
|
||||
ValueType min,
|
||||
ValueType max,
|
||||
const BucketType& defaultBucket)
|
||||
: bucketSize_(bucketSize),
|
||||
min_(min),
|
||||
max_(max) {
|
||||
CHECK_GT(bucketSize_, ValueType(0));
|
||||
CHECK_LT(min_, max_);
|
||||
|
||||
unsigned int numBuckets = (max - min) / bucketSize;
|
||||
// Round up if the bucket size does not fit evenly
|
||||
if (numBuckets * bucketSize < max - min) {
|
||||
++numBuckets;
|
||||
}
|
||||
// Add 2 for the extra 'below min' and 'above max' buckets
|
||||
numBuckets += 2;
|
||||
buckets_.assign(numBuckets, defaultBucket);
|
||||
}
|
||||
|
||||
template <typename T, typename BucketType>
|
||||
unsigned int HistogramBuckets<T, BucketType>::getBucketIdx(
|
||||
ValueType value) const {
|
||||
if (value < min_) {
|
||||
return 0;
|
||||
} else if (value >= max_) {
|
||||
return buckets_.size() - 1;
|
||||
} else {
|
||||
// the 1 is the below_min bucket
|
||||
return ((value - min_) / bucketSize_) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename BucketType>
|
||||
template <typename CountFn>
|
||||
unsigned int HistogramBuckets<T, BucketType>::getPercentileBucketIdx(
|
||||
double pct,
|
||||
CountFn countFromBucket,
|
||||
double* lowPct, double* highPct) const {
|
||||
CHECK_GE(pct, 0.0);
|
||||
CHECK_LE(pct, 1.0);
|
||||
|
||||
unsigned int numBuckets = buckets_.size();
|
||||
|
||||
// Compute the counts in each bucket
|
||||
std::vector<uint64_t> counts(numBuckets);
|
||||
uint64_t totalCount = 0;
|
||||
for (unsigned int n = 0; n < numBuckets; ++n) {
|
||||
uint64_t bucketCount =
|
||||
countFromBucket(const_cast<const BucketType&>(buckets_[n]));
|
||||
counts[n] = bucketCount;
|
||||
totalCount += bucketCount;
|
||||
}
|
||||
|
||||
// If there are no elements, just return the lowest bucket.
|
||||
// Note that we return bucket 1, which is the first bucket in the
|
||||
// histogram range; bucket 0 is for all values below min_.
|
||||
if (totalCount == 0) {
|
||||
// Set lowPct and highPct both to 0.
|
||||
// getPercentileEstimate() will recognize this to mean that the histogram
|
||||
// is empty.
|
||||
if (lowPct) {
|
||||
*lowPct = 0.0;
|
||||
}
|
||||
if (highPct) {
|
||||
*highPct = 0.0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Loop through all the buckets, keeping track of each bucket's
|
||||
// percentile range: [0,10], [10,17], [17,45], etc. When we find a range
|
||||
// that includes our desired percentile, we return that bucket index.
|
||||
double prevPct = 0.0;
|
||||
double curPct = 0.0;
|
||||
uint64_t curCount = 0;
|
||||
unsigned int idx;
|
||||
for (idx = 0; idx < numBuckets; ++idx) {
|
||||
if (counts[idx] == 0) {
|
||||
// skip empty buckets
|
||||
continue;
|
||||
}
|
||||
|
||||
prevPct = curPct;
|
||||
curCount += counts[idx];
|
||||
curPct = static_cast<double>(curCount) / totalCount;
|
||||
if (pct <= curPct) {
|
||||
// This is the desired bucket
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lowPct) {
|
||||
*lowPct = prevPct;
|
||||
}
|
||||
if (highPct) {
|
||||
*highPct = curPct;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
template <typename T, typename BucketType>
|
||||
template <typename CountFn, typename AvgFn>
|
||||
T HistogramBuckets<T, BucketType>::getPercentileEstimate(
|
||||
double pct,
|
||||
CountFn countFromBucket,
|
||||
AvgFn avgFromBucket) const {
|
||||
|
||||
// Find the bucket where this percentile falls
|
||||
double lowPct;
|
||||
double highPct;
|
||||
unsigned int bucketIdx = getPercentileBucketIdx(pct, countFromBucket,
|
||||
&lowPct, &highPct);
|
||||
if (lowPct == 0.0 && highPct == 0.0) {
|
||||
// Invalid range -- the buckets must all be empty
|
||||
// Return the default value for ValueType.
|
||||
return ValueType();
|
||||
}
|
||||
if (lowPct == highPct) {
|
||||
// Unlikely to have exact equality,
|
||||
// but just return the bucket average in this case.
|
||||
// We handle this here to avoid division by 0 below.
|
||||
return avgFromBucket(buckets_[bucketIdx]);
|
||||
}
|
||||
|
||||
CHECK_GE(pct, lowPct);
|
||||
CHECK_LE(pct, highPct);
|
||||
CHECK_LT(lowPct, highPct);
|
||||
|
||||
// Compute information about this bucket
|
||||
ValueType avg = avgFromBucket(buckets_[bucketIdx]);
|
||||
ValueType low;
|
||||
ValueType high;
|
||||
if (bucketIdx == 0) {
|
||||
if (avg > min_) {
|
||||
// This normally shouldn't happen. This bucket is only supposed to track
|
||||
// values less than min_. Most likely this means that integer overflow
|
||||
// occurred, and the code in avgFromBucket() returned a huge value
|
||||
// instead of a small one. Just return the minimum possible value for
|
||||
// now.
|
||||
//
|
||||
// (Note that if the counter keeps being decremented, eventually it will
|
||||
// wrap and become small enough that we won't detect this any more, and
|
||||
// we will return bogus information.)
|
||||
LOG(ERROR) << "invalid average value in histogram minimum bucket: " <<
|
||||
avg << " > " << min_ << ": possible integer overflow?";
|
||||
return getBucketMin(bucketIdx);
|
||||
}
|
||||
// For the below-min bucket, just assume the lowest value ever seen is
|
||||
// twice as far away from min_ as avg.
|
||||
high = min_;
|
||||
low = high - (2 * (high - avg));
|
||||
// Adjust low in case it wrapped
|
||||
if (low > avg) {
|
||||
low = std::numeric_limits<ValueType>::min();
|
||||
}
|
||||
} else if (bucketIdx == buckets_.size() - 1) {
|
||||
if (avg < max_) {
|
||||
// Most likely this means integer overflow occurred. See the comments
|
||||
// above in the minimum case.
|
||||
LOG(ERROR) << "invalid average value in histogram maximum bucket: " <<
|
||||
avg << " < " << max_ << ": possible integer overflow?";
|
||||
return getBucketMax(bucketIdx);
|
||||
}
|
||||
// Similarly for the above-max bucket, assume the highest value ever seen
|
||||
// is twice as far away from max_ as avg.
|
||||
low = max_;
|
||||
high = low + (2 * (avg - low));
|
||||
// Adjust high in case it wrapped
|
||||
if (high < avg) {
|
||||
high = std::numeric_limits<ValueType>::max();
|
||||
}
|
||||
} else {
|
||||
low = getBucketMin(bucketIdx);
|
||||
high = getBucketMax(bucketIdx);
|
||||
if (avg < low || avg > high) {
|
||||
// Most likely this means an integer overflow occurred.
|
||||
// See the comments above. Return the midpoint between low and high
|
||||
// as a best guess, since avg is meaningless.
|
||||
LOG(ERROR) << "invalid average value in histogram bucket: " <<
|
||||
avg << " not in range [" << low << ", " << high <<
|
||||
"]: possible integer overflow?";
|
||||
return (low + high) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Since we know the average value in this bucket, we can do slightly better
|
||||
// than just assuming the data points in this bucket are uniformly
|
||||
// distributed between low and high.
|
||||
//
|
||||
// Assume that the median value in this bucket is the same as the average
|
||||
// value.
|
||||
double medianPct = (lowPct + highPct) / 2.0;
|
||||
if (pct < medianPct) {
|
||||
// Assume that the data points lower than the median of this bucket
|
||||
// are uniformly distributed between low and avg
|
||||
double pctThroughSection = (pct - lowPct) / (medianPct - lowPct);
|
||||
return low + ((avg - low) * pctThroughSection);
|
||||
} else {
|
||||
// Assume that the data points greater than the median of this bucket
|
||||
// are uniformly distributed between avg and high
|
||||
double pctThroughSection = (pct - medianPct) / (highPct - medianPct);
|
||||
return avg + ((high - avg) * pctThroughSection);
|
||||
}
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
|
||||
template <typename T>
|
||||
std::string Histogram<T>::debugString() const {
|
||||
std::string ret = folly::to<std::string>(
|
||||
"num buckets: ", buckets_.getNumBuckets(),
|
||||
", bucketSize: ", buckets_.getBucketSize(),
|
||||
", min: ", buckets_.getMin(), ", max: ", buckets_.getMax(), "\n");
|
||||
|
||||
for (unsigned int i = 0; i < buckets_.getNumBuckets(); ++i) {
|
||||
folly::toAppend(" ", buckets_.getBucketMin(i), ": ",
|
||||
buckets_.getByIndex(i).count, "\n",
|
||||
&ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Histogram<T>::toTSV(std::ostream& out, bool skipEmptyBuckets) const {
|
||||
for (unsigned int i = 0; i < buckets_.getNumBuckets(); ++i) {
|
||||
// Do not output empty buckets in order to reduce data file size.
|
||||
if (skipEmptyBuckets && getBucketByIndex(i).count == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto& bucket = getBucketByIndex(i);
|
||||
out << getBucketMin(i) << '\t' << getBucketMax(i) << '\t'
|
||||
<< bucket.count << '\t' << bucket.sum << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
} // folly
|
||||
|
||||
#endif // FOLLY_HISTOGRAM_INL_H_
|
||||
-408
@@ -1,408 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_HISTOGRAM_H_
|
||||
#define FOLLY_HISTOGRAM_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "folly/detail/Stats.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/*
|
||||
* A helper class to manage a set of histogram buckets.
|
||||
*/
|
||||
template <typename T, typename BucketT>
|
||||
class HistogramBuckets {
|
||||
public:
|
||||
typedef T ValueType;
|
||||
typedef BucketT BucketType;
|
||||
|
||||
/*
|
||||
* Create a set of histogram buckets.
|
||||
*
|
||||
* One bucket will be created for each bucketSize interval of values within
|
||||
* the specified range. Additionally, one bucket will be created to track
|
||||
* all values that fall below the specified minimum, and one bucket will be
|
||||
* created for all values above the specified maximum.
|
||||
*
|
||||
* If (max - min) is not a multiple of bucketSize, the last bucket will cover
|
||||
* a smaller range of values than the other buckets.
|
||||
*
|
||||
* (max - min) must be larger than or equal to bucketSize.
|
||||
*/
|
||||
HistogramBuckets(ValueType bucketSize, ValueType min, ValueType max,
|
||||
const BucketType& defaultBucket);
|
||||
|
||||
/* Returns the bucket size of each bucket in the histogram. */
|
||||
ValueType getBucketSize() const {
|
||||
return bucketSize_;
|
||||
}
|
||||
|
||||
/* Returns the min value at which bucketing begins. */
|
||||
ValueType getMin() const {
|
||||
return min_;
|
||||
}
|
||||
|
||||
/* Returns the max value at which bucketing ends. */
|
||||
ValueType getMax() const {
|
||||
return max_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the number of buckets.
|
||||
*
|
||||
* This includes the total number of buckets for the [min, max) range,
|
||||
* plus 2 extra buckets, one for handling values less than min, and one for
|
||||
* values greater than max.
|
||||
*/
|
||||
unsigned int getNumBuckets() const {
|
||||
return buckets_.size();
|
||||
}
|
||||
|
||||
/* Returns the bucket index into which the given value would fall. */
|
||||
unsigned int getBucketIdx(ValueType value) const;
|
||||
|
||||
/* Returns the bucket for the specified value */
|
||||
BucketType& getByValue(ValueType value) {
|
||||
return buckets_[getBucketIdx(value)];
|
||||
}
|
||||
|
||||
/* Returns the bucket for the specified value */
|
||||
const BucketType& getByValue(ValueType value) const {
|
||||
return buckets_[getBucketIdx(value)];
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the bucket at the specified index.
|
||||
*
|
||||
* Note that index 0 is the bucket for all values less than the specified
|
||||
* minimum. Index 1 is the first bucket in the specified bucket range.
|
||||
*/
|
||||
BucketType& getByIndex(unsigned int idx) {
|
||||
return buckets_[idx];
|
||||
}
|
||||
|
||||
/* Returns the bucket at the specified index. */
|
||||
const BucketType& getByIndex(unsigned int idx) const {
|
||||
return buckets_[idx];
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the minimum threshold for the bucket at the given index.
|
||||
*
|
||||
* The bucket at the specified index will store values in the range
|
||||
* [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
|
||||
* max is smaller than bucketMin + bucketSize.
|
||||
*/
|
||||
ValueType getBucketMin(unsigned int idx) const {
|
||||
if (idx == 0) {
|
||||
return std::numeric_limits<ValueType>::min();
|
||||
}
|
||||
if (idx == buckets_.size() - 1) {
|
||||
return max_;
|
||||
}
|
||||
|
||||
return min_ + ((idx - 1) * bucketSize_);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the maximum threshold for the bucket at the given index.
|
||||
*
|
||||
* The bucket at the specified index will store values in the range
|
||||
* [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
|
||||
* max is smaller than bucketMin + bucketSize.
|
||||
*/
|
||||
ValueType getBucketMax(unsigned int idx) const {
|
||||
if (idx == buckets_.size() - 1) {
|
||||
return std::numeric_limits<ValueType>::max();
|
||||
}
|
||||
|
||||
return min_ + (idx * bucketSize_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which bucket the specified percentile falls into.
|
||||
*
|
||||
* Looks for the bucket that contains the Nth percentile data point.
|
||||
*
|
||||
* @param pct The desired percentile to find, as a value from 0.0 to 1.0.
|
||||
* @param countFn A function that takes a const BucketType&, and returns the
|
||||
* number of values in that bucket.
|
||||
* @param lowPct The lowest percentile stored in the selected bucket will be
|
||||
* returned via this parameter.
|
||||
* @param highPct The highest percentile stored in the selected bucket will
|
||||
* be returned via this parameter.
|
||||
*
|
||||
* @return Returns the index of the bucket that contains the Nth percentile
|
||||
* data point.
|
||||
*/
|
||||
template <typename CountFn>
|
||||
unsigned int getPercentileBucketIdx(double pct,
|
||||
CountFn countFromBucket,
|
||||
double* lowPct = NULL,
|
||||
double* highPct = NULL) const;
|
||||
|
||||
/**
|
||||
* Estimate the value at the specified percentile.
|
||||
*
|
||||
* @param pct The desired percentile to find, as a value from 0.0 to 1.0.
|
||||
* @param countFn A function that takes a const BucketType&, and returns the
|
||||
* number of values in that bucket.
|
||||
* @param avgFn A function that takes a const BucketType&, and returns the
|
||||
* average of all the values in that bucket.
|
||||
*
|
||||
* @return Returns an estimate for N, where N is the number where exactly pct
|
||||
* percentage of the data points in the histogram are less than N.
|
||||
*/
|
||||
template <typename CountFn, typename AvgFn>
|
||||
ValueType getPercentileEstimate(double pct,
|
||||
CountFn countFromBucket,
|
||||
AvgFn avgFromBucket) const;
|
||||
|
||||
/*
|
||||
* Iterator access to the buckets.
|
||||
*
|
||||
* Note that the first bucket is for all values less than min, and the last
|
||||
* bucket is for all values greater than max. The buckets tracking values in
|
||||
* the [min, max) actually start at the second bucket.
|
||||
*/
|
||||
typename std::vector<BucketType>::const_iterator begin() const {
|
||||
return buckets_.begin();
|
||||
}
|
||||
typename std::vector<BucketType>::iterator begin() {
|
||||
return buckets_.begin();
|
||||
}
|
||||
typename std::vector<BucketType>::const_iterator end() const {
|
||||
return buckets_.end();
|
||||
}
|
||||
typename std::vector<BucketType>::iterator end() {
|
||||
return buckets_.end();
|
||||
}
|
||||
|
||||
private:
|
||||
const ValueType bucketSize_;
|
||||
const ValueType min_;
|
||||
const ValueType max_;
|
||||
std::vector<BucketType> buckets_;
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
|
||||
/*
|
||||
* A basic histogram class.
|
||||
*
|
||||
* Groups data points into equally-sized buckets, and stores the overall sum of
|
||||
* the data points in each bucket, as well as the number of data points in the
|
||||
* bucket.
|
||||
*
|
||||
* The caller must specify the minimum and maximum data points to expect ahead
|
||||
* of time, as well as the bucket width.
|
||||
*/
|
||||
template <typename T>
|
||||
class Histogram {
|
||||
public:
|
||||
typedef T ValueType;
|
||||
typedef detail::Bucket<T> Bucket;
|
||||
|
||||
Histogram(ValueType bucketSize, ValueType min, ValueType max)
|
||||
: buckets_(bucketSize, min, max, Bucket()) {}
|
||||
|
||||
/* Add a data point to the histogram */
|
||||
void addValue(ValueType value) {
|
||||
Bucket& bucket = buckets_.getByValue(value);
|
||||
// TODO: It would be nice to handle overflow here.
|
||||
bucket.sum += value;
|
||||
bucket.count += 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove a data point to the histogram
|
||||
*
|
||||
* Note that this method does not actually verify that this exact data point
|
||||
* had previously been added to the histogram; it merely subtracts the
|
||||
* requested value from the appropriate bucket's sum.
|
||||
*/
|
||||
void removeValue(ValueType value) {
|
||||
Bucket& bucket = buckets_.getByValue(value);
|
||||
// TODO: It would be nice to handle overflow here.
|
||||
bucket.sum -= value;
|
||||
bucket.count -= 1;
|
||||
}
|
||||
|
||||
/* Remove all data points from the histogram */
|
||||
void clear() {
|
||||
for (int i = 0; i < buckets_.getNumBuckets(); i++) {
|
||||
buckets_.getByIndex(i).clear();
|
||||
}
|
||||
}
|
||||
|
||||
/* Merge two histogram data together */
|
||||
void merge(Histogram &hist) {
|
||||
// the two histogram bucket definitions must match to support
|
||||
// a merge.
|
||||
if (getBucketSize() != hist.getBucketSize() ||
|
||||
getMin() != hist.getMin() ||
|
||||
getMax() != hist.getMax() ||
|
||||
getNumBuckets() != hist.getNumBuckets() ) {
|
||||
throw std::invalid_argument("Cannot merge from input histogram.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < buckets_.getNumBuckets(); i++) {
|
||||
buckets_.getByIndex(i) += hist.buckets_.getByIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy bucket values from another histogram */
|
||||
void copy(Histogram &hist) {
|
||||
// the two histogram bucket definition must match
|
||||
if (getBucketSize() != hist.getBucketSize() ||
|
||||
getMin() != hist.getMin() ||
|
||||
getMax() != hist.getMax() ||
|
||||
getNumBuckets() != hist.getNumBuckets() ) {
|
||||
throw std::invalid_argument("Cannot copy from input histogram.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < buckets_.getNumBuckets(); i++) {
|
||||
buckets_.getByIndex(i) = hist.buckets_.getByIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the bucket size of each bucket in the histogram. */
|
||||
ValueType getBucketSize() const {
|
||||
return buckets_.getBucketSize();
|
||||
}
|
||||
/* Returns the min value at which bucketing begins. */
|
||||
ValueType getMin() const {
|
||||
return buckets_.getMin();
|
||||
}
|
||||
/* Returns the max value at which bucketing ends. */
|
||||
ValueType getMax() const {
|
||||
return buckets_.getMax();
|
||||
}
|
||||
/* Returns the number of buckets */
|
||||
unsigned int getNumBuckets() const {
|
||||
return buckets_.getNumBuckets();
|
||||
}
|
||||
|
||||
/* Returns the specified bucket (for reading only!) */
|
||||
const Bucket& getBucketByIndex(int idx) const {
|
||||
return buckets_.getByIndex(idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the minimum threshold for the bucket at the given index.
|
||||
*
|
||||
* The bucket at the specified index will store values in the range
|
||||
* [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
|
||||
* max is smaller than bucketMin + bucketSize.
|
||||
*/
|
||||
ValueType getBucketMin(unsigned int idx) const {
|
||||
return buckets_.getBucketMin(idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the maximum threshold for the bucket at the given index.
|
||||
*
|
||||
* The bucket at the specified index will store values in the range
|
||||
* [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
|
||||
* max is smaller than bucketMin + bucketSize.
|
||||
*/
|
||||
ValueType getBucketMax(unsigned int idx) const {
|
||||
return buckets_.getBucketMax(idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the bucket that the specified percentile falls into
|
||||
*
|
||||
* The lowest and highest percentile data points in returned bucket will be
|
||||
* returned in the lowPct and highPct arguments, if they are non-NULL.
|
||||
*/
|
||||
unsigned int getPercentileBucketIdx(double pct,
|
||||
double* lowPct = NULL,
|
||||
double* highPct = NULL) const {
|
||||
// We unfortunately can't use lambdas here yet;
|
||||
// Some users of this code are still built with gcc-4.4.
|
||||
CountFromBucket countFn;
|
||||
return buckets_.getPercentileBucketIdx(pct, countFn, lowPct, highPct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate the value at the specified percentile.
|
||||
*
|
||||
* @param pct The desired percentile to find, as a value from 0.0 to 1.0.
|
||||
*
|
||||
* @return Returns an estimate for N, where N is the number where exactly pct
|
||||
* percentage of the data points in the histogram are less than N.
|
||||
*/
|
||||
ValueType getPercentileEstimate(double pct) const {
|
||||
CountFromBucket countFn;
|
||||
AvgFromBucket avgFn;
|
||||
return buckets_.getPercentileEstimate(pct, countFn, avgFn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a human-readable string describing the histogram contents
|
||||
*/
|
||||
std::string debugString() const;
|
||||
|
||||
/*
|
||||
* Write the histogram contents in tab-separated values (TSV) format.
|
||||
* Format is "min max count sum".
|
||||
*/
|
||||
void toTSV(std::ostream& out, bool skipEmptyBuckets = true) const;
|
||||
|
||||
private:
|
||||
struct CountFromBucket {
|
||||
uint64_t operator()(const Bucket& bucket) const {
|
||||
return bucket.count;
|
||||
}
|
||||
};
|
||||
struct AvgFromBucket {
|
||||
ValueType operator()(const Bucket& bucket) const {
|
||||
if (bucket.count == 0) {
|
||||
return ValueType(0);
|
||||
}
|
||||
// Cast bucket.count to a signed integer type. This ensures that we
|
||||
// perform division properly here: If bucket.sum is a signed integer
|
||||
// type but we divide by an unsigned number, unsigned division will be
|
||||
// performed and bucket.sum will be converted to unsigned first.
|
||||
// If bucket.sum is unsigned, the code will still do unsigned division
|
||||
// correctly.
|
||||
//
|
||||
// The only downside is if bucket.count is large enough to be negative
|
||||
// when treated as signed. That should be extremely unlikely, though.
|
||||
return bucket.sum / static_cast<int64_t>(bucket.count);
|
||||
}
|
||||
};
|
||||
|
||||
detail::HistogramBuckets<ValueType, Bucket> buckets_;
|
||||
};
|
||||
|
||||
} // folly
|
||||
|
||||
#include "folly/Histogram-inl.h"
|
||||
|
||||
#endif // FOLLY_HISTOGRAM_H_
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_INTRUSIVELIST_H_
|
||||
#define FOLLY_INTRUSIVELIST_H_
|
||||
|
||||
/*
|
||||
* This file contains convenience typedefs that make boost::intrusive::list
|
||||
* easier to use.
|
||||
*/
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* An auto-unlink intrusive list hook.
|
||||
*/
|
||||
typedef boost::intrusive::list_member_hook<
|
||||
boost::intrusive::link_mode<boost::intrusive::auto_unlink> >
|
||||
IntrusiveListHook;
|
||||
|
||||
/**
|
||||
* An intrusive list.
|
||||
*
|
||||
* An IntrusiveList always uses an auto-unlink hook.
|
||||
* Beware that IntrusiveList::size() is an O(n) operation, since it has to walk
|
||||
* the entire list.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* class Foo {
|
||||
* // Note that the listHook member variable needs to be visible
|
||||
* // to the code that defines the IntrusiveList instantiation.
|
||||
* // The list hook can be made public, or you can make the other class a
|
||||
* // friend.
|
||||
* IntrusiveListHook listHook;
|
||||
* };
|
||||
*
|
||||
* typedef IntrusiveList<Foo, &Foo::listHook> FooList;
|
||||
*
|
||||
* Foo *foo = new Foo();
|
||||
* FooList myList;
|
||||
* myList.push_back(*foo);
|
||||
*
|
||||
* Note that each IntrusiveListHook can only be part of a single list at any
|
||||
* given time. If you need the same object to be stored in two lists at once,
|
||||
* you need to use two different IntrusiveListHook member variables.
|
||||
*
|
||||
* The elements stored in the list must contain an IntrusiveListHook member
|
||||
* variable.
|
||||
*
|
||||
* TODO: This should really be a template alias. However, gcc doesn't support
|
||||
* template aliases yet. A subclass is a reasonable workaround for now. This
|
||||
* subclass only supports the default constructor, but we could add other
|
||||
* constructors if necessary.
|
||||
*/
|
||||
template<typename T, IntrusiveListHook T::* PtrToMember>
|
||||
class IntrusiveList : public boost::intrusive::list<
|
||||
T,
|
||||
boost::intrusive::member_hook<T, IntrusiveListHook, PtrToMember>,
|
||||
boost::intrusive::constant_time_size<false> > {
|
||||
};
|
||||
|
||||
/**
|
||||
* A safe-link intrusive list hook.
|
||||
*/
|
||||
typedef boost::intrusive::list_member_hook<
|
||||
boost::intrusive::link_mode<boost::intrusive::safe_link> >
|
||||
SafeIntrusiveListHook;
|
||||
|
||||
/**
|
||||
* An intrusive list with const-time size() method.
|
||||
*
|
||||
* A CountedIntrusiveList always uses a safe-link hook.
|
||||
* CountedIntrusiveList::size() is an O(1) operation. Users of this type
|
||||
* of lists need to remove a member from a list by calling one of the
|
||||
* methods on the list (e.g., erase(), pop_front(), etc.), rather than
|
||||
* calling unlink on the member's list hook. Given references to a
|
||||
* list and a member, a constant-time removal operation can be
|
||||
* accomplished by list.erase(list.iterator_to(member)). Also, when a
|
||||
* member is destroyed, it is NOT automatically removed from the list.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* class Foo {
|
||||
* // Note that the listHook member variable needs to be visible
|
||||
* // to the code that defines the CountedIntrusiveList instantiation.
|
||||
* // The list hook can be made public, or you can make the other class a
|
||||
* // friend.
|
||||
* SafeIntrusiveListHook listHook;
|
||||
* };
|
||||
*
|
||||
* typedef CountedIntrusiveList<Foo, &Foo::listHook> FooList;
|
||||
*
|
||||
* Foo *foo = new Foo();
|
||||
* FooList myList;
|
||||
* myList.push_back(*foo);
|
||||
* myList.pop_front();
|
||||
*
|
||||
* Note that each SafeIntrusiveListHook can only be part of a single list at any
|
||||
* given time. If you need the same object to be stored in two lists at once,
|
||||
* you need to use two different SafeIntrusiveListHook member variables.
|
||||
*
|
||||
* The elements stored in the list must contain an SafeIntrusiveListHook member
|
||||
* variable.
|
||||
*
|
||||
* TODO: This should really be a template alias. However, gcc doesn't support
|
||||
* template aliases yet. A subclass is a reasonable workaround for now. This
|
||||
* subclass only supports the default constructor, but we could add other
|
||||
* constructors if necessary.
|
||||
*/
|
||||
template<typename T, SafeIntrusiveListHook T::* PtrToMember>
|
||||
class CountedIntrusiveList : public boost::intrusive::list<
|
||||
T,
|
||||
boost::intrusive::member_hook<T, SafeIntrusiveListHook, PtrToMember>,
|
||||
boost::intrusive::constant_time_size<true> > {
|
||||
};
|
||||
|
||||
} // folly
|
||||
|
||||
#endif // FOLLY_INTRUSIVELIST_H_
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef FOLLY_LAZY_H_
|
||||
#define FOLLY_LAZY_H_
|
||||
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
#include "folly/Optional.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* Lazy -- for delayed initialization of a value. The value's
|
||||
* initialization will be computed on demand at its first use, but
|
||||
* will not be recomputed if its value is requested again. The value
|
||||
* may still be mutated after its initialization if the lazy is not
|
||||
* declared const.
|
||||
*
|
||||
* The value is created using folly::lazy, usually with a lambda, and
|
||||
* its value is requested using operator().
|
||||
*
|
||||
* Note that the value is not safe for concurrent accesses by multiple
|
||||
* threads, even if you declare it const. See note below.
|
||||
*
|
||||
*
|
||||
* Example Usage:
|
||||
*
|
||||
* void foo() {
|
||||
* auto const val = folly::lazy([&]{
|
||||
* return something_expensive(blah());
|
||||
* });
|
||||
*
|
||||
* if (condition1) {
|
||||
* use(val());
|
||||
* }
|
||||
* if (condition2) {
|
||||
* useMaybeAgain(val());
|
||||
* } else {
|
||||
* // Unneeded in this branch.
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* Rationale:
|
||||
*
|
||||
* - operator() is used to request the value instead of an implicit
|
||||
* conversion because the slight syntactic overhead in common
|
||||
* seems worth the increased clarity.
|
||||
*
|
||||
* - Lazy values do not model CopyConstructible because it is
|
||||
* unclear what semantics would be desirable. Either copies
|
||||
* should share the cached value (adding overhead to cases that
|
||||
* don't need to support copies), or they could recompute the
|
||||
* value unnecessarily. Sharing with mutable lazies would also
|
||||
* leave them with non-value semantics despite looking
|
||||
* value-like.
|
||||
*
|
||||
* - Not thread safe for const accesses. Many use cases for lazy
|
||||
* values are local variables on the stack, where multiple
|
||||
* threads shouldn't even be able to reach the value. It still
|
||||
* is useful to indicate/check that the value doesn't change with
|
||||
* const, particularly when it is captured by a large family of
|
||||
* lambdas. Adding internal synchronization seems like it would
|
||||
* pessimize the most common use case in favor of less likely use
|
||||
* cases.
|
||||
*
|
||||
*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<class Func>
|
||||
struct Lazy {
|
||||
typedef typename std::result_of<Func()>::type result_type;
|
||||
|
||||
explicit Lazy(Func&& f) : func_(std::move(f)) {}
|
||||
explicit Lazy(Func& f) : func_(f) {}
|
||||
|
||||
Lazy(Lazy&& o)
|
||||
: value_(std::move(o.value_))
|
||||
, func_(std::move(o.func_))
|
||||
{}
|
||||
|
||||
Lazy(const Lazy&) = delete;
|
||||
Lazy& operator=(const Lazy&) = delete;
|
||||
Lazy& operator=(Lazy&&) = delete;
|
||||
|
||||
const result_type& operator()() const {
|
||||
return const_cast<Lazy&>(*this)();
|
||||
}
|
||||
|
||||
result_type& operator()() {
|
||||
if (!value_) value_ = func_();
|
||||
return *value_;
|
||||
}
|
||||
|
||||
private:
|
||||
Optional<result_type> value_;
|
||||
Func func_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<class Func>
|
||||
detail::Lazy<typename std::remove_reference<Func>::type>
|
||||
lazy(Func&& fun) {
|
||||
return detail::Lazy<typename std::remove_reference<Func>::type>(
|
||||
std::forward<Func>(fun)
|
||||
);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compiler hints to indicate the fast path of an "if" branch: whether
|
||||
* the if condition is likely to be true or false.
|
||||
*
|
||||
* @author Tudor Bosman (tudorb@fb.com)
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_BASE_LIKELY_H_
|
||||
#define FOLLY_BASE_LIKELY_H_
|
||||
|
||||
#undef LIKELY
|
||||
#undef UNLIKELY
|
||||
|
||||
#if defined(__GNUC__) && __GNUC__ >= 4
|
||||
#define LIKELY(x) (__builtin_expect((x), 1))
|
||||
#define UNLIKELY(x) (__builtin_expect((x), 0))
|
||||
#else
|
||||
#define LIKELY(x) (x)
|
||||
#define UNLIKELY(x) (x)
|
||||
#endif
|
||||
|
||||
#endif /* FOLLY_BASE_LIKELY_H_ */
|
||||
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_LOGGING_H_
|
||||
#define FOLLY_LOGGING_H_
|
||||
|
||||
#include <time.h>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#ifndef FB_LOG_EVERY_MS
|
||||
/**
|
||||
* Issues a LOG(severity) no more often than every
|
||||
* milliseconds. Example:
|
||||
*
|
||||
* FB_LOG_EVERY_MS(INFO, 10000) << "At least ten seconds passed"
|
||||
* " since you last saw this.";
|
||||
*
|
||||
* The implementation uses for statements to introduce variables in
|
||||
* a nice way that doesn't mess surrounding statements.
|
||||
*/
|
||||
#define FB_LOG_EVERY_MS(severity, milliseconds) \
|
||||
for (bool LOG_EVERY_MS_once = true; LOG_EVERY_MS_once; ) \
|
||||
for (const ::clock_t LOG_EVERY_MS_now = ::clock(); LOG_EVERY_MS_once; ) \
|
||||
for (static ::clock_t LOG_EVERY_MS_last; LOG_EVERY_MS_once; \
|
||||
LOG_EVERY_MS_once = false) \
|
||||
if (1000 * (LOG_EVERY_MS_now - LOG_EVERY_MS_last) < \
|
||||
(milliseconds) * CLOCKS_PER_SEC) {} \
|
||||
else \
|
||||
(LOG_EVERY_MS_last = LOG_EVERY_MS_now, LOG(severity))
|
||||
|
||||
#endif
|
||||
|
||||
#endif // FOLLY_LOGGING_H_
|
||||
-285
@@ -1,285 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "folly/detail/MPMCPipelineDetail.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Helper tag template to use amplification > 1
|
||||
*/
|
||||
template <class T, size_t Amp> class MPMCPipelineStage;
|
||||
|
||||
/**
|
||||
* Multi-Producer, Multi-Consumer pipeline.
|
||||
*
|
||||
* A N-stage pipeline is a combination of N+1 MPMC queues (see MPMCQueue.h).
|
||||
*
|
||||
* At each stage, you may dequeue the results from the previous stage (possibly
|
||||
* from multiple threads) and enqueue results to the next stage. Regardless of
|
||||
* the order of completion, data is delivered to the next stage in the original
|
||||
* order. Each input is matched with a "ticket" which must be produced
|
||||
* when enqueueing to the next stage.
|
||||
*
|
||||
* A given stage must produce exactly K ("amplification factor", default K=1)
|
||||
* results for every input. This is enforced by requiring that each ticket
|
||||
* is used exactly K times.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* // arguments are queue sizes
|
||||
* MPMCPipeline<int, std::string, int> pipeline(10, 10, 10);
|
||||
*
|
||||
* pipeline.blockingWrite(42);
|
||||
*
|
||||
* {
|
||||
* int val;
|
||||
* auto ticket = pipeline.blockingReadStage<0>(val);
|
||||
* pipeline.blockingWriteStage<0>(ticket, folly::to<std::string>(val));
|
||||
* }
|
||||
*
|
||||
* {
|
||||
* std::string val;
|
||||
* auto ticket = pipeline.blockingReadStage<1>(val);
|
||||
* int ival = 0;
|
||||
* try {
|
||||
* ival = folly::to<int>(val);
|
||||
* } catch (...) {
|
||||
* // We must produce exactly 1 output even on exception!
|
||||
* }
|
||||
* pipeline.blockingWriteStage<1>(ticket, ival);
|
||||
* }
|
||||
*
|
||||
* int result;
|
||||
* pipeline.blockingRead(result);
|
||||
* // result == 42
|
||||
*
|
||||
* To specify amplification factors greater than 1, use
|
||||
* MPMCPipelineStage<T, amplification> instead of T in the declaration:
|
||||
*
|
||||
* MPMCPipeline<int,
|
||||
* MPMCPipelineStage<std::string, 2>,
|
||||
* MPMCPipelineStage<int, 4>>
|
||||
*
|
||||
* declares a two-stage pipeline: the first stage produces 2 strings
|
||||
* for each input int, the second stage produces 4 ints for each input string,
|
||||
* so, overall, the pipeline produces 2*4 = 8 ints for each input int.
|
||||
*
|
||||
* Implementation details: we use N+1 MPMCQueue objects; each intermediate
|
||||
* queue connects two adjacent stages. The MPMCQueue implementation is abused;
|
||||
* instead of using it as a queue, we insert in the output queue at the
|
||||
* position determined by the input queue's popTicket_. We guarantee that
|
||||
* all slots are filled (and therefore the queue doesn't freeze) because
|
||||
* we require that each step produces exactly K outputs for every input.
|
||||
*/
|
||||
template <class In, class... Stages> class MPMCPipeline {
|
||||
typedef std::tuple<detail::PipelineStageInfo<Stages>...> StageInfos;
|
||||
typedef std::tuple<
|
||||
detail::MPMCPipelineStageImpl<In>,
|
||||
detail::MPMCPipelineStageImpl<
|
||||
typename detail::PipelineStageInfo<Stages>::value_type>...>
|
||||
StageTuple;
|
||||
static constexpr size_t kAmplification =
|
||||
detail::AmplificationProduct<StageInfos>::value;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Ticket, returned by blockingReadStage, must be given back to
|
||||
* blockingWriteStage. Tickets are not thread-safe.
|
||||
*/
|
||||
template <size_t Stage>
|
||||
class Ticket {
|
||||
public:
|
||||
~Ticket() noexcept {
|
||||
CHECK_EQ(remainingUses_, 0) << "All tickets must be completely used!";
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
Ticket() noexcept
|
||||
: owner_(nullptr),
|
||||
remainingUses_(0),
|
||||
value_(0xdeadbeeffaceb00c) {
|
||||
}
|
||||
#else
|
||||
Ticket() noexcept : remainingUses_(0) { }
|
||||
#endif
|
||||
|
||||
Ticket(Ticket&& other) noexcept
|
||||
:
|
||||
#ifndef NDEBUG
|
||||
owner_(other.owner_),
|
||||
#endif
|
||||
remainingUses_(other.remainingUses_),
|
||||
value_(other.value_) {
|
||||
other.remainingUses_ = 0;
|
||||
#ifndef NDEBUG
|
||||
other.owner_ = nullptr;
|
||||
other.value_ = 0xdeadbeeffaceb00c;
|
||||
#endif
|
||||
}
|
||||
|
||||
Ticket& operator=(Ticket&& other) noexcept {
|
||||
if (this != &other) {
|
||||
this->~Ticket();
|
||||
new (this) Ticket(std::move(other));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class MPMCPipeline;
|
||||
#ifndef NDEBUG
|
||||
MPMCPipeline* owner_;
|
||||
#endif
|
||||
size_t remainingUses_;
|
||||
uint64_t value_;
|
||||
|
||||
|
||||
Ticket(MPMCPipeline* owner, size_t amplification, uint64_t value) noexcept
|
||||
:
|
||||
#ifndef NDEBUG
|
||||
owner_(owner),
|
||||
#endif
|
||||
remainingUses_(amplification),
|
||||
value_(value * amplification) {
|
||||
}
|
||||
|
||||
uint64_t use(MPMCPipeline* owner) {
|
||||
CHECK_GT(remainingUses_--, 0);
|
||||
#ifndef NDEBUG
|
||||
CHECK(owner == owner_);
|
||||
#endif
|
||||
return value_++;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Default-construct pipeline. Useful to move-assign later,
|
||||
* just like MPMCQueue, see MPMCQueue.h for more details.
|
||||
*/
|
||||
MPMCPipeline() { }
|
||||
|
||||
/**
|
||||
* Construct a pipeline with N+1 queue sizes.
|
||||
*/
|
||||
template <class... Sizes>
|
||||
explicit MPMCPipeline(Sizes... sizes) : stages_(sizes...) { }
|
||||
|
||||
/**
|
||||
* Push an element into (the first stage of) the pipeline. Blocking.
|
||||
*/
|
||||
template <class... Args>
|
||||
void blockingWrite(Args&&... args) {
|
||||
std::get<0>(stages_).blockingWrite(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to push an element into (the first stage of) the pipeline.
|
||||
* Non-blocking.
|
||||
*/
|
||||
template <class... Args>
|
||||
bool write(Args&&... args) {
|
||||
return std::get<0>(stages_).write(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an element for stage Stage and obtain a ticket. Blocking.
|
||||
*/
|
||||
template <size_t Stage>
|
||||
Ticket<Stage> blockingReadStage(
|
||||
typename std::tuple_element<Stage, StageTuple>::type::value_type& elem) {
|
||||
return Ticket<Stage>(
|
||||
this,
|
||||
std::tuple_element<Stage, StageInfos>::type::kAmplification,
|
||||
std::get<Stage>(stages_).blockingRead(elem));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to read an element for stage Stage and obtain a ticket.
|
||||
* Non-blocking.
|
||||
*/
|
||||
template <size_t Stage>
|
||||
bool readStage(
|
||||
Ticket<Stage>& ticket,
|
||||
typename std::tuple_element<Stage, StageTuple>::type::value_type& elem) {
|
||||
uint64_t tval;
|
||||
if (!std::get<Stage>(stages_).readAndGetTicket(tval, elem)) {
|
||||
return false;
|
||||
}
|
||||
ticket = Ticket<Stage>(
|
||||
this,
|
||||
std::tuple_element<Stage, StageInfos>::type::kAmplification,
|
||||
tval);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete an element in stage Stage (pushing it for stage Stage+1).
|
||||
* Blocking.
|
||||
*/
|
||||
template <size_t Stage, class... Args>
|
||||
void blockingWriteStage(Ticket<Stage>& ticket, Args&&... args) {
|
||||
std::get<Stage+1>(stages_).blockingWriteWithTicket(
|
||||
ticket.use(this),
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop an element from (the final stage of) the pipeline. Blocking.
|
||||
*/
|
||||
void blockingRead(
|
||||
typename std::tuple_element<
|
||||
sizeof...(Stages),
|
||||
StageTuple>::type::value_type& elem) {
|
||||
std::get<sizeof...(Stages)>(stages_).blockingRead(elem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to pop an element from (the final stage of) the pipeline.
|
||||
* Non-blocking.
|
||||
*/
|
||||
bool read(
|
||||
typename std::tuple_element<
|
||||
sizeof...(Stages),
|
||||
StageTuple>::type::value_type& elem) {
|
||||
return std::get<sizeof...(Stages)>(stages_).read(elem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate queue size, measured as values from the last stage.
|
||||
* (so if the pipeline has an amplification factor > 1, pushing an element
|
||||
* into the first stage will cause sizeGuess() to be == amplification factor)
|
||||
* Elements "in flight" (currently processed as part of a stage, so not
|
||||
* in any queue) are also counted.
|
||||
*/
|
||||
ssize_t sizeGuess() const noexcept {
|
||||
return (std::get<0>(stages_).writeCount() * kAmplification -
|
||||
std::get<sizeof...(Stages)>(stages_).readCount());
|
||||
}
|
||||
|
||||
private:
|
||||
StageTuple stages_;
|
||||
};
|
||||
|
||||
|
||||
} // namespaces
|
||||
|
||||
-861
@@ -1,861 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <assert.h>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <errno.h>
|
||||
#include <limits>
|
||||
#include <linux/futex.h>
|
||||
#include <string.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <folly/Traits.h>
|
||||
#include <folly/detail/Futex.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<typename T, template<typename> class Atom>
|
||||
class SingleElementQueue;
|
||||
|
||||
template <typename T> class MPMCPipelineStageImpl;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// MPMCQueue<T> is a high-performance bounded concurrent queue that
|
||||
/// supports multiple producers, multiple consumers, and optional blocking.
|
||||
/// The queue has a fixed capacity, for which all memory will be allocated
|
||||
/// up front. The bulk of the work of enqueuing and dequeuing can be
|
||||
/// performed in parallel.
|
||||
///
|
||||
/// The underlying implementation uses a ticket dispenser for the head and
|
||||
/// the tail, spreading accesses across N single-element queues to produce
|
||||
/// a queue with capacity N. The ticket dispensers use atomic increment,
|
||||
/// which is more robust to contention than a CAS loop. Each of the
|
||||
/// single-element queues uses its own CAS to serialize access, with an
|
||||
/// adaptive spin cutoff. When spinning fails on a single-element queue
|
||||
/// it uses futex()'s _BITSET operations to reduce unnecessary wakeups
|
||||
/// even if multiple waiters are present on an individual queue (such as
|
||||
/// when the MPMCQueue's capacity is smaller than the number of enqueuers
|
||||
/// or dequeuers).
|
||||
///
|
||||
/// NOEXCEPT INTERACTION: Ticket-based queues separate the assignment
|
||||
/// of In benchmarks (contained in tao/queues/ConcurrentQueueTests)
|
||||
/// it handles 1 to 1, 1 to N, N to 1, and N to M thread counts better
|
||||
/// than any of the alternatives present in fbcode, for both small (~10)
|
||||
/// and large capacities. In these benchmarks it is also faster than
|
||||
/// tbb::concurrent_bounded_queue for all configurations. When there are
|
||||
/// many more threads than cores, MPMCQueue is _much_ faster than the tbb
|
||||
/// queue because it uses futex() to block and unblock waiting threads,
|
||||
/// rather than spinning with sched_yield.
|
||||
///
|
||||
/// queue positions from the actual construction of the in-queue elements,
|
||||
/// which means that the T constructor used during enqueue must not throw
|
||||
/// an exception. This is enforced at compile time using type traits,
|
||||
/// which requires that T be adorned with accurate noexcept information.
|
||||
/// If your type does not use noexcept, you will have to wrap it in
|
||||
/// something that provides the guarantee. We provide an alternate
|
||||
/// safe implementation for types that don't use noexcept but that are
|
||||
/// marked folly::IsRelocatable and boost::has_nothrow_constructor,
|
||||
/// which is common for folly types. In particular, if you can declare
|
||||
/// FOLLY_ASSUME_FBVECTOR_COMPATIBLE then your type can be put in
|
||||
/// MPMCQueue.
|
||||
template<typename T,
|
||||
template<typename> class Atom = std::atomic,
|
||||
typename = typename std::enable_if<
|
||||
std::is_nothrow_constructible<T,T&&>::value ||
|
||||
folly::IsRelocatable<T>::value>::type>
|
||||
class MPMCQueue : boost::noncopyable {
|
||||
friend class detail::MPMCPipelineStageImpl<T>;
|
||||
public:
|
||||
typedef T value_type;
|
||||
|
||||
explicit MPMCQueue(size_t capacity)
|
||||
: capacity_(capacity)
|
||||
, slots_(new detail::SingleElementQueue<T,Atom>[capacity +
|
||||
2 * kSlotPadding])
|
||||
, stride_(computeStride(capacity))
|
||||
, pushTicket_(0)
|
||||
, popTicket_(0)
|
||||
, pushSpinCutoff_(0)
|
||||
, popSpinCutoff_(0)
|
||||
{
|
||||
// ideally this would be a static assert, but g++ doesn't allow it
|
||||
assert(alignof(MPMCQueue<T,Atom>) >= kFalseSharingRange);
|
||||
}
|
||||
|
||||
/// A default-constructed queue is useful because a usable (non-zero
|
||||
/// capacity) queue can be moved onto it or swapped with it
|
||||
MPMCQueue() noexcept
|
||||
: capacity_(0)
|
||||
, slots_(nullptr)
|
||||
, stride_(0)
|
||||
, pushTicket_(0)
|
||||
, popTicket_(0)
|
||||
, pushSpinCutoff_(0)
|
||||
, popSpinCutoff_(0)
|
||||
{}
|
||||
|
||||
/// IMPORTANT: The move constructor is here to make it easier to perform
|
||||
/// the initialization phase, it is not safe to use when there are any
|
||||
/// concurrent accesses (this is not checked).
|
||||
MPMCQueue(MPMCQueue<T,Atom>&& rhs) noexcept
|
||||
: capacity_(rhs.capacity_)
|
||||
, slots_(rhs.slots_)
|
||||
, stride_(rhs.stride_)
|
||||
, pushTicket_(rhs.pushTicket_.load(std::memory_order_relaxed))
|
||||
, popTicket_(rhs.popTicket_.load(std::memory_order_relaxed))
|
||||
, pushSpinCutoff_(rhs.pushSpinCutoff_.load(std::memory_order_relaxed))
|
||||
, popSpinCutoff_(rhs.popSpinCutoff_.load(std::memory_order_relaxed))
|
||||
{
|
||||
// relaxed ops are okay for the previous reads, since rhs queue can't
|
||||
// be in concurrent use
|
||||
|
||||
// zero out rhs
|
||||
rhs.capacity_ = 0;
|
||||
rhs.slots_ = nullptr;
|
||||
rhs.stride_ = 0;
|
||||
rhs.pushTicket_.store(0, std::memory_order_relaxed);
|
||||
rhs.popTicket_.store(0, std::memory_order_relaxed);
|
||||
rhs.pushSpinCutoff_.store(0, std::memory_order_relaxed);
|
||||
rhs.popSpinCutoff_.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/// IMPORTANT: The move operator is here to make it easier to perform
|
||||
/// the initialization phase, it is not safe to use when there are any
|
||||
/// concurrent accesses (this is not checked).
|
||||
MPMCQueue<T,Atom> const& operator= (MPMCQueue<T,Atom>&& rhs) {
|
||||
if (this != &rhs) {
|
||||
this->~MPMCQueue();
|
||||
new (this) MPMCQueue(std::move(rhs));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// MPMCQueue can only be safely destroyed when there are no
|
||||
/// pending enqueuers or dequeuers (this is not checked).
|
||||
~MPMCQueue() {
|
||||
delete[] slots_;
|
||||
}
|
||||
|
||||
/// Returns the number of successful reads minus the number of successful
|
||||
/// writes. Waiting blockingRead and blockingWrite calls are included,
|
||||
/// so this value can be negative.
|
||||
ssize_t size() const noexcept {
|
||||
// since both pushes and pops increase monotonically, we can get a
|
||||
// consistent snapshot either by bracketing a read of popTicket_ with
|
||||
// two reads of pushTicket_ that return the same value, or the other
|
||||
// way around. We maximize our chances by alternately attempting
|
||||
// both bracketings.
|
||||
uint64_t pushes = pushTicket_.load(std::memory_order_acquire); // A
|
||||
uint64_t pops = popTicket_.load(std::memory_order_acquire); // B
|
||||
while (true) {
|
||||
uint64_t nextPushes = pushTicket_.load(std::memory_order_acquire); // C
|
||||
if (pushes == nextPushes) {
|
||||
// pushTicket_ didn't change from A (or the previous C) to C,
|
||||
// so we can linearize at B (or D)
|
||||
return pushes - pops;
|
||||
}
|
||||
pushes = nextPushes;
|
||||
uint64_t nextPops = popTicket_.load(std::memory_order_acquire); // D
|
||||
if (pops == nextPops) {
|
||||
// popTicket_ didn't chance from B (or the previous D), so we
|
||||
// can linearize at C
|
||||
return pushes - pops;
|
||||
}
|
||||
pops = nextPops;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if there are no items available for dequeue
|
||||
bool isEmpty() const noexcept {
|
||||
return size() <= 0;
|
||||
}
|
||||
|
||||
/// Returns true if there is currently no empty space to enqueue
|
||||
bool isFull() const noexcept {
|
||||
// careful with signed -> unsigned promotion, since size can be negative
|
||||
return size() >= static_cast<ssize_t>(capacity_);
|
||||
}
|
||||
|
||||
/// Returns is a guess at size() for contexts that don't need a precise
|
||||
/// value, such as stats.
|
||||
ssize_t sizeGuess() const noexcept {
|
||||
return writeCount() - readCount();
|
||||
}
|
||||
|
||||
/// Doesn't change
|
||||
size_t capacity() const noexcept {
|
||||
return capacity_;
|
||||
}
|
||||
|
||||
/// Returns the total number of calls to blockingWrite or successful
|
||||
/// calls to write, including those blockingWrite calls that are
|
||||
/// currently blocking
|
||||
uint64_t writeCount() const noexcept {
|
||||
return pushTicket_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
/// Returns the total number of calls to blockingRead or successful
|
||||
/// calls to read, including those blockingRead calls that are currently
|
||||
/// blocking
|
||||
uint64_t readCount() const noexcept {
|
||||
return popTicket_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
/// Enqueues a T constructed from args, blocking until space is
|
||||
/// available. Note that this method signature allows enqueue via
|
||||
/// move, if args is a T rvalue, via copy, if args is a T lvalue, or
|
||||
/// via emplacement if args is an initializer list that can be passed
|
||||
/// to a T constructor.
|
||||
template <typename ...Args>
|
||||
void blockingWrite(Args&&... args) noexcept {
|
||||
enqueueWithTicket(pushTicket_++, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// If an item can be enqueued with no blocking, does so and returns
|
||||
/// true, otherwise returns false. This method is similar to
|
||||
/// writeIfNotFull, but if you don't have a specific need for that
|
||||
/// method you should use this one.
|
||||
///
|
||||
/// One of the common usages of this method is to enqueue via the
|
||||
/// move constructor, something like q.write(std::move(x)). If write
|
||||
/// returns false because the queue is full then x has not actually been
|
||||
/// consumed, which looks strange. To understand why it is actually okay
|
||||
/// to use x afterward, remember that std::move is just a typecast that
|
||||
/// provides an rvalue reference that enables use of a move constructor
|
||||
/// or operator. std::move doesn't actually move anything. It could
|
||||
/// more accurately be called std::rvalue_cast or std::move_permission.
|
||||
template <typename ...Args>
|
||||
bool write(Args&&... args) noexcept {
|
||||
uint64_t ticket;
|
||||
if (tryObtainReadyPushTicket(ticket)) {
|
||||
// we have pre-validated that the ticket won't block
|
||||
enqueueWithTicket(ticket, std::forward<Args>(args)...);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// If the queue is not full, enqueues and returns true, otherwise
|
||||
/// returns false. Unlike write this method can be blocked by another
|
||||
/// thread, specifically a read that has linearized (been assigned
|
||||
/// a ticket) but not yet completed. If you don't really need this
|
||||
/// function you should probably use write.
|
||||
///
|
||||
/// MPMCQueue isn't lock-free, so just because a read operation has
|
||||
/// linearized (and isFull is false) doesn't mean that space has been
|
||||
/// made available for another write. In this situation write will
|
||||
/// return false, but writeIfNotFull will wait for the dequeue to finish.
|
||||
/// This method is required if you are composing queues and managing
|
||||
/// your own wakeup, because it guarantees that after every successful
|
||||
/// write a readIfNotFull will succeed.
|
||||
template <typename ...Args>
|
||||
bool writeIfNotFull(Args&&... args) noexcept {
|
||||
uint64_t ticket;
|
||||
if (tryObtainPromisedPushTicket(ticket)) {
|
||||
// some other thread is already dequeuing the slot into which we
|
||||
// are going to enqueue, but we might have to wait for them to finish
|
||||
enqueueWithTicket(ticket, std::forward<Args>(args)...);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves a dequeued element onto elem, blocking until an element
|
||||
/// is available
|
||||
void blockingRead(T& elem) noexcept {
|
||||
dequeueWithTicket(popTicket_++, elem);
|
||||
}
|
||||
|
||||
/// If an item can be dequeued with no blocking, does so and returns
|
||||
/// true, otherwise returns false.
|
||||
bool read(T& elem) noexcept {
|
||||
uint64_t ticket;
|
||||
if (tryObtainReadyPopTicket(ticket)) {
|
||||
// the ticket has been pre-validated to not block
|
||||
dequeueWithTicket(ticket, elem);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// If the queue is not empty, dequeues and returns true, otherwise
|
||||
/// returns false. If the matching write is still in progress then this
|
||||
/// method may block waiting for it. If you don't rely on being able
|
||||
/// to dequeue (such as by counting completed write) then you should
|
||||
/// prefer read.
|
||||
bool readIfNotEmpty(T& elem) noexcept {
|
||||
uint64_t ticket;
|
||||
if (tryObtainPromisedPopTicket(ticket)) {
|
||||
// the matching enqueue already has a ticket, but might not be done
|
||||
dequeueWithTicket(ticket, elem);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
enum {
|
||||
/// Once every kAdaptationFreq we will spin longer, to try to estimate
|
||||
/// the proper spin backoff
|
||||
kAdaptationFreq = 128,
|
||||
|
||||
/// Memory locations on the same cache line are subject to false
|
||||
/// sharing, which is very bad for performance
|
||||
kFalseSharingRange = 64,
|
||||
|
||||
/// To avoid false sharing in slots_ with neighboring memory
|
||||
/// allocations, we pad it with this many SingleElementQueue-s at
|
||||
/// each end
|
||||
kSlotPadding = 1 +
|
||||
(kFalseSharingRange - 1) / sizeof(detail::SingleElementQueue<T,Atom>)
|
||||
};
|
||||
|
||||
static_assert(kFalseSharingRange == 64,
|
||||
"FOLLY_ON_NEXT_CACHE_LINE must track kFalseSharingRange");
|
||||
|
||||
// This literal "64' should be kFalseSharingRange,
|
||||
// but gcc-4.8.0 and 4.8.1 reject it.
|
||||
// FIXME: s/64/kFalseSharingRange/ if that ever changes.
|
||||
#define FOLLY_ON_NEXT_CACHE_LINE __attribute__((aligned(64)))
|
||||
|
||||
/// The maximum number of items in the queue at once
|
||||
size_t capacity_ FOLLY_ON_NEXT_CACHE_LINE;
|
||||
|
||||
/// An array of capacity_ SingleElementQueue-s, each of which holds
|
||||
/// either 0 or 1 item. We over-allocate by 2 * kSlotPadding and don't
|
||||
/// touch the slots at either end, to avoid false sharing
|
||||
detail::SingleElementQueue<T,Atom>* slots_;
|
||||
|
||||
/// The number of slots_ indices that we advance for each ticket, to
|
||||
/// avoid false sharing. Ideally slots_[i] and slots_[i + stride_]
|
||||
/// aren't on the same cache line
|
||||
int stride_;
|
||||
|
||||
/// Enqueuers get tickets from here
|
||||
Atom<uint64_t> pushTicket_ FOLLY_ON_NEXT_CACHE_LINE;
|
||||
|
||||
/// Dequeuers get tickets from here
|
||||
Atom<uint64_t> popTicket_ FOLLY_ON_NEXT_CACHE_LINE;
|
||||
|
||||
/// This is how many times we will spin before using FUTEX_WAIT when
|
||||
/// the queue is full on enqueue, adaptively computed by occasionally
|
||||
/// spinning for longer and smoothing with an exponential moving average
|
||||
Atom<int> pushSpinCutoff_ FOLLY_ON_NEXT_CACHE_LINE;
|
||||
|
||||
/// The adaptive spin cutoff when the queue is empty on dequeue
|
||||
Atom<int> popSpinCutoff_ FOLLY_ON_NEXT_CACHE_LINE;
|
||||
|
||||
/// Alignment doesn't avoid false sharing at the end of the struct,
|
||||
/// so fill out the last cache line
|
||||
char padding_[kFalseSharingRange - sizeof(Atom<int>)];
|
||||
|
||||
#undef FOLLY_ON_NEXT_CACHE_LINE
|
||||
|
||||
/// We assign tickets in increasing order, but we don't want to
|
||||
/// access neighboring elements of slots_ because that will lead to
|
||||
/// false sharing (multiple cores accessing the same cache line even
|
||||
/// though they aren't accessing the same bytes in that cache line).
|
||||
/// To avoid this we advance by stride slots per ticket.
|
||||
///
|
||||
/// We need gcd(capacity, stride) to be 1 so that we will use all
|
||||
/// of the slots. We ensure this by only considering prime strides,
|
||||
/// which either have no common divisors with capacity or else have
|
||||
/// a zero remainder after dividing by capacity. That is sufficient
|
||||
/// to guarantee correctness, but we also want to actually spread the
|
||||
/// accesses away from each other to avoid false sharing (consider a
|
||||
/// stride of 7 with a capacity of 8). To that end we try a few taking
|
||||
/// care to observe that advancing by -1 is as bad as advancing by 1
|
||||
/// when in comes to false sharing.
|
||||
///
|
||||
/// The simple way to avoid false sharing would be to pad each
|
||||
/// SingleElementQueue, but since we have capacity_ of them that could
|
||||
/// waste a lot of space.
|
||||
static int computeStride(size_t capacity) noexcept {
|
||||
static const int smallPrimes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23 };
|
||||
|
||||
int bestStride = 1;
|
||||
size_t bestSep = 1;
|
||||
for (int stride : smallPrimes) {
|
||||
if ((stride % capacity) == 0 || (capacity % stride) == 0) {
|
||||
continue;
|
||||
}
|
||||
size_t sep = stride % capacity;
|
||||
sep = std::min(sep, capacity - sep);
|
||||
if (sep > bestSep) {
|
||||
bestStride = stride;
|
||||
bestSep = sep;
|
||||
}
|
||||
}
|
||||
return bestStride;
|
||||
}
|
||||
|
||||
/// Returns the index into slots_ that should be used when enqueuing or
|
||||
/// dequeuing with the specified ticket
|
||||
size_t idx(uint64_t ticket) noexcept {
|
||||
return ((ticket * stride_) % capacity_) + kSlotPadding;
|
||||
}
|
||||
|
||||
/// Maps an enqueue or dequeue ticket to the turn should be used at the
|
||||
/// corresponding SingleElementQueue
|
||||
uint32_t turn(uint64_t ticket) noexcept {
|
||||
return ticket / capacity_;
|
||||
}
|
||||
|
||||
/// Tries to obtain a push ticket for which SingleElementQueue::enqueue
|
||||
/// won't block. Returns true on immediate success, false on immediate
|
||||
/// failure.
|
||||
bool tryObtainReadyPushTicket(uint64_t& rv) noexcept {
|
||||
auto ticket = pushTicket_.load(std::memory_order_acquire); // A
|
||||
while (true) {
|
||||
if (!slots_[idx(ticket)].mayEnqueue(turn(ticket))) {
|
||||
// if we call enqueue(ticket, ...) on the SingleElementQueue
|
||||
// right now it would block, but this might no longer be the next
|
||||
// ticket. We can increase the chance of tryEnqueue success under
|
||||
// contention (without blocking) by rechecking the ticket dispenser
|
||||
auto prev = ticket;
|
||||
ticket = pushTicket_.load(std::memory_order_acquire); // B
|
||||
if (prev == ticket) {
|
||||
// mayEnqueue was bracketed by two reads (A or prev B or prev
|
||||
// failing CAS to B), so we are definitely unable to enqueue
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// we will bracket the mayEnqueue check with a read (A or prev B
|
||||
// or prev failing CAS) and the following CAS. If the CAS fails
|
||||
// it will effect a load of pushTicket_
|
||||
if (pushTicket_.compare_exchange_strong(ticket, ticket + 1)) {
|
||||
rv = ticket;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to obtain a push ticket which can be satisfied if all
|
||||
/// in-progress pops complete. This function does not block, but
|
||||
/// blocking may be required when using the returned ticket if some
|
||||
/// other thread's pop is still in progress (ticket has been granted but
|
||||
/// pop has not yet completed).
|
||||
bool tryObtainPromisedPushTicket(uint64_t& rv) noexcept {
|
||||
auto numPushes = pushTicket_.load(std::memory_order_acquire); // A
|
||||
while (true) {
|
||||
auto numPops = popTicket_.load(std::memory_order_acquire); // B
|
||||
// n will be negative if pops are pending
|
||||
int64_t n = numPushes - numPops;
|
||||
if (n >= static_cast<ssize_t>(capacity_)) {
|
||||
// Full, linearize at B. We don't need to recheck the read we
|
||||
// performed at A, because if numPushes was stale at B then the
|
||||
// real numPushes value is even worse
|
||||
return false;
|
||||
}
|
||||
if (pushTicket_.compare_exchange_strong(numPushes, numPushes + 1)) {
|
||||
rv = numPushes;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to obtain a pop ticket for which SingleElementQueue::dequeue
|
||||
/// won't block. Returns true on immediate success, false on immediate
|
||||
/// failure.
|
||||
bool tryObtainReadyPopTicket(uint64_t& rv) noexcept {
|
||||
auto ticket = popTicket_.load(std::memory_order_acquire);
|
||||
while (true) {
|
||||
if (!slots_[idx(ticket)].mayDequeue(turn(ticket))) {
|
||||
auto prev = ticket;
|
||||
ticket = popTicket_.load(std::memory_order_acquire);
|
||||
if (prev == ticket) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (popTicket_.compare_exchange_strong(ticket, ticket + 1)) {
|
||||
rv = ticket;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to tryObtainReadyPopTicket, but returns a pop ticket whose
|
||||
/// corresponding push ticket has already been handed out, rather than
|
||||
/// returning one whose corresponding push ticket has already been
|
||||
/// completed. This means that there is a possibility that the caller
|
||||
/// will block when using the ticket, but it allows the user to rely on
|
||||
/// the fact that if enqueue has succeeded, tryObtainPromisedPopTicket
|
||||
/// will return true. The "try" part of this is that we won't have
|
||||
/// to block waiting for someone to call enqueue, although we might
|
||||
/// have to block waiting for them to finish executing code inside the
|
||||
/// MPMCQueue itself.
|
||||
bool tryObtainPromisedPopTicket(uint64_t& rv) noexcept {
|
||||
auto numPops = popTicket_.load(std::memory_order_acquire); // A
|
||||
while (true) {
|
||||
auto numPushes = pushTicket_.load(std::memory_order_acquire); // B
|
||||
if (numPops >= numPushes) {
|
||||
// Empty, or empty with pending pops. Linearize at B. We don't
|
||||
// need to recheck the read we performed at A, because if numPops
|
||||
// is stale then the fresh value is larger and the >= is still true
|
||||
return false;
|
||||
}
|
||||
if (popTicket_.compare_exchange_strong(numPops, numPops + 1)) {
|
||||
rv = numPops;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Given a ticket, constructs an enqueued item using args
|
||||
template <typename ...Args>
|
||||
void enqueueWithTicket(uint64_t ticket, Args&&... args) noexcept {
|
||||
slots_[idx(ticket)].enqueue(turn(ticket),
|
||||
pushSpinCutoff_,
|
||||
(ticket % kAdaptationFreq) == 0,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Given a ticket, dequeues the corresponding element
|
||||
void dequeueWithTicket(uint64_t ticket, T& elem) noexcept {
|
||||
slots_[idx(ticket)].dequeue(turn(ticket),
|
||||
popSpinCutoff_,
|
||||
(ticket % kAdaptationFreq) == 0,
|
||||
elem);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
namespace detail {
|
||||
|
||||
/// A TurnSequencer allows threads to order their execution according to
|
||||
/// a monotonically increasing (with wraparound) "turn" value. The two
|
||||
/// operations provided are to wait for turn T, and to move to the next
|
||||
/// turn. Every thread that is waiting for T must have arrived before
|
||||
/// that turn is marked completed (for MPMCQueue only one thread waits
|
||||
/// for any particular turn, so this is trivially true).
|
||||
///
|
||||
/// TurnSequencer's state_ holds 26 bits of the current turn (shifted
|
||||
/// left by 6), along with a 6 bit saturating value that records the
|
||||
/// maximum waiter minus the current turn. Wraparound of the turn space
|
||||
/// is expected and handled. This allows us to atomically adjust the
|
||||
/// number of outstanding waiters when we perform a FUTEX_WAKE operation.
|
||||
/// Compare this strategy to sem_t's separate num_waiters field, which
|
||||
/// isn't decremented until after the waiting thread gets scheduled,
|
||||
/// during which time more enqueues might have occurred and made pointless
|
||||
/// FUTEX_WAKE calls.
|
||||
///
|
||||
/// TurnSequencer uses futex() directly. It is optimized for the
|
||||
/// case that the highest awaited turn is 32 or less higher than the
|
||||
/// current turn. We use the FUTEX_WAIT_BITSET variant, which lets
|
||||
/// us embed 32 separate wakeup channels in a single futex. See
|
||||
/// http://locklessinc.com/articles/futex_cheat_sheet for a description.
|
||||
///
|
||||
/// We only need to keep exact track of the delta between the current
|
||||
/// turn and the maximum waiter for the 32 turns that follow the current
|
||||
/// one, because waiters at turn t+32 will be awoken at turn t. At that
|
||||
/// point they can then adjust the delta using the higher base. Since we
|
||||
/// need to encode waiter deltas of 0 to 32 inclusive, we use 6 bits.
|
||||
/// We actually store waiter deltas up to 63, since that might reduce
|
||||
/// the number of CAS operations a tiny bit.
|
||||
///
|
||||
/// To avoid some futex() calls entirely, TurnSequencer uses an adaptive
|
||||
/// spin cutoff before waiting. The overheads (and convergence rate)
|
||||
/// of separately tracking the spin cutoff for each TurnSequencer would
|
||||
/// be prohibitive, so the actual storage is passed in as a parameter and
|
||||
/// updated atomically. This also lets the caller use different adaptive
|
||||
/// cutoffs for different operations (read versus write, for example).
|
||||
/// To avoid contention, the spin cutoff is only updated when requested
|
||||
/// by the caller.
|
||||
template <template<typename> class Atom>
|
||||
struct TurnSequencer {
|
||||
explicit TurnSequencer(const uint32_t firstTurn = 0) noexcept
|
||||
: state_(encode(firstTurn << kTurnShift, 0))
|
||||
{}
|
||||
|
||||
/// Returns true iff a call to waitForTurn(turn, ...) won't block
|
||||
bool isTurn(const uint32_t turn) const noexcept {
|
||||
auto state = state_.load(std::memory_order_acquire);
|
||||
return decodeCurrentSturn(state) == (turn << kTurnShift);
|
||||
}
|
||||
|
||||
// Internally we always work with shifted turn values, which makes the
|
||||
// truncation and wraparound work correctly. This leaves us bits at
|
||||
// the bottom to store the number of waiters. We call shifted turns
|
||||
// "sturns" inside this class.
|
||||
|
||||
/// Blocks the current thread until turn has arrived. If
|
||||
/// updateSpinCutoff is true then this will spin for up to kMaxSpins tries
|
||||
/// before blocking and will adjust spinCutoff based on the results,
|
||||
/// otherwise it will spin for at most spinCutoff spins.
|
||||
void waitForTurn(const uint32_t turn,
|
||||
Atom<int>& spinCutoff,
|
||||
const bool updateSpinCutoff) noexcept {
|
||||
int prevThresh = spinCutoff.load(std::memory_order_relaxed);
|
||||
const int effectiveSpinCutoff =
|
||||
updateSpinCutoff || prevThresh == 0 ? kMaxSpins : prevThresh;
|
||||
int tries;
|
||||
|
||||
const uint32_t sturn = turn << kTurnShift;
|
||||
for (tries = 0; ; ++tries) {
|
||||
uint32_t state = state_.load(std::memory_order_acquire);
|
||||
uint32_t current_sturn = decodeCurrentSturn(state);
|
||||
if (current_sturn == sturn) {
|
||||
break;
|
||||
}
|
||||
|
||||
// wrap-safe version of assert(current_sturn < sturn)
|
||||
assert(sturn - current_sturn < std::numeric_limits<uint32_t>::max() / 2);
|
||||
|
||||
// the first effectSpinCutoff tries are spins, after that we will
|
||||
// record ourself as a waiter and block with futexWait
|
||||
if (tries < effectiveSpinCutoff) {
|
||||
asm volatile ("pause");
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t current_max_waiter_delta = decodeMaxWaitersDelta(state);
|
||||
uint32_t our_waiter_delta = (sturn - current_sturn) >> kTurnShift;
|
||||
uint32_t new_state;
|
||||
if (our_waiter_delta <= current_max_waiter_delta) {
|
||||
// state already records us as waiters, probably because this
|
||||
// isn't our first time around this loop
|
||||
new_state = state;
|
||||
} else {
|
||||
new_state = encode(current_sturn, our_waiter_delta);
|
||||
if (state != new_state &&
|
||||
!state_.compare_exchange_strong(state, new_state)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
state_.futexWait(new_state, futexChannel(turn));
|
||||
}
|
||||
|
||||
if (updateSpinCutoff || prevThresh == 0) {
|
||||
// if we hit kMaxSpins then spinning was pointless, so the right
|
||||
// spinCutoff is kMinSpins
|
||||
int target;
|
||||
if (tries >= kMaxSpins) {
|
||||
target = kMinSpins;
|
||||
} else {
|
||||
// to account for variations, we allow ourself to spin 2*N when
|
||||
// we think that N is actually required in order to succeed
|
||||
target = std::min(int{kMaxSpins}, std::max(int{kMinSpins}, tries * 2));
|
||||
}
|
||||
|
||||
if (prevThresh == 0) {
|
||||
// bootstrap
|
||||
spinCutoff = target;
|
||||
} else {
|
||||
// try once, keep moving if CAS fails. Exponential moving average
|
||||
// with alpha of 7/8
|
||||
spinCutoff.compare_exchange_weak(
|
||||
prevThresh, prevThresh + (target - prevThresh) / 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unblocks a thread running waitForTurn(turn + 1)
|
||||
void completeTurn(const uint32_t turn) noexcept {
|
||||
uint32_t state = state_.load(std::memory_order_acquire);
|
||||
while (true) {
|
||||
assert(state == encode(turn << kTurnShift, decodeMaxWaitersDelta(state)));
|
||||
uint32_t max_waiter_delta = decodeMaxWaitersDelta(state);
|
||||
uint32_t new_state = encode(
|
||||
(turn + 1) << kTurnShift,
|
||||
max_waiter_delta == 0 ? 0 : max_waiter_delta - 1);
|
||||
if (state_.compare_exchange_strong(state, new_state)) {
|
||||
if (max_waiter_delta != 0) {
|
||||
state_.futexWake(std::numeric_limits<int>::max(),
|
||||
futexChannel(turn + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
// failing compare_exchange_strong updates first arg to the value
|
||||
// that caused the failure, so no need to reread state_
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the least-most significant byte of the current uncompleted
|
||||
/// turn. The full 32 bit turn cannot be recovered.
|
||||
uint8_t uncompletedTurnLSB() const noexcept {
|
||||
return state_.load(std::memory_order_acquire) >> kTurnShift;
|
||||
}
|
||||
|
||||
private:
|
||||
enum : uint32_t {
|
||||
/// kTurnShift counts the bits that are stolen to record the delta
|
||||
/// between the current turn and the maximum waiter. It needs to be big
|
||||
/// enough to record wait deltas of 0 to 32 inclusive. Waiters more
|
||||
/// than 32 in the future will be woken up 32*n turns early (since
|
||||
/// their BITSET will hit) and will adjust the waiter count again.
|
||||
/// We go a bit beyond and let the waiter count go up to 63, which
|
||||
/// is free and might save us a few CAS
|
||||
kTurnShift = 6,
|
||||
kWaitersMask = (1 << kTurnShift) - 1,
|
||||
|
||||
/// The minimum spin count that we will adaptively select
|
||||
kMinSpins = 20,
|
||||
|
||||
/// The maximum spin count that we will adaptively select, and the
|
||||
/// spin count that will be used when probing to get a new data point
|
||||
/// for the adaptation
|
||||
kMaxSpins = 2000,
|
||||
};
|
||||
|
||||
/// This holds both the current turn, and the highest waiting turn,
|
||||
/// stored as (current_turn << 6) | min(63, max(waited_turn - current_turn))
|
||||
Futex<Atom> state_;
|
||||
|
||||
/// Returns the bitmask to pass futexWait or futexWake when communicating
|
||||
/// about the specified turn
|
||||
int futexChannel(uint32_t turn) const noexcept {
|
||||
return 1 << (turn & 31);
|
||||
}
|
||||
|
||||
uint32_t decodeCurrentSturn(uint32_t state) const noexcept {
|
||||
return state & ~kWaitersMask;
|
||||
}
|
||||
|
||||
uint32_t decodeMaxWaitersDelta(uint32_t state) const noexcept {
|
||||
return state & kWaitersMask;
|
||||
}
|
||||
|
||||
uint32_t encode(uint32_t currentSturn, uint32_t maxWaiterD) const noexcept {
|
||||
return currentSturn | std::min(uint32_t{ kWaitersMask }, maxWaiterD);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// SingleElementQueue implements a blocking queue that holds at most one
|
||||
/// item, and that requires its users to assign incrementing identifiers
|
||||
/// (turns) to each enqueue and dequeue operation. Note that the turns
|
||||
/// used by SingleElementQueue are doubled inside the TurnSequencer
|
||||
template <typename T, template <typename> class Atom>
|
||||
struct SingleElementQueue {
|
||||
|
||||
~SingleElementQueue() noexcept {
|
||||
if ((sequencer_.uncompletedTurnLSB() & 1) == 1) {
|
||||
// we are pending a dequeue, so we have a constructed item
|
||||
destroyContents();
|
||||
}
|
||||
}
|
||||
|
||||
/// enqueue using in-place noexcept construction
|
||||
template <typename ...Args,
|
||||
typename = typename std::enable_if<
|
||||
std::is_nothrow_constructible<T,Args...>::value>::type>
|
||||
void enqueue(const uint32_t turn,
|
||||
Atom<int>& spinCutoff,
|
||||
const bool updateSpinCutoff,
|
||||
Args&&... args) noexcept {
|
||||
sequencer_.waitForTurn(turn * 2, spinCutoff, updateSpinCutoff);
|
||||
new (contents_) T(std::forward<Args>(args)...);
|
||||
sequencer_.completeTurn(turn * 2);
|
||||
}
|
||||
|
||||
/// enqueue using move construction, either real (if
|
||||
/// is_nothrow_move_constructible) or simulated using relocation and
|
||||
/// default construction (if IsRelocatable and has_nothrow_constructor)
|
||||
template <typename = typename std::enable_if<
|
||||
(folly::IsRelocatable<T>::value &&
|
||||
boost::has_nothrow_constructor<T>::value) ||
|
||||
std::is_nothrow_constructible<T,T&&>::value>::type>
|
||||
void enqueue(const uint32_t turn,
|
||||
Atom<int>& spinCutoff,
|
||||
const bool updateSpinCutoff,
|
||||
T&& goner) noexcept {
|
||||
if (std::is_nothrow_constructible<T,T&&>::value) {
|
||||
// this is preferred
|
||||
sequencer_.waitForTurn(turn * 2, spinCutoff, updateSpinCutoff);
|
||||
new (contents_) T(std::move(goner));
|
||||
sequencer_.completeTurn(turn * 2);
|
||||
} else {
|
||||
// simulate nothrow move with relocation, followed by default
|
||||
// construction to fill the gap we created
|
||||
sequencer_.waitForTurn(turn * 2, spinCutoff, updateSpinCutoff);
|
||||
memcpy(contents_, &goner, sizeof(T));
|
||||
sequencer_.completeTurn(turn * 2);
|
||||
new (&goner) T();
|
||||
}
|
||||
}
|
||||
|
||||
bool mayEnqueue(const uint32_t turn) const noexcept {
|
||||
return sequencer_.isTurn(turn * 2);
|
||||
}
|
||||
|
||||
void dequeue(uint32_t turn,
|
||||
Atom<int>& spinCutoff,
|
||||
const bool updateSpinCutoff,
|
||||
T& elem) noexcept {
|
||||
if (folly::IsRelocatable<T>::value) {
|
||||
// this version is preferred, because we do as much work as possible
|
||||
// before waiting
|
||||
try {
|
||||
elem.~T();
|
||||
} catch (...) {
|
||||
// unlikely, but if we don't complete our turn the queue will die
|
||||
}
|
||||
sequencer_.waitForTurn(turn * 2 + 1, spinCutoff, updateSpinCutoff);
|
||||
memcpy(&elem, contents_, sizeof(T));
|
||||
sequencer_.completeTurn(turn * 2 + 1);
|
||||
} else {
|
||||
// use nothrow move assignment
|
||||
sequencer_.waitForTurn(turn * 2 + 1, spinCutoff, updateSpinCutoff);
|
||||
elem = std::move(*ptr());
|
||||
destroyContents();
|
||||
sequencer_.completeTurn(turn * 2 + 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool mayDequeue(const uint32_t turn) const noexcept {
|
||||
return sequencer_.isTurn(turn * 2 + 1);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Storage for a T constructed with placement new
|
||||
char contents_[sizeof(T)] __attribute__((aligned(alignof(T))));
|
||||
|
||||
/// Even turns are pushes, odd turns are pops
|
||||
TurnSequencer<Atom> sequencer_;
|
||||
|
||||
T* ptr() noexcept {
|
||||
return static_cast<T*>(static_cast<void*>(contents_));
|
||||
}
|
||||
|
||||
void destroyContents() noexcept {
|
||||
try {
|
||||
ptr()->~T();
|
||||
} catch (...) {
|
||||
// g++ doesn't seem to have std::is_nothrow_destructible yet
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
memset(contents_, 'Q', sizeof(T));
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace folly
|
||||
-229
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Functions to provide smarter use of jemalloc, if jemalloc is being used.
|
||||
// http://www.canonware.com/download/jemalloc/jemalloc-latest/doc/jemalloc.html
|
||||
|
||||
#ifndef FOLLY_MALLOC_H_
|
||||
#define FOLLY_MALLOC_H_
|
||||
|
||||
// If using fbstring from libstdc++, then just define stub code
|
||||
// here to typedef the fbstring type into the folly namespace.
|
||||
// This provides backwards compatibility for code that explicitly
|
||||
// includes and uses fbstring.
|
||||
#if defined(_GLIBCXX_USE_FB) && !defined(_LIBSTDCXX_FBSTRING)
|
||||
|
||||
#include <string>
|
||||
namespace folly {
|
||||
using std::goodMallocSize;
|
||||
using std::jemallocMinInPlaceExpandable;
|
||||
using std::usingJEMalloc;
|
||||
using std::smartRealloc;
|
||||
using std::checkedMalloc;
|
||||
using std::checkedCalloc;
|
||||
using std::checkedRealloc;
|
||||
}
|
||||
|
||||
#else // !defined(_GLIBCXX_USE_FB) || defined(_LIBSTDCXX_FBSTRING)
|
||||
|
||||
#ifdef _LIBSTDCXX_FBSTRING
|
||||
#pragma GCC system_header
|
||||
#define FOLLY_HAVE_MALLOC_H 1
|
||||
#else
|
||||
#include "folly/Portability.h"
|
||||
#endif
|
||||
|
||||
// for malloc_usable_size
|
||||
// NOTE: FreeBSD 9 doesn't have malloc.h. It's defitions
|
||||
// are found in stdlib.h.
|
||||
#ifdef FOLLY_HAVE_MALLOC_H
|
||||
#include <malloc.h>
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <new>
|
||||
|
||||
#include <bits/functexcept.h>
|
||||
|
||||
/**
|
||||
* Define various ALLOCM_* macros normally provided by jemalloc. We define
|
||||
* them so that we don't have to include jemalloc.h, in case the program is
|
||||
* built without jemalloc support.
|
||||
*/
|
||||
#ifndef ALLOCM_SUCCESS
|
||||
|
||||
#define ALLOCM_SUCCESS 0
|
||||
#define ALLOCM_ERR_OOM 1
|
||||
#define ALLOCM_ERR_NOT_MOVED 2
|
||||
|
||||
#define ALLOCM_ZERO 64
|
||||
#define ALLOCM_NO_MOVE 128
|
||||
|
||||
#define ALLOCM_LG_ALIGN(la) (la)
|
||||
|
||||
#if defined(JEMALLOC_MANGLE) && defined(JEMALLOC_EXPERIMENTAL)
|
||||
#define rallocm je_rallocm
|
||||
#endif
|
||||
|
||||
#endif /* ALLOCM_SUCCESS */
|
||||
|
||||
/**
|
||||
* Declare rallocm() and malloc_usable_size() as weak symbols. It
|
||||
* will be provided by jemalloc if we are using jemalloc, or it will
|
||||
* be NULL if we are using another malloc implementation.
|
||||
*/
|
||||
extern "C" int rallocm(void**, size_t*, size_t, size_t, int)
|
||||
__attribute__((weak));
|
||||
|
||||
#ifdef _LIBSTDCXX_FBSTRING
|
||||
namespace std _GLIBCXX_VISIBILITY(default) {
|
||||
_GLIBCXX_BEGIN_NAMESPACE_VERSION
|
||||
#else
|
||||
namespace folly {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Determine if we are using jemalloc or not.
|
||||
*/
|
||||
inline bool usingJEMalloc() {
|
||||
return rallocm != NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* For jemalloc's size classes, see
|
||||
* http://www.canonware.com/download/jemalloc/jemalloc-latest/doc/jemalloc.html
|
||||
*/
|
||||
inline size_t goodMallocSize(size_t minSize) {
|
||||
if (!usingJEMalloc()) {
|
||||
// Not using jemalloc - no smarts
|
||||
return minSize;
|
||||
}
|
||||
if (minSize <= 64) {
|
||||
// Choose smallest allocation to be 64 bytes - no tripping over
|
||||
// cache line boundaries, and small string optimization takes care
|
||||
// of short strings anyway.
|
||||
return 64;
|
||||
}
|
||||
if (minSize <= 512) {
|
||||
// Round up to the next multiple of 64; we don't want to trip over
|
||||
// cache line boundaries.
|
||||
return (minSize + 63) & ~size_t(63);
|
||||
}
|
||||
if (minSize <= 3840) {
|
||||
// Round up to the next multiple of 256
|
||||
return (minSize + 255) & ~size_t(255);
|
||||
}
|
||||
if (minSize <= 4072 * 1024) {
|
||||
// Round up to the next multiple of 4KB
|
||||
return (minSize + 4095) & ~size_t(4095);
|
||||
}
|
||||
// Holy Moly
|
||||
// Round up to the next multiple of 4MB
|
||||
return (minSize + 4194303) & ~size_t(4194303);
|
||||
}
|
||||
|
||||
// We always request "good" sizes for allocation, so jemalloc can
|
||||
// never grow in place small blocks; they're already occupied to the
|
||||
// brim. Blocks larger than or equal to 4096 bytes can in fact be
|
||||
// expanded in place, and this constant reflects that.
|
||||
static const size_t jemallocMinInPlaceExpandable = 4096;
|
||||
|
||||
/**
|
||||
* Trivial wrappers around malloc, calloc, realloc that check for allocation
|
||||
* failure and throw std::bad_alloc in that case.
|
||||
*/
|
||||
inline void* checkedMalloc(size_t size) {
|
||||
void* p = malloc(size);
|
||||
if (!p) std::__throw_bad_alloc();
|
||||
return p;
|
||||
}
|
||||
|
||||
inline void* checkedCalloc(size_t n, size_t size) {
|
||||
void* p = calloc(n, size);
|
||||
if (!p) std::__throw_bad_alloc();
|
||||
return p;
|
||||
}
|
||||
|
||||
inline void* checkedRealloc(void* ptr, size_t size) {
|
||||
void* p = realloc(ptr, size);
|
||||
if (!p) std::__throw_bad_alloc();
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function tries to reallocate a buffer of which only the first
|
||||
* currentSize bytes are used. The problem with using realloc is that
|
||||
* if currentSize is relatively small _and_ if realloc decides it
|
||||
* needs to move the memory chunk to a new buffer, then realloc ends
|
||||
* up copying data that is not used. It's impossible to hook into
|
||||
* GNU's malloc to figure whether expansion will occur in-place or as
|
||||
* a malloc-copy-free troika. (If an expand_in_place primitive would
|
||||
* be available, smartRealloc would use it.) As things stand, this
|
||||
* routine just tries to call realloc() (thus benefitting of potential
|
||||
* copy-free coalescing) unless there's too much slack memory.
|
||||
*/
|
||||
inline void* smartRealloc(void* p,
|
||||
const size_t currentSize,
|
||||
const size_t currentCapacity,
|
||||
const size_t newCapacity) {
|
||||
assert(p);
|
||||
assert(currentSize <= currentCapacity &&
|
||||
currentCapacity < newCapacity);
|
||||
|
||||
if (usingJEMalloc()) {
|
||||
// using jemalloc's API. Don't forget that jemalloc can never grow
|
||||
// in place blocks smaller than 4096 bytes.
|
||||
if (currentCapacity >= jemallocMinInPlaceExpandable &&
|
||||
rallocm(&p, NULL, newCapacity, 0, ALLOCM_NO_MOVE) == ALLOCM_SUCCESS) {
|
||||
// Managed to expand in place
|
||||
return p;
|
||||
}
|
||||
// Cannot expand; must move
|
||||
auto const result = checkedMalloc(newCapacity);
|
||||
std::memcpy(result, p, currentSize);
|
||||
free(p);
|
||||
return result;
|
||||
}
|
||||
|
||||
// No jemalloc no honey
|
||||
auto const slack = currentCapacity - currentSize;
|
||||
if (slack * 2 > currentSize) {
|
||||
// Too much slack, malloc-copy-free cycle:
|
||||
auto const result = checkedMalloc(newCapacity);
|
||||
std::memcpy(result, p, currentSize);
|
||||
free(p);
|
||||
return result;
|
||||
}
|
||||
// If there's not too much slack, we realloc in hope of coalescing
|
||||
return checkedRealloc(p, newCapacity);
|
||||
}
|
||||
|
||||
#ifdef _LIBSTDCXX_FBSTRING
|
||||
_GLIBCXX_END_NAMESPACE_VERSION
|
||||
#endif
|
||||
|
||||
} // folly
|
||||
|
||||
#endif // !defined(_GLIBCXX_USE_FB) || defined(_LIBSTDCXX_FBSTRING)
|
||||
|
||||
#endif // FOLLY_MALLOC_H_
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_MAPUTIL_H_
|
||||
#define FOLLY_MAPUTIL_H_
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Given a map and a key, return the value corresponding to the key in the map,
|
||||
* or a given default value if the key doesn't exist in the map.
|
||||
*/
|
||||
template <class Map>
|
||||
typename Map::mapped_type get_default(
|
||||
const Map& map, const typename Map::key_type& key,
|
||||
const typename Map::mapped_type& dflt =
|
||||
typename Map::mapped_type()) {
|
||||
auto pos = map.find(key);
|
||||
return (pos != map.end() ? pos->second : dflt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a map and a key, return a reference to the value corresponding to the
|
||||
* key in the map, or the given default reference if the key doesn't exist in
|
||||
* the map.
|
||||
*/
|
||||
template <class Map>
|
||||
const typename Map::mapped_type& get_ref_default(
|
||||
const Map& map, const typename Map::key_type& key,
|
||||
const typename Map::mapped_type& dflt) {
|
||||
auto pos = map.find(key);
|
||||
return (pos != map.end() ? pos->second : dflt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a map and a key, return a pointer to the value corresponding to the
|
||||
* key in the map, or nullptr if the key doesn't exist in the map.
|
||||
*/
|
||||
template <class Map>
|
||||
const typename Map::mapped_type* get_ptr(
|
||||
const Map& map, const typename Map::key_type& key) {
|
||||
auto pos = map.find(key);
|
||||
return (pos != map.end() ? &pos->second : nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-const overload of the above.
|
||||
*/
|
||||
template <class Map>
|
||||
typename Map::mapped_type* get_ptr(
|
||||
Map& map, const typename Map::key_type& key) {
|
||||
auto pos = map.find(key);
|
||||
return (pos != map.end() ? &pos->second : nullptr);
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_MAPUTIL_H_ */
|
||||
|
||||
-346
@@ -1,346 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_MEMORY_H_
|
||||
#define FOLLY_MEMORY_H_
|
||||
|
||||
#include "folly/Traits.h"
|
||||
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* For exception safety and consistency with make_shared. Erase me when
|
||||
* we have std::make_unique().
|
||||
*
|
||||
* @author Louis Brandy (ldbrandy@fb.com)
|
||||
* @author Xu Ning (xning@fb.com)
|
||||
*/
|
||||
|
||||
template<typename T, typename Dp = std::default_delete<T>, typename... Args>
|
||||
std::unique_ptr<T, Dp> make_unique(Args&&... args) {
|
||||
return std::unique_ptr<T, Dp>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/*
|
||||
* StlAllocator wraps a SimpleAllocator into a STL-compliant
|
||||
* allocator, maintaining an instance pointer to the simple allocator
|
||||
* object. The underlying SimpleAllocator object must outlive all
|
||||
* instances of StlAllocator using it.
|
||||
*
|
||||
* A SimpleAllocator must provide two methods:
|
||||
*
|
||||
* void* allocate(size_t size);
|
||||
* void deallocate(void* ptr);
|
||||
*
|
||||
* which, respectively, allocate a block of size bytes (aligned to the
|
||||
* maximum alignment required on your system), throwing std::bad_alloc
|
||||
* if the allocation can't be satisfied, and free a previously
|
||||
* allocated block.
|
||||
*
|
||||
* Note that the following allocator resembles the standard allocator
|
||||
* quite well:
|
||||
*
|
||||
* class MallocAllocator {
|
||||
* public:
|
||||
* void* allocate(size_t size) {
|
||||
* void* p = malloc(size);
|
||||
* if (!p) throw std::bad_alloc();
|
||||
* return p;
|
||||
* }
|
||||
* void deallocate(void* p) {
|
||||
* free(p);
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* But note that if you pass StlAllocator<MallocAllocator,...> to a
|
||||
* standard container it will be larger due to the contained state
|
||||
* pointer.
|
||||
*
|
||||
* author: Tudor Bosman <tudorb@fb.com>
|
||||
*/
|
||||
|
||||
// This would be so much simpler with std::allocator_traits, but gcc 4.6.2
|
||||
// doesn't support it.
|
||||
template <class Alloc, class T> class StlAllocator;
|
||||
|
||||
template <class Alloc> class StlAllocator<Alloc, void> {
|
||||
public:
|
||||
typedef void value_type;
|
||||
typedef void* pointer;
|
||||
typedef const void* const_pointer;
|
||||
|
||||
StlAllocator() : alloc_(nullptr) { }
|
||||
explicit StlAllocator(Alloc* a) : alloc_(a) { }
|
||||
|
||||
Alloc* alloc() const {
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
template <class U> struct rebind {
|
||||
typedef StlAllocator<Alloc, U> other;
|
||||
};
|
||||
|
||||
bool operator!=(const StlAllocator<Alloc, void>& other) const {
|
||||
return alloc_ != other.alloc_;
|
||||
}
|
||||
|
||||
bool operator==(const StlAllocator<Alloc, void>& other) const {
|
||||
return alloc_ == other.alloc_;
|
||||
}
|
||||
|
||||
private:
|
||||
Alloc* alloc_;
|
||||
};
|
||||
|
||||
template <class Alloc, class T>
|
||||
class StlAllocator {
|
||||
public:
|
||||
typedef T value_type;
|
||||
typedef T* pointer;
|
||||
typedef const T* const_pointer;
|
||||
typedef T& reference;
|
||||
typedef const T& const_reference;
|
||||
|
||||
typedef ptrdiff_t difference_type;
|
||||
typedef size_t size_type;
|
||||
|
||||
StlAllocator() : alloc_(nullptr) { }
|
||||
explicit StlAllocator(Alloc* a) : alloc_(a) { }
|
||||
|
||||
template <class U> StlAllocator(const StlAllocator<Alloc, U>& other)
|
||||
: alloc_(other.alloc()) { }
|
||||
|
||||
T* allocate(size_t n, const void* hint = nullptr) {
|
||||
return static_cast<T*>(alloc_->allocate(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T* p, size_t n) {
|
||||
alloc_->deallocate(p);
|
||||
}
|
||||
|
||||
size_t max_size() const {
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
T* address(T& x) const {
|
||||
return std::addressof(x);
|
||||
}
|
||||
|
||||
const T* address(const T& x) const {
|
||||
return std::addressof(x);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void construct(T* p, Args&&... args) {
|
||||
new (p) T(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void destroy(T* p) {
|
||||
p->~T();
|
||||
}
|
||||
|
||||
Alloc* alloc() const {
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
template <class U> struct rebind {
|
||||
typedef StlAllocator<Alloc, U> other;
|
||||
};
|
||||
|
||||
bool operator!=(const StlAllocator<Alloc, T>& other) const {
|
||||
return alloc_ != other.alloc_;
|
||||
}
|
||||
|
||||
bool operator==(const StlAllocator<Alloc, T>& other) const {
|
||||
return alloc_ == other.alloc_;
|
||||
}
|
||||
|
||||
private:
|
||||
Alloc* alloc_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to obtain rebound allocators
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
template <typename T, typename Allocator>
|
||||
typename Allocator::template rebind<T>::other rebind_allocator(
|
||||
Allocator const& allocator
|
||||
) {
|
||||
return typename Allocator::template rebind<T>::other(allocator);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper classes/functions for creating a unique_ptr using a custom
|
||||
* allocator.
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
|
||||
// Derives from the allocator to take advantage of the empty base
|
||||
// optimization when possible.
|
||||
template <typename Allocator>
|
||||
class allocator_delete
|
||||
: private std::remove_reference<Allocator>::type
|
||||
{
|
||||
typedef typename std::remove_reference<Allocator>::type allocator_type;
|
||||
|
||||
public:
|
||||
typedef typename Allocator::pointer pointer;
|
||||
|
||||
allocator_delete() = default;
|
||||
|
||||
explicit allocator_delete(const allocator_type& allocator)
|
||||
: allocator_type(allocator)
|
||||
{}
|
||||
|
||||
explicit allocator_delete(allocator_type&& allocator)
|
||||
: allocator_type(std::move(allocator))
|
||||
{}
|
||||
|
||||
template <typename U>
|
||||
allocator_delete(const allocator_delete<U>& other)
|
||||
: allocator_type(other.get_allocator())
|
||||
{}
|
||||
|
||||
allocator_type& get_allocator() const {
|
||||
return *const_cast<allocator_delete*>(this);
|
||||
}
|
||||
|
||||
void operator()(pointer p) const {
|
||||
if (!p) return;
|
||||
const_cast<allocator_delete*>(this)->destroy(p);
|
||||
const_cast<allocator_delete*>(this)->deallocate(p, 1);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
class is_simple_allocator {
|
||||
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_destroy, destroy);
|
||||
|
||||
typedef typename std::remove_const<
|
||||
typename std::remove_reference<Allocator>::type
|
||||
>::type allocator;
|
||||
typedef typename std::remove_reference<T>::type value_type;
|
||||
typedef value_type* pointer;
|
||||
|
||||
public:
|
||||
constexpr static bool value = !has_destroy<allocator, void(pointer)>::value
|
||||
&& !has_destroy<allocator, void(void*)>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
struct as_stl_allocator {
|
||||
typedef typename std::conditional<
|
||||
is_simple_allocator<T, Allocator>::value,
|
||||
folly::StlAllocator<
|
||||
typename std::remove_reference<Allocator>::type,
|
||||
typename std::remove_reference<T>::type
|
||||
>,
|
||||
typename std::remove_reference<Allocator>::type
|
||||
>::type type;
|
||||
};
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
typename std::enable_if<
|
||||
is_simple_allocator<T, Allocator>::value,
|
||||
folly::StlAllocator<
|
||||
typename std::remove_reference<Allocator>::type,
|
||||
typename std::remove_reference<T>::type
|
||||
>
|
||||
>::type make_stl_allocator(Allocator&& allocator) {
|
||||
return folly::StlAllocator<
|
||||
typename std::remove_reference<Allocator>::type,
|
||||
typename std::remove_reference<T>::type
|
||||
>(&allocator);
|
||||
}
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
typename std::enable_if<
|
||||
!is_simple_allocator<T, Allocator>::value,
|
||||
typename std::remove_reference<Allocator>::type
|
||||
>::type make_stl_allocator(Allocator&& allocator) {
|
||||
return std::move(allocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* AllocatorUniquePtr: a unique_ptr that supports both STL-style
|
||||
* allocators and SimpleAllocator
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
struct AllocatorUniquePtr {
|
||||
typedef std::unique_ptr<T,
|
||||
folly::allocator_delete<
|
||||
typename std::conditional<
|
||||
is_simple_allocator<T, Allocator>::value,
|
||||
folly::StlAllocator<typename std::remove_reference<Allocator>::type, T>,
|
||||
typename std::remove_reference<Allocator>::type
|
||||
>::type
|
||||
>
|
||||
> type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions to allocate a unique_ptr / shared_ptr, supporting both
|
||||
* STL-style allocators and SimpleAllocator, analog to std::allocate_shared
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
|
||||
template <typename T, typename Allocator, typename ...Args>
|
||||
typename AllocatorUniquePtr<T, Allocator>::type allocate_unique(
|
||||
Allocator&& allocator, Args&&... args
|
||||
) {
|
||||
auto stlAllocator = folly::make_stl_allocator<T>(
|
||||
std::forward<Allocator>(allocator)
|
||||
);
|
||||
auto p = stlAllocator.allocate(1);
|
||||
|
||||
try {
|
||||
stlAllocator.construct(p, std::forward<Args>(args)...);
|
||||
|
||||
return {p,
|
||||
folly::allocator_delete<decltype(stlAllocator)>(std::move(stlAllocator))
|
||||
};
|
||||
} catch (...) {
|
||||
stlAllocator.deallocate(p, 1);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Allocator, typename ...Args>
|
||||
std::shared_ptr<T> allocate_shared(Allocator&& allocator, Args&&... args) {
|
||||
return std::allocate_shared<T>(
|
||||
folly::make_stl_allocator<T>(std::forward<Allocator>(allocator)),
|
||||
std::forward<Args>(args)...
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_MEMORY_H_ */
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/MemoryMapping.h"
|
||||
#include "folly/Format.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <system_error>
|
||||
|
||||
#define FLAGS_mlock_chunk_size (1 << 20) // 1MB
|
||||
|
||||
|
||||
|
||||
namespace folly {
|
||||
|
||||
/* protected constructor */
|
||||
MemoryMapping::MemoryMapping()
|
||||
: mapStart_(nullptr)
|
||||
, mapLength_(0)
|
||||
, locked_(false) {
|
||||
}
|
||||
|
||||
MemoryMapping::MemoryMapping(File file, off_t offset, off_t length)
|
||||
: mapStart_(nullptr)
|
||||
, mapLength_(0)
|
||||
, locked_(false) {
|
||||
|
||||
init(std::move(file), offset, length, PROT_READ, false);
|
||||
}
|
||||
|
||||
void MemoryMapping::init(File file,
|
||||
off_t offset, off_t length,
|
||||
int prot,
|
||||
bool grow) {
|
||||
off_t pageSize = sysconf(_SC_PAGESIZE);
|
||||
CHECK_GE(offset, 0);
|
||||
|
||||
// Round down the start of the mapped region
|
||||
size_t skipStart = offset % pageSize;
|
||||
offset -= skipStart;
|
||||
|
||||
file_ = std::move(file);
|
||||
mapLength_ = length;
|
||||
if (mapLength_ != -1) {
|
||||
mapLength_ += skipStart;
|
||||
|
||||
// Round up the end of the mapped region
|
||||
mapLength_ = (mapLength_ + pageSize - 1) / pageSize * pageSize;
|
||||
}
|
||||
|
||||
// stat the file
|
||||
struct stat st;
|
||||
CHECK_ERR(fstat(file_.fd(), &st));
|
||||
off_t remaining = st.st_size - offset;
|
||||
if (mapLength_ == -1) {
|
||||
length = mapLength_ = remaining;
|
||||
} else {
|
||||
if (length > remaining) {
|
||||
if (grow) {
|
||||
PCHECK(0 == ftruncate(file_.fd(), offset + length))
|
||||
<< "ftructate() failed, couldn't grow file";
|
||||
remaining = length;
|
||||
} else {
|
||||
length = remaining;
|
||||
}
|
||||
}
|
||||
if (mapLength_ > remaining) mapLength_ = remaining;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
mapLength_ = 0;
|
||||
mapStart_ = nullptr;
|
||||
} else {
|
||||
unsigned char* start = static_cast<unsigned char*>(
|
||||
mmap(nullptr, mapLength_, prot, MAP_SHARED, file_.fd(), offset));
|
||||
PCHECK(start != MAP_FAILED)
|
||||
<< " offset=" << offset
|
||||
<< " length=" << mapLength_;
|
||||
mapStart_ = start;
|
||||
data_.reset(start + skipStart, length);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
off_t memOpChunkSize(off_t length) {
|
||||
off_t chunkSize = length;
|
||||
if (FLAGS_mlock_chunk_size <= 0) {
|
||||
return chunkSize;
|
||||
}
|
||||
|
||||
chunkSize = FLAGS_mlock_chunk_size;
|
||||
off_t pageSize = sysconf(_SC_PAGESIZE);
|
||||
off_t r = chunkSize % pageSize;
|
||||
if (r) {
|
||||
chunkSize += (pageSize - r);
|
||||
}
|
||||
return chunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run @op in chunks over the buffer @mem of @bufSize length.
|
||||
*
|
||||
* Return:
|
||||
* - success: true + amountSucceeded == bufSize (op success on whole buffer)
|
||||
* - failure: false + amountSucceeded == nr bytes on which op succeeded.
|
||||
*/
|
||||
bool memOpInChunks(std::function<int(void*, size_t)> op,
|
||||
void* mem, size_t bufSize,
|
||||
size_t& amountSucceeded) {
|
||||
// unmap/mlock/munlock take a kernel semaphore and block other threads from
|
||||
// doing other memory operations. If the size of the buffer is big the
|
||||
// semaphore can be down for seconds (for benchmarks see
|
||||
// http://kostja-osipov.livejournal.com/42963.html). Doing the operations in
|
||||
// chunks breaks the locking into intervals and lets other threads do memory
|
||||
// operations of their own.
|
||||
|
||||
size_t chunkSize = memOpChunkSize(bufSize);
|
||||
|
||||
char* addr = static_cast<char*>(mem);
|
||||
amountSucceeded = 0;
|
||||
|
||||
while (amountSucceeded < bufSize) {
|
||||
size_t size = std::min(chunkSize, bufSize - amountSucceeded);
|
||||
if (op(addr + amountSucceeded, size) != 0) {
|
||||
return false;
|
||||
}
|
||||
amountSucceeded += size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool MemoryMapping::mlock(LockMode lock) {
|
||||
size_t amountSucceeded = 0;
|
||||
locked_ = memOpInChunks(::mlock, mapStart_, mapLength_, amountSucceeded);
|
||||
if (locked_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto msg(folly::format(
|
||||
"mlock({}) failed at {}",
|
||||
mapLength_, amountSucceeded).str());
|
||||
|
||||
if (lock == LockMode::TRY_LOCK && (errno == EPERM || errno == ENOMEM)) {
|
||||
PLOG(WARNING) << msg;
|
||||
} else {
|
||||
PLOG(FATAL) << msg;
|
||||
}
|
||||
|
||||
// only part of the buffer was mlocked, unlock it back
|
||||
if (!memOpInChunks(::munlock, mapStart_, amountSucceeded, amountSucceeded)) {
|
||||
PLOG(WARNING) << "munlock()";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MemoryMapping::munlock(bool dontneed) {
|
||||
if (!locked_) return;
|
||||
|
||||
size_t amountSucceeded = 0;
|
||||
if (!memOpInChunks(::munlock, mapStart_, mapLength_, amountSucceeded)) {
|
||||
PLOG(WARNING) << "munlock()";
|
||||
}
|
||||
if (mapLength_ && dontneed &&
|
||||
::madvise(mapStart_, mapLength_, MADV_DONTNEED)) {
|
||||
PLOG(WARNING) << "madvise()";
|
||||
}
|
||||
locked_ = false;
|
||||
}
|
||||
|
||||
void MemoryMapping::hintLinearScan() {
|
||||
advise(MADV_SEQUENTIAL);
|
||||
}
|
||||
|
||||
MemoryMapping::~MemoryMapping() {
|
||||
if (mapLength_) {
|
||||
size_t amountSucceeded = 0;
|
||||
if (!memOpInChunks(::munmap, mapStart_, mapLength_, amountSucceeded)) {
|
||||
PLOG(FATAL) << folly::format(
|
||||
"munmap({}) failed at {}",
|
||||
mapLength_, amountSucceeded).str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryMapping::advise(int advice) const {
|
||||
if (mapLength_ && ::madvise(mapStart_, mapLength_, advice)) {
|
||||
PLOG(WARNING) << "madvise()";
|
||||
}
|
||||
}
|
||||
|
||||
WritableMemoryMapping::WritableMemoryMapping(File file, off_t offset, off_t length) {
|
||||
init(std::move(file), offset, length, PROT_READ | PROT_WRITE, true);
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
-163
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_MEMORYMAPPING_H_
|
||||
#define FOLLY_MEMORYMAPPING_H_
|
||||
|
||||
#include "folly/FBString.h"
|
||||
#include "folly/File.h"
|
||||
#include "folly/Range.h"
|
||||
#include <glog/logging.h>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Maps files in memory (read-only).
|
||||
*
|
||||
* @author Tudor Bosman (tudorb@fb.com)
|
||||
*/
|
||||
class MemoryMapping : boost::noncopyable {
|
||||
public:
|
||||
/**
|
||||
* Lock the pages in memory?
|
||||
* TRY_LOCK = try to lock, log warning if permission denied
|
||||
* MUST_LOCK = lock, fail assertion if permission denied.
|
||||
*/
|
||||
enum class LockMode {
|
||||
TRY_LOCK,
|
||||
MUST_LOCK
|
||||
};
|
||||
/**
|
||||
* Map a portion of the file indicated by filename in memory, causing a CHECK
|
||||
* failure on error.
|
||||
*
|
||||
* By default, map the whole file. length=-1: map from offset to EOF.
|
||||
* Unlike the mmap() system call, offset and length don't need to be
|
||||
* page-aligned. length is clipped to the end of the file if it's too large.
|
||||
*
|
||||
* The mapping will be destroyed (and the memory pointed-to by data() will
|
||||
* likely become inaccessible) when the MemoryMapping object is destroyed.
|
||||
*/
|
||||
explicit MemoryMapping(File file,
|
||||
off_t offset=0,
|
||||
off_t length=-1);
|
||||
|
||||
virtual ~MemoryMapping();
|
||||
|
||||
/**
|
||||
* Lock the pages in memory
|
||||
*/
|
||||
bool mlock(LockMode lock);
|
||||
|
||||
/**
|
||||
* Unlock the pages.
|
||||
* If dontneed is true, the kernel is instructed to release these pages
|
||||
* (per madvise(MADV_DONTNEED)).
|
||||
*/
|
||||
void munlock(bool dontneed=false);
|
||||
|
||||
/**
|
||||
* Hint that these pages will be scanned linearly.
|
||||
* madvise(MADV_SEQUENTIAL)
|
||||
*/
|
||||
void hintLinearScan();
|
||||
|
||||
/**
|
||||
* Advise the kernel about memory access.
|
||||
*/
|
||||
void advise(int advice) const;
|
||||
|
||||
/**
|
||||
* A bitwise cast of the mapped bytes as range of values. Only intended for
|
||||
* use with POD or in-place usable types.
|
||||
*/
|
||||
template<class T>
|
||||
Range<const T*> asRange() const {
|
||||
size_t count = data_.size() / sizeof(T);
|
||||
return Range<const T*>(static_cast<const T*>(
|
||||
static_cast<const void*>(data_.data())),
|
||||
count);
|
||||
}
|
||||
|
||||
/**
|
||||
* A range of bytes mapped by this mapping.
|
||||
*/
|
||||
Range<const uint8_t*> range() const {
|
||||
return {data_.begin(), data_.end()};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the memory area where the file was mapped.
|
||||
*/
|
||||
StringPiece data() const {
|
||||
return asRange<const char>();
|
||||
}
|
||||
|
||||
bool mlocked() const {
|
||||
return locked_;
|
||||
}
|
||||
|
||||
int fd() const { return file_.fd(); }
|
||||
|
||||
protected:
|
||||
MemoryMapping();
|
||||
|
||||
void init(File file,
|
||||
off_t offset, off_t length,
|
||||
int prot,
|
||||
bool grow);
|
||||
|
||||
File file_;
|
||||
void* mapStart_;
|
||||
off_t mapLength_;
|
||||
bool locked_;
|
||||
Range<uint8_t*> data_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps files in memory for writing.
|
||||
*
|
||||
* @author Tom Jackson (tjackson@fb.com)
|
||||
*/
|
||||
class WritableMemoryMapping : public MemoryMapping {
|
||||
public:
|
||||
explicit WritableMemoryMapping(File file,
|
||||
off_t offset = 0,
|
||||
off_t length = -1);
|
||||
/**
|
||||
* A bitwise cast of the mapped bytes as range of mutable values. Only
|
||||
* intended for use with POD or in-place usable types.
|
||||
*/
|
||||
template<class T>
|
||||
Range<T*> asWritableRange() const {
|
||||
size_t count = data_.size() / sizeof(T);
|
||||
return Range<T*>(static_cast<T*>(
|
||||
static_cast<void*>(data_.data())),
|
||||
count);
|
||||
}
|
||||
|
||||
/**
|
||||
* A range of mutable bytes mapped by this mapping.
|
||||
*/
|
||||
Range<uint8_t*> writableRange() const {
|
||||
return data_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_MEMORYMAPPING_H_ */
|
||||
-310
@@ -1,310 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_OPTIONAL_H_
|
||||
#define FOLLY_OPTIONAL_H_
|
||||
|
||||
/*
|
||||
* Optional - For conditional initialization of values, like boost::optional,
|
||||
* but with support for move semantics and emplacement. Reference type support
|
||||
* has not been included due to limited use cases and potential confusion with
|
||||
* semantics of assignment: Assigning to an optional reference could quite
|
||||
* reasonably copy its value or redirect the reference.
|
||||
*
|
||||
* Optional can be useful when a variable might or might not be needed:
|
||||
*
|
||||
* Optional<Logger> maybeLogger = ...;
|
||||
* if (maybeLogger) {
|
||||
* maybeLogger->log("hello");
|
||||
* }
|
||||
*
|
||||
* Optional enables a 'null' value for types which do not otherwise have
|
||||
* nullability, especially useful for parameter passing:
|
||||
*
|
||||
* void testIterator(const unique_ptr<Iterator>& it,
|
||||
* initializer_list<int> idsExpected,
|
||||
* Optional<initializer_list<int>> ranksExpected = none) {
|
||||
* for (int i = 0; it->next(); ++i) {
|
||||
* EXPECT_EQ(it->doc().id(), idsExpected[i]);
|
||||
* if (ranksExpected) {
|
||||
* EXPECT_EQ(it->doc().rank(), (*ranksExpected)[i]);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Optional models OptionalPointee, so calling 'get_pointer(opt)' will return a
|
||||
* pointer to nullptr if the 'opt' is empty, and a pointer to the value if it is
|
||||
* not:
|
||||
*
|
||||
* Optional<int> maybeInt = ...;
|
||||
* if (int* v = get_pointer(maybeInt)) {
|
||||
* cout << *v << endl;
|
||||
* }
|
||||
*/
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/operators.hpp>
|
||||
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace detail { struct NoneHelper {}; }
|
||||
|
||||
typedef int detail::NoneHelper::*None;
|
||||
|
||||
const None none = nullptr;
|
||||
|
||||
/**
|
||||
* gcc-4.7 warns about use of uninitialized memory around the use of storage_
|
||||
* even though this is explicitly initialized at each point.
|
||||
*/
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wuninitialized"
|
||||
# pragma GCC diagnostic ignored "-Wpragmas"
|
||||
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
||||
#endif // __GNUC__
|
||||
|
||||
template<class Value>
|
||||
class Optional {
|
||||
public:
|
||||
static_assert(!std::is_reference<Value>::value,
|
||||
"Optional may not be used with reference types");
|
||||
|
||||
Optional()
|
||||
: hasValue_(false) {
|
||||
}
|
||||
|
||||
Optional(const Optional& src) {
|
||||
if (src.hasValue()) {
|
||||
construct(src.value());
|
||||
} else {
|
||||
hasValue_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
Optional(Optional&& src) {
|
||||
if (src.hasValue()) {
|
||||
construct(std::move(src.value()));
|
||||
src.clear();
|
||||
} else {
|
||||
hasValue_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* implicit */ Optional(const None&)
|
||||
: hasValue_(false) {
|
||||
}
|
||||
|
||||
/* implicit */ Optional(Value&& newValue) {
|
||||
construct(std::move(newValue));
|
||||
}
|
||||
|
||||
/* implicit */ Optional(const Value& newValue) {
|
||||
construct(newValue);
|
||||
}
|
||||
|
||||
~Optional() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void assign(const None&) {
|
||||
clear();
|
||||
}
|
||||
|
||||
void assign(Optional&& src) {
|
||||
if (src.hasValue()) {
|
||||
assign(std::move(src.value()));
|
||||
src.clear();
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
void assign(const Optional& src) {
|
||||
if (src.hasValue()) {
|
||||
assign(src.value());
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
void assign(Value&& newValue) {
|
||||
if (hasValue()) {
|
||||
value_ = std::move(newValue);
|
||||
} else {
|
||||
construct(std::move(newValue));
|
||||
}
|
||||
}
|
||||
|
||||
void assign(const Value& newValue) {
|
||||
if (hasValue()) {
|
||||
value_ = newValue;
|
||||
} else {
|
||||
construct(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Arg>
|
||||
Optional& operator=(Arg&& arg) {
|
||||
assign(std::forward<Arg>(arg));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Optional& operator=(Optional &&other) {
|
||||
assign(std::move(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Optional& operator=(const Optional &other) {
|
||||
assign(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
void emplace(Args&&... args) {
|
||||
clear();
|
||||
construct(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if (hasValue()) {
|
||||
hasValue_ = false;
|
||||
value_.~Value();
|
||||
}
|
||||
}
|
||||
|
||||
const Value& value() const {
|
||||
assert(hasValue());
|
||||
return value_;
|
||||
}
|
||||
|
||||
Value& value() {
|
||||
assert(hasValue());
|
||||
return value_;
|
||||
}
|
||||
|
||||
bool hasValue() const { return hasValue_; }
|
||||
|
||||
explicit operator bool() const {
|
||||
return hasValue();
|
||||
}
|
||||
|
||||
const Value& operator*() const { return value(); }
|
||||
Value& operator*() { return value(); }
|
||||
|
||||
const Value* operator->() const { return &value(); }
|
||||
Value* operator->() { return &value(); }
|
||||
|
||||
private:
|
||||
template<class... Args>
|
||||
void construct(Args&&... args) {
|
||||
const void* ptr = &value_;
|
||||
// for supporting const types
|
||||
new(const_cast<void*>(ptr)) Value(std::forward<Args>(args)...);
|
||||
hasValue_ = true;
|
||||
}
|
||||
|
||||
// uninitialized
|
||||
union { Value value_; };
|
||||
bool hasValue_;
|
||||
};
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
template<class T>
|
||||
const T* get_pointer(const Optional<T>& opt) {
|
||||
return opt ? &opt.value() : nullptr;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T* get_pointer(Optional<T>& opt) {
|
||||
return opt ? &opt.value() : nullptr;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void swap(Optional<T>& a, Optional<T>& b) {
|
||||
if (a.hasValue() && b.hasValue()) {
|
||||
// both full
|
||||
using std::swap;
|
||||
swap(a.value(), b.value());
|
||||
} else if (a.hasValue() || b.hasValue()) {
|
||||
std::swap(a, b); // fall back to default implementation if they're mixed.
|
||||
}
|
||||
}
|
||||
|
||||
template<class T,
|
||||
class Opt = Optional<typename std::decay<T>::type>>
|
||||
Opt make_optional(T&& v) {
|
||||
return Opt(std::forward<T>(v));
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator< (const Optional<V>& a, const Optional<V>& b) {
|
||||
if (a.hasValue() != b.hasValue()) { return a.hasValue() < b.hasValue(); }
|
||||
if (a.hasValue()) { return a.value() < b.value(); }
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator==(const Optional<V>& a, const Optional<V>& b) {
|
||||
if (a.hasValue() != b.hasValue()) { return false; }
|
||||
if (a.hasValue()) { return a.value() == b.value(); }
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator<=(const Optional<V>& a, const Optional<V>& b) {
|
||||
return !(b < a);
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator!=(const Optional<V>& a, const Optional<V>& b) {
|
||||
return !(b == a);
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator>=(const Optional<V>& a, const Optional<V>& b) {
|
||||
return !(a < b);
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator> (const Optional<V>& a, const Optional<V>& b) {
|
||||
return b < a;
|
||||
}
|
||||
|
||||
// To supress comparability of Optional<T> with T, despite implicit conversion.
|
||||
template<class V> bool operator< (const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator<=(const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator==(const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator!=(const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator>=(const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator> (const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator< (const V& other, const Optional<V>&) = delete;
|
||||
template<class V> bool operator<=(const V& other, const Optional<V>&) = delete;
|
||||
template<class V> bool operator==(const V& other, const Optional<V>&) = delete;
|
||||
template<class V> bool operator!=(const V& other, const Optional<V>&) = delete;
|
||||
template<class V> bool operator>=(const V& other, const Optional<V>&) = delete;
|
||||
template<class V> bool operator> (const V& other, const Optional<V>&) = delete;
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif//FOLLY_OPTIONAL_H_
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_PACKEDSYNCPTR_H_
|
||||
#define FOLLY_PACKEDSYNCPTR_H_
|
||||
|
||||
#ifndef __x86_64__
|
||||
# error "PackedSyncPtr is x64-specific code."
|
||||
#endif
|
||||
|
||||
/*
|
||||
* An 8-byte pointer with an integrated spin lock and 15-bit integer
|
||||
* (you can use this for a size of the allocation, if you want, or
|
||||
* something else, or nothing).
|
||||
*
|
||||
* This is using an x64-specific detail about the effective virtual
|
||||
* address space. Long story short: the upper two bytes of all our
|
||||
* pointers will be zero in reality---and if you have a couple billion
|
||||
* such pointers in core, it makes pretty good sense to try to make
|
||||
* use of that memory. The exact details can be perused here:
|
||||
*
|
||||
* http://en.wikipedia.org/wiki/X86-64#Canonical_form_addresses
|
||||
*
|
||||
* This is not a "smart" pointer: nothing automagical is going on
|
||||
* here. Locking is up to the user. Resource deallocation is up to
|
||||
* the user. Locks are never acquired or released outside explicit
|
||||
* calls to lock() and unlock().
|
||||
*
|
||||
* Change the value of the raw pointer with set(), but you must hold
|
||||
* the lock when calling this function if multiple threads could be
|
||||
* using this class.
|
||||
*
|
||||
* TODO(jdelong): should we use the low order bit for the lock, so we
|
||||
* get a whole 16-bits for our integer? (There's also 2 more bits
|
||||
* down there if the pointer comes from malloc.)
|
||||
*
|
||||
* @author Spencer Ahrens <sahrens@fb.com>
|
||||
* @author Jordan DeLong <delong.j@fb.com>
|
||||
*/
|
||||
|
||||
#include "folly/SmallLocks.h"
|
||||
#include <type_traits>
|
||||
#include <glog/logging.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
template<class T>
|
||||
class PackedSyncPtr {
|
||||
// This just allows using this class even with T=void. Attempting
|
||||
// to use the operator* or operator[] on a PackedSyncPtr<void> will
|
||||
// still properly result in a compile error.
|
||||
typedef typename std::add_lvalue_reference<T>::type reference;
|
||||
|
||||
public:
|
||||
/*
|
||||
* If you default construct one of these, you must call this init()
|
||||
* function before using it.
|
||||
*
|
||||
* (We are avoiding a constructor to ensure gcc allows us to put
|
||||
* this class in packed structures.)
|
||||
*/
|
||||
void init(T* initialPtr = 0, uint16_t initialExtra = 0) {
|
||||
auto intPtr = reinterpret_cast<uintptr_t>(initialPtr);
|
||||
CHECK(!(intPtr >> 48));
|
||||
data_.init(intPtr);
|
||||
setExtra(initialExtra);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets a new pointer. You must hold the lock when calling this
|
||||
* function, or else be able to guarantee no other threads could be
|
||||
* using this PackedSyncPtr<>.
|
||||
*/
|
||||
void set(T* t) {
|
||||
auto intPtr = reinterpret_cast<uintptr_t>(t);
|
||||
auto shiftedExtra = uintptr_t(extra()) << 48;
|
||||
CHECK(!(intPtr >> 48));
|
||||
data_.setData(intPtr | shiftedExtra);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the pointer.
|
||||
*
|
||||
* You can call any of these without holding the lock, with the
|
||||
* normal types of behavior you'll get on x64 from reading a pointer
|
||||
* without locking.
|
||||
*/
|
||||
T* get() const {
|
||||
return reinterpret_cast<T*>(data_.getData() & (-1ull >> 16));
|
||||
}
|
||||
T* operator->() const { return get(); }
|
||||
reference operator*() const { return *get(); }
|
||||
reference operator[](std::ptrdiff_t i) const { return get()[i]; }
|
||||
|
||||
// Synchronization (logically const, even though this mutates our
|
||||
// locked state: you can lock a const PackedSyncPtr<T> to read it).
|
||||
void lock() const { data_.lock(); }
|
||||
void unlock() const { data_.unlock(); }
|
||||
bool try_lock() const { return data_.try_lock(); }
|
||||
|
||||
/*
|
||||
* Access extra data stored in unused bytes of the pointer.
|
||||
*
|
||||
* It is ok to call this without holding the lock.
|
||||
*/
|
||||
uint16_t extra() const {
|
||||
return data_.getData() >> 48;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't try to put anything into this that has the high bit set:
|
||||
* that's what we're using for the mutex.
|
||||
*
|
||||
* Don't call this without holding the lock.
|
||||
*/
|
||||
void setExtra(uint16_t extra) {
|
||||
CHECK(!(extra & 0x8000));
|
||||
auto ptr = data_.getData() & (-1ull >> 16);
|
||||
data_.setData((uintptr_t(extra) << 48) | ptr);
|
||||
}
|
||||
|
||||
// Logically private, but we can't have private data members and
|
||||
// still be considered a POD. (In C++11 we are still a standard
|
||||
// layout struct if this is private, but it doesn't matter, since
|
||||
// gcc (4.6) won't let us use this with attribute packed still in
|
||||
// that case.)
|
||||
PicoSpinLock<uintptr_t> data_;
|
||||
};
|
||||
|
||||
static_assert(sizeof(PackedSyncPtr<void>) == 8,
|
||||
"PackedSyncPtr should be only 8 bytes---something is "
|
||||
"messed up");
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
-506
@@ -1,506 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_PADDED_H_
|
||||
#define FOLLY_PADDED_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/iterator/iterator_adaptor.hpp>
|
||||
|
||||
#include "folly/Portability.h"
|
||||
|
||||
/**
|
||||
* Code that aids in storing data aligned on block (possibly cache-line)
|
||||
* boundaries, perhaps with padding.
|
||||
*
|
||||
* Class Node represents one block. Given an iterator to a container of
|
||||
* Node, class Iterator encapsulates an iterator to the underlying elements.
|
||||
* Adaptor converts a sequence of Node into a sequence of underlying elements
|
||||
* (not fully compatible with STL container requirements, see comments
|
||||
* near the Node class declaration).
|
||||
*/
|
||||
|
||||
namespace folly {
|
||||
namespace padded {
|
||||
|
||||
/**
|
||||
* A Node is a fixed-size container of as many objects of type T as would
|
||||
* fit in a region of memory of size NS. The last NS % sizeof(T)
|
||||
* bytes are ignored and uninitialized.
|
||||
*
|
||||
* Node only works for trivial types, which is usually not a concern. This
|
||||
* is intentional: Node itself is trivial, which means that it can be
|
||||
* serialized / deserialized using a simple memcpy.
|
||||
*/
|
||||
template <class T, size_t NS, class Enable=void>
|
||||
class Node;
|
||||
|
||||
namespace detail {
|
||||
// Shortcut to avoid writing the long enable_if expression every time
|
||||
template <class T, size_t NS, class Enable=void> struct NodeValid;
|
||||
template <class T, size_t NS>
|
||||
struct NodeValid<T, NS,
|
||||
typename std::enable_if<(
|
||||
std::is_trivial<T>::value &&
|
||||
sizeof(T) <= NS &&
|
||||
NS % alignof(T) == 0)>::type> {
|
||||
typedef void type;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <class T, size_t NS>
|
||||
class Node<T, NS, typename detail::NodeValid<T,NS>::type> {
|
||||
public:
|
||||
typedef T value_type;
|
||||
static constexpr size_t kNodeSize = NS;
|
||||
static constexpr size_t kElementCount = NS / sizeof(T);
|
||||
static constexpr size_t kPaddingBytes = NS % sizeof(T);
|
||||
|
||||
T* data() { return storage_.data; }
|
||||
const T* data() const { return storage_.data; }
|
||||
|
||||
bool operator==(const Node& other) const {
|
||||
return memcmp(data(), other.data(), sizeof(T) * kElementCount) == 0;
|
||||
}
|
||||
bool operator!=(const Node& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of nodes needed to represent n values. Rounds up.
|
||||
*/
|
||||
static constexpr size_t nodeCount(size_t n) {
|
||||
return (n + kElementCount - 1) / kElementCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total byte size needed to represent n values, rounded up
|
||||
* to the nearest full node.
|
||||
*/
|
||||
static constexpr size_t paddedByteSize(size_t n) {
|
||||
return nodeCount(n) * NS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of bytes used for padding n values.
|
||||
* Note that, even if n is a multiple of kElementCount, this may
|
||||
* return non-zero if kPaddingBytes != 0, as the padding at the end of
|
||||
* the last node is not included in the result.
|
||||
*/
|
||||
static constexpr size_t paddingBytes(size_t n) {
|
||||
return (n ? (kPaddingBytes +
|
||||
(kElementCount - 1 - (n-1) % kElementCount) * sizeof(T)) :
|
||||
0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minimum byte size needed to represent n values.
|
||||
* Does not round up. Even if n is a multiple of kElementCount, this
|
||||
* may be different from paddedByteSize() if kPaddingBytes != 0, as
|
||||
* the padding at the end of the last node is not included in the result.
|
||||
* Note that the calculation below works for n=0 correctly (returns 0).
|
||||
*/
|
||||
static constexpr size_t unpaddedByteSize(size_t n) {
|
||||
return paddedByteSize(n) - paddingBytes(n);
|
||||
}
|
||||
|
||||
private:
|
||||
union Storage {
|
||||
unsigned char bytes[NS];
|
||||
T data[kElementCount];
|
||||
} storage_;
|
||||
};
|
||||
|
||||
// We must define kElementCount and kPaddingBytes to work around a bug
|
||||
// in gtest that odr-uses them.
|
||||
template <class T, size_t NS> constexpr size_t
|
||||
Node<T, NS, typename detail::NodeValid<T,NS>::type>::kNodeSize;
|
||||
template <class T, size_t NS> constexpr size_t
|
||||
Node<T, NS, typename detail::NodeValid<T,NS>::type>::kElementCount;
|
||||
template <class T, size_t NS> constexpr size_t
|
||||
Node<T, NS, typename detail::NodeValid<T,NS>::type>::kPaddingBytes;
|
||||
|
||||
template <class Iter> class Iterator;
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Helper class to transfer the constness from From (a lvalue reference)
|
||||
// and create a lvalue reference to To.
|
||||
//
|
||||
// TransferReferenceConstness<const string&, int> -> const int&
|
||||
// TransferReferenceConstness<string&, int> -> int&
|
||||
// TransferReferenceConstness<string&, const int> -> const int&
|
||||
template <class From, class To, class Enable=void>
|
||||
struct TransferReferenceConstness;
|
||||
|
||||
template <class From, class To>
|
||||
struct TransferReferenceConstness<
|
||||
From, To, typename std::enable_if<std::is_const<
|
||||
typename std::remove_reference<From>::type>::value>::type> {
|
||||
typedef typename std::add_lvalue_reference<
|
||||
typename std::add_const<To>::type>::type type;
|
||||
};
|
||||
|
||||
template <class From, class To>
|
||||
struct TransferReferenceConstness<
|
||||
From, To, typename std::enable_if<!std::is_const<
|
||||
typename std::remove_reference<From>::type>::value>::type> {
|
||||
typedef typename std::add_lvalue_reference<To>::type type;
|
||||
};
|
||||
|
||||
// Helper class template to define a base class for Iterator (below) and save
|
||||
// typing.
|
||||
template <class Iter>
|
||||
struct IteratorBase {
|
||||
typedef boost::iterator_adaptor<
|
||||
// CRTC
|
||||
Iterator<Iter>,
|
||||
// Base iterator type
|
||||
Iter,
|
||||
// Value type
|
||||
typename std::iterator_traits<Iter>::value_type::value_type,
|
||||
// Category or traversal
|
||||
boost::use_default,
|
||||
// Reference type
|
||||
typename detail::TransferReferenceConstness<
|
||||
typename std::iterator_traits<Iter>::reference,
|
||||
typename std::iterator_traits<Iter>::value_type::value_type
|
||||
>::type
|
||||
> type;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Wrapper around iterators to Node to return iterators to the underlying
|
||||
* node elements.
|
||||
*/
|
||||
template <class Iter>
|
||||
class Iterator : public detail::IteratorBase<Iter>::type {
|
||||
typedef typename detail::IteratorBase<Iter>::type Super;
|
||||
public:
|
||||
typedef typename std::iterator_traits<Iter>::value_type Node;
|
||||
|
||||
Iterator() : pos_(0) { }
|
||||
|
||||
explicit Iterator(Iter base)
|
||||
: Super(base),
|
||||
pos_(0) {
|
||||
}
|
||||
|
||||
// Return the current node and the position inside the node
|
||||
const Node& node() const { return *this->base_reference(); }
|
||||
size_t pos() const { return pos_; }
|
||||
|
||||
private:
|
||||
typename Super::reference dereference() const {
|
||||
return (*this->base_reference()).data()[pos_];
|
||||
}
|
||||
|
||||
bool equal(const Iterator& other) const {
|
||||
return (this->base_reference() == other.base_reference() &&
|
||||
pos_ == other.pos_);
|
||||
}
|
||||
|
||||
void advance(typename Super::difference_type n) {
|
||||
constexpr ssize_t elementCount = Node::kElementCount; // signed!
|
||||
ssize_t newPos = pos_ + n;
|
||||
if (newPos >= 0 && newPos < elementCount) {
|
||||
pos_ = newPos;
|
||||
return;
|
||||
}
|
||||
ssize_t nblocks = newPos / elementCount;
|
||||
newPos %= elementCount;
|
||||
if (newPos < 0) {
|
||||
--nblocks; // negative
|
||||
newPos += elementCount;
|
||||
}
|
||||
this->base_reference() += nblocks;
|
||||
pos_ = newPos;
|
||||
}
|
||||
|
||||
void increment() {
|
||||
if (++pos_ == Node::kElementCount) {
|
||||
++this->base_reference();
|
||||
pos_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void decrement() {
|
||||
if (--pos_ == -1) {
|
||||
--this->base_reference();
|
||||
pos_ = Node::kElementCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
typename Super::difference_type distance_to(const Iterator& other) const {
|
||||
constexpr ssize_t elementCount = Node::kElementCount; // signed!
|
||||
ssize_t nblocks =
|
||||
std::distance(this->base_reference(), other.base_reference());
|
||||
return nblocks * elementCount + (other.pos_ - pos_);
|
||||
}
|
||||
|
||||
friend class boost::iterator_core_access;
|
||||
ssize_t pos_; // signed for easier advance() implementation
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a container to Node, return iterators to the first element in
|
||||
* the first Node / one past the last element in the last Node.
|
||||
* Note that the last node is assumed to be full; if that's not the case,
|
||||
* subtract from end() as appropriate.
|
||||
*/
|
||||
|
||||
template <class Container>
|
||||
Iterator<typename Container::const_iterator> cbegin(const Container& c) {
|
||||
return Iterator<typename Container::const_iterator>(std::begin(c));
|
||||
}
|
||||
|
||||
template <class Container>
|
||||
Iterator<typename Container::const_iterator> cend(const Container& c) {
|
||||
return Iterator<typename Container::const_iterator>(std::end(c));
|
||||
}
|
||||
|
||||
template <class Container>
|
||||
Iterator<typename Container::const_iterator> begin(const Container& c) {
|
||||
return cbegin(c);
|
||||
}
|
||||
|
||||
template <class Container>
|
||||
Iterator<typename Container::const_iterator> end(const Container& c) {
|
||||
return cend(c);
|
||||
}
|
||||
|
||||
template <class Container>
|
||||
Iterator<typename Container::iterator> begin(Container& c) {
|
||||
return Iterator<typename Container::iterator>(std::begin(c));
|
||||
}
|
||||
|
||||
template <class Container>
|
||||
Iterator<typename Container::iterator> end(Container& c) {
|
||||
return Iterator<typename Container::iterator>(std::end(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adaptor around a STL sequence container.
|
||||
*
|
||||
* Converts a sequence of Node into a sequence of its underlying elements
|
||||
* (with enough functionality to make it useful, although it's not fully
|
||||
* compatible with the STL containre requiremenets, see below).
|
||||
*
|
||||
* Provides iterators (of the same category as those of the underlying
|
||||
* container), size(), front(), back(), push_back(), pop_back(), and const /
|
||||
* non-const versions of operator[] (if the underlying container supports
|
||||
* them). Does not provide push_front() / pop_front() or arbitrary insert /
|
||||
* emplace / erase. Also provides reserve() / capacity() if supported by the
|
||||
* underlying container.
|
||||
*
|
||||
* Yes, it's called Adaptor, not Adapter, as that's the name used by the STL
|
||||
* and by boost. Deal with it.
|
||||
*
|
||||
* Internally, we hold a container of Node and the number of elements in
|
||||
* the last block. We don't keep empty blocks, so the number of elements in
|
||||
* the last block is always between 1 and Node::kElementCount (inclusive).
|
||||
* (this is true if the container is empty as well to make push_back() simpler,
|
||||
* see the implementation of the size() method for details).
|
||||
*/
|
||||
template <class Container>
|
||||
class Adaptor {
|
||||
public:
|
||||
typedef typename Container::value_type Node;
|
||||
typedef typename Node::value_type value_type;
|
||||
typedef value_type& reference;
|
||||
typedef const value_type& const_reference;
|
||||
typedef Iterator<typename Container::iterator> iterator;
|
||||
typedef Iterator<typename Container::const_iterator> const_iterator;
|
||||
typedef typename const_iterator::difference_type difference_type;
|
||||
typedef typename Container::size_type size_type;
|
||||
|
||||
static constexpr size_t kElementsPerNode = Node::kElementCount;
|
||||
// Constructors
|
||||
Adaptor() : lastCount_(Node::kElementCount) { }
|
||||
explicit Adaptor(Container c, size_t lastCount=Node::kElementCount)
|
||||
: c_(std::move(c)),
|
||||
lastCount_(lastCount) {
|
||||
}
|
||||
explicit Adaptor(size_t n, const value_type& value = value_type())
|
||||
: c_(Node::nodeCount(n), fullNode(value)),
|
||||
lastCount_(n % Node::kElementCount ?: Node::kElementCount) {
|
||||
}
|
||||
|
||||
Adaptor(const Adaptor&) = default;
|
||||
Adaptor& operator=(const Adaptor&) = default;
|
||||
Adaptor(Adaptor&& other)
|
||||
: c_(std::move(other.c_)),
|
||||
lastCount_(other.lastCount_) {
|
||||
other.lastCount_ = Node::kElementCount;
|
||||
}
|
||||
Adaptor& operator=(Adaptor&& other) {
|
||||
if (this != &other) {
|
||||
c_ = std::move(other.c_);
|
||||
lastCount_ = other.lastCount_;
|
||||
other.lastCount_ = Node::kElementCount;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Iterators
|
||||
const_iterator cbegin() const {
|
||||
return const_iterator(c_.begin());
|
||||
}
|
||||
const_iterator cend() const {
|
||||
auto it = const_iterator(c_.end());
|
||||
if (lastCount_ != Node::kElementCount) {
|
||||
it -= (Node::kElementCount - lastCount_);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
const_iterator begin() const { return cbegin(); }
|
||||
const_iterator end() const { return cend(); }
|
||||
iterator begin() {
|
||||
return iterator(c_.begin());
|
||||
}
|
||||
iterator end() {
|
||||
auto it = iterator(c_.end());
|
||||
if (lastCount_ != Node::kElementCount) {
|
||||
it -= (Node::kElementCount - lastCount_);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
void swap(Adaptor& other) {
|
||||
using std::swap;
|
||||
swap(c_, other.c_);
|
||||
swap(lastCount_, other.lastCount_);
|
||||
}
|
||||
bool empty() const {
|
||||
return c_.empty();
|
||||
}
|
||||
size_type size() const {
|
||||
return (c_.empty() ? 0 :
|
||||
(c_.size() - 1) * Node::kElementCount + lastCount_);
|
||||
}
|
||||
size_type max_size() const {
|
||||
return ((c_.max_size() <= std::numeric_limits<size_type>::max() /
|
||||
Node::kElementCount) ?
|
||||
c_.max_size() * Node::kElementCount :
|
||||
std::numeric_limits<size_type>::max());
|
||||
}
|
||||
|
||||
const value_type& front() const {
|
||||
assert(!empty());
|
||||
return c_.front().data()[0];
|
||||
}
|
||||
value_type& front() {
|
||||
assert(!empty());
|
||||
return c_.front().data()[0];
|
||||
}
|
||||
|
||||
const value_type& back() const {
|
||||
assert(!empty());
|
||||
return c_.back().data()[lastCount_ - 1];
|
||||
}
|
||||
value_type& back() {
|
||||
assert(!empty());
|
||||
return c_.back().data()[lastCount_ - 1];
|
||||
}
|
||||
|
||||
void push_back(value_type x) {
|
||||
if (lastCount_ == Node::kElementCount) {
|
||||
c_.push_back(Node());
|
||||
lastCount_ = 0;
|
||||
}
|
||||
c_.back().data()[lastCount_++] = std::move(x);
|
||||
}
|
||||
|
||||
void pop_back() {
|
||||
assert(!empty());
|
||||
if (--lastCount_ == 0) {
|
||||
c_.pop_back();
|
||||
lastCount_ = Node::kElementCount;
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
c_.clear();
|
||||
lastCount_ = Node::kElementCount;
|
||||
}
|
||||
|
||||
void reserve(size_type n) {
|
||||
assert(n >= 0);
|
||||
c_.reserve(Node::nodeCount(n));
|
||||
}
|
||||
|
||||
size_type capacity() const {
|
||||
return c_.capacity() * Node::kElementCount;
|
||||
}
|
||||
|
||||
const value_type& operator[](size_type idx) const {
|
||||
return c_[idx / Node::kElementCount].data()[idx % Node::kElementCount];
|
||||
}
|
||||
value_type& operator[](size_type idx) {
|
||||
return c_[idx / Node::kElementCount].data()[idx % Node::kElementCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying container and number of elements in the last block,
|
||||
* and clear *this. Useful when you want to process the data as Nodes
|
||||
* (again) and want to avoid copies.
|
||||
*/
|
||||
std::pair<Container, size_t> move() {
|
||||
std::pair<Container, size_t> p(std::move(c_), lastCount_);
|
||||
lastCount_ = Node::kElementCount;
|
||||
return std::move(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a const reference to the underlying container and the current
|
||||
* number of elements in the last block.
|
||||
*/
|
||||
std::pair<const Container&, size_t> peek() const {
|
||||
return std::make_pair(std::cref(c_), lastCount_);
|
||||
}
|
||||
|
||||
void padToFullNode(const value_type& padValue) {
|
||||
// the if is necessary because c_ may be empty so we can't call c_.back()
|
||||
if (lastCount_ != Node::kElementCount) {
|
||||
auto last = c_.back().data();
|
||||
std::fill(last + lastCount_, last + Node::kElementCount, padValue);
|
||||
lastCount_ = Node::kElementCount;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static Node fullNode(const value_type& value) {
|
||||
Node n;
|
||||
std::fill(n.data(), n.data() + kElementsPerNode, value);
|
||||
return n;
|
||||
}
|
||||
Container c_; // container of Nodes
|
||||
size_t lastCount_; // number of elements in last Node
|
||||
};
|
||||
|
||||
} // namespace padded
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_PADDED_H_ */
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_PORTABILITY_H_
|
||||
#define FOLLY_PORTABILITY_H_
|
||||
|
||||
#ifndef FOLLY_NO_CONFIG
|
||||
#include "folly-config.h"
|
||||
#endif
|
||||
|
||||
#ifdef FOLLY_HAVE_FEATURES_H
|
||||
#include <features.h>
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef FOLLY_HAVE_SCHED_H
|
||||
#include <sched.h>
|
||||
#ifndef FOLLY_HAVE_PTHREAD_YIELD
|
||||
#define pthread_yield sched_yield
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
// MaxAlign: max_align_t isn't supported by gcc
|
||||
#ifdef __GNUC__
|
||||
struct MaxAlign { char c; } __attribute__((aligned));
|
||||
#else /* !__GNUC__ */
|
||||
# error Cannot define MaxAlign on this platform
|
||||
#endif
|
||||
|
||||
|
||||
// noreturn
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
# define FOLLY_NORETURN __attribute__((noreturn))
|
||||
#else
|
||||
# define FOLLY_NORETURN
|
||||
#endif
|
||||
|
||||
|
||||
// portable version check
|
||||
#ifndef __GNUC_PREREQ
|
||||
# if defined __GNUC__ && defined __GNUC_MINOR__
|
||||
# define __GNUC_PREREQ(maj, min) ((__GNUC__ << 16) + __GNUC_MINOR__ >= \
|
||||
((maj) << 16) + (min))
|
||||
# else
|
||||
# define __GNUC_PREREQ(maj, min) 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
/* Define macro wrappers for C++11's "final" and "override" keywords, which
|
||||
* are supported in gcc 4.7 but not gcc 4.6. */
|
||||
#if !defined(FOLLY_FINAL) && !defined(FOLLY_OVERRIDE)
|
||||
# if defined(__clang__) || __GNUC_PREREQ(4, 7)
|
||||
# define FOLLY_FINAL final
|
||||
# define FOLLY_OVERRIDE override
|
||||
# else
|
||||
# define FOLLY_FINAL /**/
|
||||
# define FOLLY_OVERRIDE /**/
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
// Define to 1 if you have the `preadv' and `pwritev' functions, respectively
|
||||
#if !defined(FOLLY_HAVE_PREADV) && !defined(FOLLY_HAVE_PWRITEV)
|
||||
# if defined(__GLIBC_PREREQ)
|
||||
# if __GLIBC_PREREQ(2, 10)
|
||||
# define FOLLY_HAVE_PREADV 1
|
||||
# define FOLLY_HAVE_PWRITEV 1
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#endif // FOLLY_PORTABILITY_H_
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @author: Andrei Alexandrescu
|
||||
|
||||
#ifndef FOLLY_PREPROCESSOR_
|
||||
#define FOLLY_PREPROCESSOR_
|
||||
|
||||
/**
|
||||
* Necessarily evil preprocessor-related amenities.
|
||||
*/
|
||||
|
||||
/**
|
||||
* FB_ONE_OR_NONE(hello, world) expands to hello and
|
||||
* FB_ONE_OR_NONE(hello) expands to nothing. This macro is used to
|
||||
* insert or eliminate text based on the presence of another argument.
|
||||
*/
|
||||
#define FB_ONE_OR_NONE(a, ...) FB_THIRD(a, ## __VA_ARGS__, a)
|
||||
#define FB_THIRD(a, b, ...) __VA_ARGS__
|
||||
|
||||
/**
|
||||
* Helper macro that extracts the first argument out of a list of any
|
||||
* number of arguments.
|
||||
*/
|
||||
#define FB_ARG_1(a, ...) a
|
||||
|
||||
/**
|
||||
* Helper macro that extracts the second argument out of a list of any
|
||||
* number of arguments. If only one argument is given, it returns
|
||||
* that.
|
||||
*/
|
||||
#define FB_ARG_2_OR_1(...) FB_ARG_2_OR_1_IMPL(__VA_ARGS__, __VA_ARGS__)
|
||||
// Support macro for the above
|
||||
#define FB_ARG_2_OR_1_IMPL(a, b, ...) b
|
||||
|
||||
/**
|
||||
* FB_ANONYMOUS_VARIABLE(str) introduces an identifier starting with
|
||||
* str and ending with a number that varies with the line.
|
||||
*/
|
||||
#ifndef FB_ANONYMOUS_VARIABLE
|
||||
#define FB_CONCATENATE_IMPL(s1, s2) s1##s2
|
||||
#define FB_CONCATENATE(s1, s2) FB_CONCATENATE_IMPL(s1, s2)
|
||||
#ifdef __COUNTER__
|
||||
#define FB_ANONYMOUS_VARIABLE(str) FB_CONCATENATE(str, __COUNTER__)
|
||||
#else
|
||||
#define FB_ANONYMOUS_VARIABLE(str) FB_CONCATENATE(str, __LINE__)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Use FB_STRINGIZE(name) when you'd want to do what #name does inside
|
||||
* another macro expansion.
|
||||
*/
|
||||
#define FB_STRINGIZE(name) #name
|
||||
|
||||
|
||||
#endif // FOLLY_PREPROCESSOR_
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @author Bo Hu (bhu@fb.com)
|
||||
// @author Jordan DeLong (delong.j@fb.com)
|
||||
|
||||
#ifndef PRODUCER_CONSUMER_QUEUE_H_
|
||||
#define PRODUCER_CONSUMER_QUEUE_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/type_traits.hpp>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/*
|
||||
* ProducerConsumerQueue is a one producer and one consumer queue
|
||||
* without locks.
|
||||
*/
|
||||
template<class T>
|
||||
struct ProducerConsumerQueue : private boost::noncopyable {
|
||||
typedef T value_type;
|
||||
|
||||
// size must be >= 2.
|
||||
//
|
||||
// Also, note that the number of usable slots in the queue at any
|
||||
// given time is actually (size-1), so if you start with an empty queue,
|
||||
// isFull() will return true after size-1 insertions.
|
||||
explicit ProducerConsumerQueue(uint32_t size)
|
||||
: size_(size)
|
||||
, records_(static_cast<T*>(std::malloc(sizeof(T) * size)))
|
||||
, readIndex_(0)
|
||||
, writeIndex_(0)
|
||||
{
|
||||
assert(size >= 2);
|
||||
if (!records_) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
}
|
||||
|
||||
~ProducerConsumerQueue() {
|
||||
// We need to destruct anything that may still exist in our queue.
|
||||
// (No real synchronization needed at destructor time: only one
|
||||
// thread can be doing this.)
|
||||
if (!boost::has_trivial_destructor<T>::value) {
|
||||
int read = readIndex_;
|
||||
int end = writeIndex_;
|
||||
while (read != end) {
|
||||
records_[read].~T();
|
||||
if (++read == size_) {
|
||||
read = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::free(records_);
|
||||
}
|
||||
|
||||
template<class ...Args>
|
||||
bool write(Args&&... recordArgs) {
|
||||
auto const currentWrite = writeIndex_.load(std::memory_order_relaxed);
|
||||
auto nextRecord = currentWrite + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
if (nextRecord != readIndex_.load(std::memory_order_acquire)) {
|
||||
new (&records_[currentWrite]) T(std::forward<Args>(recordArgs)...);
|
||||
writeIndex_.store(nextRecord, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
// queue is full
|
||||
return false;
|
||||
}
|
||||
|
||||
// move (or copy) the value at the front of the queue to given variable
|
||||
bool read(T& record) {
|
||||
auto const currentRead = readIndex_.load(std::memory_order_relaxed);
|
||||
if (currentRead == writeIndex_.load(std::memory_order_acquire)) {
|
||||
// queue is empty
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nextRecord = currentRead + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
record = std::move(records_[currentRead]);
|
||||
records_[currentRead].~T();
|
||||
readIndex_.store(nextRecord, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
// pointer to the value at the front of the queue (for use in-place) or
|
||||
// nullptr if empty.
|
||||
T* frontPtr() {
|
||||
auto const currentRead = readIndex_.load(std::memory_order_relaxed);
|
||||
if (currentRead == writeIndex_.load(std::memory_order_acquire)) {
|
||||
// queue is empty
|
||||
return nullptr;
|
||||
}
|
||||
return &records_[currentRead];
|
||||
}
|
||||
|
||||
// queue must not be empty
|
||||
void popFront() {
|
||||
auto const currentRead = readIndex_.load(std::memory_order_relaxed);
|
||||
assert(currentRead != writeIndex_.load(std::memory_order_acquire));
|
||||
|
||||
auto nextRecord = currentRead + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
records_[currentRead].~T();
|
||||
readIndex_.store(nextRecord, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return readIndex_.load(std::memory_order_consume) ==
|
||||
writeIndex_.load(std::memory_order_consume);
|
||||
}
|
||||
|
||||
bool isFull() const {
|
||||
auto nextRecord = writeIndex_.load(std::memory_order_consume) + 1;
|
||||
if (nextRecord == size_) {
|
||||
nextRecord = 0;
|
||||
}
|
||||
if (nextRecord != readIndex_.load(std::memory_order_consume)) {
|
||||
return false;
|
||||
}
|
||||
// queue is full
|
||||
return true;
|
||||
}
|
||||
|
||||
// * If called by consumer, then true size may be more (because producer may
|
||||
// be adding items concurrently).
|
||||
// * If called by producer, then true size may be less (because consumer may
|
||||
// be removing items concurrently).
|
||||
// * It is undefined to call this from any other thread.
|
||||
size_t sizeGuess() const {
|
||||
int ret = writeIndex_.load(std::memory_order_consume) -
|
||||
readIndex_.load(std::memory_order_consume);
|
||||
if (ret < 0) {
|
||||
ret += size_;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t size_;
|
||||
T* const records_;
|
||||
|
||||
std::atomic<int> readIndex_;
|
||||
std::atomic<int> writeIndex_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
-743
@@ -1,743 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Two Read-Write spin lock implementations.
|
||||
*
|
||||
* Ref: http://locklessinc.com/articles/locks
|
||||
*
|
||||
* Both locks here are faster than pthread_rwlock and have very low
|
||||
* overhead (usually 20-30ns). They don't use any system mutexes and
|
||||
* are very compact (4/8 bytes), so are suitable for per-instance
|
||||
* based locking, particularly when contention is not expected.
|
||||
*
|
||||
* In most cases, RWSpinLock is a reasonable choice. It has minimal
|
||||
* overhead, and comparable contention performance when the number of
|
||||
* competing threads is less than or equal to the number of logical
|
||||
* CPUs. Even as the number of threads gets larger, RWSpinLock can
|
||||
* still be very competitive in READ, although it is slower on WRITE,
|
||||
* and also inherently unfair to writers.
|
||||
*
|
||||
* RWTicketSpinLock shows more balanced READ/WRITE performance. If
|
||||
* your application really needs a lot more threads, and a
|
||||
* higher-priority writer, prefer one of the RWTicketSpinLock locks.
|
||||
*
|
||||
* Caveats:
|
||||
*
|
||||
* RWTicketSpinLock locks can only be used with GCC on x86/x86-64
|
||||
* based systems.
|
||||
*
|
||||
* RWTicketSpinLock<32> only allows up to 2^8 - 1 concurrent
|
||||
* readers and writers.
|
||||
*
|
||||
* RWTicketSpinLock<64> only allows up to 2^16 - 1 concurrent
|
||||
* readers and writers.
|
||||
*
|
||||
* RWSpinLock handles 2^30 - 1 concurrent readers.
|
||||
*
|
||||
* @author Xin Liu <xliux@fb.com>
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_RWSPINLOCK_H_
|
||||
#define FOLLY_RWSPINLOCK_H_
|
||||
|
||||
/*
|
||||
========================================================================
|
||||
Benchmark on (Intel(R) Xeon(R) CPU L5630 @ 2.13GHz) 8 cores(16 HTs)
|
||||
========================================================================
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
1. Single thread benchmark (read/write lock + unlock overhead)
|
||||
Benchmark Iters Total t t/iter iter/sec
|
||||
-------------------------------------------------------------------------------
|
||||
* BM_RWSpinLockRead 100000 1.786 ms 17.86 ns 53.4M
|
||||
+30.5% BM_RWSpinLockWrite 100000 2.331 ms 23.31 ns 40.91M
|
||||
+85.7% BM_RWTicketSpinLock32Read 100000 3.317 ms 33.17 ns 28.75M
|
||||
+96.0% BM_RWTicketSpinLock32Write 100000 3.5 ms 35 ns 27.25M
|
||||
+85.6% BM_RWTicketSpinLock64Read 100000 3.315 ms 33.15 ns 28.77M
|
||||
+96.0% BM_RWTicketSpinLock64Write 100000 3.5 ms 35 ns 27.25M
|
||||
+85.7% BM_RWTicketSpinLock32FavorWriterRead 100000 3.317 ms 33.17 ns 28.75M
|
||||
+29.7% BM_RWTicketSpinLock32FavorWriterWrite 100000 2.316 ms 23.16 ns 41.18M
|
||||
+85.3% BM_RWTicketSpinLock64FavorWriterRead 100000 3.309 ms 33.09 ns 28.82M
|
||||
+30.2% BM_RWTicketSpinLock64FavorWriterWrite 100000 2.325 ms 23.25 ns 41.02M
|
||||
+ 175% BM_PThreadRWMutexRead 100000 4.917 ms 49.17 ns 19.4M
|
||||
+ 166% BM_PThreadRWMutexWrite 100000 4.757 ms 47.57 ns 20.05M
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
2. Contention Benchmark 90% read 10% write
|
||||
Benchmark hits average min max sigma
|
||||
------------------------------------------------------------------------------
|
||||
---------- 8 threads ------------
|
||||
RWSpinLock Write 142666 220ns 78ns 40.8us 269ns
|
||||
RWSpinLock Read 1282297 222ns 80ns 37.7us 248ns
|
||||
RWTicketSpinLock Write 85692 209ns 71ns 17.9us 252ns
|
||||
RWTicketSpinLock Read 769571 215ns 78ns 33.4us 251ns
|
||||
pthread_rwlock_t Write 84248 2.48us 99ns 269us 8.19us
|
||||
pthread_rwlock_t Read 761646 933ns 101ns 374us 3.25us
|
||||
|
||||
---------- 16 threads ------------
|
||||
RWSpinLock Write 124236 237ns 78ns 261us 801ns
|
||||
RWSpinLock Read 1115807 236ns 78ns 2.27ms 2.17us
|
||||
RWTicketSpinLock Write 81781 231ns 71ns 31.4us 351ns
|
||||
RWTicketSpinLock Read 734518 238ns 78ns 73.6us 379ns
|
||||
pthread_rwlock_t Write 83363 7.12us 99ns 785us 28.1us
|
||||
pthread_rwlock_t Read 754978 2.18us 101ns 1.02ms 14.3us
|
||||
|
||||
---------- 50 threads ------------
|
||||
RWSpinLock Write 131142 1.37us 82ns 7.53ms 68.2us
|
||||
RWSpinLock Read 1181240 262ns 78ns 6.62ms 12.7us
|
||||
RWTicketSpinLock Write 83045 397ns 73ns 7.01ms 31.5us
|
||||
RWTicketSpinLock Read 744133 386ns 78ns 11ms 31.4us
|
||||
pthread_rwlock_t Write 80849 112us 103ns 4.52ms 263us
|
||||
pthread_rwlock_t Read 728698 24us 101ns 7.28ms 194us
|
||||
|
||||
*/
|
||||
|
||||
#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64__) || \
|
||||
defined(ARCH_K8))
|
||||
#define RW_SPINLOCK_USE_X86_INTRINSIC_
|
||||
#include <x86intrin.h>
|
||||
#else
|
||||
#undef RW_SPINLOCK_USE_X86_INTRINSIC_
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <sched.h>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "folly/Likely.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
/*
|
||||
* A simple, small (4-bytes), but unfair rwlock. Use it when you want
|
||||
* a nice writer and don't expect a lot of write/read contention, or
|
||||
* when you need small rwlocks since you are creating a large number
|
||||
* of them.
|
||||
*
|
||||
* Note that the unfairness here is extreme: if the lock is
|
||||
* continually accessed for read, writers will never get a chance. If
|
||||
* the lock can be that highly contended this class is probably not an
|
||||
* ideal choice anyway.
|
||||
*
|
||||
* It currently implements most of the Lockable, SharedLockable and
|
||||
* UpgradeLockable concepts except the TimedLockable related locking/unlocking
|
||||
* interfaces.
|
||||
*/
|
||||
class RWSpinLock : boost::noncopyable {
|
||||
enum : int32_t { READER = 4, UPGRADED = 2, WRITER = 1 };
|
||||
public:
|
||||
RWSpinLock() : bits_(0) {}
|
||||
|
||||
// Lockable Concept
|
||||
void lock() {
|
||||
int count = 0;
|
||||
while (!LIKELY(try_lock())) {
|
||||
if (++count > 1000) sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
// Writer is responsible for clearing up both the UPGRADED and WRITER bits.
|
||||
void unlock() {
|
||||
static_assert(READER > WRITER + UPGRADED, "wrong bits!");
|
||||
bits_.fetch_and(~(WRITER | UPGRADED), std::memory_order_release);
|
||||
}
|
||||
|
||||
// SharedLockable Concept
|
||||
void lock_shared() {
|
||||
int count = 0;
|
||||
while (!LIKELY(try_lock_shared())) {
|
||||
if (++count > 1000) sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
void unlock_shared() {
|
||||
bits_.fetch_add(-READER, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Downgrade the lock from writer status to reader status.
|
||||
void unlock_and_lock_shared() {
|
||||
bits_.fetch_add(READER, std::memory_order_acquire);
|
||||
unlock();
|
||||
}
|
||||
|
||||
// UpgradeLockable Concept
|
||||
void lock_upgrade() {
|
||||
int count = 0;
|
||||
while (!try_lock_upgrade()) {
|
||||
if (++count > 1000) sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
void unlock_upgrade() {
|
||||
bits_.fetch_add(-UPGRADED, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
// unlock upgrade and try to acquire write lock
|
||||
void unlock_upgrade_and_lock() {
|
||||
int64_t count = 0;
|
||||
while (!try_unlock_upgrade_and_lock()) {
|
||||
if (++count > 1000) sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
// unlock upgrade and read lock atomically
|
||||
void unlock_upgrade_and_lock_shared() {
|
||||
bits_.fetch_add(READER - UPGRADED, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
// write unlock and upgrade lock atomically
|
||||
void unlock_and_lock_upgrade() {
|
||||
// need to do it in two steps here -- as the UPGRADED bit might be OR-ed at
|
||||
// the same time when other threads are trying do try_lock_upgrade().
|
||||
bits_.fetch_or(UPGRADED, std::memory_order_acquire);
|
||||
bits_.fetch_add(-WRITER, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
// Attempt to acquire writer permission. Return false if we didn't get it.
|
||||
bool try_lock() {
|
||||
int32_t expect = 0;
|
||||
return bits_.compare_exchange_strong(expect, WRITER,
|
||||
std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
// Try to get reader permission on the lock. This can fail if we
|
||||
// find out someone is a writer or upgrader.
|
||||
// Setting the UPGRADED bit would allow a writer-to-be to indicate
|
||||
// its intention to write and block any new readers while waiting
|
||||
// for existing readers to finish and release their read locks. This
|
||||
// helps avoid starving writers (promoted from upgraders).
|
||||
bool try_lock_shared() {
|
||||
// fetch_add is considerably (100%) faster than compare_exchange,
|
||||
// so here we are optimizing for the common (lock success) case.
|
||||
int32_t value = bits_.fetch_add(READER, std::memory_order_acquire);
|
||||
if (UNLIKELY(value & (WRITER|UPGRADED))) {
|
||||
bits_.fetch_add(-READER, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// try to unlock upgrade and write lock atomically
|
||||
bool try_unlock_upgrade_and_lock() {
|
||||
int32_t expect = UPGRADED;
|
||||
return bits_.compare_exchange_strong(expect, WRITER,
|
||||
std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
// try to acquire an upgradable lock.
|
||||
bool try_lock_upgrade() {
|
||||
int32_t value = bits_.fetch_or(UPGRADED, std::memory_order_acquire);
|
||||
|
||||
// Note: when failed, we cannot flip the UPGRADED bit back,
|
||||
// as in this case there is either another upgrade lock or a write lock.
|
||||
// If it's a write lock, the bit will get cleared up when that lock's done
|
||||
// with unlock().
|
||||
return ((value & (UPGRADED | WRITER)) == 0);
|
||||
}
|
||||
|
||||
// mainly for debugging purposes.
|
||||
int32_t bits() const { return bits_.load(std::memory_order_acquire); }
|
||||
|
||||
class ReadHolder;
|
||||
class UpgradedHolder;
|
||||
class WriteHolder;
|
||||
|
||||
class ReadHolder {
|
||||
public:
|
||||
explicit ReadHolder(RWSpinLock* lock = nullptr) : lock_(lock) {
|
||||
if (lock_) lock_->lock_shared();
|
||||
}
|
||||
|
||||
explicit ReadHolder(RWSpinLock& lock) : lock_(&lock) {
|
||||
lock_->lock_shared();
|
||||
}
|
||||
|
||||
ReadHolder(ReadHolder&& other) : lock_(other.lock_) {
|
||||
other.lock_ = nullptr;
|
||||
}
|
||||
|
||||
// down-grade
|
||||
explicit ReadHolder(UpgradedHolder&& upgraded) : lock_(upgraded.lock_) {
|
||||
upgraded.lock_ = nullptr;
|
||||
if (lock_) lock_->unlock_upgrade_and_lock_shared();
|
||||
}
|
||||
|
||||
explicit ReadHolder(WriteHolder&& writer) : lock_(writer.lock_) {
|
||||
writer.lock_ = nullptr;
|
||||
if (lock_) lock_->unlock_and_lock_shared();
|
||||
}
|
||||
|
||||
ReadHolder& operator=(ReadHolder&& other) {
|
||||
using std::swap;
|
||||
swap(lock_, other.lock_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ReadHolder(const ReadHolder& other) = delete;
|
||||
ReadHolder& operator=(const ReadHolder& other) = delete;
|
||||
|
||||
~ReadHolder() { if (lock_) lock_->unlock_shared(); }
|
||||
|
||||
void reset(RWSpinLock* lock = nullptr) {
|
||||
if (lock == lock_) return;
|
||||
if (lock_) lock_->unlock_shared();
|
||||
lock_ = lock;
|
||||
if (lock_) lock_->lock_shared();
|
||||
}
|
||||
|
||||
void swap(ReadHolder* other) {
|
||||
std::swap(lock_, other->lock_);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class UpgradedHolder;
|
||||
friend class WriteHolder;
|
||||
RWSpinLock* lock_;
|
||||
};
|
||||
|
||||
class UpgradedHolder {
|
||||
public:
|
||||
explicit UpgradedHolder(RWSpinLock* lock = nullptr) : lock_(lock) {
|
||||
if (lock_) lock_->lock_upgrade();
|
||||
}
|
||||
|
||||
explicit UpgradedHolder(RWSpinLock& lock) : lock_(&lock) {
|
||||
lock_->lock_upgrade();
|
||||
}
|
||||
|
||||
explicit UpgradedHolder(WriteHolder&& writer) {
|
||||
lock_ = writer.lock_;
|
||||
writer.lock_ = nullptr;
|
||||
if (lock_) lock_->unlock_and_lock_upgrade();
|
||||
}
|
||||
|
||||
UpgradedHolder(UpgradedHolder&& other) : lock_(other.lock_) {
|
||||
other.lock_ = nullptr;
|
||||
}
|
||||
|
||||
UpgradedHolder& operator =(UpgradedHolder&& other) {
|
||||
using std::swap;
|
||||
swap(lock_, other.lock_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
UpgradedHolder(const UpgradedHolder& other) = delete;
|
||||
UpgradedHolder& operator =(const UpgradedHolder& other) = delete;
|
||||
|
||||
~UpgradedHolder() { if (lock_) lock_->unlock_upgrade(); }
|
||||
|
||||
void reset(RWSpinLock* lock = nullptr) {
|
||||
if (lock == lock_) return;
|
||||
if (lock_) lock_->unlock_upgrade();
|
||||
lock_ = lock;
|
||||
if (lock_) lock_->lock_upgrade();
|
||||
}
|
||||
|
||||
void swap(UpgradedHolder* other) {
|
||||
using std::swap;
|
||||
swap(lock_, other->lock_);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class WriteHolder;
|
||||
friend class ReadHolder;
|
||||
RWSpinLock* lock_;
|
||||
};
|
||||
|
||||
class WriteHolder {
|
||||
public:
|
||||
explicit WriteHolder(RWSpinLock* lock = nullptr) : lock_(lock) {
|
||||
if (lock_) lock_->lock();
|
||||
}
|
||||
|
||||
explicit WriteHolder(RWSpinLock& lock) : lock_(&lock) {
|
||||
lock_->lock();
|
||||
}
|
||||
|
||||
// promoted from an upgrade lock holder
|
||||
explicit WriteHolder(UpgradedHolder&& upgraded) {
|
||||
lock_ = upgraded.lock_;
|
||||
upgraded.lock_ = nullptr;
|
||||
if (lock_) lock_->unlock_upgrade_and_lock();
|
||||
}
|
||||
|
||||
WriteHolder(WriteHolder&& other) : lock_(other.lock_) {
|
||||
other.lock_ = nullptr;
|
||||
}
|
||||
|
||||
WriteHolder& operator =(WriteHolder&& other) {
|
||||
using std::swap;
|
||||
swap(lock_, other.lock_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
WriteHolder(const WriteHolder& other) = delete;
|
||||
WriteHolder& operator =(const WriteHolder& other) = delete;
|
||||
|
||||
~WriteHolder () { if (lock_) lock_->unlock(); }
|
||||
|
||||
void reset(RWSpinLock* lock = nullptr) {
|
||||
if (lock == lock_) return;
|
||||
if (lock_) lock_->unlock();
|
||||
lock_ = lock;
|
||||
if (lock_) lock_->lock();
|
||||
}
|
||||
|
||||
void swap(WriteHolder* other) {
|
||||
using std::swap;
|
||||
swap(lock_, other->lock_);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ReadHolder;
|
||||
friend class UpgradedHolder;
|
||||
RWSpinLock* lock_;
|
||||
};
|
||||
|
||||
// Synchronized<> adaptors
|
||||
friend void acquireRead(RWSpinLock& l) { return l.lock_shared(); }
|
||||
friend void acquireReadWrite(RWSpinLock& l) { return l.lock(); }
|
||||
friend void releaseRead(RWSpinLock& l) { return l.unlock_shared(); }
|
||||
friend void releaseReadWrite(RWSpinLock& l) { return l.unlock(); }
|
||||
|
||||
private:
|
||||
std::atomic<int32_t> bits_;
|
||||
};
|
||||
|
||||
|
||||
#ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
|
||||
// A more balanced Read-Write spin lock implemented based on GCC intrinsics.
|
||||
|
||||
namespace detail {
|
||||
template <size_t kBitWidth> struct RWTicketIntTrait {
|
||||
static_assert(kBitWidth == 32 || kBitWidth == 64,
|
||||
"bit width has to be either 32 or 64 ");
|
||||
};
|
||||
|
||||
template <>
|
||||
struct RWTicketIntTrait<64> {
|
||||
typedef uint64_t FullInt;
|
||||
typedef uint32_t HalfInt;
|
||||
typedef uint16_t QuarterInt;
|
||||
|
||||
#ifdef __SSE2__
|
||||
static __m128i make128(const uint16_t v[4]) {
|
||||
return _mm_set_epi16(0, 0, 0, 0, v[3], v[2], v[1], v[0]);
|
||||
}
|
||||
static inline __m128i fromInteger(uint64_t from) {
|
||||
return _mm_cvtsi64_si128(from);
|
||||
}
|
||||
static inline uint64_t toInteger(__m128i in) {
|
||||
return _mm_cvtsi128_si64(in);
|
||||
}
|
||||
static inline uint64_t addParallel(__m128i in, __m128i kDelta) {
|
||||
return toInteger(_mm_add_epi16(in, kDelta));
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
template <>
|
||||
struct RWTicketIntTrait<32> {
|
||||
typedef uint32_t FullInt;
|
||||
typedef uint16_t HalfInt;
|
||||
typedef uint8_t QuarterInt;
|
||||
|
||||
#ifdef __SSE2__
|
||||
static __m128i make128(const uint8_t v[4]) {
|
||||
return _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, v[3], v[2], v[1], v[0]);
|
||||
}
|
||||
static inline __m128i fromInteger(uint32_t from) {
|
||||
return _mm_cvtsi32_si128(from);
|
||||
}
|
||||
static inline uint32_t toInteger(__m128i in) {
|
||||
return _mm_cvtsi128_si32(in);
|
||||
}
|
||||
static inline uint32_t addParallel(__m128i in, __m128i kDelta) {
|
||||
return toInteger(_mm_add_epi8(in, kDelta));
|
||||
}
|
||||
#endif
|
||||
};
|
||||
} // detail
|
||||
|
||||
|
||||
template<size_t kBitWidth, bool kFavorWriter=false>
|
||||
class RWTicketSpinLockT : boost::noncopyable {
|
||||
typedef detail::RWTicketIntTrait<kBitWidth> IntTraitType;
|
||||
typedef typename detail::RWTicketIntTrait<kBitWidth>::FullInt FullInt;
|
||||
typedef typename detail::RWTicketIntTrait<kBitWidth>::HalfInt HalfInt;
|
||||
typedef typename detail::RWTicketIntTrait<kBitWidth>::QuarterInt
|
||||
QuarterInt;
|
||||
|
||||
union RWTicket {
|
||||
FullInt whole;
|
||||
HalfInt readWrite;
|
||||
__extension__ struct {
|
||||
QuarterInt write;
|
||||
QuarterInt read;
|
||||
QuarterInt users;
|
||||
};
|
||||
} ticket;
|
||||
|
||||
private: // Some x64-specific utilities for atomic access to ticket.
|
||||
template<class T> static T load_acquire(T* addr) {
|
||||
T t = *addr; // acquire barrier
|
||||
asm volatile("" : : : "memory");
|
||||
return t;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static void store_release(T* addr, T v) {
|
||||
asm volatile("" : : : "memory");
|
||||
*addr = v; // release barrier
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
RWTicketSpinLockT() {
|
||||
store_release(&ticket.whole, FullInt(0));
|
||||
}
|
||||
|
||||
void lock() {
|
||||
if (kFavorWriter) {
|
||||
writeLockAggressive();
|
||||
} else {
|
||||
writeLockNice();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Both try_lock and try_lock_shared diverge in our implementation from the
|
||||
* lock algorithm described in the link above.
|
||||
*
|
||||
* In the read case, it is undesirable that the readers could wait
|
||||
* for another reader (before increasing ticket.read in the other
|
||||
* implementation). Our approach gives up on
|
||||
* first-come-first-serve, but our benchmarks showed improve
|
||||
* performance for both readers and writers under heavily contended
|
||||
* cases, particularly when the number of threads exceeds the number
|
||||
* of logical CPUs.
|
||||
*
|
||||
* We have writeLockAggressive() using the original implementation
|
||||
* for a writer, which gives some advantage to the writer over the
|
||||
* readers---for that path it is guaranteed that the writer will
|
||||
* acquire the lock after all the existing readers exit.
|
||||
*/
|
||||
bool try_lock() {
|
||||
RWTicket t;
|
||||
FullInt old = t.whole = load_acquire(&ticket.whole);
|
||||
if (t.users != t.write) return false;
|
||||
++t.users;
|
||||
return __sync_bool_compare_and_swap(&ticket.whole, old, t.whole);
|
||||
}
|
||||
|
||||
/*
|
||||
* Call this if you want to prioritize writer to avoid starvation.
|
||||
* Unlike writeLockNice, immediately acquires the write lock when
|
||||
* the existing readers (arriving before the writer) finish their
|
||||
* turns.
|
||||
*/
|
||||
void writeLockAggressive() {
|
||||
// sched_yield() is needed here to avoid a pathology if the number
|
||||
// of threads attempting concurrent writes is >= the number of real
|
||||
// cores allocated to this process. This is less likely than the
|
||||
// corresponding situation in lock_shared(), but we still want to
|
||||
// avoid it
|
||||
int count = 0;
|
||||
QuarterInt val = __sync_fetch_and_add(&ticket.users, 1);
|
||||
while (val != load_acquire(&ticket.write)) {
|
||||
asm volatile("pause");
|
||||
if (UNLIKELY(++count > 1000)) sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
// Call this when the writer should be nicer to the readers.
|
||||
void writeLockNice() {
|
||||
// Here it doesn't cpu-relax the writer.
|
||||
//
|
||||
// This is because usually we have many more readers than the
|
||||
// writers, so the writer has less chance to get the lock when
|
||||
// there are a lot of competing readers. The aggressive spinning
|
||||
// can help to avoid starving writers.
|
||||
//
|
||||
// We don't worry about sched_yield() here because the caller
|
||||
// has already explicitly abandoned fairness.
|
||||
while (!try_lock()) {}
|
||||
}
|
||||
|
||||
// Atomically unlock the write-lock from writer and acquire the read-lock.
|
||||
void unlock_and_lock_shared() {
|
||||
QuarterInt val = __sync_fetch_and_add(&ticket.read, 1);
|
||||
}
|
||||
|
||||
// Release writer permission on the lock.
|
||||
void unlock() {
|
||||
RWTicket t;
|
||||
t.whole = load_acquire(&ticket.whole);
|
||||
FullInt old = t.whole;
|
||||
|
||||
#ifdef __SSE2__
|
||||
// SSE2 can reduce the lock and unlock overhead by 10%
|
||||
static const QuarterInt kDeltaBuf[4] = { 1, 1, 0, 0 }; // write/read/user
|
||||
static const __m128i kDelta = IntTraitType::make128(kDeltaBuf);
|
||||
__m128i m = IntTraitType::fromInteger(old);
|
||||
t.whole = IntTraitType::addParallel(m, kDelta);
|
||||
#else
|
||||
++t.read;
|
||||
++t.write;
|
||||
#endif
|
||||
store_release(&ticket.readWrite, t.readWrite);
|
||||
}
|
||||
|
||||
void lock_shared() {
|
||||
// sched_yield() is important here because we can't grab the
|
||||
// shared lock if there is a pending writeLockAggressive, so we
|
||||
// need to let threads that already have a shared lock complete
|
||||
int count = 0;
|
||||
while (!LIKELY(try_lock_shared())) {
|
||||
asm volatile("pause");
|
||||
if (UNLIKELY((++count & 1023) == 0)) sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
bool try_lock_shared() {
|
||||
RWTicket t, old;
|
||||
old.whole = t.whole = load_acquire(&ticket.whole);
|
||||
old.users = old.read;
|
||||
#ifdef __SSE2__
|
||||
// SSE2 may reduce the total lock and unlock overhead by 10%
|
||||
static const QuarterInt kDeltaBuf[4] = { 0, 1, 1, 0 }; // write/read/user
|
||||
static const __m128i kDelta = IntTraitType::make128(kDeltaBuf);
|
||||
__m128i m = IntTraitType::fromInteger(old.whole);
|
||||
t.whole = IntTraitType::addParallel(m, kDelta);
|
||||
#else
|
||||
++t.read;
|
||||
++t.users;
|
||||
#endif
|
||||
return __sync_bool_compare_and_swap(&ticket.whole, old.whole, t.whole);
|
||||
}
|
||||
|
||||
void unlock_shared() {
|
||||
QuarterInt val = __sync_fetch_and_add(&ticket.write, 1);
|
||||
}
|
||||
|
||||
class WriteHolder;
|
||||
|
||||
typedef RWTicketSpinLockT<kBitWidth, kFavorWriter> RWSpinLock;
|
||||
class ReadHolder : boost::noncopyable {
|
||||
public:
|
||||
explicit ReadHolder(RWSpinLock *lock = nullptr) :
|
||||
lock_(lock) {
|
||||
if (lock_) lock_->lock_shared();
|
||||
}
|
||||
|
||||
explicit ReadHolder(RWSpinLock &lock) : lock_ (&lock) {
|
||||
if (lock_) lock_->lock_shared();
|
||||
}
|
||||
|
||||
// atomically unlock the write-lock from writer and acquire the read-lock
|
||||
explicit ReadHolder(WriteHolder *writer) : lock_(nullptr) {
|
||||
std::swap(this->lock_, writer->lock_);
|
||||
if (lock_) {
|
||||
lock_->unlock_and_lock_shared();
|
||||
}
|
||||
}
|
||||
|
||||
~ReadHolder() {
|
||||
if (lock_) lock_->unlock_shared();
|
||||
}
|
||||
|
||||
void reset(RWSpinLock *lock = nullptr) {
|
||||
if (lock_) lock_->unlock_shared();
|
||||
lock_ = lock;
|
||||
if (lock_) lock_->lock_shared();
|
||||
}
|
||||
|
||||
void swap(ReadHolder *other) {
|
||||
std::swap(this->lock_, other->lock_);
|
||||
}
|
||||
|
||||
private:
|
||||
RWSpinLock *lock_;
|
||||
};
|
||||
|
||||
class WriteHolder : boost::noncopyable {
|
||||
public:
|
||||
explicit WriteHolder(RWSpinLock *lock = nullptr) : lock_(lock) {
|
||||
if (lock_) lock_->lock();
|
||||
}
|
||||
explicit WriteHolder(RWSpinLock &lock) : lock_ (&lock) {
|
||||
if (lock_) lock_->lock();
|
||||
}
|
||||
|
||||
~WriteHolder() {
|
||||
if (lock_) lock_->unlock();
|
||||
}
|
||||
|
||||
void reset(RWSpinLock *lock = nullptr) {
|
||||
if (lock == lock_) return;
|
||||
if (lock_) lock_->unlock();
|
||||
lock_ = lock;
|
||||
if (lock_) lock_->lock();
|
||||
}
|
||||
|
||||
void swap(WriteHolder *other) {
|
||||
std::swap(this->lock_, other->lock_);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ReadHolder;
|
||||
RWSpinLock *lock_;
|
||||
};
|
||||
|
||||
// Synchronized<> adaptors.
|
||||
friend void acquireRead(RWTicketSpinLockT& mutex) {
|
||||
mutex.lock_shared();
|
||||
}
|
||||
friend void acquireReadWrite(RWTicketSpinLockT& mutex) {
|
||||
mutex.lock();
|
||||
}
|
||||
friend bool acquireReadWrite(RWTicketSpinLockT& mutex,
|
||||
unsigned int milliseconds) {
|
||||
mutex.lock();
|
||||
return true;
|
||||
}
|
||||
friend void releaseRead(RWTicketSpinLockT& mutex) {
|
||||
mutex.unlock_shared();
|
||||
}
|
||||
friend void releaseReadWrite(RWTicketSpinLockT& mutex) {
|
||||
mutex.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
typedef RWTicketSpinLockT<32> RWTicketSpinLock32;
|
||||
typedef RWTicketSpinLockT<64> RWTicketSpinLock64;
|
||||
|
||||
#endif // RW_SPINLOCK_USE_X86_INTRINSIC_
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#ifdef RW_SPINLOCK_USE_X86_INTRINSIC_
|
||||
#undef RW_SPINLOCK_USE_X86_INTRINSIC_
|
||||
#endif
|
||||
|
||||
#endif // FOLLY_RWSPINLOCK_H_
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/Random.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace {
|
||||
std::atomic<uint32_t> seedInput(0);
|
||||
}
|
||||
|
||||
uint32_t randomNumberSeed() {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
const uint32_t kPrime0 = 51551;
|
||||
const uint32_t kPrime1 = 61631;
|
||||
const uint32_t kPrime2 = 64997;
|
||||
const uint32_t kPrime3 = 111857;
|
||||
return kPrime0 * (seedInput++)
|
||||
+ kPrime1 * static_cast<uint32_t>(getpid())
|
||||
+ kPrime2 * static_cast<uint32_t>(tv.tv_sec)
|
||||
+ kPrime3 * static_cast<uint32_t>(tv.tv_usec);
|
||||
}
|
||||
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_BASE_RANDOM_H_
|
||||
#define FOLLY_BASE_RANDOM_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/*
|
||||
* Return a good seed for a random number generator.
|
||||
*/
|
||||
uint32_t randomNumberSeed();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
-279
@@ -1,279 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @author Mark Rabkin (mrabkin@fb.com)
|
||||
// @author Andrei Alexandrescu (andrei.alexandrescu@fb.com)
|
||||
|
||||
#include "folly/Range.h"
|
||||
|
||||
#include <emmintrin.h> // __v16qi
|
||||
#include <iostream>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Predicates that can be used with qfind and startsWith
|
||||
*/
|
||||
const AsciiCaseSensitive asciiCaseSensitive = AsciiCaseSensitive();
|
||||
const AsciiCaseInsensitive asciiCaseInsensitive = AsciiCaseInsensitive();
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const StringPiece& piece) {
|
||||
os.write(piece.start(), piece.size());
|
||||
return os;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
size_t qfind_first_byte_of_memchr(const StringPiece& haystack,
|
||||
const StringPiece& needles) {
|
||||
size_t best = haystack.size();
|
||||
for (char needle: needles) {
|
||||
const void* ptr = memchr(haystack.data(), needle, best);
|
||||
if (ptr) {
|
||||
auto found = static_cast<const char*>(ptr) - haystack.data();
|
||||
best = std::min<size_t>(best, found);
|
||||
}
|
||||
}
|
||||
if (best == haystack.size()) {
|
||||
return StringPiece::npos;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
namespace {
|
||||
|
||||
// It's okay if pages are bigger than this (as powers of two), but they should
|
||||
// not be smaller.
|
||||
constexpr size_t kMinPageSize = 4096;
|
||||
static_assert(kMinPageSize >= 16,
|
||||
"kMinPageSize must be at least SSE register size");
|
||||
#define PAGE_FOR(addr) \
|
||||
(reinterpret_cast<uintptr_t>(addr) / kMinPageSize)
|
||||
|
||||
|
||||
#if FOLLY_HAVE_EMMINTRIN_H
|
||||
inline size_t nextAlignedIndex(const char* arr) {
|
||||
auto firstPossible = reinterpret_cast<uintptr_t>(arr) + 1;
|
||||
return 1 + // add 1 because the index starts at 'arr'
|
||||
((firstPossible + 15) & ~0xF) // round up to next multiple of 16
|
||||
- firstPossible;
|
||||
}
|
||||
|
||||
// build sse4.2-optimized version even if -msse4.2 is not passed to GCC
|
||||
size_t qfind_first_byte_of_needles16(const StringPiece& haystack,
|
||||
const StringPiece& needles)
|
||||
__attribute__ ((__target__("sse4.2"), noinline));
|
||||
|
||||
// helper method for case where needles.size() <= 16
|
||||
size_t qfind_first_byte_of_needles16(const StringPiece& haystack,
|
||||
const StringPiece& needles) {
|
||||
DCHECK(!haystack.empty());
|
||||
DCHECK(!needles.empty());
|
||||
DCHECK_LE(needles.size(), 16);
|
||||
// benchmarking shows that memchr beats out SSE for small needle-sets
|
||||
// with large haystacks.
|
||||
if ((needles.size() <= 2 && haystack.size() >= 256) ||
|
||||
// must bail if we can't even SSE-load a single segment of haystack
|
||||
(haystack.size() < 16 &&
|
||||
PAGE_FOR(haystack.end() - 1) != PAGE_FOR(haystack.data() + 15)) ||
|
||||
// can't load needles into SSE register if it could cross page boundary
|
||||
PAGE_FOR(needles.end() - 1) != PAGE_FOR(needles.data() + 15)) {
|
||||
return detail::qfind_first_byte_of_memchr(haystack, needles);
|
||||
}
|
||||
|
||||
auto arr2 = __builtin_ia32_loaddqu(needles.data());
|
||||
// do an unaligned load for first block of haystack
|
||||
auto arr1 = __builtin_ia32_loaddqu(haystack.data());
|
||||
auto index = __builtin_ia32_pcmpestri128(arr2, needles.size(),
|
||||
arr1, haystack.size(), 0);
|
||||
if (index < 16) {
|
||||
return index;
|
||||
}
|
||||
|
||||
// Now, we can do aligned loads hereafter...
|
||||
size_t i = nextAlignedIndex(haystack.data());
|
||||
for (; i < haystack.size(); i+= 16) {
|
||||
void* ptr1 = __builtin_assume_aligned(haystack.data() + i, 16);
|
||||
auto arr1 = *reinterpret_cast<const __v16qi*>(ptr1);
|
||||
auto index = __builtin_ia32_pcmpestri128(arr2, needles.size(),
|
||||
arr1, haystack.size() - i, 0);
|
||||
if (index < 16) {
|
||||
return i + index;
|
||||
}
|
||||
}
|
||||
return StringPiece::npos;
|
||||
}
|
||||
#endif // FOLLY_HAVE_EMMINTRIN_H
|
||||
|
||||
// Aho, Hopcroft, and Ullman refer to this trick in "The Design and Analysis
|
||||
// of Computer Algorithms" (1974), but the best description is here:
|
||||
// http://research.swtch.com/sparse
|
||||
class FastByteSet {
|
||||
public:
|
||||
FastByteSet() : size_(0) { } // no init of arrays required!
|
||||
|
||||
inline void add(uint8_t i) {
|
||||
if (!contains(i)) {
|
||||
dense_[size_] = i;
|
||||
sparse_[i] = size_;
|
||||
size_++;
|
||||
}
|
||||
}
|
||||
inline bool contains(uint8_t i) const {
|
||||
DCHECK_LE(size_, 256);
|
||||
return sparse_[i] < size_ && dense_[sparse_[i]] == i;
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t size_; // can't use uint8_t because it would overflow if all
|
||||
// possible values were inserted.
|
||||
uint8_t sparse_[256];
|
||||
uint8_t dense_[256];
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace detail {
|
||||
|
||||
size_t qfind_first_byte_of_byteset(const StringPiece& haystack,
|
||||
const StringPiece& needles) {
|
||||
FastByteSet s;
|
||||
for (auto needle: needles) {
|
||||
s.add(needle);
|
||||
}
|
||||
for (size_t index = 0; index < haystack.size(); ++index) {
|
||||
if (s.contains(haystack[index])) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return StringPiece::npos;
|
||||
}
|
||||
|
||||
#if FOLLY_HAVE_EMMINTRIN_H
|
||||
|
||||
template <bool HAYSTACK_ALIGNED>
|
||||
inline size_t scanHaystackBlock(const StringPiece& haystack,
|
||||
const StringPiece& needles,
|
||||
int64_t idx)
|
||||
// inline is okay because it's only called from other sse4.2 functions
|
||||
__attribute__ ((__target__("sse4.2")));
|
||||
|
||||
// Scans a 16-byte block of haystack (starting at blockStartIdx) to find first
|
||||
// needle. If HAYSTACK_ALIGNED, then haystack must be 16byte aligned.
|
||||
// If !HAYSTACK_ALIGNED, then caller must ensure that it is safe to load the
|
||||
// block.
|
||||
template <bool HAYSTACK_ALIGNED>
|
||||
inline size_t scanHaystackBlock(const StringPiece& haystack,
|
||||
const StringPiece& needles,
|
||||
int64_t blockStartIdx) {
|
||||
DCHECK_GT(needles.size(), 16); // should handled by *needles16() method
|
||||
DCHECK(blockStartIdx + 16 <= haystack.size() ||
|
||||
(PAGE_FOR(haystack.data() + blockStartIdx) ==
|
||||
PAGE_FOR(haystack.data() + blockStartIdx + 15)));
|
||||
|
||||
__v16qi arr1;
|
||||
if (HAYSTACK_ALIGNED) {
|
||||
void* ptr1 = __builtin_assume_aligned(haystack.data() + blockStartIdx, 16);
|
||||
arr1 = *reinterpret_cast<const __v16qi*>(ptr1);
|
||||
} else {
|
||||
arr1 = __builtin_ia32_loaddqu(haystack.data() + blockStartIdx);
|
||||
}
|
||||
|
||||
// This load is safe because needles.size() >= 16
|
||||
auto arr2 = __builtin_ia32_loaddqu(needles.data());
|
||||
size_t b = __builtin_ia32_pcmpestri128(
|
||||
arr2, 16, arr1, haystack.size() - blockStartIdx, 0);
|
||||
|
||||
size_t j = nextAlignedIndex(needles.data());
|
||||
for (; j < needles.size(); j += 16) {
|
||||
void* ptr2 = __builtin_assume_aligned(needles.data() + j, 16);
|
||||
arr2 = *reinterpret_cast<const __v16qi*>(ptr2);
|
||||
|
||||
auto index = __builtin_ia32_pcmpestri128(
|
||||
arr2, needles.size() - j, arr1, haystack.size() - blockStartIdx, 0);
|
||||
b = std::min<size_t>(index, b);
|
||||
}
|
||||
|
||||
if (b < 16) {
|
||||
return blockStartIdx + b;
|
||||
}
|
||||
return StringPiece::npos;
|
||||
}
|
||||
|
||||
size_t qfind_first_byte_of_sse42(const StringPiece& haystack,
|
||||
const StringPiece& needles)
|
||||
__attribute__ ((__target__("sse4.2"), noinline));
|
||||
|
||||
size_t qfind_first_byte_of_sse42(const StringPiece& haystack,
|
||||
const StringPiece& needles) {
|
||||
if (UNLIKELY(needles.empty() || haystack.empty())) {
|
||||
return StringPiece::npos;
|
||||
} else if (needles.size() <= 16) {
|
||||
// we can save some unnecessary load instructions by optimizing for
|
||||
// the common case of needles.size() <= 16
|
||||
return qfind_first_byte_of_needles16(haystack, needles);
|
||||
}
|
||||
|
||||
if (haystack.size() < 16 &&
|
||||
PAGE_FOR(haystack.end() - 1) != PAGE_FOR(haystack.data() + 16)) {
|
||||
// We can't safely SSE-load haystack. Use a different approach.
|
||||
if (haystack.size() <= 2) {
|
||||
return qfind_first_of(haystack, needles, asciiCaseSensitive);
|
||||
}
|
||||
return qfind_first_byte_of_byteset(haystack, needles);
|
||||
}
|
||||
|
||||
auto ret = scanHaystackBlock<false>(haystack, needles, 0);
|
||||
if (ret != StringPiece::npos) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t i = nextAlignedIndex(haystack.data());
|
||||
for (; i < haystack.size(); i += 16) {
|
||||
auto ret = scanHaystackBlock<true>(haystack, needles, i);
|
||||
if (ret != StringPiece::npos) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return StringPiece::npos;
|
||||
}
|
||||
#endif // FOLLY_HAVE_EMMINTRIN_H
|
||||
|
||||
size_t qfind_first_byte_of_nosse(const StringPiece& haystack,
|
||||
const StringPiece& needles) {
|
||||
if (UNLIKELY(needles.empty() || haystack.empty())) {
|
||||
return StringPiece::npos;
|
||||
}
|
||||
// The thresholds below were empirically determined by benchmarking.
|
||||
// This is not an exact science since it depends on the CPU, the size of
|
||||
// needles, and the size of haystack.
|
||||
if (haystack.size() == 1 ||
|
||||
(haystack.size() < 4 && needles.size() <= 16)) {
|
||||
return qfind_first_of(haystack, needles, asciiCaseSensitive);
|
||||
} else if ((needles.size() >= 4 && haystack.size() <= 10) ||
|
||||
(needles.size() >= 16 && haystack.size() <= 64) ||
|
||||
needles.size() >= 32) {
|
||||
return qfind_first_byte_of_byteset(haystack, needles);
|
||||
}
|
||||
|
||||
return qfind_first_byte_of_memchr(haystack, needles);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace folly
|
||||
-757
@@ -1,757 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @author Mark Rabkin (mrabkin@fb.com)
|
||||
// @author Andrei Alexandrescu (andrei.alexandrescu@fb.com)
|
||||
|
||||
#ifndef FOLLY_RANGE_H_
|
||||
#define FOLLY_RANGE_H_
|
||||
|
||||
#include "folly/Portability.h"
|
||||
#include "folly/FBString.h"
|
||||
#include <glog/logging.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <boost/operators.hpp>
|
||||
#include <bits/c++config.h>
|
||||
#include "folly/CpuId.h"
|
||||
#include "folly/Traits.h"
|
||||
#include "folly/Likely.h"
|
||||
|
||||
// Ignore shadowing warnings within this file, so includers can use -Wshadow.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
|
||||
namespace folly {
|
||||
|
||||
template <class T> class Range;
|
||||
|
||||
/**
|
||||
* Finds the first occurrence of needle in haystack. The algorithm is on
|
||||
* average faster than O(haystack.size() * needle.size()) but not as fast
|
||||
* as Boyer-Moore. On the upside, it does not do any upfront
|
||||
* preprocessing and does not allocate memory.
|
||||
*/
|
||||
template <class T, class Comp = std::equal_to<typename Range<T>::value_type>>
|
||||
inline size_t qfind(const Range<T> & haystack,
|
||||
const Range<T> & needle,
|
||||
Comp eq = Comp());
|
||||
|
||||
/**
|
||||
* Finds the first occurrence of needle in haystack. The result is the
|
||||
* offset reported to the beginning of haystack, or string::npos if
|
||||
* needle wasn't found.
|
||||
*/
|
||||
template <class T>
|
||||
size_t qfind(const Range<T> & haystack,
|
||||
const typename Range<T>::value_type& needle);
|
||||
|
||||
/**
|
||||
* Finds the last occurrence of needle in haystack. The result is the
|
||||
* offset reported to the beginning of haystack, or string::npos if
|
||||
* needle wasn't found.
|
||||
*/
|
||||
template <class T>
|
||||
size_t rfind(const Range<T> & haystack,
|
||||
const typename Range<T>::value_type& needle);
|
||||
|
||||
|
||||
/**
|
||||
* Finds the first occurrence of any element of needle in
|
||||
* haystack. The algorithm is O(haystack.size() * needle.size()).
|
||||
*/
|
||||
template <class T>
|
||||
inline size_t qfind_first_of(const Range<T> & haystack,
|
||||
const Range<T> & needle);
|
||||
|
||||
/**
|
||||
* Small internal helper - returns the value just before an iterator.
|
||||
*/
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* For random-access iterators, the value before is simply i[-1].
|
||||
*/
|
||||
template <class Iter>
|
||||
typename std::enable_if<
|
||||
std::is_same<typename std::iterator_traits<Iter>::iterator_category,
|
||||
std::random_access_iterator_tag>::value,
|
||||
typename std::iterator_traits<Iter>::reference>::type
|
||||
value_before(Iter i) {
|
||||
return i[-1];
|
||||
}
|
||||
|
||||
/**
|
||||
* For all other iterators, we need to use the decrement operator.
|
||||
*/
|
||||
template <class Iter>
|
||||
typename std::enable_if<
|
||||
!std::is_same<typename std::iterator_traits<Iter>::iterator_category,
|
||||
std::random_access_iterator_tag>::value,
|
||||
typename std::iterator_traits<Iter>::reference>::type
|
||||
value_before(Iter i) {
|
||||
return *--i;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Range abstraction keeping a pair of iterators. We couldn't use
|
||||
* boost's similar range abstraction because we need an API identical
|
||||
* with the former StringPiece class, which is used by a lot of other
|
||||
* code. This abstraction does fulfill the needs of boost's
|
||||
* range-oriented algorithms though.
|
||||
*
|
||||
* (Keep memory lifetime in mind when using this class, since it
|
||||
* doesn't manage the data it refers to - just like an iterator
|
||||
* wouldn't.)
|
||||
*/
|
||||
template <class Iter>
|
||||
class Range : private boost::totally_ordered<Range<Iter> > {
|
||||
public:
|
||||
typedef std::size_t size_type;
|
||||
typedef Iter iterator;
|
||||
typedef Iter const_iterator;
|
||||
typedef typename std::remove_reference<
|
||||
typename std::iterator_traits<Iter>::reference>::type
|
||||
value_type;
|
||||
typedef typename std::iterator_traits<Iter>::reference reference;
|
||||
typedef std::char_traits<typename std::remove_const<value_type>::type>
|
||||
traits_type;
|
||||
|
||||
static const size_type npos;
|
||||
|
||||
// Works for all iterators
|
||||
Range() : b_(), e_() {
|
||||
}
|
||||
|
||||
public:
|
||||
// Works for all iterators
|
||||
Range(Iter start, Iter end) : b_(start), e_(end) {
|
||||
}
|
||||
|
||||
// Works only for random-access iterators
|
||||
Range(Iter start, size_t size)
|
||||
: b_(start), e_(start + size) { }
|
||||
|
||||
// Works only for Range<const char*>
|
||||
/* implicit */ Range(Iter str)
|
||||
: b_(str), e_(b_ + strlen(str)) {}
|
||||
// Works only for Range<const char*>
|
||||
/* implicit */ Range(const std::string& str)
|
||||
: b_(str.data()), e_(b_ + str.size()) {}
|
||||
// Works only for Range<const char*>
|
||||
Range(const std::string& str, std::string::size_type startFrom) {
|
||||
if (UNLIKELY(startFrom > str.size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ = str.data() + startFrom;
|
||||
e_ = str.data() + str.size();
|
||||
}
|
||||
// Works only for Range<const char*>
|
||||
Range(const std::string& str,
|
||||
std::string::size_type startFrom,
|
||||
std::string::size_type size) {
|
||||
if (UNLIKELY(startFrom > str.size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ = str.data() + startFrom;
|
||||
if (str.size() - startFrom < size) {
|
||||
e_ = str.data() + str.size();
|
||||
} else {
|
||||
e_ = b_ + size;
|
||||
}
|
||||
}
|
||||
Range(const Range<Iter>& str,
|
||||
size_t startFrom,
|
||||
size_t size) {
|
||||
if (UNLIKELY(startFrom > str.size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ = str.b_ + startFrom;
|
||||
if (str.size() - startFrom < size) {
|
||||
e_ = str.e_;
|
||||
} else {
|
||||
e_ = b_ + size;
|
||||
}
|
||||
}
|
||||
// Works only for Range<const char*>
|
||||
/* implicit */ Range(const fbstring& str)
|
||||
: b_(str.data()), e_(b_ + str.size()) { }
|
||||
// Works only for Range<const char*>
|
||||
Range(const fbstring& str, fbstring::size_type startFrom) {
|
||||
if (UNLIKELY(startFrom > str.size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ = str.data() + startFrom;
|
||||
e_ = str.data() + str.size();
|
||||
}
|
||||
// Works only for Range<const char*>
|
||||
Range(const fbstring& str, fbstring::size_type startFrom,
|
||||
fbstring::size_type size) {
|
||||
if (UNLIKELY(startFrom > str.size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ = str.data() + startFrom;
|
||||
if (str.size() - startFrom < size) {
|
||||
e_ = str.data() + str.size();
|
||||
} else {
|
||||
e_ = b_ + size;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow implicit conversion from Range<const char*> (aka StringPiece) to
|
||||
// Range<const unsigned char*> (aka ByteRange), as they're both frequently
|
||||
// used to represent ranges of bytes. Allow explicit conversion in the other
|
||||
// direction.
|
||||
template <class OtherIter, typename std::enable_if<
|
||||
(std::is_same<Iter, const unsigned char*>::value &&
|
||||
std::is_same<OtherIter, const char*>::value), int>::type = 0>
|
||||
/* implicit */ Range(const Range<OtherIter>& other)
|
||||
: b_(reinterpret_cast<const unsigned char*>(other.begin())),
|
||||
e_(reinterpret_cast<const unsigned char*>(other.end())) {
|
||||
}
|
||||
|
||||
template <class OtherIter, typename std::enable_if<
|
||||
(std::is_same<Iter, const char*>::value &&
|
||||
std::is_same<OtherIter, const unsigned char*>::value), int>::type = 0>
|
||||
explicit Range(const Range<OtherIter>& other)
|
||||
: b_(reinterpret_cast<const char*>(other.begin())),
|
||||
e_(reinterpret_cast<const char*>(other.end())) {
|
||||
}
|
||||
|
||||
void clear() {
|
||||
b_ = Iter();
|
||||
e_ = Iter();
|
||||
}
|
||||
|
||||
void assign(Iter start, Iter end) {
|
||||
b_ = start;
|
||||
e_ = end;
|
||||
}
|
||||
|
||||
void reset(Iter start, size_type size) {
|
||||
b_ = start;
|
||||
e_ = start + size;
|
||||
}
|
||||
|
||||
// Works only for Range<const char*>
|
||||
void reset(const std::string& str) {
|
||||
reset(str.data(), str.size());
|
||||
}
|
||||
|
||||
size_type size() const {
|
||||
assert(b_ <= e_);
|
||||
return e_ - b_;
|
||||
}
|
||||
size_type walk_size() const {
|
||||
assert(b_ <= e_);
|
||||
return std::distance(b_, e_);
|
||||
}
|
||||
bool empty() const { return b_ == e_; }
|
||||
Iter data() const { return b_; }
|
||||
Iter start() const { return b_; }
|
||||
Iter begin() const { return b_; }
|
||||
Iter end() const { return e_; }
|
||||
Iter cbegin() const { return b_; }
|
||||
Iter cend() const { return e_; }
|
||||
value_type& front() {
|
||||
assert(b_ < e_);
|
||||
return *b_;
|
||||
}
|
||||
value_type& back() {
|
||||
assert(b_ < e_);
|
||||
return detail::value_before(e_);
|
||||
}
|
||||
const value_type& front() const {
|
||||
assert(b_ < e_);
|
||||
return *b_;
|
||||
}
|
||||
const value_type& back() const {
|
||||
assert(b_ < e_);
|
||||
return detail::value_before(e_);
|
||||
}
|
||||
// Works only for Range<const char*>
|
||||
std::string str() const { return std::string(b_, size()); }
|
||||
std::string toString() const { return str(); }
|
||||
// Works only for Range<const char*>
|
||||
fbstring fbstr() const { return fbstring(b_, size()); }
|
||||
fbstring toFbstring() const { return fbstr(); }
|
||||
|
||||
// Works only for Range<const char*>
|
||||
int compare(const Range& o) const {
|
||||
const size_type tsize = this->size();
|
||||
const size_type osize = o.size();
|
||||
const size_type msize = std::min(tsize, osize);
|
||||
int r = traits_type::compare(data(), o.data(), msize);
|
||||
if (r == 0) r = tsize - osize;
|
||||
return r;
|
||||
}
|
||||
|
||||
value_type& operator[](size_t i) {
|
||||
CHECK_GT(size(), i);
|
||||
return b_[i];
|
||||
}
|
||||
|
||||
const value_type& operator[](size_t i) const {
|
||||
CHECK_GT(size(), i);
|
||||
return b_[i];
|
||||
}
|
||||
|
||||
value_type& at(size_t i) {
|
||||
if (i >= size()) throw std::out_of_range("index out of range");
|
||||
return b_[i];
|
||||
}
|
||||
|
||||
const value_type& at(size_t i) const {
|
||||
if (i >= size()) throw std::out_of_range("index out of range");
|
||||
return b_[i];
|
||||
}
|
||||
|
||||
// Works only for Range<const char*>
|
||||
uint32_t hash() const {
|
||||
// Taken from fbi/nstring.h:
|
||||
// Quick and dirty bernstein hash...fine for short ascii strings
|
||||
uint32_t hash = 5381;
|
||||
for (size_t ix = 0; ix < size(); ix++) {
|
||||
hash = ((hash << 5) + hash) + b_[ix];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
void advance(size_type n) {
|
||||
if (UNLIKELY(n > size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ += n;
|
||||
}
|
||||
|
||||
void subtract(size_type n) {
|
||||
if (UNLIKELY(n > size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
e_ -= n;
|
||||
}
|
||||
|
||||
void pop_front() {
|
||||
assert(b_ < e_);
|
||||
++b_;
|
||||
}
|
||||
|
||||
void pop_back() {
|
||||
assert(b_ < e_);
|
||||
--e_;
|
||||
}
|
||||
|
||||
Range subpiece(size_type first,
|
||||
size_type length = std::string::npos) const {
|
||||
if (UNLIKELY(first > size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
return Range(b_ + first,
|
||||
std::min<std::string::size_type>(length, size() - first));
|
||||
}
|
||||
|
||||
// string work-alike functions
|
||||
size_type find(Range str) const {
|
||||
return qfind(*this, str);
|
||||
}
|
||||
|
||||
size_type find(Range str, size_t pos) const {
|
||||
if (pos > size()) return std::string::npos;
|
||||
size_t ret = qfind(subpiece(pos), str);
|
||||
return ret == npos ? ret : ret + pos;
|
||||
}
|
||||
|
||||
size_type find(Iter s, size_t pos, size_t n) const {
|
||||
if (pos > size()) return std::string::npos;
|
||||
size_t ret = qfind(pos ? subpiece(pos) : *this, Range(s, n));
|
||||
return ret == npos ? ret : ret + pos;
|
||||
}
|
||||
|
||||
// Works only for Range<const (unsigned) char*> which have Range(Iter) ctor
|
||||
size_type find(const Iter s) const {
|
||||
return qfind(*this, Range(s));
|
||||
}
|
||||
|
||||
// Works only for Range<const (unsigned) char*> which have Range(Iter) ctor
|
||||
size_type find(const Iter s, size_t pos) const {
|
||||
if (pos > size()) return std::string::npos;
|
||||
size_type ret = qfind(subpiece(pos), Range(s));
|
||||
return ret == npos ? ret : ret + pos;
|
||||
}
|
||||
|
||||
size_type find(value_type c) const {
|
||||
return qfind(*this, c);
|
||||
}
|
||||
|
||||
size_type rfind(value_type c) const {
|
||||
return folly::rfind(*this, c);
|
||||
}
|
||||
|
||||
size_type find(value_type c, size_t pos) const {
|
||||
if (pos > size()) return std::string::npos;
|
||||
size_type ret = qfind(subpiece(pos), c);
|
||||
return ret == npos ? ret : ret + pos;
|
||||
}
|
||||
|
||||
size_type find_first_of(Range needles) const {
|
||||
return qfind_first_of(*this, needles);
|
||||
}
|
||||
|
||||
size_type find_first_of(Range needles, size_t pos) const {
|
||||
if (pos > size()) return std::string::npos;
|
||||
size_type ret = qfind_first_of(subpiece(pos), needles);
|
||||
return ret == npos ? ret : ret + pos;
|
||||
}
|
||||
|
||||
// Works only for Range<const (unsigned) char*> which have Range(Iter) ctor
|
||||
size_type find_first_of(Iter needles) const {
|
||||
return find_first_of(Range(needles));
|
||||
}
|
||||
|
||||
// Works only for Range<const (unsigned) char*> which have Range(Iter) ctor
|
||||
size_type find_first_of(Iter needles, size_t pos) const {
|
||||
return find_first_of(Range(needles), pos);
|
||||
}
|
||||
|
||||
size_type find_first_of(Iter needles, size_t pos, size_t n) const {
|
||||
return find_first_of(Range(needles, n), pos);
|
||||
}
|
||||
|
||||
size_type find_first_of(value_type c) const {
|
||||
return find(c);
|
||||
}
|
||||
|
||||
size_type find_first_of(value_type c, size_t pos) const {
|
||||
return find(c, pos);
|
||||
}
|
||||
|
||||
void swap(Range& rhs) {
|
||||
std::swap(b_, rhs.b_);
|
||||
std::swap(e_, rhs.e_);
|
||||
}
|
||||
|
||||
private:
|
||||
Iter b_, e_;
|
||||
};
|
||||
|
||||
template <class Iter>
|
||||
const typename Range<Iter>::size_type Range<Iter>::npos = std::string::npos;
|
||||
|
||||
template <class T>
|
||||
void swap(Range<T>& lhs, Range<T>& rhs) {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a range from two iterators, with type deduction.
|
||||
*/
|
||||
template <class Iter>
|
||||
Range<Iter> makeRange(Iter first, Iter last) {
|
||||
return Range<Iter>(first, last);
|
||||
}
|
||||
|
||||
typedef Range<const char*> StringPiece;
|
||||
typedef Range<const unsigned char*> ByteRange;
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const StringPiece& piece);
|
||||
|
||||
/**
|
||||
* Templated comparison operators
|
||||
*/
|
||||
|
||||
template <class T>
|
||||
inline bool operator==(const Range<T>& lhs, const Range<T>& rhs) {
|
||||
return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline bool operator<(const Range<T>& lhs, const Range<T>& rhs) {
|
||||
return lhs.compare(rhs) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specializations of comparison operators for StringPiece
|
||||
*/
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class A, class B>
|
||||
struct ComparableAsStringPiece {
|
||||
enum {
|
||||
value =
|
||||
(std::is_convertible<A, StringPiece>::value
|
||||
&& std::is_same<B, StringPiece>::value)
|
||||
||
|
||||
(std::is_convertible<B, StringPiece>::value
|
||||
&& std::is_same<A, StringPiece>::value)
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* operator== through conversion for Range<const char*>
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename
|
||||
std::enable_if<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
operator==(const T& lhs, const U& rhs) {
|
||||
return StringPiece(lhs) == StringPiece(rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* operator< through conversion for Range<const char*>
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename
|
||||
std::enable_if<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
operator<(const T& lhs, const U& rhs) {
|
||||
return StringPiece(lhs) < StringPiece(rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* operator> through conversion for Range<const char*>
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename
|
||||
std::enable_if<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
operator>(const T& lhs, const U& rhs) {
|
||||
return StringPiece(lhs) > StringPiece(rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* operator< through conversion for Range<const char*>
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename
|
||||
std::enable_if<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
operator<=(const T& lhs, const U& rhs) {
|
||||
return StringPiece(lhs) <= StringPiece(rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* operator> through conversion for Range<const char*>
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename
|
||||
std::enable_if<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
operator>=(const T& lhs, const U& rhs) {
|
||||
return StringPiece(lhs) >= StringPiece(rhs);
|
||||
}
|
||||
|
||||
struct StringPieceHash {
|
||||
std::size_t operator()(const StringPiece& str) const {
|
||||
return static_cast<std::size_t>(str.hash());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds substrings faster than brute force by borrowing from Boyer-Moore
|
||||
*/
|
||||
template <class T, class Comp>
|
||||
size_t qfind(const Range<T>& haystack,
|
||||
const Range<T>& needle,
|
||||
Comp eq) {
|
||||
// Don't use std::search, use a Boyer-Moore-like trick by comparing
|
||||
// the last characters first
|
||||
auto const nsize = needle.size();
|
||||
if (haystack.size() < nsize) {
|
||||
return std::string::npos;
|
||||
}
|
||||
if (!nsize) return 0;
|
||||
auto const nsize_1 = nsize - 1;
|
||||
auto const lastNeedle = needle[nsize_1];
|
||||
|
||||
// Boyer-Moore skip value for the last char in the needle. Zero is
|
||||
// not a valid value; skip will be computed the first time it's
|
||||
// needed.
|
||||
std::string::size_type skip = 0;
|
||||
|
||||
auto i = haystack.begin();
|
||||
auto iEnd = haystack.end() - nsize_1;
|
||||
|
||||
while (i < iEnd) {
|
||||
// Boyer-Moore: match the last element in the needle
|
||||
while (!eq(i[nsize_1], lastNeedle)) {
|
||||
if (++i == iEnd) {
|
||||
// not found
|
||||
return std::string::npos;
|
||||
}
|
||||
}
|
||||
// Here we know that the last char matches
|
||||
// Continue in pedestrian mode
|
||||
for (size_t j = 0; ; ) {
|
||||
assert(j < nsize);
|
||||
if (!eq(i[j], needle[j])) {
|
||||
// Not found, we can skip
|
||||
// Compute the skip value lazily
|
||||
if (skip == 0) {
|
||||
skip = 1;
|
||||
while (skip <= nsize_1 && !eq(needle[nsize_1 - skip], lastNeedle)) {
|
||||
++skip;
|
||||
}
|
||||
}
|
||||
i += skip;
|
||||
break;
|
||||
}
|
||||
// Check if done searching
|
||||
if (++j == nsize) {
|
||||
// Yay
|
||||
return i - haystack.begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
size_t qfind_first_byte_of_nosse(const StringPiece& haystack,
|
||||
const StringPiece& needles);
|
||||
|
||||
#if FOLLY_HAVE_EMMINTRIN_H
|
||||
size_t qfind_first_byte_of_sse42(const StringPiece& haystack,
|
||||
const StringPiece& needles);
|
||||
|
||||
inline size_t qfind_first_byte_of(const StringPiece& haystack,
|
||||
const StringPiece& needles) {
|
||||
static auto const qfind_first_byte_of_fn =
|
||||
folly::CpuId().sse42() ? qfind_first_byte_of_sse42
|
||||
: qfind_first_byte_of_nosse;
|
||||
return qfind_first_byte_of_fn(haystack, needles);
|
||||
}
|
||||
|
||||
#else
|
||||
inline size_t qfind_first_byte_of(const StringPiece& haystack,
|
||||
const StringPiece& needles) {
|
||||
return qfind_first_byte_of_nosse(haystack, needles);
|
||||
}
|
||||
#endif // FOLLY_HAVE_EMMINTRIN_H
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <class T, class Comp>
|
||||
size_t qfind_first_of(const Range<T> & haystack,
|
||||
const Range<T> & needles,
|
||||
Comp eq) {
|
||||
auto ret = std::find_first_of(haystack.begin(), haystack.end(),
|
||||
needles.begin(), needles.end(),
|
||||
eq);
|
||||
return ret == haystack.end() ? std::string::npos : ret - haystack.begin();
|
||||
}
|
||||
|
||||
struct AsciiCaseSensitive {
|
||||
bool operator()(char lhs, char rhs) const {
|
||||
return lhs == rhs;
|
||||
}
|
||||
};
|
||||
|
||||
struct AsciiCaseInsensitive {
|
||||
bool operator()(char lhs, char rhs) const {
|
||||
return toupper(lhs) == toupper(rhs);
|
||||
}
|
||||
};
|
||||
|
||||
extern const AsciiCaseSensitive asciiCaseSensitive;
|
||||
extern const AsciiCaseInsensitive asciiCaseInsensitive;
|
||||
|
||||
template <class T>
|
||||
size_t qfind(const Range<T>& haystack,
|
||||
const typename Range<T>::value_type& needle) {
|
||||
auto pos = std::find(haystack.begin(), haystack.end(), needle);
|
||||
return pos == haystack.end() ? std::string::npos : pos - haystack.data();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
size_t rfind(const Range<T>& haystack,
|
||||
const typename Range<T>::value_type& needle) {
|
||||
for (auto i = haystack.size(); i-- > 0; ) {
|
||||
if (haystack[i] == needle) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
// specialization for StringPiece
|
||||
template <>
|
||||
inline size_t qfind(const Range<const char*>& haystack, const char& needle) {
|
||||
auto pos = static_cast<const char*>(
|
||||
::memchr(haystack.data(), needle, haystack.size()));
|
||||
return pos == nullptr ? std::string::npos : pos - haystack.data();
|
||||
}
|
||||
|
||||
#if FOLLY_HAVE_MEMRCHR
|
||||
template <>
|
||||
inline size_t rfind(const Range<const char*>& haystack, const char& needle) {
|
||||
auto pos = static_cast<const char*>(
|
||||
::memrchr(haystack.data(), needle, haystack.size()));
|
||||
return pos == nullptr ? std::string::npos : pos - haystack.data();
|
||||
}
|
||||
#endif
|
||||
|
||||
// specialization for ByteRange
|
||||
template <>
|
||||
inline size_t qfind(const Range<const unsigned char*>& haystack,
|
||||
const unsigned char& needle) {
|
||||
auto pos = static_cast<const unsigned char*>(
|
||||
::memchr(haystack.data(), needle, haystack.size()));
|
||||
return pos == nullptr ? std::string::npos : pos - haystack.data();
|
||||
}
|
||||
|
||||
#if FOLLY_HAVE_MEMRCHR
|
||||
template <>
|
||||
inline size_t rfind(const Range<const unsigned char*>& haystack,
|
||||
const unsigned char& needle) {
|
||||
auto pos = static_cast<const unsigned char*>(
|
||||
::memrchr(haystack.data(), needle, haystack.size()));
|
||||
return pos == nullptr ? std::string::npos : pos - haystack.data();
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
size_t qfind_first_of(const Range<T>& haystack,
|
||||
const Range<T>& needles) {
|
||||
return qfind_first_of(haystack, needles, asciiCaseSensitive);
|
||||
}
|
||||
|
||||
// specialization for StringPiece
|
||||
template <>
|
||||
inline size_t qfind_first_of(const Range<const char*>& haystack,
|
||||
const Range<const char*>& needles) {
|
||||
return detail::qfind_first_byte_of(haystack, needles);
|
||||
}
|
||||
|
||||
// specialization for ByteRange
|
||||
template <>
|
||||
inline size_t qfind_first_of(const Range<const unsigned char*>& haystack,
|
||||
const Range<const unsigned char*>& needles) {
|
||||
return detail::qfind_first_byte_of(StringPiece(haystack),
|
||||
StringPiece(needles));
|
||||
}
|
||||
} // !namespace folly
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(folly::Range);
|
||||
|
||||
#endif // FOLLY_RANGE_H_
|
||||
-147
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_SCOPEGUARD_H_
|
||||
#define FOLLY_SCOPEGUARD_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <new>
|
||||
|
||||
#include "folly/Preprocessor.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* ScopeGuard is a general implementation of the "Initialization is
|
||||
* Resource Acquisition" idiom. Basically, it guarantees that a function
|
||||
* is executed upon leaving the currrent scope unless otherwise told.
|
||||
*
|
||||
* The makeGuard() function is used to create a new ScopeGuard object.
|
||||
* It can be instantiated with a lambda function, a std::function<void()>,
|
||||
* a functor, or a void(*)() function pointer.
|
||||
*
|
||||
*
|
||||
* Usage example: Add a friend to memory iff it is also added to the db.
|
||||
*
|
||||
* void User::addFriend(User& newFriend) {
|
||||
* // add the friend to memory
|
||||
* friends_.push_back(&newFriend);
|
||||
*
|
||||
* // If the db insertion that follows fails, we should
|
||||
* // remove it from memory.
|
||||
* // (You could also declare this as "auto guard = makeGuard(...)")
|
||||
* ScopeGuard guard = makeGuard([&] { friends_.pop_back(); });
|
||||
*
|
||||
* // this will throw an exception upon error, which
|
||||
* // makes the ScopeGuard execute UserCont::pop_back()
|
||||
* // once the Guard's destructor is called.
|
||||
* db_->addFriend(GetName(), newFriend.GetName());
|
||||
*
|
||||
* // an exception was not thrown, so don't execute
|
||||
* // the Guard.
|
||||
* guard.dismiss();
|
||||
* }
|
||||
*
|
||||
* Examine ScopeGuardTest.cpp for some more sample usage.
|
||||
*
|
||||
* Stolen from:
|
||||
* Andrei's and Petru Marginean's CUJ article:
|
||||
* http://drdobbs.com/184403758
|
||||
* and the loki library:
|
||||
* http://loki-lib.sourceforge.net/index.php?n=Idioms.ScopeGuardPointer
|
||||
* and triendl.kj article:
|
||||
* http://www.codeproject.com/KB/cpp/scope_guard.aspx
|
||||
*/
|
||||
class ScopeGuardImplBase {
|
||||
public:
|
||||
void dismiss() noexcept {
|
||||
dismissed_ = true;
|
||||
}
|
||||
|
||||
protected:
|
||||
ScopeGuardImplBase()
|
||||
: dismissed_(false) {}
|
||||
|
||||
ScopeGuardImplBase(ScopeGuardImplBase&& other)
|
||||
: dismissed_(other.dismissed_) {
|
||||
other.dismissed_ = true;
|
||||
}
|
||||
|
||||
bool dismissed_;
|
||||
};
|
||||
|
||||
template<typename FunctionType>
|
||||
class ScopeGuardImpl : public ScopeGuardImplBase {
|
||||
public:
|
||||
explicit ScopeGuardImpl(const FunctionType& fn)
|
||||
: function_(fn) {}
|
||||
|
||||
explicit ScopeGuardImpl(FunctionType&& fn)
|
||||
: function_(std::move(fn)) {}
|
||||
|
||||
ScopeGuardImpl(ScopeGuardImpl&& other)
|
||||
: ScopeGuardImplBase(std::move(other)),
|
||||
function_(std::move(other.function_)) {
|
||||
}
|
||||
|
||||
~ScopeGuardImpl() noexcept {
|
||||
if (!dismissed_) {
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void* operator new(size_t) = delete;
|
||||
|
||||
void execute() noexcept { function_(); }
|
||||
|
||||
FunctionType function_;
|
||||
};
|
||||
|
||||
template<typename FunctionType>
|
||||
ScopeGuardImpl<typename std::decay<FunctionType>::type>
|
||||
makeGuard(FunctionType&& fn) {
|
||||
return ScopeGuardImpl<typename std::decay<FunctionType>::type>(
|
||||
std::forward<FunctionType>(fn));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is largely unneeded if you just use auto for your guards.
|
||||
*/
|
||||
typedef ScopeGuardImplBase&& ScopeGuard;
|
||||
|
||||
namespace detail {
|
||||
/**
|
||||
* Internal use for the macro SCOPE_EXIT below
|
||||
*/
|
||||
enum class ScopeGuardOnExit {};
|
||||
|
||||
template <typename FunctionType>
|
||||
ScopeGuardImpl<typename std::decay<FunctionType>::type>
|
||||
operator+(detail::ScopeGuardOnExit, FunctionType&& fn) {
|
||||
return ScopeGuardImpl<typename std::decay<FunctionType>::type>(
|
||||
std::forward<FunctionType>(fn));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
} // folly
|
||||
|
||||
#define SCOPE_EXIT \
|
||||
auto FB_ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
|
||||
= ::folly::detail::ScopeGuardOnExit() + [&]
|
||||
|
||||
#endif // FOLLY_SCOPEGUARD_H_
|
||||
-331
@@ -1,331 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_SMALLLOCKS_H_
|
||||
#define FOLLY_SMALLLOCKS_H_
|
||||
|
||||
/*
|
||||
* This header defines a few very small mutex types. These are useful
|
||||
* in highly memory-constrained environments where contention is
|
||||
* unlikely.
|
||||
*
|
||||
* Note: these locks are for use when you aren't likely to contend on
|
||||
* the critical section, or when the critical section is incredibly
|
||||
* small. Given that, both of the locks defined in this header are
|
||||
* inherently unfair: that is, the longer a thread is waiting, the
|
||||
* longer it waits between attempts to acquire, so newer waiters are
|
||||
* more likely to get the mutex. For the intended use-case this is
|
||||
* fine.
|
||||
*
|
||||
* @author Keith Adams <kma@fb.com>
|
||||
* @author Jordan DeLong <delong.j@fb.com>
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <type_traits>
|
||||
#include <ctime>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <cstdlib>
|
||||
#include <pthread.h>
|
||||
#include <mutex>
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
#ifndef __x86_64__
|
||||
# error "SmallLocks.h is currently x64-only."
|
||||
#endif
|
||||
|
||||
#include "folly/Portability.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
/*
|
||||
* A helper object for the condended case. Starts off with eager
|
||||
* spinning, and falls back to sleeping for small quantums.
|
||||
*/
|
||||
class Sleeper {
|
||||
static const uint32_t kMaxActiveSpin = 4000;
|
||||
|
||||
uint32_t spinCount;
|
||||
|
||||
public:
|
||||
Sleeper() : spinCount(0) {}
|
||||
|
||||
void wait() {
|
||||
if (spinCount < kMaxActiveSpin) {
|
||||
++spinCount;
|
||||
asm volatile("pause");
|
||||
} else {
|
||||
/*
|
||||
* Always sleep 0.5ms, assuming this will make the kernel put
|
||||
* us down for whatever its minimum timer resolution is (in
|
||||
* linux this varies by kernel version from 1ms to 10ms).
|
||||
*/
|
||||
struct timespec ts = { 0, 500000 };
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* A really, *really* small spinlock for fine-grained locking of lots
|
||||
* of teeny-tiny data.
|
||||
*
|
||||
* Zero initializing these is guaranteed to be as good as calling
|
||||
* init(), since the free state is guaranteed to be all-bits zero.
|
||||
*
|
||||
* This class should be kept a POD, so we can used it in other packed
|
||||
* structs (gcc does not allow __attribute__((packed)) on structs that
|
||||
* contain non-POD data). This means avoid adding a constructor, or
|
||||
* making some members private, etc.
|
||||
*/
|
||||
struct MicroSpinLock {
|
||||
enum { FREE = 0, LOCKED = 1 };
|
||||
uint8_t lock_;
|
||||
|
||||
/*
|
||||
* Atomically move lock_ from "compare" to "newval". Return boolean
|
||||
* success. Do not play on or around.
|
||||
*/
|
||||
bool cas(uint8_t compare, uint8_t newVal) {
|
||||
bool out;
|
||||
asm volatile("lock; cmpxchgb %2, (%3);"
|
||||
"setz %0;"
|
||||
: "=r" (out)
|
||||
: "a" (compare), // cmpxchgb constrains this to be in %al
|
||||
"q" (newVal), // Needs to be byte-accessible
|
||||
"r" (&lock_)
|
||||
: "memory", "flags");
|
||||
return out;
|
||||
}
|
||||
|
||||
// Initialize this MSL. It is unnecessary to call this if you
|
||||
// zero-initialize the MicroSpinLock.
|
||||
void init() {
|
||||
lock_ = FREE;
|
||||
}
|
||||
|
||||
bool try_lock() {
|
||||
return cas(FREE, LOCKED);
|
||||
}
|
||||
|
||||
void lock() {
|
||||
detail::Sleeper sleeper;
|
||||
do {
|
||||
while (lock_ != FREE) {
|
||||
asm volatile("" : : : "memory");
|
||||
sleeper.wait();
|
||||
}
|
||||
} while (!try_lock());
|
||||
DCHECK(lock_ == LOCKED);
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
CHECK(lock_ == LOCKED);
|
||||
asm volatile("" : : : "memory");
|
||||
lock_ = FREE; // release barrier on x86
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* Spin lock on a single bit in an integral type. You can use this
|
||||
* with 16, 32, or 64-bit integral types.
|
||||
*
|
||||
* This is useful if you want a small lock and already have an int
|
||||
* with a bit in it that you aren't using. But note that it can't be
|
||||
* as small as MicroSpinLock (1 byte), if you don't already have a
|
||||
* convenient int with an unused bit lying around to put it on.
|
||||
*
|
||||
* To construct these, either use init() or zero initialize. We don't
|
||||
* have a real constructor because we want this to be a POD type so we
|
||||
* can put it into packed structs.
|
||||
*/
|
||||
template<class IntType, int Bit = sizeof(IntType) * 8 - 1>
|
||||
struct PicoSpinLock {
|
||||
// Internally we deal with the unsigned version of the type.
|
||||
typedef typename std::make_unsigned<IntType>::type UIntType;
|
||||
|
||||
static_assert(std::is_integral<IntType>::value,
|
||||
"PicoSpinLock needs an integral type");
|
||||
static_assert(sizeof(IntType) == 2 || sizeof(IntType) == 4 ||
|
||||
sizeof(IntType) == 8,
|
||||
"PicoSpinLock can't work on integers smaller than 2 bytes");
|
||||
|
||||
public:
|
||||
static const UIntType kLockBitMask_ = UIntType(1) << Bit;
|
||||
UIntType lock_;
|
||||
|
||||
/*
|
||||
* You must call this function before using this class, if you
|
||||
* default constructed it. If you zero-initialized it you can
|
||||
* assume the PicoSpinLock is in a valid unlocked state with
|
||||
* getData() == 0.
|
||||
*
|
||||
* (This doesn't use a constructor because we want to be a POD.)
|
||||
*/
|
||||
void init(IntType initialValue = 0) {
|
||||
CHECK(!(initialValue & kLockBitMask_));
|
||||
lock_ = initialValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the value of the integer we using for our lock, except
|
||||
* with the bit we are using as a lock cleared, regardless of
|
||||
* whether the lock is held.
|
||||
*
|
||||
* It is 'safe' to call this without holding the lock. (As in: you
|
||||
* get the same guarantees for simultaneous accesses to an integer
|
||||
* as you normally get.)
|
||||
*/
|
||||
IntType getData() const {
|
||||
return static_cast<IntType>(lock_ & ~kLockBitMask_);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the value of the other bits in our integer.
|
||||
*
|
||||
* Don't use this when you aren't holding the lock, unless it can be
|
||||
* guaranteed that no other threads may be trying to use this.
|
||||
*/
|
||||
void setData(IntType w) {
|
||||
CHECK(!(w & kLockBitMask_));
|
||||
lock_ = (lock_ & kLockBitMask_) | w;
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to get the lock without blocking: returns whether or not we
|
||||
* got it.
|
||||
*/
|
||||
bool try_lock() const {
|
||||
bool ret = false;
|
||||
|
||||
#define FB_DOBTS(size) \
|
||||
asm volatile("lock; bts" #size " %1, (%2); setnc %0" \
|
||||
: "=r" (ret) \
|
||||
: "i" (Bit), \
|
||||
"r" (&lock_) \
|
||||
: "memory", "flags")
|
||||
|
||||
switch (sizeof(IntType)) {
|
||||
case 2: FB_DOBTS(w); break;
|
||||
case 4: FB_DOBTS(l); break;
|
||||
case 8: FB_DOBTS(q); break;
|
||||
}
|
||||
|
||||
#undef FB_DOBTS
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Block until we can acquire the lock. Uses Sleeper to wait.
|
||||
*/
|
||||
void lock() const {
|
||||
detail::Sleeper sleeper;
|
||||
while (!try_lock()) {
|
||||
sleeper.wait();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Release the lock, without changing the value of the rest of the
|
||||
* integer.
|
||||
*/
|
||||
void unlock() const {
|
||||
#define FB_DOBTR(size) \
|
||||
asm volatile("lock; btr" #size " %0, (%1)" \
|
||||
: \
|
||||
: "i" (Bit), \
|
||||
"r" (&lock_) \
|
||||
: "memory", "flags")
|
||||
|
||||
|
||||
// Reads and writes can not be reordered wrt locked instructions,
|
||||
// so we don't need a memory fence here.
|
||||
switch (sizeof(IntType)) {
|
||||
case 2: FB_DOBTR(w); break;
|
||||
case 4: FB_DOBTR(l); break;
|
||||
case 8: FB_DOBTR(q); break;
|
||||
}
|
||||
|
||||
#undef FB_DOBTR
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Array of spinlocks where each one is padded to prevent false sharing.
|
||||
* Useful for shard-based locking implementations in environments where
|
||||
* contention is unlikely.
|
||||
*/
|
||||
|
||||
// TODO: generate it from configure (`getconf LEVEL1_DCACHE_LINESIZE`)
|
||||
#define FOLLY_CACHE_LINE_SIZE 64
|
||||
|
||||
template <class T, size_t N>
|
||||
struct SpinLockArray {
|
||||
T& operator[](size_t i) {
|
||||
return data_[i].lock;
|
||||
}
|
||||
|
||||
const T& operator[](size_t i) const {
|
||||
return data_[i].lock;
|
||||
}
|
||||
|
||||
constexpr size_t size() const { return N; }
|
||||
|
||||
private:
|
||||
struct PaddedSpinLock {
|
||||
PaddedSpinLock() : lock() { }
|
||||
T lock;
|
||||
char padding[FOLLY_CACHE_LINE_SIZE - sizeof(T)];
|
||||
};
|
||||
static_assert(sizeof(PaddedSpinLock) == FOLLY_CACHE_LINE_SIZE,
|
||||
"Invalid size of PaddedSpinLock");
|
||||
|
||||
// Check if T can theoretically cross a cache line.
|
||||
// NOTE: It should be alignof(std::max_align_t), but max_align_t
|
||||
// isn't supported by gcc 4.6.2.
|
||||
static_assert(alignof(MaxAlign) > 0 &&
|
||||
FOLLY_CACHE_LINE_SIZE % alignof(MaxAlign) == 0 &&
|
||||
sizeof(T) <= alignof(MaxAlign),
|
||||
"T can cross cache line boundaries");
|
||||
|
||||
char padding_[FOLLY_CACHE_LINE_SIZE];
|
||||
std::array<PaddedSpinLock, N> data_;
|
||||
} __attribute__((aligned));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef std::lock_guard<MicroSpinLock> MSLGuard;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
-374
@@ -1,374 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Spooky Hash
|
||||
// A 128-bit noncryptographic hash, for checksums and table lookup
|
||||
// By Bob Jenkins. Public domain.
|
||||
// Oct 31 2010: published framework, disclaimer ShortHash isn't right
|
||||
// Nov 7 2010: disabled ShortHash
|
||||
// Oct 31 2011: replace End, ShortMix, ShortEnd, enable ShortHash again
|
||||
// April 10 2012: buffer overflow on platforms without unaligned reads
|
||||
// July 12 2012: was passing out variables in final to in/out in short
|
||||
// July 30 2012: I reintroduced the buffer overflow
|
||||
|
||||
#include "folly/SpookyHash.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#define ALLOW_UNALIGNED_READS 1
|
||||
|
||||
namespace folly {
|
||||
namespace hash {
|
||||
|
||||
//
|
||||
// short hash ... it could be used on any message,
|
||||
// but it's used by Spooky just for short messages.
|
||||
//
|
||||
void SpookyHash::Short(
|
||||
const void *message,
|
||||
size_t length,
|
||||
uint64_t *hash1,
|
||||
uint64_t *hash2)
|
||||
{
|
||||
uint64_t buf[2*sc_numVars];
|
||||
union
|
||||
{
|
||||
const uint8_t *p8;
|
||||
uint32_t *p32;
|
||||
uint64_t *p64;
|
||||
size_t i;
|
||||
} u;
|
||||
|
||||
u.p8 = (const uint8_t *)message;
|
||||
|
||||
if (!ALLOW_UNALIGNED_READS && (u.i & 0x7))
|
||||
{
|
||||
memcpy(buf, message, length);
|
||||
u.p64 = buf;
|
||||
}
|
||||
|
||||
size_t remainder = length%32;
|
||||
uint64_t a=*hash1;
|
||||
uint64_t b=*hash2;
|
||||
uint64_t c=sc_const;
|
||||
uint64_t d=sc_const;
|
||||
|
||||
if (length > 15)
|
||||
{
|
||||
const uint64_t *end = u.p64 + (length/32)*4;
|
||||
|
||||
// handle all complete sets of 32 bytes
|
||||
for (; u.p64 < end; u.p64 += 4)
|
||||
{
|
||||
c += u.p64[0];
|
||||
d += u.p64[1];
|
||||
ShortMix(a,b,c,d);
|
||||
a += u.p64[2];
|
||||
b += u.p64[3];
|
||||
}
|
||||
|
||||
//Handle the case of 16+ remaining bytes.
|
||||
if (remainder >= 16)
|
||||
{
|
||||
c += u.p64[0];
|
||||
d += u.p64[1];
|
||||
ShortMix(a,b,c,d);
|
||||
u.p64 += 2;
|
||||
remainder -= 16;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last 0..15 bytes, and its length
|
||||
d = ((uint64_t)length) << 56;
|
||||
switch (remainder)
|
||||
{
|
||||
case 15:
|
||||
d += ((uint64_t)u.p8[14]) << 48;
|
||||
case 14:
|
||||
d += ((uint64_t)u.p8[13]) << 40;
|
||||
case 13:
|
||||
d += ((uint64_t)u.p8[12]) << 32;
|
||||
case 12:
|
||||
d += u.p32[2];
|
||||
c += u.p64[0];
|
||||
break;
|
||||
case 11:
|
||||
d += ((uint64_t)u.p8[10]) << 16;
|
||||
case 10:
|
||||
d += ((uint64_t)u.p8[9]) << 8;
|
||||
case 9:
|
||||
d += (uint64_t)u.p8[8];
|
||||
case 8:
|
||||
c += u.p64[0];
|
||||
break;
|
||||
case 7:
|
||||
c += ((uint64_t)u.p8[6]) << 48;
|
||||
case 6:
|
||||
c += ((uint64_t)u.p8[5]) << 40;
|
||||
case 5:
|
||||
c += ((uint64_t)u.p8[4]) << 32;
|
||||
case 4:
|
||||
c += u.p32[0];
|
||||
break;
|
||||
case 3:
|
||||
c += ((uint64_t)u.p8[2]) << 16;
|
||||
case 2:
|
||||
c += ((uint64_t)u.p8[1]) << 8;
|
||||
case 1:
|
||||
c += (uint64_t)u.p8[0];
|
||||
break;
|
||||
case 0:
|
||||
c += sc_const;
|
||||
d += sc_const;
|
||||
}
|
||||
ShortEnd(a,b,c,d);
|
||||
*hash1 = a;
|
||||
*hash2 = b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// do the whole hash in one call
|
||||
void SpookyHash::Hash128(
|
||||
const void *message,
|
||||
size_t length,
|
||||
uint64_t *hash1,
|
||||
uint64_t *hash2)
|
||||
{
|
||||
if (length < sc_bufSize)
|
||||
{
|
||||
Short(message, length, hash1, hash2);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
|
||||
uint64_t buf[sc_numVars];
|
||||
uint64_t *end;
|
||||
union
|
||||
{
|
||||
const uint8_t *p8;
|
||||
uint64_t *p64;
|
||||
size_t i;
|
||||
} u;
|
||||
size_t remainder;
|
||||
|
||||
h0=h3=h6=h9 = *hash1;
|
||||
h1=h4=h7=h10 = *hash2;
|
||||
h2=h5=h8=h11 = sc_const;
|
||||
|
||||
u.p8 = (const uint8_t *)message;
|
||||
end = u.p64 + (length/sc_blockSize)*sc_numVars;
|
||||
|
||||
// handle all whole sc_blockSize blocks of bytes
|
||||
if (ALLOW_UNALIGNED_READS || ((u.i & 0x7) == 0))
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
memcpy(buf, u.p64, sc_blockSize);
|
||||
Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
|
||||
// handle the last partial block of sc_blockSize bytes
|
||||
remainder = (length - ((const uint8_t *)end-(const uint8_t *)message));
|
||||
memcpy(buf, end, remainder);
|
||||
memset(((uint8_t *)buf)+remainder, 0, sc_blockSize-remainder);
|
||||
((uint8_t *)buf)[sc_blockSize-1] = remainder;
|
||||
Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
|
||||
// do some final mixing
|
||||
End(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
*hash1 = h0;
|
||||
*hash2 = h1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// init spooky state
|
||||
void SpookyHash::Init(uint64_t seed1, uint64_t seed2)
|
||||
{
|
||||
m_length = 0;
|
||||
m_remainder = 0;
|
||||
m_state[0] = seed1;
|
||||
m_state[1] = seed2;
|
||||
}
|
||||
|
||||
|
||||
// add a message fragment to the state
|
||||
void SpookyHash::Update(const void *message, size_t length)
|
||||
{
|
||||
uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
|
||||
size_t newLength = length + m_remainder;
|
||||
uint8_t remainder;
|
||||
union
|
||||
{
|
||||
const uint8_t *p8;
|
||||
uint64_t *p64;
|
||||
size_t i;
|
||||
} u;
|
||||
const uint64_t *end;
|
||||
|
||||
// Is this message fragment too short? If it is, stuff it away.
|
||||
if (newLength < sc_bufSize)
|
||||
{
|
||||
memcpy(&((uint8_t *)m_data)[m_remainder], message, length);
|
||||
m_length = length + m_length;
|
||||
m_remainder = (uint8_t)newLength;
|
||||
return;
|
||||
}
|
||||
|
||||
// init the variables
|
||||
if (m_length < sc_bufSize)
|
||||
{
|
||||
h0=h3=h6=h9 = m_state[0];
|
||||
h1=h4=h7=h10 = m_state[1];
|
||||
h2=h5=h8=h11 = sc_const;
|
||||
}
|
||||
else
|
||||
{
|
||||
h0 = m_state[0];
|
||||
h1 = m_state[1];
|
||||
h2 = m_state[2];
|
||||
h3 = m_state[3];
|
||||
h4 = m_state[4];
|
||||
h5 = m_state[5];
|
||||
h6 = m_state[6];
|
||||
h7 = m_state[7];
|
||||
h8 = m_state[8];
|
||||
h9 = m_state[9];
|
||||
h10 = m_state[10];
|
||||
h11 = m_state[11];
|
||||
}
|
||||
m_length = length + m_length;
|
||||
|
||||
// if we've got anything stuffed away, use it now
|
||||
if (m_remainder)
|
||||
{
|
||||
uint8_t prefix = sc_bufSize-m_remainder;
|
||||
memcpy(&(((uint8_t *)m_data)[m_remainder]), message, prefix);
|
||||
u.p64 = m_data;
|
||||
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
Mix(&u.p64[sc_numVars], h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p8 = ((const uint8_t *)message) + prefix;
|
||||
length -= prefix;
|
||||
}
|
||||
else
|
||||
{
|
||||
u.p8 = (const uint8_t *)message;
|
||||
}
|
||||
|
||||
// handle all whole blocks of sc_blockSize bytes
|
||||
end = u.p64 + (length/sc_blockSize)*sc_numVars;
|
||||
remainder = (uint8_t)(length-((const uint8_t *)end-u.p8));
|
||||
if (ALLOW_UNALIGNED_READS || (u.i & 0x7) == 0)
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
memcpy(m_data, u.p8, sc_blockSize);
|
||||
Mix(m_data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
|
||||
// stuff away the last few bytes
|
||||
m_remainder = remainder;
|
||||
memcpy(m_data, end, remainder);
|
||||
|
||||
// stuff away the variables
|
||||
m_state[0] = h0;
|
||||
m_state[1] = h1;
|
||||
m_state[2] = h2;
|
||||
m_state[3] = h3;
|
||||
m_state[4] = h4;
|
||||
m_state[5] = h5;
|
||||
m_state[6] = h6;
|
||||
m_state[7] = h7;
|
||||
m_state[8] = h8;
|
||||
m_state[9] = h9;
|
||||
m_state[10] = h10;
|
||||
m_state[11] = h11;
|
||||
}
|
||||
|
||||
|
||||
// report the hash for the concatenation of all message fragments so far
|
||||
void SpookyHash::Final(uint64_t *hash1, uint64_t *hash2)
|
||||
{
|
||||
// init the variables
|
||||
if (m_length < sc_bufSize)
|
||||
{
|
||||
*hash1 = m_state[0];
|
||||
*hash2 = m_state[1];
|
||||
Short( m_data, m_length, hash1, hash2);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t *data = (const uint64_t *)m_data;
|
||||
uint8_t remainder = m_remainder;
|
||||
|
||||
uint64_t h0 = m_state[0];
|
||||
uint64_t h1 = m_state[1];
|
||||
uint64_t h2 = m_state[2];
|
||||
uint64_t h3 = m_state[3];
|
||||
uint64_t h4 = m_state[4];
|
||||
uint64_t h5 = m_state[5];
|
||||
uint64_t h6 = m_state[6];
|
||||
uint64_t h7 = m_state[7];
|
||||
uint64_t h8 = m_state[8];
|
||||
uint64_t h9 = m_state[9];
|
||||
uint64_t h10 = m_state[10];
|
||||
uint64_t h11 = m_state[11];
|
||||
|
||||
if (remainder >= sc_blockSize)
|
||||
{
|
||||
// m_data can contain two blocks; handle any whole first block
|
||||
Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
data += sc_numVars;
|
||||
remainder -= sc_blockSize;
|
||||
}
|
||||
|
||||
// mix in the last partial block, and the length mod sc_blockSize
|
||||
memset(&((uint8_t *)data)[remainder], 0, (sc_blockSize-remainder));
|
||||
|
||||
((uint8_t *)data)[sc_blockSize-1] = remainder;
|
||||
Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
|
||||
// do some final mixing
|
||||
End(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
|
||||
*hash1 = h0;
|
||||
*hash2 = h1;
|
||||
}
|
||||
|
||||
} // namespace hash
|
||||
} // namespace folly
|
||||
-304
@@ -1,304 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// SpookyHash: a 128-bit noncryptographic hash function
|
||||
// By Bob Jenkins, public domain
|
||||
// Oct 31 2010: alpha, framework + SpookyHash::Mix appears right
|
||||
// Oct 31 2011: alpha again, Mix only good to 2^^69 but rest appears right
|
||||
// Dec 31 2011: beta, improved Mix, tested it for 2-bit deltas
|
||||
// Feb 2 2012: production, same bits as beta
|
||||
// Feb 5 2012: adjusted definitions of uint* to be more portable
|
||||
// Mar 30 2012: 3 bytes/cycle, not 4. Alpha was 4 but wasn't thorough enough.
|
||||
//
|
||||
// Up to 3 bytes/cycle for long messages. Reasonably fast for short messages.
|
||||
// All 1 or 2 bit deltas achieve avalanche within 1% bias per output bit.
|
||||
//
|
||||
// This was developed for and tested on 64-bit x86-compatible processors.
|
||||
// It assumes the processor is little-endian. There is a macro
|
||||
// controlling whether unaligned reads are allowed (by default they are).
|
||||
// This should be an equally good hash on big-endian machines, but it will
|
||||
// compute different results on them than on little-endian machines.
|
||||
//
|
||||
// Google's CityHash has similar specs to SpookyHash, and CityHash is faster
|
||||
// on some platforms. MD4 and MD5 also have similar specs, but they are orders
|
||||
// of magnitude slower. CRCs are two or more times slower, but unlike
|
||||
// SpookyHash, they have nice math for combining the CRCs of pieces to form
|
||||
// the CRCs of wholes. There are also cryptographic hashes, but those are even
|
||||
// slower than MD5.
|
||||
//
|
||||
|
||||
#ifndef FOLLY_SPOOKYHASH_H_
|
||||
#define FOLLY_SPOOKYHASH_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace folly {
|
||||
namespace hash {
|
||||
|
||||
class SpookyHash
|
||||
{
|
||||
public:
|
||||
//
|
||||
// SpookyHash: hash a single message in one call, produce 128-bit output
|
||||
//
|
||||
static void Hash128(
|
||||
const void *message, // message to hash
|
||||
size_t length, // length of message in bytes
|
||||
uint64_t *hash1, // in/out: in seed 1, out hash value 1
|
||||
uint64_t *hash2); // in/out: in seed 2, out hash value 2
|
||||
|
||||
//
|
||||
// Hash64: hash a single message in one call, return 64-bit output
|
||||
//
|
||||
static uint64_t Hash64(
|
||||
const void *message, // message to hash
|
||||
size_t length, // length of message in bytes
|
||||
uint64_t seed) // seed
|
||||
{
|
||||
uint64_t hash1 = seed;
|
||||
Hash128(message, length, &hash1, &seed);
|
||||
return hash1;
|
||||
}
|
||||
|
||||
//
|
||||
// Hash32: hash a single message in one call, produce 32-bit output
|
||||
//
|
||||
static uint32_t Hash32(
|
||||
const void *message, // message to hash
|
||||
size_t length, // length of message in bytes
|
||||
uint32_t seed) // seed
|
||||
{
|
||||
uint64_t hash1 = seed, hash2 = seed;
|
||||
Hash128(message, length, &hash1, &hash2);
|
||||
return (uint32_t)hash1;
|
||||
}
|
||||
|
||||
//
|
||||
// Init: initialize the context of a SpookyHash
|
||||
//
|
||||
void Init(
|
||||
uint64_t seed1, // any 64-bit value will do, including 0
|
||||
uint64_t seed2); // different seeds produce independent hashes
|
||||
|
||||
//
|
||||
// Update: add a piece of a message to a SpookyHash state
|
||||
//
|
||||
void Update(
|
||||
const void *message, // message fragment
|
||||
size_t length); // length of message fragment in bytes
|
||||
|
||||
|
||||
//
|
||||
// Final: compute the hash for the current SpookyHash state
|
||||
//
|
||||
// This does not modify the state; you can keep updating it afterward
|
||||
//
|
||||
// The result is the same as if SpookyHash() had been called with
|
||||
// all the pieces concatenated into one message.
|
||||
//
|
||||
void Final(
|
||||
uint64_t *hash1, // out only: first 64 bits of hash value.
|
||||
uint64_t *hash2); // out only: second 64 bits of hash value.
|
||||
|
||||
//
|
||||
// left rotate a 64-bit value by k bytes
|
||||
//
|
||||
static inline uint64_t Rot64(uint64_t x, int k)
|
||||
{
|
||||
return (x << k) | (x >> (64 - k));
|
||||
}
|
||||
|
||||
//
|
||||
// This is used if the input is 96 bytes long or longer.
|
||||
//
|
||||
// The internal state is fully overwritten every 96 bytes.
|
||||
// Every input bit appears to cause at least 128 bits of entropy
|
||||
// before 96 other bytes are combined, when run forward or backward
|
||||
// For every input bit,
|
||||
// Two inputs differing in just that input bit
|
||||
// Where "differ" means xor or subtraction
|
||||
// And the base value is random
|
||||
// When run forward or backwards one Mix
|
||||
// I tried 3 pairs of each; they all differed by at least 212 bits.
|
||||
//
|
||||
static inline void Mix(
|
||||
const uint64_t *data,
|
||||
uint64_t &s0, uint64_t &s1, uint64_t &s2, uint64_t &s3,
|
||||
uint64_t &s4, uint64_t &s5, uint64_t &s6, uint64_t &s7,
|
||||
uint64_t &s8, uint64_t &s9, uint64_t &s10,uint64_t &s11)
|
||||
{
|
||||
s0 += data[0]; s2 ^= s10; s11 ^= s0; s0 = Rot64(s0,11); s11 += s1;
|
||||
s1 += data[1]; s3 ^= s11; s0 ^= s1; s1 = Rot64(s1,32); s0 += s2;
|
||||
s2 += data[2]; s4 ^= s0; s1 ^= s2; s2 = Rot64(s2,43); s1 += s3;
|
||||
s3 += data[3]; s5 ^= s1; s2 ^= s3; s3 = Rot64(s3,31); s2 += s4;
|
||||
s4 += data[4]; s6 ^= s2; s3 ^= s4; s4 = Rot64(s4,17); s3 += s5;
|
||||
s5 += data[5]; s7 ^= s3; s4 ^= s5; s5 = Rot64(s5,28); s4 += s6;
|
||||
s6 += data[6]; s8 ^= s4; s5 ^= s6; s6 = Rot64(s6,39); s5 += s7;
|
||||
s7 += data[7]; s9 ^= s5; s6 ^= s7; s7 = Rot64(s7,57); s6 += s8;
|
||||
s8 += data[8]; s10 ^= s6; s7 ^= s8; s8 = Rot64(s8,55); s7 += s9;
|
||||
s9 += data[9]; s11 ^= s7; s8 ^= s9; s9 = Rot64(s9,54); s8 += s10;
|
||||
s10 += data[10]; s0 ^= s8; s9 ^= s10; s10 = Rot64(s10,22); s9 += s11;
|
||||
s11 += data[11]; s1 ^= s9; s10 ^= s11; s11 = Rot64(s11,46); s10 += s0;
|
||||
}
|
||||
|
||||
//
|
||||
// Mix all 12 inputs together so that h0, h1 are a hash of them all.
|
||||
//
|
||||
// For two inputs differing in just the input bits
|
||||
// Where "differ" means xor or subtraction
|
||||
// And the base value is random, or a counting value starting at that bit
|
||||
// The final result will have each bit of h0, h1 flip
|
||||
// For every input bit,
|
||||
// with probability 50 +- .3%
|
||||
// For every pair of input bits,
|
||||
// with probability 50 +- 3%
|
||||
//
|
||||
// This does not rely on the last Mix() call having already mixed some.
|
||||
// Two iterations was almost good enough for a 64-bit result, but a
|
||||
// 128-bit result is reported, so End() does three iterations.
|
||||
//
|
||||
static inline void EndPartial(
|
||||
uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
|
||||
uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
|
||||
uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
|
||||
{
|
||||
h11+= h1; h2 ^= h11; h1 = Rot64(h1,44);
|
||||
h0 += h2; h3 ^= h0; h2 = Rot64(h2,15);
|
||||
h1 += h3; h4 ^= h1; h3 = Rot64(h3,34);
|
||||
h2 += h4; h5 ^= h2; h4 = Rot64(h4,21);
|
||||
h3 += h5; h6 ^= h3; h5 = Rot64(h5,38);
|
||||
h4 += h6; h7 ^= h4; h6 = Rot64(h6,33);
|
||||
h5 += h7; h8 ^= h5; h7 = Rot64(h7,10);
|
||||
h6 += h8; h9 ^= h6; h8 = Rot64(h8,13);
|
||||
h7 += h9; h10^= h7; h9 = Rot64(h9,38);
|
||||
h8 += h10; h11^= h8; h10= Rot64(h10,53);
|
||||
h9 += h11; h0 ^= h9; h11= Rot64(h11,42);
|
||||
h10+= h0; h1 ^= h10; h0 = Rot64(h0,54);
|
||||
}
|
||||
|
||||
static inline void End(
|
||||
uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
|
||||
uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
|
||||
uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
|
||||
{
|
||||
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
}
|
||||
|
||||
//
|
||||
// The goal is for each bit of the input to expand into 128 bits of
|
||||
// apparent entropy before it is fully overwritten.
|
||||
// n trials both set and cleared at least m bits of h0 h1 h2 h3
|
||||
// n: 2 m: 29
|
||||
// n: 3 m: 46
|
||||
// n: 4 m: 57
|
||||
// n: 5 m: 107
|
||||
// n: 6 m: 146
|
||||
// n: 7 m: 152
|
||||
// when run forwards or backwards
|
||||
// for all 1-bit and 2-bit diffs
|
||||
// with diffs defined by either xor or subtraction
|
||||
// with a base of all zeros plus a counter, or plus another bit, or random
|
||||
//
|
||||
static inline void ShortMix(uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3)
|
||||
{
|
||||
h2 = Rot64(h2,50); h2 += h3; h0 ^= h2;
|
||||
h3 = Rot64(h3,52); h3 += h0; h1 ^= h3;
|
||||
h0 = Rot64(h0,30); h0 += h1; h2 ^= h0;
|
||||
h1 = Rot64(h1,41); h1 += h2; h3 ^= h1;
|
||||
h2 = Rot64(h2,54); h2 += h3; h0 ^= h2;
|
||||
h3 = Rot64(h3,48); h3 += h0; h1 ^= h3;
|
||||
h0 = Rot64(h0,38); h0 += h1; h2 ^= h0;
|
||||
h1 = Rot64(h1,37); h1 += h2; h3 ^= h1;
|
||||
h2 = Rot64(h2,62); h2 += h3; h0 ^= h2;
|
||||
h3 = Rot64(h3,34); h3 += h0; h1 ^= h3;
|
||||
h0 = Rot64(h0,5); h0 += h1; h2 ^= h0;
|
||||
h1 = Rot64(h1,36); h1 += h2; h3 ^= h1;
|
||||
}
|
||||
|
||||
//
|
||||
// Mix all 4 inputs together so that h0, h1 are a hash of them all.
|
||||
//
|
||||
// For two inputs differing in just the input bits
|
||||
// Where "differ" means xor or subtraction
|
||||
// And the base value is random, or a counting value starting at that bit
|
||||
// The final result will have each bit of h0, h1 flip
|
||||
// For every input bit,
|
||||
// with probability 50 +- .3% (it is probably better than that)
|
||||
// For every pair of input bits,
|
||||
// with probability 50 +- .75% (the worst case is approximately that)
|
||||
//
|
||||
static inline void ShortEnd(uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3)
|
||||
{
|
||||
h3 ^= h2; h2 = Rot64(h2,15); h3 += h2;
|
||||
h0 ^= h3; h3 = Rot64(h3,52); h0 += h3;
|
||||
h1 ^= h0; h0 = Rot64(h0,26); h1 += h0;
|
||||
h2 ^= h1; h1 = Rot64(h1,51); h2 += h1;
|
||||
h3 ^= h2; h2 = Rot64(h2,28); h3 += h2;
|
||||
h0 ^= h3; h3 = Rot64(h3,9); h0 += h3;
|
||||
h1 ^= h0; h0 = Rot64(h0,47); h1 += h0;
|
||||
h2 ^= h1; h1 = Rot64(h1,54); h2 += h1;
|
||||
h3 ^= h2; h2 = Rot64(h2,32); h3 += h2;
|
||||
h0 ^= h3; h3 = Rot64(h3,25); h0 += h3;
|
||||
h1 ^= h0; h0 = Rot64(h0,63); h1 += h0;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
//
|
||||
// Short is used for messages under 192 bytes in length
|
||||
// Short has a low startup cost, the normal mode is good for long
|
||||
// keys, the cost crossover is at about 192 bytes. The two modes were
|
||||
// held to the same quality bar.
|
||||
//
|
||||
static void Short(
|
||||
const void *message, // message (array of bytes, not necessarily aligned)
|
||||
size_t length, // length of message (in bytes)
|
||||
uint64_t *hash1, // in/out: in the seed, out the hash value
|
||||
uint64_t *hash2); // in/out: in the seed, out the hash value
|
||||
|
||||
// number of uint64_t's in internal state
|
||||
static const size_t sc_numVars = 12;
|
||||
|
||||
// size of the internal state
|
||||
static const size_t sc_blockSize = sc_numVars*8;
|
||||
|
||||
// size of buffer of unhashed data, in bytes
|
||||
static const size_t sc_bufSize = 2*sc_blockSize;
|
||||
|
||||
//
|
||||
// sc_const: a constant which:
|
||||
// * is not zero
|
||||
// * is odd
|
||||
// * is a not-very-regular mix of 1's and 0's
|
||||
// * does not need any other special mathematical properties
|
||||
//
|
||||
static const uint64_t sc_const = 0xdeadbeefdeadbeefLL;
|
||||
|
||||
uint64_t m_data[2*sc_numVars]; // unhashed data, for partial messages
|
||||
uint64_t m_state[sc_numVars]; // internal state of the hash
|
||||
size_t m_length; // total length of the input so far
|
||||
uint8_t m_remainder; // length of unhashed data stashed in m_data
|
||||
};
|
||||
|
||||
} // namespace hash
|
||||
} // namespace folly
|
||||
|
||||
#endif
|
||||
|
||||
-374
@@ -1,374 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Spooky Hash
|
||||
// A 128-bit noncryptographic hash, for checksums and table lookup
|
||||
// By Bob Jenkins. Public domain.
|
||||
// Oct 31 2010: published framework, disclaimer ShortHash isn't right
|
||||
// Nov 7 2010: disabled ShortHash
|
||||
// Oct 31 2011: replace End, ShortMix, ShortEnd, enable ShortHash again
|
||||
// April 10 2012: buffer overflow on platforms without unaligned reads
|
||||
// July 12 2012: was passing out variables in final to in/out in short
|
||||
// July 30 2012: I reintroduced the buffer overflow
|
||||
|
||||
#include "folly/SpookyHashV1.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#define ALLOW_UNALIGNED_READS 1
|
||||
|
||||
namespace folly {
|
||||
namespace hash {
|
||||
|
||||
//
|
||||
// short hash ... it could be used on any message,
|
||||
// but it's used by Spooky just for short messages.
|
||||
//
|
||||
void SpookyHashV1::Short(
|
||||
const void *message,
|
||||
size_t length,
|
||||
uint64_t *hash1,
|
||||
uint64_t *hash2)
|
||||
{
|
||||
uint64_t buf[2*sc_numVars];
|
||||
union
|
||||
{
|
||||
const uint8_t *p8;
|
||||
uint32_t *p32;
|
||||
uint64_t *p64;
|
||||
size_t i;
|
||||
} u;
|
||||
|
||||
u.p8 = (const uint8_t *)message;
|
||||
|
||||
if (!ALLOW_UNALIGNED_READS && (u.i & 0x7))
|
||||
{
|
||||
memcpy(buf, message, length);
|
||||
u.p64 = buf;
|
||||
}
|
||||
|
||||
size_t remainder = length%32;
|
||||
uint64_t a=*hash1;
|
||||
uint64_t b=*hash2;
|
||||
uint64_t c=sc_const;
|
||||
uint64_t d=sc_const;
|
||||
|
||||
if (length > 15)
|
||||
{
|
||||
const uint64_t *end = u.p64 + (length/32)*4;
|
||||
|
||||
// handle all complete sets of 32 bytes
|
||||
for (; u.p64 < end; u.p64 += 4)
|
||||
{
|
||||
c += u.p64[0];
|
||||
d += u.p64[1];
|
||||
ShortMix(a,b,c,d);
|
||||
a += u.p64[2];
|
||||
b += u.p64[3];
|
||||
}
|
||||
|
||||
//Handle the case of 16+ remaining bytes.
|
||||
if (remainder >= 16)
|
||||
{
|
||||
c += u.p64[0];
|
||||
d += u.p64[1];
|
||||
ShortMix(a,b,c,d);
|
||||
u.p64 += 2;
|
||||
remainder -= 16;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last 0..15 bytes, and its length
|
||||
d = ((uint64_t)length) << 56;
|
||||
switch (remainder)
|
||||
{
|
||||
case 15:
|
||||
d += ((uint64_t)u.p8[14]) << 48;
|
||||
case 14:
|
||||
d += ((uint64_t)u.p8[13]) << 40;
|
||||
case 13:
|
||||
d += ((uint64_t)u.p8[12]) << 32;
|
||||
case 12:
|
||||
d += u.p32[2];
|
||||
c += u.p64[0];
|
||||
break;
|
||||
case 11:
|
||||
d += ((uint64_t)u.p8[10]) << 16;
|
||||
case 10:
|
||||
d += ((uint64_t)u.p8[9]) << 8;
|
||||
case 9:
|
||||
d += (uint64_t)u.p8[8];
|
||||
case 8:
|
||||
c += u.p64[0];
|
||||
break;
|
||||
case 7:
|
||||
c += ((uint64_t)u.p8[6]) << 48;
|
||||
case 6:
|
||||
c += ((uint64_t)u.p8[5]) << 40;
|
||||
case 5:
|
||||
c += ((uint64_t)u.p8[4]) << 32;
|
||||
case 4:
|
||||
c += u.p32[0];
|
||||
break;
|
||||
case 3:
|
||||
c += ((uint64_t)u.p8[2]) << 16;
|
||||
case 2:
|
||||
c += ((uint64_t)u.p8[1]) << 8;
|
||||
case 1:
|
||||
c += (uint64_t)u.p8[0];
|
||||
break;
|
||||
case 0:
|
||||
c += sc_const;
|
||||
d += sc_const;
|
||||
}
|
||||
ShortEnd(a,b,c,d);
|
||||
*hash1 = a;
|
||||
*hash2 = b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// do the whole hash in one call
|
||||
void SpookyHashV1::Hash128(
|
||||
const void *message,
|
||||
size_t length,
|
||||
uint64_t *hash1,
|
||||
uint64_t *hash2)
|
||||
{
|
||||
if (length < sc_bufSize)
|
||||
{
|
||||
Short(message, length, hash1, hash2);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
|
||||
uint64_t buf[sc_numVars];
|
||||
uint64_t *end;
|
||||
union
|
||||
{
|
||||
const uint8_t *p8;
|
||||
uint64_t *p64;
|
||||
size_t i;
|
||||
} u;
|
||||
size_t remainder;
|
||||
|
||||
h0=h3=h6=h9 = *hash1;
|
||||
h1=h4=h7=h10 = *hash2;
|
||||
h2=h5=h8=h11 = sc_const;
|
||||
|
||||
u.p8 = (const uint8_t *)message;
|
||||
end = u.p64 + (length/sc_blockSize)*sc_numVars;
|
||||
|
||||
// handle all whole sc_blockSize blocks of bytes
|
||||
if (ALLOW_UNALIGNED_READS || ((u.i & 0x7) == 0))
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
memcpy(buf, u.p64, sc_blockSize);
|
||||
Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
|
||||
// handle the last partial block of sc_blockSize bytes
|
||||
remainder = (length - ((const uint8_t *)end-(const uint8_t *)message));
|
||||
memcpy(buf, end, remainder);
|
||||
memset(((uint8_t *)buf)+remainder, 0, sc_blockSize-remainder);
|
||||
((uint8_t *)buf)[sc_blockSize-1] = remainder;
|
||||
Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
|
||||
// do some final mixing
|
||||
End(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
*hash1 = h0;
|
||||
*hash2 = h1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// init spooky state
|
||||
void SpookyHashV1::Init(uint64_t seed1, uint64_t seed2)
|
||||
{
|
||||
m_length = 0;
|
||||
m_remainder = 0;
|
||||
m_state[0] = seed1;
|
||||
m_state[1] = seed2;
|
||||
}
|
||||
|
||||
|
||||
// add a message fragment to the state
|
||||
void SpookyHashV1::Update(const void *message, size_t length)
|
||||
{
|
||||
uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
|
||||
size_t newLength = length + m_remainder;
|
||||
uint8_t remainder;
|
||||
union
|
||||
{
|
||||
const uint8_t *p8;
|
||||
uint64_t *p64;
|
||||
size_t i;
|
||||
} u;
|
||||
const uint64_t *end;
|
||||
|
||||
// Is this message fragment too short? If it is, stuff it away.
|
||||
if (newLength < sc_bufSize)
|
||||
{
|
||||
memcpy(&((uint8_t *)m_data)[m_remainder], message, length);
|
||||
m_length = length + m_length;
|
||||
m_remainder = (uint8_t)newLength;
|
||||
return;
|
||||
}
|
||||
|
||||
// init the variables
|
||||
if (m_length < sc_bufSize)
|
||||
{
|
||||
h0=h3=h6=h9 = m_state[0];
|
||||
h1=h4=h7=h10 = m_state[1];
|
||||
h2=h5=h8=h11 = sc_const;
|
||||
}
|
||||
else
|
||||
{
|
||||
h0 = m_state[0];
|
||||
h1 = m_state[1];
|
||||
h2 = m_state[2];
|
||||
h3 = m_state[3];
|
||||
h4 = m_state[4];
|
||||
h5 = m_state[5];
|
||||
h6 = m_state[6];
|
||||
h7 = m_state[7];
|
||||
h8 = m_state[8];
|
||||
h9 = m_state[9];
|
||||
h10 = m_state[10];
|
||||
h11 = m_state[11];
|
||||
}
|
||||
m_length = length + m_length;
|
||||
|
||||
// if we've got anything stuffed away, use it now
|
||||
if (m_remainder)
|
||||
{
|
||||
uint8_t prefix = sc_bufSize-m_remainder;
|
||||
memcpy(&(((uint8_t *)m_data)[m_remainder]), message, prefix);
|
||||
u.p64 = m_data;
|
||||
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
Mix(&u.p64[sc_numVars], h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p8 = ((const uint8_t *)message) + prefix;
|
||||
length -= prefix;
|
||||
}
|
||||
else
|
||||
{
|
||||
u.p8 = (const uint8_t *)message;
|
||||
}
|
||||
|
||||
// handle all whole blocks of sc_blockSize bytes
|
||||
end = u.p64 + (length/sc_blockSize)*sc_numVars;
|
||||
remainder = (uint8_t)(length-((const uint8_t *)end-u.p8));
|
||||
if (ALLOW_UNALIGNED_READS || (u.i & 0x7) == 0)
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
memcpy(m_data, u.p8, sc_blockSize);
|
||||
Mix(m_data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
|
||||
// stuff away the last few bytes
|
||||
m_remainder = remainder;
|
||||
memcpy(m_data, end, remainder);
|
||||
|
||||
// stuff away the variables
|
||||
m_state[0] = h0;
|
||||
m_state[1] = h1;
|
||||
m_state[2] = h2;
|
||||
m_state[3] = h3;
|
||||
m_state[4] = h4;
|
||||
m_state[5] = h5;
|
||||
m_state[6] = h6;
|
||||
m_state[7] = h7;
|
||||
m_state[8] = h8;
|
||||
m_state[9] = h9;
|
||||
m_state[10] = h10;
|
||||
m_state[11] = h11;
|
||||
}
|
||||
|
||||
|
||||
// report the hash for the concatenation of all message fragments so far
|
||||
void SpookyHashV1::Final(uint64_t *hash1, uint64_t *hash2)
|
||||
{
|
||||
// init the variables
|
||||
if (m_length < sc_bufSize)
|
||||
{
|
||||
*hash1 = m_state[0];
|
||||
*hash2 = m_state[1];
|
||||
Short( m_data, m_length, hash1, hash2);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t *data = (const uint64_t *)m_data;
|
||||
uint8_t remainder = m_remainder;
|
||||
|
||||
uint64_t h0 = m_state[0];
|
||||
uint64_t h1 = m_state[1];
|
||||
uint64_t h2 = m_state[2];
|
||||
uint64_t h3 = m_state[3];
|
||||
uint64_t h4 = m_state[4];
|
||||
uint64_t h5 = m_state[5];
|
||||
uint64_t h6 = m_state[6];
|
||||
uint64_t h7 = m_state[7];
|
||||
uint64_t h8 = m_state[8];
|
||||
uint64_t h9 = m_state[9];
|
||||
uint64_t h10 = m_state[10];
|
||||
uint64_t h11 = m_state[11];
|
||||
|
||||
if (remainder >= sc_blockSize)
|
||||
{
|
||||
// m_data can contain two blocks; handle any whole first block
|
||||
Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
data += sc_numVars;
|
||||
remainder -= sc_blockSize;
|
||||
}
|
||||
|
||||
// mix in the last partial block, and the length mod sc_blockSize
|
||||
memset(&((uint8_t *)data)[remainder], 0, (sc_blockSize-remainder));
|
||||
|
||||
((uint8_t *)data)[sc_blockSize-1] = remainder;
|
||||
Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
|
||||
// do some final mixing
|
||||
End(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
|
||||
*hash1 = h0;
|
||||
*hash2 = h1;
|
||||
}
|
||||
|
||||
} // namespace hash
|
||||
} // namespace folly
|
||||
-304
@@ -1,304 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This is version 1 of SpookyHash, incompatible with version 2.
|
||||
//
|
||||
// SpookyHash: a 128-bit noncryptographic hash function
|
||||
// By Bob Jenkins, public domain
|
||||
// Oct 31 2010: alpha, framework + SpookyHash::Mix appears right
|
||||
// Oct 31 2011: alpha again, Mix only good to 2^^69 but rest appears right
|
||||
// Dec 31 2011: beta, improved Mix, tested it for 2-bit deltas
|
||||
// Feb 2 2012: production, same bits as beta
|
||||
// Feb 5 2012: adjusted definitions of uint* to be more portable
|
||||
// Mar 30 2012: 3 bytes/cycle, not 4. Alpha was 4 but wasn't thorough enough.
|
||||
//
|
||||
// Up to 3 bytes/cycle for long messages. Reasonably fast for short messages.
|
||||
// All 1 or 2 bit deltas achieve avalanche within 1% bias per output bit.
|
||||
//
|
||||
// This was developed for and tested on 64-bit x86-compatible processors.
|
||||
// It assumes the processor is little-endian. There is a macro
|
||||
// controlling whether unaligned reads are allowed (by default they are).
|
||||
// This should be an equally good hash on big-endian machines, but it will
|
||||
// compute different results on them than on little-endian machines.
|
||||
//
|
||||
// Google's CityHash has similar specs to SpookyHash, and CityHash is faster
|
||||
// on some platforms. MD4 and MD5 also have similar specs, but they are orders
|
||||
// of magnitude slower. CRCs are two or more times slower, but unlike
|
||||
// SpookyHash, they have nice math for combining the CRCs of pieces to form
|
||||
// the CRCs of wholes. There are also cryptographic hashes, but those are even
|
||||
// slower than MD5.
|
||||
//
|
||||
|
||||
#ifndef FOLLY_SPOOKYHASHV1_H_
|
||||
#define FOLLY_SPOOKYHASHV1_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace folly {
|
||||
namespace hash {
|
||||
|
||||
class SpookyHashV1
|
||||
{
|
||||
public:
|
||||
//
|
||||
// SpookyHash: hash a single message in one call, produce 128-bit output
|
||||
//
|
||||
static void Hash128(
|
||||
const void *message, // message to hash
|
||||
size_t length, // length of message in bytes
|
||||
uint64_t *hash1, // in/out: in seed 1, out hash value 1
|
||||
uint64_t *hash2); // in/out: in seed 2, out hash value 2
|
||||
|
||||
//
|
||||
// Hash64: hash a single message in one call, return 64-bit output
|
||||
//
|
||||
static uint64_t Hash64(
|
||||
const void *message, // message to hash
|
||||
size_t length, // length of message in bytes
|
||||
uint64_t seed) // seed
|
||||
{
|
||||
uint64_t hash1 = seed;
|
||||
Hash128(message, length, &hash1, &seed);
|
||||
return hash1;
|
||||
}
|
||||
|
||||
//
|
||||
// Hash32: hash a single message in one call, produce 32-bit output
|
||||
//
|
||||
static uint32_t Hash32(
|
||||
const void *message, // message to hash
|
||||
size_t length, // length of message in bytes
|
||||
uint32_t seed) // seed
|
||||
{
|
||||
uint64_t hash1 = seed, hash2 = seed;
|
||||
Hash128(message, length, &hash1, &hash2);
|
||||
return (uint32_t)hash1;
|
||||
}
|
||||
|
||||
//
|
||||
// Init: initialize the context of a SpookyHash
|
||||
//
|
||||
void Init(
|
||||
uint64_t seed1, // any 64-bit value will do, including 0
|
||||
uint64_t seed2); // different seeds produce independent hashes
|
||||
|
||||
//
|
||||
// Update: add a piece of a message to a SpookyHash state
|
||||
//
|
||||
void Update(
|
||||
const void *message, // message fragment
|
||||
size_t length); // length of message fragment in bytes
|
||||
|
||||
|
||||
//
|
||||
// Final: compute the hash for the current SpookyHash state
|
||||
//
|
||||
// This does not modify the state; you can keep updating it afterward
|
||||
//
|
||||
// The result is the same as if SpookyHash() had been called with
|
||||
// all the pieces concatenated into one message.
|
||||
//
|
||||
void Final(
|
||||
uint64_t *hash1, // out only: first 64 bits of hash value.
|
||||
uint64_t *hash2); // out only: second 64 bits of hash value.
|
||||
|
||||
//
|
||||
// left rotate a 64-bit value by k bytes
|
||||
//
|
||||
static inline uint64_t Rot64(uint64_t x, int k)
|
||||
{
|
||||
return (x << k) | (x >> (64 - k));
|
||||
}
|
||||
|
||||
//
|
||||
// This is used if the input is 96 bytes long or longer.
|
||||
//
|
||||
// The internal state is fully overwritten every 96 bytes.
|
||||
// Every input bit appears to cause at least 128 bits of entropy
|
||||
// before 96 other bytes are combined, when run forward or backward
|
||||
// For every input bit,
|
||||
// Two inputs differing in just that input bit
|
||||
// Where "differ" means xor or subtraction
|
||||
// And the base value is random
|
||||
// When run forward or backwards one Mix
|
||||
// I tried 3 pairs of each; they all differed by at least 212 bits.
|
||||
//
|
||||
static inline void Mix(
|
||||
const uint64_t *data,
|
||||
uint64_t &s0, uint64_t &s1, uint64_t &s2, uint64_t &s3,
|
||||
uint64_t &s4, uint64_t &s5, uint64_t &s6, uint64_t &s7,
|
||||
uint64_t &s8, uint64_t &s9, uint64_t &s10,uint64_t &s11)
|
||||
{
|
||||
s0 += data[0]; s2 ^= s10; s11 ^= s0; s0 = Rot64(s0,11); s11 += s1;
|
||||
s1 += data[1]; s3 ^= s11; s0 ^= s1; s1 = Rot64(s1,32); s0 += s2;
|
||||
s2 += data[2]; s4 ^= s0; s1 ^= s2; s2 = Rot64(s2,43); s1 += s3;
|
||||
s3 += data[3]; s5 ^= s1; s2 ^= s3; s3 = Rot64(s3,31); s2 += s4;
|
||||
s4 += data[4]; s6 ^= s2; s3 ^= s4; s4 = Rot64(s4,17); s3 += s5;
|
||||
s5 += data[5]; s7 ^= s3; s4 ^= s5; s5 = Rot64(s5,28); s4 += s6;
|
||||
s6 += data[6]; s8 ^= s4; s5 ^= s6; s6 = Rot64(s6,39); s5 += s7;
|
||||
s7 += data[7]; s9 ^= s5; s6 ^= s7; s7 = Rot64(s7,57); s6 += s8;
|
||||
s8 += data[8]; s10 ^= s6; s7 ^= s8; s8 = Rot64(s8,55); s7 += s9;
|
||||
s9 += data[9]; s11 ^= s7; s8 ^= s9; s9 = Rot64(s9,54); s8 += s10;
|
||||
s10 += data[10]; s0 ^= s8; s9 ^= s10; s10 = Rot64(s10,22); s9 += s11;
|
||||
s11 += data[11]; s1 ^= s9; s10 ^= s11; s11 = Rot64(s11,46); s10 += s0;
|
||||
}
|
||||
|
||||
//
|
||||
// Mix all 12 inputs together so that h0, h1 are a hash of them all.
|
||||
//
|
||||
// For two inputs differing in just the input bits
|
||||
// Where "differ" means xor or subtraction
|
||||
// And the base value is random, or a counting value starting at that bit
|
||||
// The final result will have each bit of h0, h1 flip
|
||||
// For every input bit,
|
||||
// with probability 50 +- .3%
|
||||
// For every pair of input bits,
|
||||
// with probability 50 +- 3%
|
||||
//
|
||||
// This does not rely on the last Mix() call having already mixed some.
|
||||
// Two iterations was almost good enough for a 64-bit result, but a
|
||||
// 128-bit result is reported, so End() does three iterations.
|
||||
//
|
||||
static inline void EndPartial(
|
||||
uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
|
||||
uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
|
||||
uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
|
||||
{
|
||||
h11+= h1; h2 ^= h11; h1 = Rot64(h1,44);
|
||||
h0 += h2; h3 ^= h0; h2 = Rot64(h2,15);
|
||||
h1 += h3; h4 ^= h1; h3 = Rot64(h3,34);
|
||||
h2 += h4; h5 ^= h2; h4 = Rot64(h4,21);
|
||||
h3 += h5; h6 ^= h3; h5 = Rot64(h5,38);
|
||||
h4 += h6; h7 ^= h4; h6 = Rot64(h6,33);
|
||||
h5 += h7; h8 ^= h5; h7 = Rot64(h7,10);
|
||||
h6 += h8; h9 ^= h6; h8 = Rot64(h8,13);
|
||||
h7 += h9; h10^= h7; h9 = Rot64(h9,38);
|
||||
h8 += h10; h11^= h8; h10= Rot64(h10,53);
|
||||
h9 += h11; h0 ^= h9; h11= Rot64(h11,42);
|
||||
h10+= h0; h1 ^= h10; h0 = Rot64(h0,54);
|
||||
}
|
||||
|
||||
static inline void End(
|
||||
uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
|
||||
uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
|
||||
uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
|
||||
{
|
||||
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
}
|
||||
|
||||
//
|
||||
// The goal is for each bit of the input to expand into 128 bits of
|
||||
// apparent entropy before it is fully overwritten.
|
||||
// n trials both set and cleared at least m bits of h0 h1 h2 h3
|
||||
// n: 2 m: 29
|
||||
// n: 3 m: 46
|
||||
// n: 4 m: 57
|
||||
// n: 5 m: 107
|
||||
// n: 6 m: 146
|
||||
// n: 7 m: 152
|
||||
// when run forwards or backwards
|
||||
// for all 1-bit and 2-bit diffs
|
||||
// with diffs defined by either xor or subtraction
|
||||
// with a base of all zeros plus a counter, or plus another bit, or random
|
||||
//
|
||||
static inline void ShortMix(uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3)
|
||||
{
|
||||
h2 = Rot64(h2,50); h2 += h3; h0 ^= h2;
|
||||
h3 = Rot64(h3,52); h3 += h0; h1 ^= h3;
|
||||
h0 = Rot64(h0,30); h0 += h1; h2 ^= h0;
|
||||
h1 = Rot64(h1,41); h1 += h2; h3 ^= h1;
|
||||
h2 = Rot64(h2,54); h2 += h3; h0 ^= h2;
|
||||
h3 = Rot64(h3,48); h3 += h0; h1 ^= h3;
|
||||
h0 = Rot64(h0,38); h0 += h1; h2 ^= h0;
|
||||
h1 = Rot64(h1,37); h1 += h2; h3 ^= h1;
|
||||
h2 = Rot64(h2,62); h2 += h3; h0 ^= h2;
|
||||
h3 = Rot64(h3,34); h3 += h0; h1 ^= h3;
|
||||
h0 = Rot64(h0,5); h0 += h1; h2 ^= h0;
|
||||
h1 = Rot64(h1,36); h1 += h2; h3 ^= h1;
|
||||
}
|
||||
|
||||
//
|
||||
// Mix all 4 inputs together so that h0, h1 are a hash of them all.
|
||||
//
|
||||
// For two inputs differing in just the input bits
|
||||
// Where "differ" means xor or subtraction
|
||||
// And the base value is random, or a counting value starting at that bit
|
||||
// The final result will have each bit of h0, h1 flip
|
||||
// For every input bit,
|
||||
// with probability 50 +- .3% (it is probably better than that)
|
||||
// For every pair of input bits,
|
||||
// with probability 50 +- .75% (the worst case is approximately that)
|
||||
//
|
||||
static inline void ShortEnd(uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3)
|
||||
{
|
||||
h3 ^= h2; h2 = Rot64(h2,15); h3 += h2;
|
||||
h0 ^= h3; h3 = Rot64(h3,52); h0 += h3;
|
||||
h1 ^= h0; h0 = Rot64(h0,26); h1 += h0;
|
||||
h2 ^= h1; h1 = Rot64(h1,51); h2 += h1;
|
||||
h3 ^= h2; h2 = Rot64(h2,28); h3 += h2;
|
||||
h0 ^= h3; h3 = Rot64(h3,9); h0 += h3;
|
||||
h1 ^= h0; h0 = Rot64(h0,47); h1 += h0;
|
||||
h2 ^= h1; h1 = Rot64(h1,54); h2 += h1;
|
||||
h3 ^= h2; h2 = Rot64(h2,32); h3 += h2;
|
||||
h0 ^= h3; h3 = Rot64(h3,25); h0 += h3;
|
||||
h1 ^= h0; h0 = Rot64(h0,63); h1 += h0;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
//
|
||||
// Short is used for messages under 192 bytes in length
|
||||
// Short has a low startup cost, the normal mode is good for long
|
||||
// keys, the cost crossover is at about 192 bytes. The two modes were
|
||||
// held to the same quality bar.
|
||||
//
|
||||
static void Short(
|
||||
const void *message, // message (array of bytes, not necessarily aligned)
|
||||
size_t length, // length of message (in bytes)
|
||||
uint64_t *hash1, // in/out: in the seed, out the hash value
|
||||
uint64_t *hash2); // in/out: in the seed, out the hash value
|
||||
|
||||
// number of uint64_t's in internal state
|
||||
static const size_t sc_numVars = 12;
|
||||
|
||||
// size of the internal state
|
||||
static const size_t sc_blockSize = sc_numVars*8;
|
||||
|
||||
// size of buffer of unhashed data, in bytes
|
||||
static const size_t sc_bufSize = 2*sc_blockSize;
|
||||
|
||||
//
|
||||
// sc_const: a constant which:
|
||||
// * is not zero
|
||||
// * is odd
|
||||
// * is a not-very-regular mix of 1's and 0's
|
||||
// * does not need any other special mathematical properties
|
||||
//
|
||||
static const uint64_t sc_const = 0xdeadbeefdeadbeefLL;
|
||||
|
||||
uint64_t m_data[2*sc_numVars]; // unhashed data, for partial messages
|
||||
uint64_t m_state[sc_numVars]; // internal state of the hash
|
||||
size_t m_length; // total length of the input so far
|
||||
uint8_t m_remainder; // length of unhashed data stashed in m_data
|
||||
};
|
||||
|
||||
} // namespace hash
|
||||
} // namespace folly
|
||||
|
||||
#endif
|
||||
-373
@@ -1,373 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Spooky Hash
|
||||
// A 128-bit noncryptographic hash, for checksums and table lookup
|
||||
// By Bob Jenkins. Public domain.
|
||||
// Oct 31 2010: published framework, disclaimer ShortHash isn't right
|
||||
// Nov 7 2010: disabled ShortHash
|
||||
// Oct 31 2011: replace End, ShortMix, ShortEnd, enable ShortHash again
|
||||
// April 10 2012: buffer overflow on platforms without unaligned reads
|
||||
// July 12 2012: was passing out variables in final to in/out in short
|
||||
// July 30 2012: I reintroduced the buffer overflow
|
||||
// August 5 2012: SpookyV2: d = should be d += in short hash, and remove extra mix from long hash
|
||||
|
||||
#include "folly/SpookyHashV2.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#define ALLOW_UNALIGNED_READS 1
|
||||
|
||||
namespace folly {
|
||||
namespace hash {
|
||||
|
||||
//
|
||||
// short hash ... it could be used on any message,
|
||||
// but it's used by Spooky just for short messages.
|
||||
//
|
||||
void SpookyHashV2::Short(
|
||||
const void *message,
|
||||
size_t length,
|
||||
uint64_t *hash1,
|
||||
uint64_t *hash2)
|
||||
{
|
||||
uint64_t buf[2*sc_numVars];
|
||||
union
|
||||
{
|
||||
const uint8_t *p8;
|
||||
uint32_t *p32;
|
||||
uint64_t *p64;
|
||||
size_t i;
|
||||
} u;
|
||||
|
||||
u.p8 = (const uint8_t *)message;
|
||||
|
||||
if (!ALLOW_UNALIGNED_READS && (u.i & 0x7))
|
||||
{
|
||||
memcpy(buf, message, length);
|
||||
u.p64 = buf;
|
||||
}
|
||||
|
||||
size_t remainder = length%32;
|
||||
uint64_t a=*hash1;
|
||||
uint64_t b=*hash2;
|
||||
uint64_t c=sc_const;
|
||||
uint64_t d=sc_const;
|
||||
|
||||
if (length > 15)
|
||||
{
|
||||
const uint64_t *end = u.p64 + (length/32)*4;
|
||||
|
||||
// handle all complete sets of 32 bytes
|
||||
for (; u.p64 < end; u.p64 += 4)
|
||||
{
|
||||
c += u.p64[0];
|
||||
d += u.p64[1];
|
||||
ShortMix(a,b,c,d);
|
||||
a += u.p64[2];
|
||||
b += u.p64[3];
|
||||
}
|
||||
|
||||
//Handle the case of 16+ remaining bytes.
|
||||
if (remainder >= 16)
|
||||
{
|
||||
c += u.p64[0];
|
||||
d += u.p64[1];
|
||||
ShortMix(a,b,c,d);
|
||||
u.p64 += 2;
|
||||
remainder -= 16;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last 0..15 bytes, and its length
|
||||
d += ((uint64_t)length) << 56;
|
||||
switch (remainder)
|
||||
{
|
||||
case 15:
|
||||
d += ((uint64_t)u.p8[14]) << 48;
|
||||
case 14:
|
||||
d += ((uint64_t)u.p8[13]) << 40;
|
||||
case 13:
|
||||
d += ((uint64_t)u.p8[12]) << 32;
|
||||
case 12:
|
||||
d += u.p32[2];
|
||||
c += u.p64[0];
|
||||
break;
|
||||
case 11:
|
||||
d += ((uint64_t)u.p8[10]) << 16;
|
||||
case 10:
|
||||
d += ((uint64_t)u.p8[9]) << 8;
|
||||
case 9:
|
||||
d += (uint64_t)u.p8[8];
|
||||
case 8:
|
||||
c += u.p64[0];
|
||||
break;
|
||||
case 7:
|
||||
c += ((uint64_t)u.p8[6]) << 48;
|
||||
case 6:
|
||||
c += ((uint64_t)u.p8[5]) << 40;
|
||||
case 5:
|
||||
c += ((uint64_t)u.p8[4]) << 32;
|
||||
case 4:
|
||||
c += u.p32[0];
|
||||
break;
|
||||
case 3:
|
||||
c += ((uint64_t)u.p8[2]) << 16;
|
||||
case 2:
|
||||
c += ((uint64_t)u.p8[1]) << 8;
|
||||
case 1:
|
||||
c += (uint64_t)u.p8[0];
|
||||
break;
|
||||
case 0:
|
||||
c += sc_const;
|
||||
d += sc_const;
|
||||
}
|
||||
ShortEnd(a,b,c,d);
|
||||
*hash1 = a;
|
||||
*hash2 = b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// do the whole hash in one call
|
||||
void SpookyHashV2::Hash128(
|
||||
const void *message,
|
||||
size_t length,
|
||||
uint64_t *hash1,
|
||||
uint64_t *hash2)
|
||||
{
|
||||
if (length < sc_bufSize)
|
||||
{
|
||||
Short(message, length, hash1, hash2);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
|
||||
uint64_t buf[sc_numVars];
|
||||
uint64_t *end;
|
||||
union
|
||||
{
|
||||
const uint8_t *p8;
|
||||
uint64_t *p64;
|
||||
size_t i;
|
||||
} u;
|
||||
size_t remainder;
|
||||
|
||||
h0=h3=h6=h9 = *hash1;
|
||||
h1=h4=h7=h10 = *hash2;
|
||||
h2=h5=h8=h11 = sc_const;
|
||||
|
||||
u.p8 = (const uint8_t *)message;
|
||||
end = u.p64 + (length/sc_blockSize)*sc_numVars;
|
||||
|
||||
// handle all whole sc_blockSize blocks of bytes
|
||||
if (ALLOW_UNALIGNED_READS || ((u.i & 0x7) == 0))
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
memcpy(buf, u.p64, sc_blockSize);
|
||||
Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
|
||||
// handle the last partial block of sc_blockSize bytes
|
||||
remainder = (length - ((const uint8_t *)end-(const uint8_t *)message));
|
||||
memcpy(buf, end, remainder);
|
||||
memset(((uint8_t *)buf)+remainder, 0, sc_blockSize-remainder);
|
||||
((uint8_t *)buf)[sc_blockSize-1] = remainder;
|
||||
|
||||
// do some final mixing
|
||||
End(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
*hash1 = h0;
|
||||
*hash2 = h1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// init spooky state
|
||||
void SpookyHashV2::Init(uint64_t seed1, uint64_t seed2)
|
||||
{
|
||||
m_length = 0;
|
||||
m_remainder = 0;
|
||||
m_state[0] = seed1;
|
||||
m_state[1] = seed2;
|
||||
}
|
||||
|
||||
|
||||
// add a message fragment to the state
|
||||
void SpookyHashV2::Update(const void *message, size_t length)
|
||||
{
|
||||
uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
|
||||
size_t newLength = length + m_remainder;
|
||||
uint8_t remainder;
|
||||
union
|
||||
{
|
||||
const uint8_t *p8;
|
||||
uint64_t *p64;
|
||||
size_t i;
|
||||
} u;
|
||||
const uint64_t *end;
|
||||
|
||||
// Is this message fragment too short? If it is, stuff it away.
|
||||
if (newLength < sc_bufSize)
|
||||
{
|
||||
memcpy(&((uint8_t *)m_data)[m_remainder], message, length);
|
||||
m_length = length + m_length;
|
||||
m_remainder = (uint8_t)newLength;
|
||||
return;
|
||||
}
|
||||
|
||||
// init the variables
|
||||
if (m_length < sc_bufSize)
|
||||
{
|
||||
h0=h3=h6=h9 = m_state[0];
|
||||
h1=h4=h7=h10 = m_state[1];
|
||||
h2=h5=h8=h11 = sc_const;
|
||||
}
|
||||
else
|
||||
{
|
||||
h0 = m_state[0];
|
||||
h1 = m_state[1];
|
||||
h2 = m_state[2];
|
||||
h3 = m_state[3];
|
||||
h4 = m_state[4];
|
||||
h5 = m_state[5];
|
||||
h6 = m_state[6];
|
||||
h7 = m_state[7];
|
||||
h8 = m_state[8];
|
||||
h9 = m_state[9];
|
||||
h10 = m_state[10];
|
||||
h11 = m_state[11];
|
||||
}
|
||||
m_length = length + m_length;
|
||||
|
||||
// if we've got anything stuffed away, use it now
|
||||
if (m_remainder)
|
||||
{
|
||||
uint8_t prefix = sc_bufSize-m_remainder;
|
||||
memcpy(&(((uint8_t *)m_data)[m_remainder]), message, prefix);
|
||||
u.p64 = m_data;
|
||||
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
Mix(&u.p64[sc_numVars], h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p8 = ((const uint8_t *)message) + prefix;
|
||||
length -= prefix;
|
||||
}
|
||||
else
|
||||
{
|
||||
u.p8 = (const uint8_t *)message;
|
||||
}
|
||||
|
||||
// handle all whole blocks of sc_blockSize bytes
|
||||
end = u.p64 + (length/sc_blockSize)*sc_numVars;
|
||||
remainder = (uint8_t)(length-((const uint8_t *)end-u.p8));
|
||||
if (ALLOW_UNALIGNED_READS || (u.i & 0x7) == 0)
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (u.p64 < end)
|
||||
{
|
||||
memcpy(m_data, u.p8, sc_blockSize);
|
||||
Mix(m_data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
u.p64 += sc_numVars;
|
||||
}
|
||||
}
|
||||
|
||||
// stuff away the last few bytes
|
||||
m_remainder = remainder;
|
||||
memcpy(m_data, end, remainder);
|
||||
|
||||
// stuff away the variables
|
||||
m_state[0] = h0;
|
||||
m_state[1] = h1;
|
||||
m_state[2] = h2;
|
||||
m_state[3] = h3;
|
||||
m_state[4] = h4;
|
||||
m_state[5] = h5;
|
||||
m_state[6] = h6;
|
||||
m_state[7] = h7;
|
||||
m_state[8] = h8;
|
||||
m_state[9] = h9;
|
||||
m_state[10] = h10;
|
||||
m_state[11] = h11;
|
||||
}
|
||||
|
||||
|
||||
// report the hash for the concatenation of all message fragments so far
|
||||
void SpookyHashV2::Final(uint64_t *hash1, uint64_t *hash2)
|
||||
{
|
||||
// init the variables
|
||||
if (m_length < sc_bufSize)
|
||||
{
|
||||
*hash1 = m_state[0];
|
||||
*hash2 = m_state[1];
|
||||
Short( m_data, m_length, hash1, hash2);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t *data = (const uint64_t *)m_data;
|
||||
uint8_t remainder = m_remainder;
|
||||
|
||||
uint64_t h0 = m_state[0];
|
||||
uint64_t h1 = m_state[1];
|
||||
uint64_t h2 = m_state[2];
|
||||
uint64_t h3 = m_state[3];
|
||||
uint64_t h4 = m_state[4];
|
||||
uint64_t h5 = m_state[5];
|
||||
uint64_t h6 = m_state[6];
|
||||
uint64_t h7 = m_state[7];
|
||||
uint64_t h8 = m_state[8];
|
||||
uint64_t h9 = m_state[9];
|
||||
uint64_t h10 = m_state[10];
|
||||
uint64_t h11 = m_state[11];
|
||||
|
||||
if (remainder >= sc_blockSize)
|
||||
{
|
||||
// m_data can contain two blocks; handle any whole first block
|
||||
Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
data += sc_numVars;
|
||||
remainder -= sc_blockSize;
|
||||
}
|
||||
|
||||
// mix in the last partial block, and the length mod sc_blockSize
|
||||
memset(&((uint8_t *)data)[remainder], 0, (sc_blockSize-remainder));
|
||||
|
||||
((uint8_t *)data)[sc_blockSize-1] = remainder;
|
||||
|
||||
// do some final mixing
|
||||
End(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
|
||||
*hash1 = h0;
|
||||
*hash2 = h1;
|
||||
}
|
||||
|
||||
} // namespace hash
|
||||
} // namespace folly
|
||||
-309
@@ -1,309 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This is version 2 of SpookyHash, incompatible with version 1.
|
||||
//
|
||||
// SpookyHash: a 128-bit noncryptographic hash function
|
||||
// By Bob Jenkins, public domain
|
||||
// Oct 31 2010: alpha, framework + SpookyHash::Mix appears right
|
||||
// Oct 31 2011: alpha again, Mix only good to 2^^69 but rest appears right
|
||||
// Dec 31 2011: beta, improved Mix, tested it for 2-bit deltas
|
||||
// Feb 2 2012: production, same bits as beta
|
||||
// Feb 5 2012: adjusted definitions of uint* to be more portable
|
||||
// Mar 30 2012: 3 bytes/cycle, not 4. Alpha was 4 but wasn't thorough enough.
|
||||
// August 5 2012: SpookyV2 (different results)
|
||||
//
|
||||
// Up to 3 bytes/cycle for long messages. Reasonably fast for short messages.
|
||||
// All 1 or 2 bit deltas achieve avalanche within 1% bias per output bit.
|
||||
//
|
||||
// This was developed for and tested on 64-bit x86-compatible processors.
|
||||
// It assumes the processor is little-endian. There is a macro
|
||||
// controlling whether unaligned reads are allowed (by default they are).
|
||||
// This should be an equally good hash on big-endian machines, but it will
|
||||
// compute different results on them than on little-endian machines.
|
||||
//
|
||||
// Google's CityHash has similar specs to SpookyHash, and CityHash is faster
|
||||
// on new Intel boxes. MD4 and MD5 also have similar specs, but they are orders
|
||||
// of magnitude slower. CRCs are two or more times slower, but unlike
|
||||
// SpookyHash, they have nice math for combining the CRCs of pieces to form
|
||||
// the CRCs of wholes. There are also cryptographic hashes, but those are even
|
||||
// slower than MD5.
|
||||
//
|
||||
|
||||
#ifndef FOLLY_SPOOKYHASHV2_H_
|
||||
#define FOLLY_SPOOKYHASHV2_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace folly {
|
||||
namespace hash {
|
||||
|
||||
class SpookyHashV2
|
||||
{
|
||||
public:
|
||||
//
|
||||
// SpookyHash: hash a single message in one call, produce 128-bit output
|
||||
//
|
||||
static void Hash128(
|
||||
const void *message, // message to hash
|
||||
size_t length, // length of message in bytes
|
||||
uint64_t *hash1, // in/out: in seed 1, out hash value 1
|
||||
uint64_t *hash2); // in/out: in seed 2, out hash value 2
|
||||
|
||||
//
|
||||
// Hash64: hash a single message in one call, return 64-bit output
|
||||
//
|
||||
static uint64_t Hash64(
|
||||
const void *message, // message to hash
|
||||
size_t length, // length of message in bytes
|
||||
uint64_t seed) // seed
|
||||
{
|
||||
uint64_t hash1 = seed;
|
||||
Hash128(message, length, &hash1, &seed);
|
||||
return hash1;
|
||||
}
|
||||
|
||||
//
|
||||
// Hash32: hash a single message in one call, produce 32-bit output
|
||||
//
|
||||
static uint32_t Hash32(
|
||||
const void *message, // message to hash
|
||||
size_t length, // length of message in bytes
|
||||
uint32_t seed) // seed
|
||||
{
|
||||
uint64_t hash1 = seed, hash2 = seed;
|
||||
Hash128(message, length, &hash1, &hash2);
|
||||
return (uint32_t)hash1;
|
||||
}
|
||||
|
||||
//
|
||||
// Init: initialize the context of a SpookyHash
|
||||
//
|
||||
void Init(
|
||||
uint64_t seed1, // any 64-bit value will do, including 0
|
||||
uint64_t seed2); // different seeds produce independent hashes
|
||||
|
||||
//
|
||||
// Update: add a piece of a message to a SpookyHash state
|
||||
//
|
||||
void Update(
|
||||
const void *message, // message fragment
|
||||
size_t length); // length of message fragment in bytes
|
||||
|
||||
|
||||
//
|
||||
// Final: compute the hash for the current SpookyHash state
|
||||
//
|
||||
// This does not modify the state; you can keep updating it afterward
|
||||
//
|
||||
// The result is the same as if SpookyHash() had been called with
|
||||
// all the pieces concatenated into one message.
|
||||
//
|
||||
void Final(
|
||||
uint64_t *hash1, // out only: first 64 bits of hash value.
|
||||
uint64_t *hash2); // out only: second 64 bits of hash value.
|
||||
|
||||
//
|
||||
// left rotate a 64-bit value by k bytes
|
||||
//
|
||||
static inline uint64_t Rot64(uint64_t x, int k)
|
||||
{
|
||||
return (x << k) | (x >> (64 - k));
|
||||
}
|
||||
|
||||
//
|
||||
// This is used if the input is 96 bytes long or longer.
|
||||
//
|
||||
// The internal state is fully overwritten every 96 bytes.
|
||||
// Every input bit appears to cause at least 128 bits of entropy
|
||||
// before 96 other bytes are combined, when run forward or backward
|
||||
// For every input bit,
|
||||
// Two inputs differing in just that input bit
|
||||
// Where "differ" means xor or subtraction
|
||||
// And the base value is random
|
||||
// When run forward or backwards one Mix
|
||||
// I tried 3 pairs of each; they all differed by at least 212 bits.
|
||||
//
|
||||
static inline void Mix(
|
||||
const uint64_t *data,
|
||||
uint64_t &s0, uint64_t &s1, uint64_t &s2, uint64_t &s3,
|
||||
uint64_t &s4, uint64_t &s5, uint64_t &s6, uint64_t &s7,
|
||||
uint64_t &s8, uint64_t &s9, uint64_t &s10,uint64_t &s11)
|
||||
{
|
||||
s0 += data[0]; s2 ^= s10; s11 ^= s0; s0 = Rot64(s0,11); s11 += s1;
|
||||
s1 += data[1]; s3 ^= s11; s0 ^= s1; s1 = Rot64(s1,32); s0 += s2;
|
||||
s2 += data[2]; s4 ^= s0; s1 ^= s2; s2 = Rot64(s2,43); s1 += s3;
|
||||
s3 += data[3]; s5 ^= s1; s2 ^= s3; s3 = Rot64(s3,31); s2 += s4;
|
||||
s4 += data[4]; s6 ^= s2; s3 ^= s4; s4 = Rot64(s4,17); s3 += s5;
|
||||
s5 += data[5]; s7 ^= s3; s4 ^= s5; s5 = Rot64(s5,28); s4 += s6;
|
||||
s6 += data[6]; s8 ^= s4; s5 ^= s6; s6 = Rot64(s6,39); s5 += s7;
|
||||
s7 += data[7]; s9 ^= s5; s6 ^= s7; s7 = Rot64(s7,57); s6 += s8;
|
||||
s8 += data[8]; s10 ^= s6; s7 ^= s8; s8 = Rot64(s8,55); s7 += s9;
|
||||
s9 += data[9]; s11 ^= s7; s8 ^= s9; s9 = Rot64(s9,54); s8 += s10;
|
||||
s10 += data[10]; s0 ^= s8; s9 ^= s10; s10 = Rot64(s10,22); s9 += s11;
|
||||
s11 += data[11]; s1 ^= s9; s10 ^= s11; s11 = Rot64(s11,46); s10 += s0;
|
||||
}
|
||||
|
||||
//
|
||||
// Mix all 12 inputs together so that h0, h1 are a hash of them all.
|
||||
//
|
||||
// For two inputs differing in just the input bits
|
||||
// Where "differ" means xor or subtraction
|
||||
// And the base value is random, or a counting value starting at that bit
|
||||
// The final result will have each bit of h0, h1 flip
|
||||
// For every input bit,
|
||||
// with probability 50 +- .3%
|
||||
// For every pair of input bits,
|
||||
// with probability 50 +- 3%
|
||||
//
|
||||
// This does not rely on the last Mix() call having already mixed some.
|
||||
// Two iterations was almost good enough for a 64-bit result, but a
|
||||
// 128-bit result is reported, so End() does three iterations.
|
||||
//
|
||||
static inline void EndPartial(
|
||||
uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
|
||||
uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
|
||||
uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
|
||||
{
|
||||
h11+= h1; h2 ^= h11; h1 = Rot64(h1,44);
|
||||
h0 += h2; h3 ^= h0; h2 = Rot64(h2,15);
|
||||
h1 += h3; h4 ^= h1; h3 = Rot64(h3,34);
|
||||
h2 += h4; h5 ^= h2; h4 = Rot64(h4,21);
|
||||
h3 += h5; h6 ^= h3; h5 = Rot64(h5,38);
|
||||
h4 += h6; h7 ^= h4; h6 = Rot64(h6,33);
|
||||
h5 += h7; h8 ^= h5; h7 = Rot64(h7,10);
|
||||
h6 += h8; h9 ^= h6; h8 = Rot64(h8,13);
|
||||
h7 += h9; h10^= h7; h9 = Rot64(h9,38);
|
||||
h8 += h10; h11^= h8; h10= Rot64(h10,53);
|
||||
h9 += h11; h0 ^= h9; h11= Rot64(h11,42);
|
||||
h10+= h0; h1 ^= h10; h0 = Rot64(h0,54);
|
||||
}
|
||||
|
||||
static inline void End(
|
||||
const uint64_t *data,
|
||||
uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
|
||||
uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
|
||||
uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
|
||||
{
|
||||
h0 += data[0]; h1 += data[1]; h2 += data[2]; h3 += data[3];
|
||||
h4 += data[4]; h5 += data[5]; h6 += data[6]; h7 += data[7];
|
||||
h8 += data[8]; h9 += data[9]; h10 += data[10]; h11 += data[11];
|
||||
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
|
||||
}
|
||||
|
||||
//
|
||||
// The goal is for each bit of the input to expand into 128 bits of
|
||||
// apparent entropy before it is fully overwritten.
|
||||
// n trials both set and cleared at least m bits of h0 h1 h2 h3
|
||||
// n: 2 m: 29
|
||||
// n: 3 m: 46
|
||||
// n: 4 m: 57
|
||||
// n: 5 m: 107
|
||||
// n: 6 m: 146
|
||||
// n: 7 m: 152
|
||||
// when run forwards or backwards
|
||||
// for all 1-bit and 2-bit diffs
|
||||
// with diffs defined by either xor or subtraction
|
||||
// with a base of all zeros plus a counter, or plus another bit, or random
|
||||
//
|
||||
static inline void ShortMix(uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3)
|
||||
{
|
||||
h2 = Rot64(h2,50); h2 += h3; h0 ^= h2;
|
||||
h3 = Rot64(h3,52); h3 += h0; h1 ^= h3;
|
||||
h0 = Rot64(h0,30); h0 += h1; h2 ^= h0;
|
||||
h1 = Rot64(h1,41); h1 += h2; h3 ^= h1;
|
||||
h2 = Rot64(h2,54); h2 += h3; h0 ^= h2;
|
||||
h3 = Rot64(h3,48); h3 += h0; h1 ^= h3;
|
||||
h0 = Rot64(h0,38); h0 += h1; h2 ^= h0;
|
||||
h1 = Rot64(h1,37); h1 += h2; h3 ^= h1;
|
||||
h2 = Rot64(h2,62); h2 += h3; h0 ^= h2;
|
||||
h3 = Rot64(h3,34); h3 += h0; h1 ^= h3;
|
||||
h0 = Rot64(h0,5); h0 += h1; h2 ^= h0;
|
||||
h1 = Rot64(h1,36); h1 += h2; h3 ^= h1;
|
||||
}
|
||||
|
||||
//
|
||||
// Mix all 4 inputs together so that h0, h1 are a hash of them all.
|
||||
//
|
||||
// For two inputs differing in just the input bits
|
||||
// Where "differ" means xor or subtraction
|
||||
// And the base value is random, or a counting value starting at that bit
|
||||
// The final result will have each bit of h0, h1 flip
|
||||
// For every input bit,
|
||||
// with probability 50 +- .3% (it is probably better than that)
|
||||
// For every pair of input bits,
|
||||
// with probability 50 +- .75% (the worst case is approximately that)
|
||||
//
|
||||
static inline void ShortEnd(uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3)
|
||||
{
|
||||
h3 ^= h2; h2 = Rot64(h2,15); h3 += h2;
|
||||
h0 ^= h3; h3 = Rot64(h3,52); h0 += h3;
|
||||
h1 ^= h0; h0 = Rot64(h0,26); h1 += h0;
|
||||
h2 ^= h1; h1 = Rot64(h1,51); h2 += h1;
|
||||
h3 ^= h2; h2 = Rot64(h2,28); h3 += h2;
|
||||
h0 ^= h3; h3 = Rot64(h3,9); h0 += h3;
|
||||
h1 ^= h0; h0 = Rot64(h0,47); h1 += h0;
|
||||
h2 ^= h1; h1 = Rot64(h1,54); h2 += h1;
|
||||
h3 ^= h2; h2 = Rot64(h2,32); h3 += h2;
|
||||
h0 ^= h3; h3 = Rot64(h3,25); h0 += h3;
|
||||
h1 ^= h0; h0 = Rot64(h0,63); h1 += h0;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
//
|
||||
// Short is used for messages under 192 bytes in length
|
||||
// Short has a low startup cost, the normal mode is good for long
|
||||
// keys, the cost crossover is at about 192 bytes. The two modes were
|
||||
// held to the same quality bar.
|
||||
//
|
||||
static void Short(
|
||||
const void *message, // message (array of bytes, not necessarily aligned)
|
||||
size_t length, // length of message (in bytes)
|
||||
uint64_t *hash1, // in/out: in the seed, out the hash value
|
||||
uint64_t *hash2); // in/out: in the seed, out the hash value
|
||||
|
||||
// number of uint64_t's in internal state
|
||||
static const size_t sc_numVars = 12;
|
||||
|
||||
// size of the internal state
|
||||
static const size_t sc_blockSize = sc_numVars*8;
|
||||
|
||||
// size of buffer of unhashed data, in bytes
|
||||
static const size_t sc_bufSize = 2*sc_blockSize;
|
||||
|
||||
//
|
||||
// sc_const: a constant which:
|
||||
// * is not zero
|
||||
// * is odd
|
||||
// * is a not-very-regular mix of 1's and 0's
|
||||
// * does not need any other special mathematical properties
|
||||
//
|
||||
static const uint64_t sc_const = 0xdeadbeefdeadbeefLL;
|
||||
|
||||
uint64_t m_data[2*sc_numVars]; // unhashed data, for partial messages
|
||||
uint64_t m_state[sc_numVars]; // internal state of the hash
|
||||
size_t m_length; // total length of the input so far
|
||||
uint8_t m_remainder; // length of unhashed data stashed in m_data
|
||||
};
|
||||
|
||||
} // namespace hash
|
||||
} // namespace folly
|
||||
|
||||
#endif
|
||||
-267
@@ -1,267 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_STLALLOCATOR_H_
|
||||
#define FOLLY_STLALLOCATOR_H_
|
||||
|
||||
#include "folly/Traits.h"
|
||||
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Wrap a SimpleAllocator into a STL-compliant allocator.
|
||||
*
|
||||
* The SimpleAllocator must provide two methods:
|
||||
* void* allocate(size_t size);
|
||||
* void deallocate(void* ptr);
|
||||
* which, respectively, allocate a block of size bytes (aligned to the maximum
|
||||
* alignment required on your system), throwing std::bad_alloc if the
|
||||
* allocation can't be satisfied, and free a previously allocated block.
|
||||
*
|
||||
* Note that the following allocator resembles the standard allocator
|
||||
* quite well:
|
||||
*
|
||||
* class MallocAllocator {
|
||||
* public:
|
||||
* void* allocate(size_t size) {
|
||||
* void* p = malloc(size);
|
||||
* if (!p) throw std::bad_alloc();
|
||||
* return p;
|
||||
* }
|
||||
* void deallocate(void* p) {
|
||||
* free(p);
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* author: Tudor Bosman <tudorb@fb.com>
|
||||
*/
|
||||
|
||||
// This would be so much simpler with std::allocator_traits, but gcc 4.6.2
|
||||
// doesn't support it
|
||||
template <class Alloc, class T> class StlAllocator;
|
||||
|
||||
template <class Alloc> class StlAllocator<Alloc, void> {
|
||||
public:
|
||||
typedef void value_type;
|
||||
typedef void* pointer;
|
||||
typedef const void* const_pointer;
|
||||
template <class U> struct rebind {
|
||||
typedef StlAllocator<Alloc, U> other;
|
||||
};
|
||||
};
|
||||
|
||||
template <class Alloc, class T>
|
||||
class StlAllocator {
|
||||
public:
|
||||
typedef T value_type;
|
||||
typedef T* pointer;
|
||||
typedef const T* const_pointer;
|
||||
typedef T& reference;
|
||||
typedef const T& const_reference;
|
||||
|
||||
typedef ptrdiff_t difference_type;
|
||||
typedef size_t size_type;
|
||||
|
||||
StlAllocator() : alloc_(nullptr) { }
|
||||
explicit StlAllocator(Alloc* alloc) : alloc_(alloc) { }
|
||||
|
||||
template <class U> StlAllocator(const StlAllocator<Alloc, U>& other)
|
||||
: alloc_(other.alloc()) { }
|
||||
|
||||
T* allocate(size_t n, const void* hint = nullptr) {
|
||||
return static_cast<T*>(alloc_->allocate(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T* p, size_t n) {
|
||||
alloc_->deallocate(p);
|
||||
}
|
||||
|
||||
size_t max_size() const {
|
||||
return std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
T* address(T& x) const {
|
||||
return std::addressof(x);
|
||||
}
|
||||
|
||||
const T* address(const T& x) const {
|
||||
return std::addressof(x);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void construct(T* p, Args&&... args) {
|
||||
new (p) T(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void destroy(T* p) {
|
||||
p->~T();
|
||||
}
|
||||
|
||||
Alloc* alloc() const {
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
template <class U> struct rebind {
|
||||
typedef StlAllocator<Alloc, U> other;
|
||||
};
|
||||
|
||||
bool operator!=(const StlAllocator<Alloc, T>& other) const {
|
||||
return alloc_ != other.alloc_;
|
||||
}
|
||||
|
||||
bool operator==(const StlAllocator<Alloc, T>& other) const {
|
||||
return alloc_ == other.alloc_;
|
||||
}
|
||||
|
||||
private:
|
||||
Alloc* alloc_;
|
||||
};
|
||||
|
||||
/*
|
||||
* Helper classes/functions for creating a unique_ptr using a custom allocator
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
|
||||
// A deleter implementation based on std::default_delete,
|
||||
// which uses a custom allocator to free memory
|
||||
template <typename Allocator>
|
||||
class allocator_delete {
|
||||
typedef typename std::remove_reference<Allocator>::type allocator_type;
|
||||
|
||||
public:
|
||||
allocator_delete() = default;
|
||||
|
||||
explicit allocator_delete(const allocator_type& allocator):
|
||||
allocator_(allocator)
|
||||
{}
|
||||
|
||||
explicit allocator_delete(allocator_type&& allocator):
|
||||
allocator_(std::move(allocator))
|
||||
{}
|
||||
|
||||
template <typename U>
|
||||
allocator_delete(const allocator_delete<U>& other):
|
||||
allocator_(other.get_allocator())
|
||||
{}
|
||||
|
||||
allocator_type& get_allocator() const {
|
||||
return allocator_;
|
||||
}
|
||||
|
||||
void operator()(typename allocator_type::pointer p) const {
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
|
||||
allocator_.destroy(p);
|
||||
allocator_.deallocate(p, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable allocator_type allocator_;
|
||||
};
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
class is_simple_allocator {
|
||||
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_destroy, destroy);
|
||||
|
||||
typedef typename std::remove_const<
|
||||
typename std::remove_reference<Allocator>::type
|
||||
>::type allocator;
|
||||
typedef typename std::remove_reference<T>::type value_type;
|
||||
typedef value_type* pointer;
|
||||
|
||||
public:
|
||||
constexpr static bool value = !has_destroy<allocator, void(pointer)>::value
|
||||
&& !has_destroy<allocator, void(void*)>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
typename std::enable_if<
|
||||
is_simple_allocator<T, Allocator>::value,
|
||||
folly::StlAllocator<
|
||||
typename std::remove_reference<Allocator>::type,
|
||||
typename std::remove_reference<T>::type
|
||||
>
|
||||
>::type make_stl_allocator(Allocator&& allocator) {
|
||||
return folly::StlAllocator<
|
||||
typename std::remove_reference<Allocator>::type,
|
||||
typename std::remove_reference<T>::type
|
||||
>(&allocator);
|
||||
}
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
typename std::enable_if<
|
||||
!is_simple_allocator<T, Allocator>::value,
|
||||
typename std::remove_reference<Allocator>::type
|
||||
>::type make_stl_allocator(Allocator&& allocator) {
|
||||
return std::move(allocator);
|
||||
}
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
struct AllocatorUniquePtr {
|
||||
typedef std::unique_ptr<T,
|
||||
folly::allocator_delete<
|
||||
typename std::conditional<
|
||||
is_simple_allocator<T, Allocator>::value,
|
||||
folly::StlAllocator<typename std::remove_reference<Allocator>::type, T>,
|
||||
typename std::remove_reference<Allocator>::type
|
||||
>::type
|
||||
>
|
||||
> type;
|
||||
};
|
||||
|
||||
template <typename T, typename Allocator, typename ...Args>
|
||||
typename AllocatorUniquePtr<T, Allocator>::type allocate_unique(
|
||||
Allocator&& allocator, Args&&... args
|
||||
) {
|
||||
auto stlAllocator = folly::make_stl_allocator<T>(
|
||||
std::forward<Allocator>(allocator)
|
||||
);
|
||||
auto p = stlAllocator.allocate(1);
|
||||
|
||||
try {
|
||||
stlAllocator.construct(p, std::forward<Args>(args)...);
|
||||
|
||||
return {p,
|
||||
folly::allocator_delete<decltype(stlAllocator)>(std::move(stlAllocator))
|
||||
};
|
||||
} catch (...) {
|
||||
stlAllocator.deallocate(p, 1);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Allocator, typename ...Args>
|
||||
std::shared_ptr<T> allocate_shared(Allocator&& allocator, Args&&... args) {
|
||||
return std::allocate_shared<T>(
|
||||
folly::make_stl_allocator<T>(std::forward<Allocator>(allocator)),
|
||||
std::forward<Args>(args)...
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_STLALLOCATOR_H_ */
|
||||
-651
@@ -1,651 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_STRING_INL_H_
|
||||
#define FOLLY_STRING_INL_H_
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iterator>
|
||||
|
||||
#ifndef FOLLY_BASE_STRING_H_
|
||||
#error This file may only be included from String.h
|
||||
#endif
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace detail {
|
||||
// Map from character code to value of one-character escape sequence
|
||||
// ('\n' = 10 maps to 'n'), 'O' if the character should be printed as
|
||||
// an octal escape sequence, or 'P' if the character is printable and
|
||||
// should be printed as is.
|
||||
extern const char cEscapeTable[];
|
||||
} // namespace detail
|
||||
|
||||
template <class String>
|
||||
void cEscape(StringPiece str, String& out) {
|
||||
char esc[4];
|
||||
esc[0] = '\\';
|
||||
out.reserve(out.size() + str.size());
|
||||
auto p = str.begin();
|
||||
auto last = p; // last regular character
|
||||
// We advance over runs of regular characters (printable, not double-quote or
|
||||
// backslash) and copy them in one go; this is faster than calling push_back
|
||||
// repeatedly.
|
||||
while (p != str.end()) {
|
||||
char c = *p;
|
||||
unsigned char v = static_cast<unsigned char>(c);
|
||||
char e = detail::cEscapeTable[v];
|
||||
if (e == 'P') { // printable
|
||||
++p;
|
||||
} else if (e == 'O') { // octal
|
||||
out.append(&*last, p - last);
|
||||
esc[1] = '0' + ((v >> 6) & 7);
|
||||
esc[2] = '0' + ((v >> 3) & 7);
|
||||
esc[3] = '0' + (v & 7);
|
||||
out.append(esc, 4);
|
||||
++p;
|
||||
last = p;
|
||||
} else { // special 1-character escape
|
||||
out.append(&*last, p - last);
|
||||
esc[1] = e;
|
||||
out.append(esc, 2);
|
||||
++p;
|
||||
last = p;
|
||||
}
|
||||
}
|
||||
out.append(&*last, p - last);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
// Map from the character code of the character following a backslash to
|
||||
// the unescaped character if a valid one-character escape sequence
|
||||
// ('n' maps to 10 = '\n'), 'O' if this is the first character of an
|
||||
// octal escape sequence, 'X' if this is the first character of a
|
||||
// hexadecimal escape sequence, or 'I' if this escape sequence is invalid.
|
||||
extern const char cUnescapeTable[];
|
||||
|
||||
// Map from the character code to the hex value, or 16 if invalid hex char.
|
||||
extern const unsigned char hexTable[];
|
||||
} // namespace detail
|
||||
|
||||
template <class String>
|
||||
void cUnescape(StringPiece str, String& out, bool strict) {
|
||||
out.reserve(out.size() + str.size());
|
||||
auto p = str.begin();
|
||||
auto last = p; // last regular character (not part of an escape sequence)
|
||||
// We advance over runs of regular characters (not backslash) and copy them
|
||||
// in one go; this is faster than calling push_back repeatedly.
|
||||
while (p != str.end()) {
|
||||
char c = *p;
|
||||
if (c != '\\') { // normal case
|
||||
++p;
|
||||
continue;
|
||||
}
|
||||
out.append(&*last, p - last);
|
||||
if (p == str.end()) { // backslash at end of string
|
||||
if (strict) {
|
||||
throw std::invalid_argument("incomplete escape sequence");
|
||||
}
|
||||
out.push_back('\\');
|
||||
last = p;
|
||||
continue;
|
||||
}
|
||||
++p;
|
||||
char e = detail::cUnescapeTable[static_cast<unsigned char>(*p)];
|
||||
if (e == 'O') { // octal
|
||||
unsigned char val = 0;
|
||||
for (int i = 0; i < 3 && p != str.end() && *p >= '0' && *p <= '7';
|
||||
++i, ++p) {
|
||||
val = (val << 3) | (*p - '0');
|
||||
}
|
||||
out.push_back(val);
|
||||
last = p;
|
||||
} else if (e == 'X') { // hex
|
||||
++p;
|
||||
if (p == str.end()) { // \x at end of string
|
||||
if (strict) {
|
||||
throw std::invalid_argument("incomplete hex escape sequence");
|
||||
}
|
||||
out.append("\\x");
|
||||
last = p;
|
||||
continue;
|
||||
}
|
||||
unsigned char val = 0;
|
||||
unsigned char h;
|
||||
for (; (p != str.end() &&
|
||||
(h = detail::hexTable[static_cast<unsigned char>(*p)]) < 16);
|
||||
++p) {
|
||||
val = (val << 4) | h;
|
||||
}
|
||||
out.push_back(val);
|
||||
last = p;
|
||||
} else if (e == 'I') { // invalid
|
||||
if (strict) {
|
||||
throw std::invalid_argument("invalid escape sequence");
|
||||
}
|
||||
out.push_back('\\');
|
||||
out.push_back(*p);
|
||||
++p;
|
||||
last = p;
|
||||
} else { // standard escape sequence, \' etc
|
||||
out.push_back(e);
|
||||
++p;
|
||||
last = p;
|
||||
}
|
||||
}
|
||||
out.append(&*last, p - last);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
// Map from character code to escape mode:
|
||||
// 0 = pass through
|
||||
// 1 = unused
|
||||
// 2 = pass through in PATH mode
|
||||
// 3 = space, replace with '+' in QUERY mode
|
||||
// 4 = percent-encode
|
||||
extern const unsigned char uriEscapeTable[];
|
||||
} // namespace detail
|
||||
|
||||
template <class String>
|
||||
void uriEscape(StringPiece str, String& out, UriEscapeMode mode) {
|
||||
static const char hexValues[] = "0123456789abcdef";
|
||||
char esc[3];
|
||||
esc[0] = '%';
|
||||
// Preallocate assuming that 25% of the input string will be escaped
|
||||
out.reserve(out.size() + str.size() + 3 * (str.size() / 4));
|
||||
auto p = str.begin();
|
||||
auto last = p; // last regular character
|
||||
// We advance over runs of passthrough characters and copy them in one go;
|
||||
// this is faster than calling push_back repeatedly.
|
||||
unsigned char minEncode = static_cast<unsigned char>(mode);
|
||||
while (p != str.end()) {
|
||||
char c = *p;
|
||||
unsigned char v = static_cast<unsigned char>(c);
|
||||
unsigned char discriminator = detail::uriEscapeTable[v];
|
||||
if (LIKELY(discriminator <= minEncode)) {
|
||||
++p;
|
||||
} else if (mode == UriEscapeMode::QUERY && discriminator == 3) {
|
||||
out.append(&*last, p - last);
|
||||
out.push_back('+');
|
||||
++p;
|
||||
last = p;
|
||||
} else {
|
||||
out.append(&*last, p - last);
|
||||
esc[1] = hexValues[v >> 4];
|
||||
esc[2] = hexValues[v & 0x0f];
|
||||
out.append(esc, 3);
|
||||
++p;
|
||||
last = p;
|
||||
}
|
||||
}
|
||||
out.append(&*last, p - last);
|
||||
}
|
||||
|
||||
template <class String>
|
||||
void uriUnescape(StringPiece str, String& out, UriEscapeMode mode) {
|
||||
out.reserve(out.size() + str.size());
|
||||
auto p = str.begin();
|
||||
auto last = p;
|
||||
// We advance over runs of passthrough characters and copy them in one go;
|
||||
// this is faster than calling push_back repeatedly.
|
||||
while (p != str.end()) {
|
||||
char c = *p;
|
||||
unsigned char v = static_cast<unsigned char>(v);
|
||||
switch (c) {
|
||||
case '%':
|
||||
{
|
||||
if (UNLIKELY(std::distance(p, str.end()) < 3)) {
|
||||
throw std::invalid_argument("incomplete percent encode sequence");
|
||||
}
|
||||
auto h1 = detail::hexTable[static_cast<unsigned char>(p[1])];
|
||||
auto h2 = detail::hexTable[static_cast<unsigned char>(p[2])];
|
||||
if (UNLIKELY(h1 == 16 || h2 == 16)) {
|
||||
throw std::invalid_argument("invalid percent encode sequence");
|
||||
}
|
||||
out.append(&*last, p - last);
|
||||
out.push_back((h1 << 4) | h2);
|
||||
p += 3;
|
||||
last = p;
|
||||
break;
|
||||
}
|
||||
case '+':
|
||||
if (mode == UriEscapeMode::QUERY) {
|
||||
out.append(&*last, p - last);
|
||||
out.push_back(' ');
|
||||
++p;
|
||||
last = p;
|
||||
break;
|
||||
}
|
||||
// else fallthrough
|
||||
default:
|
||||
++p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
out.append(&*last, p - last);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
/*
|
||||
* The following functions are type-overloaded helpers for
|
||||
* internalSplit().
|
||||
*/
|
||||
inline size_t delimSize(char) { return 1; }
|
||||
inline size_t delimSize(StringPiece s) { return s.size(); }
|
||||
inline bool atDelim(const char* s, char c) {
|
||||
return *s == c;
|
||||
}
|
||||
inline bool atDelim(const char* s, StringPiece sp) {
|
||||
return !std::memcmp(s, sp.start(), sp.size());
|
||||
}
|
||||
|
||||
// These are used to short-circuit internalSplit() in the case of
|
||||
// 1-character strings.
|
||||
inline char delimFront(char c) {
|
||||
// This one exists only for compile-time; it should never be called.
|
||||
std::abort();
|
||||
return c;
|
||||
}
|
||||
inline char delimFront(StringPiece s) {
|
||||
assert(!s.empty() && s.start() != nullptr);
|
||||
return *s.start();
|
||||
}
|
||||
|
||||
/*
|
||||
* These output conversion templates allow us to support multiple
|
||||
* output string types, even when we are using an arbitrary
|
||||
* OutputIterator.
|
||||
*/
|
||||
template<class OutStringT> struct OutputConverter {};
|
||||
|
||||
template<> struct OutputConverter<std::string> {
|
||||
std::string operator()(StringPiece sp) const {
|
||||
return sp.toString();
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct OutputConverter<fbstring> {
|
||||
fbstring operator()(StringPiece sp) const {
|
||||
return sp.toFbstring();
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct OutputConverter<StringPiece> {
|
||||
StringPiece operator()(StringPiece sp) const { return sp; }
|
||||
};
|
||||
|
||||
/*
|
||||
* Shared implementation for all the split() overloads.
|
||||
*
|
||||
* This uses some external helpers that are overloaded to let this
|
||||
* algorithm be more performant if the deliminator is a single
|
||||
* character instead of a whole string.
|
||||
*
|
||||
* @param ignoreEmpty iff true, don't copy empty segments to output
|
||||
*/
|
||||
template<class OutStringT, class DelimT, class OutputIterator>
|
||||
void internalSplit(DelimT delim, StringPiece sp, OutputIterator out,
|
||||
bool ignoreEmpty) {
|
||||
assert(sp.empty() || sp.start() != nullptr);
|
||||
|
||||
const char* s = sp.start();
|
||||
const size_t strSize = sp.size();
|
||||
const size_t dSize = delimSize(delim);
|
||||
|
||||
OutputConverter<OutStringT> conv;
|
||||
|
||||
if (dSize > strSize || dSize == 0) {
|
||||
if (!ignoreEmpty || strSize > 0) {
|
||||
*out++ = conv(sp);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (boost::is_same<DelimT,StringPiece>::value && dSize == 1) {
|
||||
// Call the char version because it is significantly faster.
|
||||
return internalSplit<OutStringT>(delimFront(delim), sp, out,
|
||||
ignoreEmpty);
|
||||
}
|
||||
|
||||
int tokenStartPos = 0;
|
||||
int tokenSize = 0;
|
||||
for (int i = 0; i <= strSize - dSize; ++i) {
|
||||
if (atDelim(&s[i], delim)) {
|
||||
if (!ignoreEmpty || tokenSize > 0) {
|
||||
*out++ = conv(StringPiece(&s[tokenStartPos], tokenSize));
|
||||
}
|
||||
|
||||
tokenStartPos = i + dSize;
|
||||
tokenSize = 0;
|
||||
i += dSize - 1;
|
||||
} else {
|
||||
++tokenSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignoreEmpty || tokenSize > 0) {
|
||||
tokenSize = strSize - tokenStartPos;
|
||||
*out++ = conv(StringPiece(&s[tokenStartPos], tokenSize));
|
||||
}
|
||||
}
|
||||
|
||||
template<class String> StringPiece prepareDelim(const String& s) {
|
||||
return StringPiece(s);
|
||||
}
|
||||
inline char prepareDelim(char c) { return c; }
|
||||
|
||||
template<bool exact,
|
||||
class Delim>
|
||||
bool splitFixed(const Delim& delimiter,
|
||||
StringPiece input,
|
||||
StringPiece& out) {
|
||||
if (exact && UNLIKELY(std::string::npos != input.find(delimiter))) {
|
||||
return false;
|
||||
}
|
||||
out = input;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool exact,
|
||||
class Delim,
|
||||
class... StringPieces>
|
||||
bool splitFixed(const Delim& delimiter,
|
||||
StringPiece input,
|
||||
StringPiece& outHead,
|
||||
StringPieces&... outTail) {
|
||||
size_t cut = input.find(delimiter);
|
||||
if (UNLIKELY(cut == std::string::npos)) {
|
||||
return false;
|
||||
}
|
||||
StringPiece head(input.begin(), input.begin() + cut);
|
||||
StringPiece tail(input.begin() + cut + detail::delimSize(delimiter),
|
||||
input.end());
|
||||
if (LIKELY(splitFixed<exact>(delimiter, tail, outTail...))) {
|
||||
outHead = head;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
template<class Delim, class String, class OutputType>
|
||||
void split(const Delim& delimiter,
|
||||
const String& input,
|
||||
std::vector<OutputType>& out,
|
||||
bool ignoreEmpty) {
|
||||
detail::internalSplit<OutputType>(
|
||||
detail::prepareDelim(delimiter),
|
||||
StringPiece(input),
|
||||
std::back_inserter(out),
|
||||
ignoreEmpty);
|
||||
}
|
||||
|
||||
template<class Delim, class String, class OutputType>
|
||||
void split(const Delim& delimiter,
|
||||
const String& input,
|
||||
fbvector<OutputType>& out,
|
||||
bool ignoreEmpty) {
|
||||
detail::internalSplit<OutputType>(
|
||||
detail::prepareDelim(delimiter),
|
||||
StringPiece(input),
|
||||
std::back_inserter(out),
|
||||
ignoreEmpty);
|
||||
}
|
||||
|
||||
template<class OutputValueType, class Delim, class String,
|
||||
class OutputIterator>
|
||||
void splitTo(const Delim& delimiter,
|
||||
const String& input,
|
||||
OutputIterator out,
|
||||
bool ignoreEmpty) {
|
||||
detail::internalSplit<OutputValueType>(
|
||||
detail::prepareDelim(delimiter),
|
||||
StringPiece(input),
|
||||
out,
|
||||
ignoreEmpty);
|
||||
}
|
||||
|
||||
template<bool exact,
|
||||
class Delim,
|
||||
class... StringPieces>
|
||||
bool split(const Delim& delimiter,
|
||||
StringPiece input,
|
||||
StringPiece& outHead,
|
||||
StringPieces&... outTail) {
|
||||
return detail::splitFixed<exact>(
|
||||
detail::prepareDelim(delimiter),
|
||||
input,
|
||||
outHead,
|
||||
outTail...);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Iterator>
|
||||
struct IsStringContainerIterator :
|
||||
IsSomeString<typename std::iterator_traits<Iterator>::value_type> {
|
||||
};
|
||||
|
||||
template <class Delim, class Iterator, class String>
|
||||
void internalJoinAppend(Delim delimiter,
|
||||
Iterator begin,
|
||||
Iterator end,
|
||||
String& output) {
|
||||
assert(begin != end);
|
||||
if (std::is_same<Delim, StringPiece>::value &&
|
||||
delimSize(delimiter) == 1) {
|
||||
internalJoinAppend(delimFront(delimiter), begin, end, output);
|
||||
return;
|
||||
}
|
||||
toAppend(*begin, &output);
|
||||
while (++begin != end) {
|
||||
toAppend(delimiter, *begin, &output);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Delim, class Iterator, class String>
|
||||
typename std::enable_if<IsStringContainerIterator<Iterator>::value>::type
|
||||
internalJoin(Delim delimiter,
|
||||
Iterator begin,
|
||||
Iterator end,
|
||||
String& output) {
|
||||
output.clear();
|
||||
if (begin == end) {
|
||||
return;
|
||||
}
|
||||
const size_t dsize = delimSize(delimiter);
|
||||
Iterator it = begin;
|
||||
size_t size = it->size();
|
||||
while (++it != end) {
|
||||
size += dsize + it->size();
|
||||
}
|
||||
output.reserve(size);
|
||||
internalJoinAppend(delimiter, begin, end, output);
|
||||
}
|
||||
|
||||
template <class Delim, class Iterator, class String>
|
||||
typename std::enable_if<!IsStringContainerIterator<Iterator>::value>::type
|
||||
internalJoin(Delim delimiter,
|
||||
Iterator begin,
|
||||
Iterator end,
|
||||
String& output) {
|
||||
output.clear();
|
||||
if (begin == end) {
|
||||
return;
|
||||
}
|
||||
internalJoinAppend(delimiter, begin, end, output);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <class Delim, class Iterator, class String>
|
||||
void join(const Delim& delimiter,
|
||||
Iterator begin,
|
||||
Iterator end,
|
||||
String& output) {
|
||||
detail::internalJoin(
|
||||
detail::prepareDelim(delimiter),
|
||||
begin,
|
||||
end,
|
||||
output);
|
||||
}
|
||||
|
||||
template <class String1, class String2>
|
||||
void backslashify(const String1& input, String2& output, bool hex_style) {
|
||||
static const char hexValues[] = "0123456789abcdef";
|
||||
output.clear();
|
||||
output.reserve(3 * input.size());
|
||||
for (unsigned char c : input) {
|
||||
// less than space or greater than '~' are considered unprintable
|
||||
if (c < 0x20 || c > 0x7e || c == '\\') {
|
||||
bool hex_append = false;
|
||||
output.push_back('\\');
|
||||
if (hex_style) {
|
||||
hex_append = true;
|
||||
} else {
|
||||
if (c == '\r') output += 'r';
|
||||
else if (c == '\n') output += 'n';
|
||||
else if (c == '\t') output += 't';
|
||||
else if (c == '\a') output += 'a';
|
||||
else if (c == '\b') output += 'b';
|
||||
else if (c == '\0') output += '0';
|
||||
else if (c == '\\') output += '\\';
|
||||
else {
|
||||
hex_append = true;
|
||||
}
|
||||
}
|
||||
if (hex_append) {
|
||||
output.push_back('x');
|
||||
output.push_back(hexValues[(c >> 4) & 0xf]);
|
||||
output.push_back(hexValues[c & 0xf]);
|
||||
}
|
||||
} else {
|
||||
output += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class String1, class String2>
|
||||
void humanify(const String1& input, String2& output) {
|
||||
int numUnprintable = 0;
|
||||
int numPrintablePrefix = 0;
|
||||
for (unsigned char c : input) {
|
||||
if (c < 0x20 || c > 0x7e || c == '\\') {
|
||||
++numUnprintable;
|
||||
}
|
||||
if (numUnprintable == 0) {
|
||||
++numPrintablePrefix;
|
||||
}
|
||||
}
|
||||
|
||||
// hexlify doubles a string's size; backslashify can potentially
|
||||
// explode it by 4x. Now, the printable range of the ascii
|
||||
// "spectrum" is around 95 out of 256 values, so a "random" binary
|
||||
// string should be around 60% unprintable. We use a 50% hueristic
|
||||
// here, so if a string is 60% unprintable, then we just use hex
|
||||
// output. Otherwise we backslash.
|
||||
//
|
||||
// UTF8 is completely ignored; as a result, utf8 characters will
|
||||
// likely be \x escaped (since most common glyphs fit in two bytes).
|
||||
// This is a tradeoff of complexity/speed instead of a convenience
|
||||
// that likely would rarely matter. Moreover, this function is more
|
||||
// about displaying underlying bytes, not about displaying glyphs
|
||||
// from languages.
|
||||
if (numUnprintable == 0) {
|
||||
output = input;
|
||||
} else if (5 * numUnprintable >= 3 * input.size()) {
|
||||
// However! If we have a "meaningful" prefix of printable
|
||||
// characters, say 20% of the string, we backslashify under the
|
||||
// assumption viewing the prefix as ascii is worth blowing the
|
||||
// output size up a bit.
|
||||
if (5 * numPrintablePrefix >= input.size()) {
|
||||
backslashify(input, output);
|
||||
} else {
|
||||
output = "0x";
|
||||
hexlify(input, output, true /* append output */);
|
||||
}
|
||||
} else {
|
||||
backslashify(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
template<class InputString, class OutputString>
|
||||
bool hexlify(const InputString& input, OutputString& output,
|
||||
bool append_output) {
|
||||
if (!append_output) output.clear();
|
||||
|
||||
static char hexValues[] = "0123456789abcdef";
|
||||
int j = output.size();
|
||||
output.resize(2 * input.size() + output.size());
|
||||
for (int i = 0; i < input.size(); ++i) {
|
||||
int ch = input[i];
|
||||
output[j++] = hexValues[(ch >> 4) & 0xf];
|
||||
output[j++] = hexValues[ch & 0xf];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class InputString, class OutputString>
|
||||
bool unhexlify(const InputString& input, OutputString& output) {
|
||||
if (input.size() % 2 != 0) {
|
||||
return false;
|
||||
}
|
||||
output.resize(input.size() / 2);
|
||||
int j = 0;
|
||||
auto unhex = [](char c) -> int {
|
||||
return c >= '0' && c <= '9' ? c - '0' :
|
||||
c >= 'A' && c <= 'F' ? c - 'A' + 10 :
|
||||
c >= 'a' && c <= 'f' ? c - 'a' + 10 :
|
||||
-1;
|
||||
};
|
||||
|
||||
for (int i = 0; i < input.size(); i += 2) {
|
||||
int highBits = unhex(input[i]);
|
||||
int lowBits = unhex(input[i + 1]);
|
||||
if (highBits < 0 || lowBits < 0) {
|
||||
return false;
|
||||
}
|
||||
output[j++] = (highBits << 4) + lowBits;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
/**
|
||||
* Hex-dump at most 16 bytes starting at offset from a memory area of size
|
||||
* bytes. Return the number of bytes actually dumped.
|
||||
*/
|
||||
size_t hexDumpLine(const void* ptr, size_t offset, size_t size,
|
||||
std::string& line);
|
||||
} // namespace detail
|
||||
|
||||
template <class OutIt>
|
||||
void hexDump(const void* ptr, size_t size, OutIt out) {
|
||||
size_t offset = 0;
|
||||
std::string line;
|
||||
while (offset < size) {
|
||||
offset += detail::hexDumpLine(ptr, offset, size, line);
|
||||
*out++ = line;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_STRING_INL_H_ */
|
||||
|
||||
-335
@@ -1,335 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/String.h"
|
||||
#include "folly/Format.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <iterator>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#undef FOLLY_DEMANGLE
|
||||
#if defined(__GNUG__) && __GNUG__ >= 4
|
||||
# include <cxxabi.h>
|
||||
# define FOLLY_DEMANGLE 1
|
||||
#endif
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace {
|
||||
|
||||
inline void stringPrintfImpl(std::string& output, const char* format,
|
||||
va_list args) {
|
||||
// Tru to the space at the end of output for our output buffer.
|
||||
// Find out write point then inflate its size temporarily to its
|
||||
// capacity; we will later shrink it to the size needed to represent
|
||||
// the formatted string. If this buffer isn't large enough, we do a
|
||||
// resize and try again.
|
||||
|
||||
const auto write_point = output.size();
|
||||
auto remaining = output.capacity() - write_point;
|
||||
output.resize(output.capacity());
|
||||
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
int bytes_used = vsnprintf(&output[write_point], remaining, format,
|
||||
args_copy);
|
||||
va_end(args_copy);
|
||||
if (bytes_used < 0) {
|
||||
throw std::runtime_error(
|
||||
to<std::string>("Invalid format string; snprintf returned negative "
|
||||
"with format string: ", format));
|
||||
} else if (bytes_used < remaining) {
|
||||
// There was enough room, just shrink and return.
|
||||
output.resize(write_point + bytes_used);
|
||||
} else {
|
||||
output.resize(write_point + bytes_used + 1);
|
||||
remaining = bytes_used + 1;
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
bytes_used = vsnprintf(&output[write_point], remaining, format,
|
||||
args_copy);
|
||||
va_end(args_copy);
|
||||
if (bytes_used + 1 != remaining) {
|
||||
throw std::runtime_error(
|
||||
to<std::string>("vsnprint retry did not manage to work "
|
||||
"with format string: ", format));
|
||||
}
|
||||
output.resize(write_point + bytes_used);
|
||||
}
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
std::string stringPrintf(const char* format, ...) {
|
||||
// snprintf will tell us how large the output buffer should be, but
|
||||
// we then have to call it a second time, which is costly. By
|
||||
// guestimating the final size, we avoid the double snprintf in many
|
||||
// cases, resulting in a performance win. We use this constructor
|
||||
// of std::string to avoid a double allocation, though it does pad
|
||||
// the resulting string with nul bytes. Our guestimation is twice
|
||||
// the format string size, or 32 bytes, whichever is larger. This
|
||||
// is a hueristic that doesn't affect correctness but attempts to be
|
||||
// reasonably fast for the most common cases.
|
||||
std::string ret(std::max(32UL, strlen(format) * 2), '\0');
|
||||
ret.resize(0);
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
stringPrintfImpl(ret, format, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Basic declarations; allow for parameters of strings and string
|
||||
// pieces to be specified.
|
||||
std::string& stringAppendf(std::string* output, const char* format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
stringPrintfImpl(*output, format, ap);
|
||||
va_end(ap);
|
||||
return *output;
|
||||
}
|
||||
|
||||
void stringPrintf(std::string* output, const char* format, ...) {
|
||||
output->clear();
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
stringPrintfImpl(*output, format, ap);
|
||||
va_end(ap);
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
struct PrettySuffix {
|
||||
const char* suffix;
|
||||
double val;
|
||||
};
|
||||
|
||||
const PrettySuffix kPrettyTimeSuffixes[] = {
|
||||
{ "s ", 1e0L },
|
||||
{ "ms", 1e-3L },
|
||||
{ "us", 1e-6L },
|
||||
{ "ns", 1e-9L },
|
||||
{ "ps", 1e-12L },
|
||||
{ "s ", 0 },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
const PrettySuffix kPrettyBytesMetricSuffixes[] = {
|
||||
{ "TB", 1e12L },
|
||||
{ "GB", 1e9L },
|
||||
{ "MB", 1e6L },
|
||||
{ "kB", 1e3L },
|
||||
{ "B ", 0L },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
const PrettySuffix kPrettyBytesBinarySuffixes[] = {
|
||||
{ "TB", int64_t(1) << 40 },
|
||||
{ "GB", int64_t(1) << 30 },
|
||||
{ "MB", int64_t(1) << 20 },
|
||||
{ "kB", int64_t(1) << 10 },
|
||||
{ "B ", 0L },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
const PrettySuffix kPrettyBytesBinaryIECSuffixes[] = {
|
||||
{ "TiB", int64_t(1) << 40 },
|
||||
{ "GiB", int64_t(1) << 30 },
|
||||
{ "MiB", int64_t(1) << 20 },
|
||||
{ "KiB", int64_t(1) << 10 },
|
||||
{ "B ", 0L },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
const PrettySuffix kPrettyUnitsMetricSuffixes[] = {
|
||||
{ "tril", 1e12L },
|
||||
{ "bil", 1e9L },
|
||||
{ "M", 1e6L },
|
||||
{ "k", 1e3L },
|
||||
{ " ", 0 },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
const PrettySuffix kPrettyUnitsBinarySuffixes[] = {
|
||||
{ "T", int64_t(1) << 40 },
|
||||
{ "G", int64_t(1) << 30 },
|
||||
{ "M", int64_t(1) << 20 },
|
||||
{ "k", int64_t(1) << 10 },
|
||||
{ " ", 0 },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
const PrettySuffix kPrettyUnitsBinaryIECSuffixes[] = {
|
||||
{ "Ti", int64_t(1) << 40 },
|
||||
{ "Gi", int64_t(1) << 30 },
|
||||
{ "Mi", int64_t(1) << 20 },
|
||||
{ "Ki", int64_t(1) << 10 },
|
||||
{ " ", 0 },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
const PrettySuffix* const kPrettySuffixes[PRETTY_NUM_TYPES] = {
|
||||
kPrettyTimeSuffixes,
|
||||
kPrettyBytesMetricSuffixes,
|
||||
kPrettyBytesBinarySuffixes,
|
||||
kPrettyBytesBinaryIECSuffixes,
|
||||
kPrettyUnitsMetricSuffixes,
|
||||
kPrettyUnitsBinarySuffixes,
|
||||
kPrettyUnitsBinaryIECSuffixes,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string prettyPrint(double val, PrettyType type, bool addSpace) {
|
||||
char buf[100];
|
||||
|
||||
// pick the suffixes to use
|
||||
assert(type >= 0);
|
||||
assert(type < PRETTY_NUM_TYPES);
|
||||
const PrettySuffix* suffixes = kPrettySuffixes[type];
|
||||
|
||||
// find the first suffix we're bigger than -- then use it
|
||||
double abs_val = fabs(val);
|
||||
for (int i = 0; suffixes[i].suffix; ++i) {
|
||||
if (abs_val >= suffixes[i].val) {
|
||||
snprintf(buf, sizeof buf, "%.4g%s%s",
|
||||
(suffixes[i].val ? (val / suffixes[i].val)
|
||||
: val),
|
||||
(addSpace ? " " : ""),
|
||||
suffixes[i].suffix);
|
||||
return std::string(buf);
|
||||
}
|
||||
}
|
||||
|
||||
// no suffix, we've got a tiny value -- just print it in sci-notation
|
||||
snprintf(buf, sizeof buf, "%.4g", val);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string hexDump(const void* ptr, size_t size) {
|
||||
std::ostringstream os;
|
||||
hexDump(ptr, size, std::ostream_iterator<StringPiece>(os, "\n"));
|
||||
return os.str();
|
||||
}
|
||||
|
||||
fbstring errnoStr(int err) {
|
||||
int savedErrno = errno;
|
||||
|
||||
// Ensure that we reset errno upon exit.
|
||||
auto guard(makeGuard([&] { errno = savedErrno; }));
|
||||
|
||||
char buf[1024];
|
||||
buf[0] = '\0';
|
||||
|
||||
fbstring result;
|
||||
|
||||
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/strerror_r.3.html
|
||||
// http://www.kernel.org/doc/man-pages/online/pages/man3/strerror.3.html
|
||||
#if defined(__APPLE__) || defined(__FreeBSD__) || \
|
||||
((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && !_GNU_SOURCE)
|
||||
// Using XSI-compatible strerror_r
|
||||
int r = strerror_r(err, buf, sizeof(buf));
|
||||
|
||||
// OSX/FreeBSD use EINVAL and Linux uses -1 so just check for non-zero
|
||||
if (r != 0) {
|
||||
result = to<fbstring>(
|
||||
"Unknown error ", err,
|
||||
" (strerror_r failed with error ", errno, ")");
|
||||
} else {
|
||||
result.assign(buf);
|
||||
}
|
||||
#else
|
||||
// Using GNU strerror_r
|
||||
result.assign(strerror_r(err, buf, sizeof(buf)));
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef FOLLY_DEMANGLE
|
||||
|
||||
fbstring demangle(const char* name) {
|
||||
int status;
|
||||
size_t len = 0;
|
||||
// malloc() memory for the demangled type name
|
||||
char* demangled = abi::__cxa_demangle(name, nullptr, &len, &status);
|
||||
if (status != 0) {
|
||||
return name;
|
||||
}
|
||||
// len is the length of the buffer (including NUL terminator and maybe
|
||||
// other junk)
|
||||
return fbstring(demangled, strlen(demangled), len, AcquireMallocatedString());
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
fbstring demangle(const char* name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
#endif
|
||||
#undef FOLLY_DEMANGLE
|
||||
|
||||
namespace detail {
|
||||
|
||||
size_t hexDumpLine(const void* ptr, size_t offset, size_t size,
|
||||
std::string& line) {
|
||||
// Line layout:
|
||||
// 8: address
|
||||
// 1: space
|
||||
// (1+2)*16: hex bytes, each preceded by a space
|
||||
// 1: space separating the two halves
|
||||
// 3: " |"
|
||||
// 16: characters
|
||||
// 1: "|"
|
||||
// Total: 78
|
||||
line.clear();
|
||||
line.reserve(78);
|
||||
const uint8_t* p = reinterpret_cast<const uint8_t*>(ptr) + offset;
|
||||
size_t n = std::min(size - offset, size_t(16));
|
||||
format("{:08x} ", offset).appendTo(line);
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (i == 8) {
|
||||
line.push_back(' ');
|
||||
}
|
||||
format(" {:02x}", p[i]).appendTo(line);
|
||||
}
|
||||
|
||||
// 3 spaces for each byte we're not printing, one separating the halves
|
||||
// if necessary
|
||||
line.append(3 * (16 - n) + (n <= 8), ' ');
|
||||
line.append(" |");
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
char c = (p[i] >= 32 && p[i] <= 126 ? static_cast<char>(p[i]) : '.');
|
||||
line.push_back(c);
|
||||
}
|
||||
line.append(16 - n, ' ');
|
||||
line.push_back('|');
|
||||
DCHECK_EQ(line.size(), 78);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace folly
|
||||
-502
@@ -1,502 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_BASE_STRING_H_
|
||||
#define FOLLY_BASE_STRING_H_
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <boost/type_traits.hpp>
|
||||
|
||||
#ifdef __GNUC__
|
||||
# include <ext/hash_set>
|
||||
# include <ext/hash_map>
|
||||
#endif
|
||||
|
||||
#include "folly/Conv.h"
|
||||
#include "folly/FBString.h"
|
||||
#include "folly/FBVector.h"
|
||||
#include "folly/Range.h"
|
||||
#include "folly/ScopeGuard.h"
|
||||
|
||||
// Compatibility function, to make sure toStdString(s) can be called
|
||||
// to convert a std::string or fbstring variable s into type std::string
|
||||
// with very little overhead if s was already std::string
|
||||
namespace folly {
|
||||
|
||||
inline
|
||||
std::string toStdString(const folly::fbstring& s) {
|
||||
return std::string(s.data(), s.size());
|
||||
}
|
||||
|
||||
inline
|
||||
const std::string& toStdString(const std::string& s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// If called with a temporary, the compiler will select this overload instead
|
||||
// of the above, so we don't return a (lvalue) reference to a temporary.
|
||||
inline
|
||||
std::string&& toStdString(std::string&& s) {
|
||||
return std::move(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* C-Escape a string, making it suitable for representation as a C string
|
||||
* literal. Appends the result to the output string.
|
||||
*
|
||||
* Backslashes all occurrences of backslash and double-quote:
|
||||
* " -> \"
|
||||
* \ -> \\
|
||||
*
|
||||
* Replaces all non-printable ASCII characters with backslash-octal
|
||||
* representation:
|
||||
* <ASCII 254> -> \376
|
||||
*
|
||||
* Note that we use backslash-octal instead of backslash-hex because the octal
|
||||
* representation is guaranteed to consume no more than 3 characters; "\3760"
|
||||
* represents two characters, one with value 254, and one with value 48 ('0'),
|
||||
* whereas "\xfe0" represents only one character (with value 4064, which leads
|
||||
* to implementation-defined behavior).
|
||||
*/
|
||||
template <class String>
|
||||
void cEscape(StringPiece str, String& out);
|
||||
|
||||
/**
|
||||
* Similar to cEscape above, but returns the escaped string.
|
||||
*/
|
||||
template <class String>
|
||||
String cEscape(StringPiece str) {
|
||||
String out;
|
||||
cEscape(str, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* C-Unescape a string; the opposite of cEscape above. Appends the result
|
||||
* to the output string.
|
||||
*
|
||||
* Recognizes the standard C escape sequences:
|
||||
*
|
||||
* \' \" \? \\ \a \b \f \n \r \t \v
|
||||
* \[0-7]+
|
||||
* \x[0-9a-fA-F]+
|
||||
*
|
||||
* In strict mode (default), throws std::invalid_argument if it encounters
|
||||
* an unrecognized escape sequence. In non-strict mode, it leaves
|
||||
* the escape sequence unchanged.
|
||||
*/
|
||||
template <class String>
|
||||
void cUnescape(StringPiece str, String& out, bool strict = true);
|
||||
|
||||
/**
|
||||
* Similar to cUnescape above, but returns the escaped string.
|
||||
*/
|
||||
template <class String>
|
||||
String cUnescape(StringPiece str, bool strict = true) {
|
||||
String out;
|
||||
cUnescape(str, out, strict);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* URI-escape a string. Appends the result to the output string.
|
||||
*
|
||||
* Alphanumeric characters and other characters marked as "unreserved" in RFC
|
||||
* 3986 ( -_.~ ) are left unchanged. In PATH mode, the forward slash (/) is
|
||||
* also left unchanged. In QUERY mode, spaces are replaced by '+'. All other
|
||||
* characters are percent-encoded.
|
||||
*/
|
||||
enum class UriEscapeMode : unsigned char {
|
||||
// The values are meaningful, see generate_escape_tables.py
|
||||
ALL = 0,
|
||||
QUERY = 1,
|
||||
PATH = 2
|
||||
};
|
||||
template <class String>
|
||||
void uriEscape(StringPiece str,
|
||||
String& out,
|
||||
UriEscapeMode mode = UriEscapeMode::ALL);
|
||||
|
||||
/**
|
||||
* Similar to uriEscape above, but returns the escaped string.
|
||||
*/
|
||||
template <class String>
|
||||
String uriEscape(StringPiece str, UriEscapeMode mode = UriEscapeMode::ALL) {
|
||||
String out;
|
||||
uriEscape(str, out, mode);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* URI-unescape a string. Appends the result to the output string.
|
||||
*
|
||||
* In QUERY mode, '+' are replaced by space. %XX sequences are decoded if
|
||||
* XX is a valid hex sequence, otherwise we throw invalid_argument.
|
||||
*/
|
||||
template <class String>
|
||||
void uriUnescape(StringPiece str,
|
||||
String& out,
|
||||
UriEscapeMode mode = UriEscapeMode::ALL);
|
||||
|
||||
/**
|
||||
* Similar to uriUnescape above, but returns the unescaped string.
|
||||
*/
|
||||
template <class String>
|
||||
String uriUnescape(StringPiece str, UriEscapeMode mode = UriEscapeMode::ALL) {
|
||||
String out;
|
||||
uriUnescape(str, out, mode);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* stringPrintf is much like printf but deposits its result into a
|
||||
* string. Two signatures are supported: the first simply returns the
|
||||
* resulting string, and the second appends the produced characters to
|
||||
* the specified string and returns a reference to it.
|
||||
*/
|
||||
std::string stringPrintf(const char* format, ...)
|
||||
__attribute__ ((format (printf, 1, 2)));
|
||||
|
||||
/** Similar to stringPrintf, with different signiture.
|
||||
*/
|
||||
void stringPrintf(std::string* out, const char* fmt, ...)
|
||||
__attribute__ ((format (printf, 2, 3)));
|
||||
|
||||
std::string& stringAppendf(std::string* output, const char* format, ...)
|
||||
__attribute__ ((format (printf, 2, 3)));
|
||||
|
||||
/**
|
||||
* Backslashify a string, that is, replace non-printable characters
|
||||
* with C-style (but NOT C compliant) "\xHH" encoding. If hex_style
|
||||
* is false, then shorthand notations like "\0" will be used instead
|
||||
* of "\x00" for the most common backslash cases.
|
||||
*
|
||||
* There are two forms, one returning the input string, and one
|
||||
* creating output in the specified output string.
|
||||
*
|
||||
* This is mainly intended for printing to a terminal, so it is not
|
||||
* particularly optimized.
|
||||
*
|
||||
* Do *not* use this in situations where you expect to be able to feed
|
||||
* the string to a C or C++ compiler, as there are nuances with how C
|
||||
* parses such strings that lead to failures. This is for display
|
||||
* purposed only. If you want a string you can embed for use in C or
|
||||
* C++, use cEscape instead. This function is for display purposes
|
||||
* only.
|
||||
*/
|
||||
template <class String1, class String2>
|
||||
void backslashify(const String1& input, String2& output, bool hex_style=false);
|
||||
|
||||
template <class String>
|
||||
String backslashify(const String& input, bool hex_style=false) {
|
||||
String output;
|
||||
backslashify(input, output, hex_style);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a string and "humanify" it -- that is, make it look better.
|
||||
* Since "better" is subjective, caveat emptor. The basic approach is
|
||||
* to count the number of unprintable characters. If there are none,
|
||||
* then the output is the input. If there are relatively few, or if
|
||||
* there is a long "enough" prefix of printable characters, use
|
||||
* backslashify. If it is mostly binary, then simply hex encode.
|
||||
*
|
||||
* This is an attempt to make a computer smart, and so likely is wrong
|
||||
* most of the time.
|
||||
*/
|
||||
template <class String1, class String2>
|
||||
void humanify(const String1& input, String2& output);
|
||||
|
||||
template <class String>
|
||||
String humanify(const String& input) {
|
||||
String output;
|
||||
humanify(input, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same functionality as Python's binascii.hexlify. Returns true
|
||||
* on successful conversion.
|
||||
*
|
||||
* If append_output is true, append data to the output rather than
|
||||
* replace it.
|
||||
*/
|
||||
template<class InputString, class OutputString>
|
||||
bool hexlify(const InputString& input, OutputString& output,
|
||||
bool append=false);
|
||||
|
||||
/**
|
||||
* Same functionality as Python's binascii.unhexlify. Returns true
|
||||
* on successful conversion.
|
||||
*/
|
||||
template<class InputString, class OutputString>
|
||||
bool unhexlify(const InputString& input, OutputString& output);
|
||||
|
||||
/*
|
||||
* A pretty-printer for numbers that appends suffixes of units of the
|
||||
* given type. It prints 4 sig-figs of value with the most
|
||||
* appropriate unit.
|
||||
*
|
||||
* If `addSpace' is true, we put a space between the units suffix and
|
||||
* the value.
|
||||
*
|
||||
* Current types are:
|
||||
* PRETTY_TIME - s, ms, us, ns, etc.
|
||||
* PRETTY_BYTES_METRIC - kB, MB, GB, etc (goes up by 10^3 = 1000 each time)
|
||||
* PRETTY_BYTES - kB, MB, GB, etc (goes up by 2^10 = 1024 each time)
|
||||
* PRETTY_BYTES_IEC - KiB, MiB, GiB, etc
|
||||
* PRETTY_UNITS_METRIC - k, M, G, etc (goes up by 10^3 = 1000 each time)
|
||||
* PRETTY_UNITS_BINARY - k, M, G, etc (goes up by 2^10 = 1024 each time)
|
||||
* PRETTY_UNITS_BINARY_IEC - Ki, Mi, Gi, etc
|
||||
*
|
||||
* @author Mark Rabkin <mrabkin@fb.com>
|
||||
*/
|
||||
enum PrettyType {
|
||||
PRETTY_TIME,
|
||||
|
||||
PRETTY_BYTES_METRIC,
|
||||
PRETTY_BYTES_BINARY,
|
||||
PRETTY_BYTES = PRETTY_BYTES_BINARY,
|
||||
PRETTY_BYTES_BINARY_IEC,
|
||||
PRETTY_BYTES_IEC = PRETTY_BYTES_BINARY_IEC,
|
||||
|
||||
PRETTY_UNITS_METRIC,
|
||||
PRETTY_UNITS_BINARY,
|
||||
PRETTY_UNITS_BINARY_IEC,
|
||||
|
||||
PRETTY_NUM_TYPES
|
||||
};
|
||||
|
||||
std::string prettyPrint(double val, PrettyType, bool addSpace = true);
|
||||
|
||||
/**
|
||||
* Write a hex dump of size bytes starting at ptr to out.
|
||||
*
|
||||
* The hex dump is formatted as follows:
|
||||
*
|
||||
* for the string "abcdefghijklmnopqrstuvwxyz\x02"
|
||||
00000000 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 |abcdefghijklmnop|
|
||||
00000010 71 72 73 74 75 76 77 78 79 7a 02 |qrstuvwxyz. |
|
||||
*
|
||||
* that is, we write 16 bytes per line, both as hex bytes and as printable
|
||||
* characters. Non-printable characters are replaced with '.'
|
||||
* Lines are written to out one by one (one StringPiece at a time) without
|
||||
* delimiters.
|
||||
*/
|
||||
template <class OutIt>
|
||||
void hexDump(const void* ptr, size_t size, OutIt out);
|
||||
|
||||
/**
|
||||
* Return the hex dump of size bytes starting at ptr as a string.
|
||||
*/
|
||||
std::string hexDump(const void* ptr, size_t size);
|
||||
|
||||
/**
|
||||
* Return a fbstring containing the description of the given errno value.
|
||||
* Takes care not to overwrite the actual system errno, so calling
|
||||
* errnoStr(errno) is valid.
|
||||
*/
|
||||
fbstring errnoStr(int err);
|
||||
|
||||
/**
|
||||
* Return the demangled (prettyfied) version of a C++ type.
|
||||
*
|
||||
* This function tries to produce a human-readable type, but the type name will
|
||||
* be returned unchanged in case of error or if demangling isn't supported on
|
||||
* your system.
|
||||
*
|
||||
* Use for debugging -- do not rely on demangle() returning anything useful.
|
||||
*
|
||||
* This function may allocate memory (and therefore throw).
|
||||
*/
|
||||
fbstring demangle(const char* name);
|
||||
inline fbstring demangle(const std::type_info& type) {
|
||||
return demangle(type.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug string for an exception: include type and what().
|
||||
*/
|
||||
inline fbstring exceptionStr(const std::exception& e) {
|
||||
return folly::to<fbstring>(demangle(typeid(e)), ": ", e.what());
|
||||
}
|
||||
|
||||
inline fbstring exceptionStr(std::exception_ptr ep) {
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (const std::exception& e) {
|
||||
return exceptionStr(e);
|
||||
} catch (...) {
|
||||
return "<unknown exception>";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Split a string into a list of tokens by delimiter.
|
||||
*
|
||||
* The split interface here supports different output types, selected
|
||||
* at compile time: StringPiece, fbstring, or std::string. If you are
|
||||
* using a vector to hold the output, it detects the type based on
|
||||
* what your vector contains. If the output vector is not empty, split
|
||||
* will append to the end of the vector.
|
||||
*
|
||||
* You can also use splitTo() to write the output to an arbitrary
|
||||
* OutputIterator (e.g. std::inserter() on a std::set<>), in which
|
||||
* case you have to tell the function the type. (Rationale:
|
||||
* OutputIterators don't have a value_type, so we can't detect the
|
||||
* type in splitTo without being told.)
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* std::vector<folly::StringPiece> v;
|
||||
* folly::split(":", "asd:bsd", v);
|
||||
*
|
||||
* std::set<StringPiece> s;
|
||||
* folly::splitTo<StringPiece>(":", "asd:bsd:asd:csd",
|
||||
* std::inserter(s, s.begin()));
|
||||
*
|
||||
* Split also takes a flag (ignoreEmpty) that indicates whether adjacent
|
||||
* delimiters should be treated as one single separator (ignoring empty tokens)
|
||||
* or not (generating empty tokens).
|
||||
*/
|
||||
|
||||
template<class Delim, class String, class OutputType>
|
||||
void split(const Delim& delimiter,
|
||||
const String& input,
|
||||
std::vector<OutputType>& out,
|
||||
bool ignoreEmpty = false);
|
||||
|
||||
template<class Delim, class String, class OutputType>
|
||||
void split(const Delim& delimiter,
|
||||
const String& input,
|
||||
folly::fbvector<OutputType>& out,
|
||||
bool ignoreEmpty = false);
|
||||
|
||||
template<class OutputValueType, class Delim, class String,
|
||||
class OutputIterator>
|
||||
void splitTo(const Delim& delimiter,
|
||||
const String& input,
|
||||
OutputIterator out,
|
||||
bool ignoreEmpty = false);
|
||||
|
||||
/*
|
||||
* Split a string into a fixed number of pieces by delimiter. Returns 'true' if
|
||||
* the fields were all successfully populated.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* folly::StringPiece name, key, value;
|
||||
* if (folly::split('\t', line, name, key, value))
|
||||
* ...
|
||||
*
|
||||
* The 'exact' template paremeter specifies how the function behaves when too
|
||||
* many fields are present in the input string. When 'exact' is set to its
|
||||
* default value of 'true', a call to split will fail if the number of fields in
|
||||
* the input string does not exactly match the number of output parameters
|
||||
* passed. If 'exact' is overridden to 'false', all remaining fields will be
|
||||
* stored, unsplit, in the last field, as shown below:
|
||||
*
|
||||
* folly::StringPiece x, y.
|
||||
* if (folly::split<false>(':', "a:b:c", x, y))
|
||||
* assert(x == "a" && y == "b:c");
|
||||
*/
|
||||
template<bool exact = true,
|
||||
class Delim,
|
||||
class... StringPieces>
|
||||
bool split(const Delim& delimiter,
|
||||
StringPiece input,
|
||||
StringPiece& outHead,
|
||||
StringPieces&... outTail);
|
||||
|
||||
/*
|
||||
* Join list of tokens.
|
||||
*
|
||||
* Stores a string representation of tokens in the same order with
|
||||
* deliminer between each element.
|
||||
*/
|
||||
|
||||
template <class Delim, class Iterator, class String>
|
||||
void join(const Delim& delimiter,
|
||||
Iterator begin,
|
||||
Iterator end,
|
||||
String& output);
|
||||
|
||||
template <class Delim, class Container, class String>
|
||||
void join(const Delim& delimiter,
|
||||
const Container& container,
|
||||
String& output) {
|
||||
join(delimiter, container.begin(), container.end(), output);
|
||||
}
|
||||
|
||||
template <class Delim, class Value, class String>
|
||||
void join(const Delim& delimiter,
|
||||
const std::initializer_list<Value>& values,
|
||||
String& output) {
|
||||
join(delimiter, values.begin(), values.end(), output);
|
||||
}
|
||||
|
||||
template <class Delim, class Container>
|
||||
std::string join(const Delim& delimiter,
|
||||
const Container& container) {
|
||||
std::string output;
|
||||
join(delimiter, container.begin(), container.end(), output);
|
||||
return output;
|
||||
}
|
||||
|
||||
template <class Delim, class Value>
|
||||
std::string join(const Delim& delimiter,
|
||||
const std::initializer_list<Value>& values) {
|
||||
std::string output;
|
||||
join(delimiter, values.begin(), values.end(), output);
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
// Hash functions for string and fbstring usable with e.g. hash_map
|
||||
#ifdef __GNUC__
|
||||
namespace __gnu_cxx {
|
||||
|
||||
template <class C>
|
||||
struct hash<folly::basic_fbstring<C> > : private hash<const C*> {
|
||||
size_t operator()(const folly::basic_fbstring<C> & s) const {
|
||||
return hash<const C*>::operator()(s.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
template <class C>
|
||||
struct hash<std::basic_string<C> > : private hash<const C*> {
|
||||
size_t operator()(const std::basic_string<C> & s) const {
|
||||
return hash<const C*>::operator()(s.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace __gnu_cxx
|
||||
#endif
|
||||
|
||||
// Hook into boost's type traits
|
||||
namespace boost {
|
||||
template <class T>
|
||||
struct has_nothrow_constructor<folly::basic_fbstring<T> > : true_type {
|
||||
enum { value = true };
|
||||
};
|
||||
} // namespace boost
|
||||
|
||||
#include "folly/String-inl.h"
|
||||
|
||||
#endif
|
||||
-779
@@ -1,779 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/Subprocess.h"
|
||||
|
||||
#include <sys/prctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
#include <wait.h>
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <system_error>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/range/adaptors.hpp>
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "folly/Conv.h"
|
||||
#include "folly/Exception.h"
|
||||
#include "folly/FileUtil.h"
|
||||
#include "folly/ScopeGuard.h"
|
||||
#include "folly/String.h"
|
||||
#include "folly/io/Cursor.h"
|
||||
|
||||
extern char** environ;
|
||||
|
||||
constexpr int kExecFailure = 127;
|
||||
constexpr int kChildFailure = 126;
|
||||
|
||||
namespace folly {
|
||||
|
||||
ProcessReturnCode::State ProcessReturnCode::state() const {
|
||||
if (rawStatus_ == RV_NOT_STARTED) return NOT_STARTED;
|
||||
if (rawStatus_ == RV_RUNNING) return RUNNING;
|
||||
if (WIFEXITED(rawStatus_)) return EXITED;
|
||||
if (WIFSIGNALED(rawStatus_)) return KILLED;
|
||||
throw std::runtime_error(to<std::string>(
|
||||
"Invalid ProcessReturnCode: ", rawStatus_));
|
||||
}
|
||||
|
||||
void ProcessReturnCode::enforce(State expected) const {
|
||||
State s = state();
|
||||
if (s != expected) {
|
||||
throw std::logic_error(to<std::string>("Invalid state ", s,
|
||||
" expected ", expected));
|
||||
}
|
||||
}
|
||||
|
||||
int ProcessReturnCode::exitStatus() const {
|
||||
enforce(EXITED);
|
||||
return WEXITSTATUS(rawStatus_);
|
||||
}
|
||||
|
||||
int ProcessReturnCode::killSignal() const {
|
||||
enforce(KILLED);
|
||||
return WTERMSIG(rawStatus_);
|
||||
}
|
||||
|
||||
bool ProcessReturnCode::coreDumped() const {
|
||||
enforce(KILLED);
|
||||
return WCOREDUMP(rawStatus_);
|
||||
}
|
||||
|
||||
std::string ProcessReturnCode::str() const {
|
||||
switch (state()) {
|
||||
case NOT_STARTED:
|
||||
return "not started";
|
||||
case RUNNING:
|
||||
return "running";
|
||||
case EXITED:
|
||||
return to<std::string>("exited with status ", exitStatus());
|
||||
case KILLED:
|
||||
return to<std::string>("killed by signal ", killSignal(),
|
||||
(coreDumped() ? " (core dumped)" : ""));
|
||||
}
|
||||
CHECK(false); // unreached
|
||||
}
|
||||
|
||||
CalledProcessError::CalledProcessError(ProcessReturnCode rc)
|
||||
: returnCode_(rc),
|
||||
what_(returnCode_.str()) {
|
||||
}
|
||||
|
||||
SubprocessSpawnError::SubprocessSpawnError(const char* executable,
|
||||
int errCode,
|
||||
int errnoValue)
|
||||
: errnoValue_(errnoValue),
|
||||
what_(to<std::string>(errCode == kExecFailure ?
|
||||
"failed to execute " :
|
||||
"error preparing to execute ",
|
||||
executable, ": ", errnoStr(errnoValue))) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Copy pointers to the given strings in a format suitable for posix_spawn
|
||||
std::unique_ptr<const char*[]> cloneStrings(const std::vector<std::string>& s) {
|
||||
std::unique_ptr<const char*[]> d(new const char*[s.size() + 1]);
|
||||
for (int i = 0; i < s.size(); i++) {
|
||||
d[i] = s[i].c_str();
|
||||
}
|
||||
d[s.size()] = nullptr;
|
||||
return d;
|
||||
}
|
||||
|
||||
// Check a wait() status, throw on non-successful
|
||||
void checkStatus(ProcessReturnCode returnCode) {
|
||||
if (returnCode.state() != ProcessReturnCode::EXITED ||
|
||||
returnCode.exitStatus() != 0) {
|
||||
throw CalledProcessError(returnCode);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Subprocess::Options& Subprocess::Options::fd(int fd, int action) {
|
||||
if (action == Subprocess::PIPE) {
|
||||
if (fd == 0) {
|
||||
action = Subprocess::PIPE_IN;
|
||||
} else if (fd == 1 || fd == 2) {
|
||||
action = Subprocess::PIPE_OUT;
|
||||
} else {
|
||||
throw std::invalid_argument(
|
||||
to<std::string>("Only fds 0, 1, 2 are valid for action=PIPE: ", fd));
|
||||
}
|
||||
}
|
||||
fdActions_[fd] = action;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Subprocess::Subprocess(
|
||||
const std::vector<std::string>& argv,
|
||||
const Options& options,
|
||||
const char* executable,
|
||||
const std::vector<std::string>* env)
|
||||
: pid_(-1),
|
||||
returnCode_(RV_NOT_STARTED) {
|
||||
if (argv.empty()) {
|
||||
throw std::invalid_argument("argv must not be empty");
|
||||
}
|
||||
if (!executable) executable = argv[0].c_str();
|
||||
spawn(cloneStrings(argv), executable, options, env);
|
||||
}
|
||||
|
||||
Subprocess::Subprocess(
|
||||
const std::string& cmd,
|
||||
const Options& options,
|
||||
const std::vector<std::string>* env)
|
||||
: pid_(-1),
|
||||
returnCode_(RV_NOT_STARTED) {
|
||||
if (options.usePath_) {
|
||||
throw std::invalid_argument("usePath() not allowed when running in shell");
|
||||
}
|
||||
const char* shell = getenv("SHELL");
|
||||
if (!shell) {
|
||||
shell = "/bin/sh";
|
||||
}
|
||||
|
||||
std::unique_ptr<const char*[]> argv(new const char*[4]);
|
||||
argv[0] = shell;
|
||||
argv[1] = "-c";
|
||||
argv[2] = cmd.c_str();
|
||||
argv[3] = nullptr;
|
||||
spawn(std::move(argv), shell, options, env);
|
||||
}
|
||||
|
||||
Subprocess::~Subprocess() {
|
||||
CHECK_NE(returnCode_.state(), ProcessReturnCode::RUNNING)
|
||||
<< "Subprocess destroyed without reaping child";
|
||||
closeAll();
|
||||
}
|
||||
|
||||
namespace {
|
||||
void closeChecked(int fd) {
|
||||
checkUnixError(::close(fd), "close");
|
||||
}
|
||||
|
||||
struct ChildErrorInfo {
|
||||
int errCode;
|
||||
int errnoValue;
|
||||
};
|
||||
|
||||
void childError(int errFd, int errCode, int errnoValue) FOLLY_NORETURN;
|
||||
void childError(int errFd, int errCode, int errnoValue) {
|
||||
ChildErrorInfo info = {errCode, errnoValue};
|
||||
// Write the error information over the pipe to our parent process.
|
||||
// We can't really do anything else if this write call fails.
|
||||
writeNoInt(errFd, &info, sizeof(info));
|
||||
// exit
|
||||
_exit(errCode);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Subprocess::closeAll() {
|
||||
for (auto& p : pipes_) {
|
||||
closeChecked(p.parentFd);
|
||||
}
|
||||
pipes_.clear();
|
||||
}
|
||||
|
||||
void Subprocess::setAllNonBlocking() {
|
||||
for (auto& p : pipes_) {
|
||||
int fd = p.parentFd;
|
||||
int flags = ::fcntl(fd, F_GETFL);
|
||||
checkUnixError(flags, "fcntl");
|
||||
int r = ::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
checkUnixError(r, "fcntl");
|
||||
}
|
||||
}
|
||||
|
||||
void Subprocess::spawn(
|
||||
std::unique_ptr<const char*[]> argv,
|
||||
const char* executable,
|
||||
const Options& optionsIn,
|
||||
const std::vector<std::string>* env) {
|
||||
if (optionsIn.usePath_ && env) {
|
||||
throw std::invalid_argument(
|
||||
"usePath() not allowed when overriding environment");
|
||||
}
|
||||
|
||||
// Make a copy, we'll mutate options
|
||||
Options options(optionsIn);
|
||||
|
||||
// On error, close all of the pipes_
|
||||
auto pipesGuard = makeGuard([&] {
|
||||
for (auto& p : this->pipes_) {
|
||||
CHECK_ERR(::close(p.parentFd));
|
||||
}
|
||||
});
|
||||
|
||||
// Create a pipe to use to receive error information from the child,
|
||||
// in case it fails before calling exec()
|
||||
int errFds[2];
|
||||
int r = ::pipe(errFds);
|
||||
checkUnixError(r, "pipe");
|
||||
SCOPE_EXIT {
|
||||
CHECK_ERR(::close(errFds[0]));
|
||||
if (errFds[1] >= 0) {
|
||||
CHECK_ERR(::close(errFds[1]));
|
||||
}
|
||||
};
|
||||
// Ask the child to close the read end of the error pipe.
|
||||
options.fdActions_[errFds[0]] = CLOSE;
|
||||
// Set the close-on-exec flag on the write side of the pipe.
|
||||
// This way the pipe will be closed automatically in the child if execve()
|
||||
// succeeds. If the exec fails the child can write error information to the
|
||||
// pipe.
|
||||
r = fcntl(errFds[1], F_SETFD, FD_CLOEXEC);
|
||||
checkUnixError(r, "set FD_CLOEXEC");
|
||||
|
||||
// Perform the actual work of setting up pipes then forking and
|
||||
// executing the child.
|
||||
spawnInternal(std::move(argv), executable, options, env, errFds[1]);
|
||||
|
||||
// After spawnInternal() returns the child is alive. We have to be very
|
||||
// careful about throwing after this point. We are inside the constructor,
|
||||
// so if we throw the Subprocess object will have never existed, and the
|
||||
// destructor will never be called.
|
||||
//
|
||||
// We should only throw if we got an error via the errFd, and we know the
|
||||
// child has exited and can be immediately waited for. In all other cases,
|
||||
// we have no way of cleaning up the child.
|
||||
|
||||
// Close writable side of the errFd pipe in the parent process
|
||||
CHECK_ERR(::close(errFds[1]));
|
||||
errFds[1] = -1;
|
||||
|
||||
// Read from the errFd pipe, to tell if the child ran into any errors before
|
||||
// calling exec()
|
||||
readChildErrorPipe(errFds[0], executable);
|
||||
|
||||
// We have fully succeeded now, so release the guard on pipes_
|
||||
pipesGuard.dismiss();
|
||||
}
|
||||
|
||||
void Subprocess::spawnInternal(
|
||||
std::unique_ptr<const char*[]> argv,
|
||||
const char* executable,
|
||||
Options& options,
|
||||
const std::vector<std::string>* env,
|
||||
int errFd) {
|
||||
// Parent work, pre-fork: create pipes
|
||||
std::vector<int> childFds;
|
||||
// Close all of the childFds as we leave this scope
|
||||
SCOPE_EXIT {
|
||||
// These are only pipes, closing them shouldn't fail
|
||||
for (int cfd : childFds) {
|
||||
CHECK_ERR(::close(cfd));
|
||||
}
|
||||
};
|
||||
|
||||
int r;
|
||||
for (auto& p : options.fdActions_) {
|
||||
if (p.second == PIPE_IN || p.second == PIPE_OUT) {
|
||||
int fds[2];
|
||||
r = ::pipe(fds);
|
||||
checkUnixError(r, "pipe");
|
||||
PipeInfo pinfo;
|
||||
pinfo.direction = p.second;
|
||||
int cfd;
|
||||
if (p.second == PIPE_IN) {
|
||||
// Child gets reading end
|
||||
pinfo.parentFd = fds[1];
|
||||
cfd = fds[0];
|
||||
} else {
|
||||
pinfo.parentFd = fds[0];
|
||||
cfd = fds[1];
|
||||
}
|
||||
p.second = cfd; // ensure it gets dup2()ed
|
||||
pinfo.childFd = p.first;
|
||||
childFds.push_back(cfd);
|
||||
pipes_.push_back(pinfo);
|
||||
}
|
||||
}
|
||||
|
||||
// This should already be sorted, as options.fdActions_ is
|
||||
DCHECK(std::is_sorted(pipes_.begin(), pipes_.end()));
|
||||
|
||||
// Note that the const casts below are legit, per
|
||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/exec.html
|
||||
|
||||
char** argVec = const_cast<char**>(argv.get());
|
||||
|
||||
// Set up environment
|
||||
std::unique_ptr<const char*[]> envHolder;
|
||||
char** envVec;
|
||||
if (env) {
|
||||
envHolder = cloneStrings(*env);
|
||||
envVec = const_cast<char**>(envHolder.get());
|
||||
} else {
|
||||
envVec = environ;
|
||||
}
|
||||
|
||||
// Block all signals around vfork; see http://ewontfix.com/7/.
|
||||
//
|
||||
// As the child may run in the same address space as the parent until
|
||||
// the actual execve() system call, any (custom) signal handlers that
|
||||
// the parent has might alter parent's memory if invoked in the child,
|
||||
// with undefined results. So we block all signals in the parent before
|
||||
// vfork(), which will cause them to be blocked in the child as well (we
|
||||
// rely on the fact that Linux, just like all sane implementations, only
|
||||
// clones the calling thread). Then, in the child, we reset all signals
|
||||
// to their default dispositions (while still blocked), and unblock them
|
||||
// (so the exec()ed process inherits the parent's signal mask)
|
||||
//
|
||||
// The parent also unblocks all signals as soon as vfork() returns.
|
||||
sigset_t allBlocked;
|
||||
r = ::sigfillset(&allBlocked);
|
||||
checkUnixError(r, "sigfillset");
|
||||
sigset_t oldSignals;
|
||||
|
||||
r = pthread_sigmask(SIG_SETMASK, &allBlocked, &oldSignals);
|
||||
checkPosixError(r, "pthread_sigmask");
|
||||
SCOPE_EXIT {
|
||||
// Restore signal mask
|
||||
r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr);
|
||||
CHECK_EQ(r, 0) << "pthread_sigmask: " << errnoStr(r); // shouldn't fail
|
||||
};
|
||||
|
||||
pid_t pid = vfork();
|
||||
if (pid == 0) {
|
||||
int errnoValue = prepareChild(options, &oldSignals);
|
||||
if (errnoValue != 0) {
|
||||
childError(errFd, kChildFailure, errnoValue);
|
||||
}
|
||||
|
||||
errnoValue = runChild(executable, argVec, envVec, options);
|
||||
// If we get here, exec() failed.
|
||||
childError(errFd, kExecFailure, errnoValue);
|
||||
}
|
||||
// In parent. Make sure vfork() succeeded.
|
||||
checkUnixError(pid, errno, "vfork");
|
||||
|
||||
// Child is alive. We have to be very careful about throwing after this
|
||||
// point. We are inside the constructor, so if we throw the Subprocess
|
||||
// object will have never existed, and the destructor will never be called.
|
||||
//
|
||||
// We should only throw if we got an error via the errFd, and we know the
|
||||
// child has exited and can be immediately waited for. In all other cases,
|
||||
// we have no way of cleaning up the child.
|
||||
pid_ = pid;
|
||||
returnCode_ = ProcessReturnCode(RV_RUNNING);
|
||||
}
|
||||
|
||||
int Subprocess::prepareChild(const Options& options,
|
||||
const sigset_t* sigmask) const {
|
||||
// While all signals are blocked, we must reset their
|
||||
// dispositions to default.
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
::signal(sig, SIG_DFL);
|
||||
}
|
||||
// Unblock signals; restore signal mask.
|
||||
int r = pthread_sigmask(SIG_SETMASK, sigmask, nullptr);
|
||||
if (r != 0) {
|
||||
return r; // pthread_sigmask() returns an errno value
|
||||
}
|
||||
|
||||
// Close parent's ends of all pipes
|
||||
for (auto& p : pipes_) {
|
||||
r = ::close(p.parentFd);
|
||||
if (r == -1) {
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
// Close all fds that we're supposed to close.
|
||||
// Note that we're ignoring errors here, in case some of these
|
||||
// fds were set to close on exec.
|
||||
for (auto& p : options.fdActions_) {
|
||||
if (p.second == CLOSE) {
|
||||
::close(p.first);
|
||||
} else {
|
||||
r = ::dup2(p.second, p.first);
|
||||
if (r == -1) {
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If requested, close all other file descriptors. Don't close
|
||||
// any fds in options.fdActions_, and don't touch stdin, stdout, stderr.
|
||||
// Ignore errors.
|
||||
if (options.closeOtherFds_) {
|
||||
for (int fd = getdtablesize() - 1; fd >= 3; --fd) {
|
||||
if (options.fdActions_.count(fd) == 0) {
|
||||
::close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Opt to receive signal on parent death, if requested
|
||||
if (options.parentDeathSignal_ != 0) {
|
||||
r = prctl(PR_SET_PDEATHSIG, options.parentDeathSignal_, 0, 0, 0);
|
||||
if (r == -1) {
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Subprocess::runChild(const char* executable,
|
||||
char** argv, char** env,
|
||||
const Options& options) const {
|
||||
// Now, finally, exec.
|
||||
int r;
|
||||
if (options.usePath_) {
|
||||
::execvp(executable, argv);
|
||||
} else {
|
||||
::execve(executable, argv, env);
|
||||
}
|
||||
return errno;
|
||||
}
|
||||
|
||||
void Subprocess::readChildErrorPipe(int pfd, const char* executable) {
|
||||
ChildErrorInfo info;
|
||||
auto rc = readNoInt(pfd, &info, sizeof(info));
|
||||
if (rc == 0) {
|
||||
// No data means the child executed successfully, and the pipe
|
||||
// was closed due to the close-on-exec flag being set.
|
||||
return;
|
||||
} else if (rc != sizeof(ChildErrorInfo)) {
|
||||
// An error occurred trying to read from the pipe, or we got a partial read.
|
||||
// Neither of these cases should really occur in practice.
|
||||
//
|
||||
// We can't get any error data from the child in this case, and we don't
|
||||
// know if it is successfully running or not. All we can do is to return
|
||||
// normally, as if the child executed successfully. If something bad
|
||||
// happened the caller should at least get a non-normal exit status from
|
||||
// the child.
|
||||
LOG(ERROR) << "unexpected error trying to read from child error pipe " <<
|
||||
"rc=" << rc << ", errno=" << errno;
|
||||
return;
|
||||
}
|
||||
|
||||
// We got error data from the child. The child should exit immediately in
|
||||
// this case, so wait on it to clean up.
|
||||
wait();
|
||||
|
||||
// Throw to signal the error
|
||||
throw SubprocessSpawnError(executable, info.errCode, info.errnoValue);
|
||||
}
|
||||
|
||||
ProcessReturnCode Subprocess::poll() {
|
||||
returnCode_.enforce(ProcessReturnCode::RUNNING);
|
||||
DCHECK_GT(pid_, 0);
|
||||
int status;
|
||||
pid_t found = ::waitpid(pid_, &status, WNOHANG);
|
||||
checkUnixError(found, "waitpid");
|
||||
if (found != 0) {
|
||||
returnCode_ = ProcessReturnCode(status);
|
||||
pid_ = -1;
|
||||
}
|
||||
return returnCode_;
|
||||
}
|
||||
|
||||
bool Subprocess::pollChecked() {
|
||||
if (poll().state() == ProcessReturnCode::RUNNING) {
|
||||
return false;
|
||||
}
|
||||
checkStatus(returnCode_);
|
||||
return true;
|
||||
}
|
||||
|
||||
ProcessReturnCode Subprocess::wait() {
|
||||
returnCode_.enforce(ProcessReturnCode::RUNNING);
|
||||
DCHECK_GT(pid_, 0);
|
||||
int status;
|
||||
pid_t found;
|
||||
do {
|
||||
found = ::waitpid(pid_, &status, 0);
|
||||
} while (found == -1 && errno == EINTR);
|
||||
checkUnixError(found, "waitpid");
|
||||
DCHECK_EQ(found, pid_);
|
||||
returnCode_ = ProcessReturnCode(status);
|
||||
pid_ = -1;
|
||||
return returnCode_;
|
||||
}
|
||||
|
||||
void Subprocess::waitChecked() {
|
||||
wait();
|
||||
checkStatus(returnCode_);
|
||||
}
|
||||
|
||||
void Subprocess::sendSignal(int signal) {
|
||||
returnCode_.enforce(ProcessReturnCode::RUNNING);
|
||||
int r = ::kill(pid_, signal);
|
||||
checkUnixError(r, "kill");
|
||||
}
|
||||
|
||||
pid_t Subprocess::pid() const {
|
||||
return pid_;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::pair<const uint8_t*, size_t> queueFront(const IOBufQueue& queue) {
|
||||
auto* p = queue.front();
|
||||
if (!p) return std::make_pair(nullptr, 0);
|
||||
return io::Cursor(p).peek();
|
||||
}
|
||||
|
||||
// fd write
|
||||
bool handleWrite(int fd, IOBufQueue& queue) {
|
||||
for (;;) {
|
||||
auto p = queueFront(queue);
|
||||
if (p.second == 0) {
|
||||
return true; // EOF
|
||||
}
|
||||
|
||||
ssize_t n;
|
||||
do {
|
||||
n = ::write(fd, p.first, p.second);
|
||||
} while (n == -1 && errno == EINTR);
|
||||
if (n == -1 && errno == EAGAIN) {
|
||||
return false;
|
||||
}
|
||||
checkUnixError(n, "write");
|
||||
queue.trimStart(n);
|
||||
}
|
||||
}
|
||||
|
||||
// fd read
|
||||
bool handleRead(int fd, IOBufQueue& queue) {
|
||||
for (;;) {
|
||||
auto p = queue.preallocate(100, 65000);
|
||||
ssize_t n;
|
||||
do {
|
||||
n = ::read(fd, p.first, p.second);
|
||||
} while (n == -1 && errno == EINTR);
|
||||
if (n == -1 && errno == EAGAIN) {
|
||||
return false;
|
||||
}
|
||||
checkUnixError(n, "read");
|
||||
if (n == 0) {
|
||||
return true;
|
||||
}
|
||||
queue.postallocate(n);
|
||||
}
|
||||
}
|
||||
|
||||
bool discardRead(int fd) {
|
||||
static const size_t bufSize = 65000;
|
||||
// Thread unsafe, but it doesn't matter.
|
||||
static std::unique_ptr<char[]> buf(new char[bufSize]);
|
||||
|
||||
for (;;) {
|
||||
ssize_t n;
|
||||
do {
|
||||
n = ::read(fd, buf.get(), bufSize);
|
||||
} while (n == -1 && errno == EINTR);
|
||||
if (n == -1 && errno == EAGAIN) {
|
||||
return false;
|
||||
}
|
||||
checkUnixError(n, "read");
|
||||
if (n == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::pair<std::string, std::string> Subprocess::communicate(
|
||||
StringPiece input) {
|
||||
IOBufQueue inputQueue;
|
||||
inputQueue.wrapBuffer(input.data(), input.size());
|
||||
|
||||
auto outQueues = communicateIOBuf(std::move(inputQueue));
|
||||
auto outBufs = std::make_pair(outQueues.first.move(),
|
||||
outQueues.second.move());
|
||||
std::pair<std::string, std::string> out;
|
||||
if (outBufs.first) {
|
||||
outBufs.first->coalesce();
|
||||
out.first.assign(reinterpret_cast<const char*>(outBufs.first->data()),
|
||||
outBufs.first->length());
|
||||
}
|
||||
if (outBufs.second) {
|
||||
outBufs.second->coalesce();
|
||||
out.second.assign(reinterpret_cast<const char*>(outBufs.second->data()),
|
||||
outBufs.second->length());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::pair<IOBufQueue, IOBufQueue> Subprocess::communicateIOBuf(
|
||||
IOBufQueue input) {
|
||||
// If the user supplied a non-empty input buffer, make sure
|
||||
// that stdin is a pipe so we can write the data.
|
||||
if (!input.empty()) {
|
||||
// findByChildFd() will throw std::invalid_argument if no pipe for
|
||||
// STDIN_FILENO exists
|
||||
findByChildFd(STDIN_FILENO);
|
||||
}
|
||||
|
||||
std::pair<IOBufQueue, IOBufQueue> out;
|
||||
|
||||
auto readCallback = [&] (int pfd, int cfd) -> bool {
|
||||
if (cfd == STDOUT_FILENO) {
|
||||
return handleRead(pfd, out.first);
|
||||
} else if (cfd == STDERR_FILENO) {
|
||||
return handleRead(pfd, out.second);
|
||||
} else {
|
||||
// Don't close the file descriptor, the child might not like SIGPIPE,
|
||||
// just read and throw the data away.
|
||||
return discardRead(pfd);
|
||||
}
|
||||
};
|
||||
|
||||
auto writeCallback = [&] (int pfd, int cfd) -> bool {
|
||||
if (cfd == STDIN_FILENO) {
|
||||
return handleWrite(pfd, input);
|
||||
} else {
|
||||
// If we don't want to write to this fd, just close it.
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
communicate(std::move(readCallback), std::move(writeCallback));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void Subprocess::communicate(FdCallback readCallback,
|
||||
FdCallback writeCallback) {
|
||||
returnCode_.enforce(ProcessReturnCode::RUNNING);
|
||||
setAllNonBlocking();
|
||||
|
||||
std::vector<pollfd> fds;
|
||||
fds.reserve(pipes_.size());
|
||||
std::vector<int> toClose;
|
||||
toClose.reserve(pipes_.size());
|
||||
|
||||
while (!pipes_.empty()) {
|
||||
fds.clear();
|
||||
toClose.clear();
|
||||
|
||||
for (auto& p : pipes_) {
|
||||
pollfd pfd;
|
||||
pfd.fd = p.parentFd;
|
||||
// Yes, backwards, PIPE_IN / PIPE_OUT are defined from the
|
||||
// child's point of view.
|
||||
pfd.events = (p.direction == PIPE_IN ? POLLOUT : POLLIN);
|
||||
fds.push_back(pfd);
|
||||
}
|
||||
|
||||
int r;
|
||||
do {
|
||||
r = ::poll(fds.data(), fds.size(), -1);
|
||||
} while (r == -1 && errno == EINTR);
|
||||
checkUnixError(r, "poll");
|
||||
|
||||
for (int i = 0; i < pipes_.size(); ++i) {
|
||||
auto& p = pipes_[i];
|
||||
DCHECK_EQ(fds[i].fd, p.parentFd);
|
||||
short events = fds[i].revents;
|
||||
|
||||
bool closed = false;
|
||||
if (events & POLLOUT) {
|
||||
DCHECK(!(events & POLLIN));
|
||||
if (writeCallback(p.parentFd, p.childFd)) {
|
||||
toClose.push_back(i);
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (events & POLLIN) {
|
||||
DCHECK(!(events & POLLOUT));
|
||||
if (readCallback(p.parentFd, p.childFd)) {
|
||||
toClose.push_back(i);
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((events & (POLLHUP | POLLERR)) && !closed) {
|
||||
toClose.push_back(i);
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Close the fds in reverse order so the indexes hold after erase()
|
||||
for (int idx : boost::adaptors::reverse(toClose)) {
|
||||
auto pos = pipes_.begin() + idx;
|
||||
closeChecked(pos->parentFd);
|
||||
pipes_.erase(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Subprocess::findByChildFd(int childFd) const {
|
||||
auto pos = std::lower_bound(
|
||||
pipes_.begin(), pipes_.end(), childFd,
|
||||
[] (const PipeInfo& info, int fd) { return info.childFd < fd; });
|
||||
if (pos == pipes_.end() || pos->childFd != childFd) {
|
||||
throw std::invalid_argument(folly::to<std::string>(
|
||||
"child fd not found ", childFd));
|
||||
}
|
||||
return pos - pipes_.begin();
|
||||
}
|
||||
|
||||
void Subprocess::closeParentFd(int childFd) {
|
||||
int idx = findByChildFd(childFd);
|
||||
closeChecked(pipes_[idx].parentFd);
|
||||
pipes_.erase(pipes_.begin() + idx);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class Initializer {
|
||||
public:
|
||||
Initializer() {
|
||||
// We like EPIPE, thanks.
|
||||
::signal(SIGPIPE, SIG_IGN);
|
||||
}
|
||||
};
|
||||
|
||||
Initializer initializer;
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace folly
|
||||
|
||||
-528
@@ -1,528 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Subprocess library, modeled after Python's subprocess module
|
||||
* (http://docs.python.org/2/library/subprocess.html)
|
||||
*
|
||||
* This library defines one class (Subprocess) which represents a child
|
||||
* process. Subprocess has two constructors: one that takes a vector<string>
|
||||
* and executes the given executable without using the shell, and one
|
||||
* that takes a string and executes the given command using the shell.
|
||||
* Subprocess allows you to redirect the child's standard input, standard
|
||||
* output, and standard error to/from child descriptors in the parent,
|
||||
* or to create communication pipes between the child and the parent.
|
||||
*
|
||||
* The simplest example is a thread-safe version of the system() library
|
||||
* function:
|
||||
* Subprocess(cmd).wait();
|
||||
* which executes the command using the default shell and waits for it
|
||||
* to complete, returning the exit status.
|
||||
*
|
||||
* A thread-safe version of popen() (type="r", to read from the child):
|
||||
* Subprocess proc(cmd, Subprocess::pipeStdout());
|
||||
* // read from proc.stdout()
|
||||
* proc.wait();
|
||||
*
|
||||
* A thread-safe version of popen() (type="w", to write from the child):
|
||||
* Subprocess proc(cmd, Subprocess::pipeStdin());
|
||||
* // write to proc.stdin()
|
||||
* proc.wait();
|
||||
*
|
||||
* If you want to redirect both stdin and stdout to pipes, you can, but
|
||||
* note that you're subject to a variety of deadlocks. You'll want to use
|
||||
* nonblocking I/O; look at the implementation of communicate() for an example.
|
||||
*
|
||||
* communicate() is a way to communicate to a child via its standard input,
|
||||
* standard output, and standard error. It buffers everything in memory,
|
||||
* so it's not great for large amounts of data (or long-running processes),
|
||||
* but it insulates you from the deadlocks mentioned above.
|
||||
*/
|
||||
#ifndef FOLLY_SUBPROCESS_H_
|
||||
#define FOLLY_SUBPROCESS_H_
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <signal.h>
|
||||
#include <wait.h>
|
||||
|
||||
#include <exception>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/operators.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include "folly/io/IOBufQueue.h"
|
||||
#include "folly/MapUtil.h"
|
||||
#include "folly/Portability.h"
|
||||
#include "folly/Range.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Class to wrap a process return code.
|
||||
*/
|
||||
class Subprocess;
|
||||
class ProcessReturnCode {
|
||||
friend class Subprocess;
|
||||
public:
|
||||
enum State {
|
||||
NOT_STARTED,
|
||||
RUNNING,
|
||||
EXITED,
|
||||
KILLED
|
||||
};
|
||||
|
||||
/**
|
||||
* Process state. One of:
|
||||
* NOT_STARTED: process hasn't been started successfully
|
||||
* RUNNING: process is currently running
|
||||
* EXITED: process exited (successfully or not)
|
||||
* KILLED: process was killed by a signal.
|
||||
*/
|
||||
State state() const;
|
||||
|
||||
/**
|
||||
* Helper wrappers around state().
|
||||
*/
|
||||
bool notStarted() const { return state() == NOT_STARTED; }
|
||||
bool running() const { return state() == RUNNING; }
|
||||
bool exited() const { return state() == EXITED; }
|
||||
bool killed() const { return state() == KILLED; }
|
||||
|
||||
/**
|
||||
* Exit status. Only valid if state() == EXITED; throws otherwise.
|
||||
*/
|
||||
int exitStatus() const;
|
||||
|
||||
/**
|
||||
* Signal that caused the process's termination. Only valid if
|
||||
* state() == KILLED; throws otherwise.
|
||||
*/
|
||||
int killSignal() const;
|
||||
|
||||
/**
|
||||
* Was a core file generated? Only valid if state() == KILLED; throws
|
||||
* otherwise.
|
||||
*/
|
||||
bool coreDumped() const;
|
||||
|
||||
/**
|
||||
* String representation; one of
|
||||
* "not started"
|
||||
* "running"
|
||||
* "exited with status <status>"
|
||||
* "killed by signal <signal>"
|
||||
* "killed by signal <signal> (core dumped)"
|
||||
*/
|
||||
std::string str() const;
|
||||
|
||||
/**
|
||||
* Helper function to enforce a precondition based on this.
|
||||
* Throws std::logic_error if in an unexpected state.
|
||||
*/
|
||||
void enforce(State state) const;
|
||||
private:
|
||||
explicit ProcessReturnCode(int rv) : rawStatus_(rv) { }
|
||||
static constexpr int RV_NOT_STARTED = -2;
|
||||
static constexpr int RV_RUNNING = -1;
|
||||
|
||||
int rawStatus_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base exception thrown by the Subprocess methods.
|
||||
*/
|
||||
class SubprocessError : public std::exception {};
|
||||
|
||||
/**
|
||||
* Exception thrown by *Checked methods of Subprocess.
|
||||
*/
|
||||
class CalledProcessError : public SubprocessError {
|
||||
public:
|
||||
explicit CalledProcessError(ProcessReturnCode rc);
|
||||
~CalledProcessError() throw() { }
|
||||
const char* what() const throw() FOLLY_OVERRIDE { return what_.c_str(); }
|
||||
ProcessReturnCode returnCode() const { return returnCode_; }
|
||||
private:
|
||||
ProcessReturnCode returnCode_;
|
||||
std::string what_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception thrown if the subprocess cannot be started.
|
||||
*/
|
||||
class SubprocessSpawnError : public SubprocessError {
|
||||
public:
|
||||
SubprocessSpawnError(const char* executable, int errCode, int errnoValue);
|
||||
~SubprocessSpawnError() throw() {}
|
||||
const char* what() const throw() FOLLY_OVERRIDE { return what_.c_str(); }
|
||||
int errnoValue() const { return errnoValue_; }
|
||||
|
||||
private:
|
||||
int errnoValue_;
|
||||
std::string what_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Subprocess.
|
||||
*/
|
||||
class Subprocess : private boost::noncopyable {
|
||||
public:
|
||||
static const int CLOSE = -1;
|
||||
static const int PIPE = -2;
|
||||
static const int PIPE_IN = -3;
|
||||
static const int PIPE_OUT = -4;
|
||||
|
||||
/**
|
||||
* Class representing various options: file descriptor behavior, and
|
||||
* whether to use $PATH for searching for the executable,
|
||||
*
|
||||
* By default, we don't use $PATH, file descriptors are closed if
|
||||
* the close-on-exec flag is set (fcntl FD_CLOEXEC) and inherited
|
||||
* otherwise.
|
||||
*/
|
||||
class Options : private boost::orable<Options> {
|
||||
friend class Subprocess;
|
||||
public:
|
||||
Options()
|
||||
: closeOtherFds_(false),
|
||||
usePath_(false),
|
||||
parentDeathSignal_(0) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Change action for file descriptor fd.
|
||||
*
|
||||
* "action" may be another file descriptor number (dup2()ed before the
|
||||
* child execs), or one of CLOSE, PIPE_IN, and PIPE_OUT.
|
||||
*
|
||||
* CLOSE: close the file descriptor in the child
|
||||
* PIPE_IN: open a pipe *from* the child
|
||||
* PIPE_OUT: open a pipe *to* the child
|
||||
*
|
||||
* PIPE is a shortcut; same as PIPE_IN for stdin (fd 0), same as
|
||||
* PIPE_OUT for stdout (fd 1) or stderr (fd 2), and an error for
|
||||
* other file descriptors.
|
||||
*/
|
||||
Options& fd(int fd, int action);
|
||||
|
||||
/**
|
||||
* Shortcut to change the action for standard input.
|
||||
*/
|
||||
Options& stdin(int action) { return fd(STDIN_FILENO, action); }
|
||||
|
||||
/**
|
||||
* Shortcut to change the action for standard output.
|
||||
*/
|
||||
Options& stdout(int action) { return fd(STDOUT_FILENO, action); }
|
||||
|
||||
/**
|
||||
* Shortcut to change the action for standard error.
|
||||
* Note that stderr(1) will redirect the standard error to the same
|
||||
* file descriptor as standard output; the equivalent of bash's "2>&1"
|
||||
*/
|
||||
Options& stderr(int action) { return fd(STDERR_FILENO, action); }
|
||||
|
||||
Options& pipeStdin() { return fd(STDIN_FILENO, PIPE_IN); }
|
||||
Options& pipeStdout() { return fd(STDOUT_FILENO, PIPE_OUT); }
|
||||
Options& pipeStderr() { return fd(STDERR_FILENO, PIPE_OUT); }
|
||||
|
||||
/**
|
||||
* Close all other fds (other than standard input, output, error,
|
||||
* and file descriptors explicitly specified with fd()).
|
||||
*
|
||||
* This is potentially slow; it's generally a better idea to
|
||||
* set the close-on-exec flag on all file descriptors that shouldn't
|
||||
* be inherited by the child.
|
||||
*
|
||||
* Even with this option set, standard input, output, and error are
|
||||
* not closed; use stdin(CLOSE), stdout(CLOSE), stderr(CLOSE) if you
|
||||
* desire this.
|
||||
*/
|
||||
Options& closeOtherFds() { closeOtherFds_ = true; return *this; }
|
||||
|
||||
/**
|
||||
* Use the search path ($PATH) when searching for the executable.
|
||||
*/
|
||||
Options& usePath() { usePath_ = true; return *this; }
|
||||
|
||||
/**
|
||||
* Child will receive a signal when the parent exits.
|
||||
*/
|
||||
Options& parentDeathSignal(int sig) {
|
||||
parentDeathSignal_ = sig;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helpful way to combine Options.
|
||||
*/
|
||||
Options& operator|=(const Options& other);
|
||||
|
||||
private:
|
||||
typedef boost::container::flat_map<int, int> FdMap;
|
||||
FdMap fdActions_;
|
||||
bool closeOtherFds_;
|
||||
bool usePath_;
|
||||
int parentDeathSignal_;
|
||||
};
|
||||
|
||||
static Options pipeStdin() { return Options().stdin(PIPE); }
|
||||
static Options pipeStdout() { return Options().stdout(PIPE); }
|
||||
static Options pipeStderr() { return Options().stderr(PIPE); }
|
||||
|
||||
/**
|
||||
* Create a subprocess from the given arguments. argv[0] must be listed.
|
||||
* If not-null, executable must be the actual executable
|
||||
* being used (otherwise it's the same as argv[0]).
|
||||
*
|
||||
* If env is not-null, it must contain name=value strings to be used
|
||||
* as the child's environment; otherwise, we inherit the environment
|
||||
* from the parent. env must be null if options.usePath is set.
|
||||
*/
|
||||
explicit Subprocess(
|
||||
const std::vector<std::string>& argv,
|
||||
const Options& options = Options(),
|
||||
const char* executable = nullptr,
|
||||
const std::vector<std::string>* env = nullptr);
|
||||
~Subprocess();
|
||||
|
||||
/**
|
||||
* Create a subprocess run as a shell command (as shell -c 'command')
|
||||
*
|
||||
* The shell to use is taken from the environment variable $SHELL,
|
||||
* or /bin/sh if $SHELL is unset.
|
||||
*/
|
||||
explicit Subprocess(
|
||||
const std::string& cmd,
|
||||
const Options& options = Options(),
|
||||
const std::vector<std::string>* env = nullptr);
|
||||
|
||||
/**
|
||||
* Communicate with the child until all pipes to/from the child are closed.
|
||||
*
|
||||
* The input buffer is written to the process' stdin pipe, and data is read
|
||||
* from the stdout and stderr pipes. Non-blocking I/O is performed on all
|
||||
* pipes simultaneously to avoid deadlocks.
|
||||
*
|
||||
* The stdin pipe will be closed after the full input buffer has been written.
|
||||
* An error will be thrown if a non-empty input buffer is supplied but stdin
|
||||
* was not configured as a pipe.
|
||||
*
|
||||
* Returns a pair of buffers containing the data read from stdout and stderr.
|
||||
* If stdout or stderr is not a pipe, an empty IOBuf queue will be returned
|
||||
* for the respective buffer.
|
||||
*
|
||||
* Note that communicate() returns when all pipes to/from the child are
|
||||
* closed; the child might stay alive after that, so you must still wait().
|
||||
*
|
||||
* communicateIOBuf uses IOBufQueue for buffering (which has the advantage
|
||||
* that it won't try to allocate all data at once). communicate
|
||||
* uses strings for simplicity.
|
||||
*/
|
||||
std::pair<IOBufQueue, IOBufQueue> communicateIOBuf(
|
||||
IOBufQueue input = IOBufQueue());
|
||||
|
||||
std::pair<std::string, std::string> communicate(
|
||||
StringPiece input = StringPiece());
|
||||
|
||||
/**
|
||||
* Communicate with the child until all pipes to/from the child are closed.
|
||||
*
|
||||
* readCallback(pfd, cfd) will be called whenever there's data available
|
||||
* on any pipe *from* the child (PIPE_OUT). pfd is the file descriptor
|
||||
* in the parent (that you use to read from); cfd is the file descriptor
|
||||
* in the child (used for identifying the stream; 1 = child's standard
|
||||
* output, 2 = child's standard error, etc)
|
||||
*
|
||||
* writeCallback(pfd, cfd) will be called whenever a pipe *to* the child is
|
||||
* writable (PIPE_IN). pfd is the file descriptor in the parent (that you
|
||||
* use to write to); cfd is the file descriptor in the child (used for
|
||||
* identifying the stream; 0 = child's standard input, etc)
|
||||
*
|
||||
* The read and write callbacks must read from / write to pfd and return
|
||||
* false during normal operation or true at end-of-file;
|
||||
* communicate() will then close the pipe. Note that pfd is
|
||||
* nonblocking, so be prepared for read() / write() to return -1 and
|
||||
* set errno to EAGAIN (in which case you should return false).
|
||||
*
|
||||
* NOTE that you MUST consume all data passed to readCallback (or return
|
||||
* true, which will close the pipe, possibly sending SIGPIPE to the child or
|
||||
* making its writes fail with EPIPE), and you MUST write to a writable pipe
|
||||
* (or return true, which will close the pipe). To do otherwise is an
|
||||
* error. You must do this even for pipes you are not interested in.
|
||||
*
|
||||
* Note that communicate() returns when all pipes to/from the child are
|
||||
* closed; the child might stay alive after that, so you must still wait().
|
||||
*
|
||||
* Most users won't need to use this; the simpler version of communicate
|
||||
* (which buffers data in memory) will probably work fine.
|
||||
*/
|
||||
typedef std::function<bool(int, int)> FdCallback;
|
||||
void communicate(FdCallback readCallback, FdCallback writeCallback);
|
||||
|
||||
/**
|
||||
* Return the child's pid, or -1 if the child wasn't successfully spawned
|
||||
* or has already been wait()ed upon.
|
||||
*/
|
||||
pid_t pid() const;
|
||||
|
||||
/**
|
||||
* Return the child's status (as per wait()) if the process has already
|
||||
* been waited on, -1 if the process is still running, or -2 if the process
|
||||
* hasn't been successfully started. NOTE that this does not poll, but
|
||||
* returns the status stored in the Subprocess object.
|
||||
*/
|
||||
ProcessReturnCode returnCode() const { return returnCode_; }
|
||||
|
||||
/**
|
||||
* Poll the child's status and return it, return -1 if the process
|
||||
* is still running. NOTE that it is illegal to call poll again after
|
||||
* poll indicated that the process has terminated, or to call poll on a
|
||||
* process that hasn't been successfully started (the constructor threw an
|
||||
* exception).
|
||||
*/
|
||||
ProcessReturnCode poll();
|
||||
|
||||
/**
|
||||
* Poll the child's status. If the process is still running, return false.
|
||||
* Otherwise, return true if the process exited with status 0 (success),
|
||||
* or throw CalledProcessError if the process exited with a non-zero status.
|
||||
*/
|
||||
bool pollChecked();
|
||||
|
||||
/**
|
||||
* Wait for the process to terminate and return its status.
|
||||
* Similarly to poll, it is illegal to call wait after the process
|
||||
* has already been reaped or if the process has not successfully started.
|
||||
*/
|
||||
ProcessReturnCode wait();
|
||||
|
||||
/**
|
||||
* Wait for the process to terminate, throw if unsuccessful.
|
||||
*/
|
||||
void waitChecked();
|
||||
|
||||
/**
|
||||
* Set all pipes from / to child non-blocking. communicate() does
|
||||
* this for you.
|
||||
*/
|
||||
void setAllNonBlocking();
|
||||
|
||||
/**
|
||||
* Get parent file descriptor corresponding to the given file descriptor
|
||||
* in the child. Throws if childFd isn't a pipe (PIPE_IN / PIPE_OUT).
|
||||
* Do not close() the return file descriptor; use closeParentFd, below.
|
||||
*/
|
||||
int parentFd(int childFd) const {
|
||||
return pipes_[findByChildFd(childFd)].parentFd;
|
||||
}
|
||||
int stdin() const { return parentFd(0); }
|
||||
int stdout() const { return parentFd(1); }
|
||||
int stderr() const { return parentFd(2); }
|
||||
|
||||
/**
|
||||
* Close the parent file descriptor given a file descriptor in the child.
|
||||
*/
|
||||
void closeParentFd(int childFd);
|
||||
|
||||
/**
|
||||
* Send a signal to the child. Shortcuts for the commonly used Unix
|
||||
* signals are below.
|
||||
*/
|
||||
void sendSignal(int signal);
|
||||
void terminate() { sendSignal(SIGTERM); }
|
||||
void kill() { sendSignal(SIGKILL); }
|
||||
|
||||
private:
|
||||
static const int RV_RUNNING = ProcessReturnCode::RV_RUNNING;
|
||||
static const int RV_NOT_STARTED = ProcessReturnCode::RV_NOT_STARTED;
|
||||
|
||||
// spawn() sets up a pipe to read errors from the child,
|
||||
// then calls spawnInternal() to do the bulk of the work. Once
|
||||
// spawnInternal() returns it reads the error pipe to see if the child
|
||||
// encountered any errors.
|
||||
void spawn(
|
||||
std::unique_ptr<const char*[]> argv,
|
||||
const char* executable,
|
||||
const Options& options,
|
||||
const std::vector<std::string>* env);
|
||||
void spawnInternal(
|
||||
std::unique_ptr<const char*[]> argv,
|
||||
const char* executable,
|
||||
Options& options,
|
||||
const std::vector<std::string>* env,
|
||||
int errFd);
|
||||
|
||||
// Actions to run in child.
|
||||
// Note that this runs after vfork(), so tread lightly.
|
||||
// Returns 0 on success, or an errno value on failure.
|
||||
int prepareChild(const Options& options, const sigset_t* sigmask) const;
|
||||
int runChild(const char* executable, char** argv, char** env,
|
||||
const Options& options) const;
|
||||
|
||||
/**
|
||||
* Read from the error pipe, and throw SubprocessSpawnError if the child
|
||||
* failed before calling exec().
|
||||
*/
|
||||
void readChildErrorPipe(int pfd, const char* executable);
|
||||
|
||||
/**
|
||||
* Close all file descriptors.
|
||||
*/
|
||||
void closeAll();
|
||||
|
||||
// return index in pipes_
|
||||
int findByChildFd(int childFd) const;
|
||||
|
||||
pid_t pid_;
|
||||
ProcessReturnCode returnCode_;
|
||||
|
||||
// The number of pipes between parent and child is assumed to be small,
|
||||
// so we're happy with a vector here, even if it means linear erase.
|
||||
// sorted by childFd
|
||||
struct PipeInfo : private boost::totally_ordered<PipeInfo> {
|
||||
int parentFd;
|
||||
int childFd;
|
||||
int direction; // one of PIPE_IN / PIPE_OUT
|
||||
bool operator<(const PipeInfo& other) const {
|
||||
return childFd < other.childFd;
|
||||
}
|
||||
bool operator==(const PipeInfo& other) const {
|
||||
return childFd == other.childFd;
|
||||
}
|
||||
};
|
||||
std::vector<PipeInfo> pipes_;
|
||||
};
|
||||
|
||||
inline Subprocess::Options& Subprocess::Options::operator|=(
|
||||
const Subprocess::Options& other) {
|
||||
if (this == &other) return *this;
|
||||
// Replace
|
||||
for (auto& p : other.fdActions_) {
|
||||
fdActions_[p.first] = p.second;
|
||||
}
|
||||
closeOtherFds_ |= other.closeOtherFds_;
|
||||
usePath_ |= other.usePath_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_SUBPROCESS_H_ */
|
||||
|
||||
-692
@@ -1,692 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This module implements a Synchronized abstraction useful in
|
||||
* mutex-based concurrency.
|
||||
*
|
||||
* @author: Andrei Alexandrescu (andrei.alexandrescu@fb.com)
|
||||
*/
|
||||
|
||||
#ifndef SYNCHRONIZED_H_
|
||||
#define SYNCHRONIZED_H_
|
||||
|
||||
#include <type_traits>
|
||||
#include <mutex>
|
||||
#include <boost/thread.hpp>
|
||||
#include "folly/Preprocessor.h"
|
||||
#include "folly/Traits.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace detail {
|
||||
enum InternalDoNotUse {};
|
||||
|
||||
/**
|
||||
* Free function adaptors for std:: and boost::
|
||||
*/
|
||||
|
||||
/**
|
||||
* Yields true iff T has .lock() and .unlock() member functions. This
|
||||
* is done by simply enumerating the mutexes with this interface in
|
||||
* std and boost.
|
||||
*/
|
||||
template <class T>
|
||||
struct HasLockUnlock {
|
||||
enum { value = IsOneOf<T,
|
||||
std::mutex, std::recursive_mutex,
|
||||
boost::mutex, boost::recursive_mutex, boost::shared_mutex
|
||||
#ifndef __APPLE__ // OSX doesn't have timed mutexes
|
||||
,std::timed_mutex, std::recursive_timed_mutex,
|
||||
boost::timed_mutex, boost::recursive_timed_mutex
|
||||
#endif
|
||||
>::value };
|
||||
};
|
||||
|
||||
/**
|
||||
* Acquires a mutex for reading by calling .lock(). The exception is
|
||||
* boost::shared_mutex, which has a special read-lock primitive called
|
||||
* .lock_shared().
|
||||
*/
|
||||
template <class T>
|
||||
typename std::enable_if<
|
||||
HasLockUnlock<T>::value && !std::is_same<T, boost::shared_mutex>::value>::type
|
||||
acquireRead(T& mutex) {
|
||||
mutex.lock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case for boost::shared_mutex.
|
||||
*/
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_same<T, boost::shared_mutex>::value>::type
|
||||
acquireRead(T& mutex) {
|
||||
mutex.lock_shared();
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a mutex for reading with timeout by calling .timed_lock(). This
|
||||
* applies to three of the boost mutex classes as enumerated below.
|
||||
*/
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_same<T, boost::shared_mutex>::value, bool>::type
|
||||
acquireRead(T& mutex,
|
||||
unsigned int milliseconds) {
|
||||
return mutex.timed_lock_shared(boost::posix_time::milliseconds(milliseconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a mutex for reading and writing by calling .lock().
|
||||
*/
|
||||
template <class T>
|
||||
typename std::enable_if<HasLockUnlock<T>::value>::type
|
||||
acquireReadWrite(T& mutex) {
|
||||
mutex.lock();
|
||||
}
|
||||
|
||||
#ifndef __APPLE__ // OSX doesn't have timed mutexes
|
||||
/**
|
||||
* Acquires a mutex for reading and writing with timeout by calling
|
||||
* .try_lock_for(). This applies to two of the std mutex classes as
|
||||
* enumerated below.
|
||||
*/
|
||||
template <class T>
|
||||
typename std::enable_if<
|
||||
IsOneOf<T, std::timed_mutex, std::recursive_timed_mutex>::value, bool>::type
|
||||
acquireReadWrite(T& mutex,
|
||||
unsigned int milliseconds) {
|
||||
return mutex.try_lock_for(std::chrono::milliseconds(milliseconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires a mutex for reading and writing with timeout by calling
|
||||
* .timed_lock(). This applies to three of the boost mutex classes as
|
||||
* enumerated below.
|
||||
*/
|
||||
template <class T>
|
||||
typename std::enable_if<
|
||||
IsOneOf<T, boost::shared_mutex, boost::timed_mutex,
|
||||
boost::recursive_timed_mutex>::value, bool>::type
|
||||
acquireReadWrite(T& mutex,
|
||||
unsigned int milliseconds) {
|
||||
return mutex.timed_lock(boost::posix_time::milliseconds(milliseconds));
|
||||
}
|
||||
#endif // __APPLE__
|
||||
|
||||
/**
|
||||
* Releases a mutex previously acquired for reading by calling
|
||||
* .unlock(). The exception is boost::shared_mutex, which has a
|
||||
* special primitive called .unlock_shared().
|
||||
*/
|
||||
template <class T>
|
||||
typename std::enable_if<
|
||||
HasLockUnlock<T>::value && !std::is_same<T, boost::shared_mutex>::value>::type
|
||||
releaseRead(T& mutex) {
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case for boost::shared_mutex.
|
||||
*/
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_same<T, boost::shared_mutex>::value>::type
|
||||
releaseRead(T& mutex) {
|
||||
mutex.unlock_shared();
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a mutex previously acquired for reading-writing by calling
|
||||
* .unlock().
|
||||
*/
|
||||
template <class T>
|
||||
typename std::enable_if<HasLockUnlock<T>::value>::type
|
||||
releaseReadWrite(T& mutex) {
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Synchronized<T> encapsulates an object of type T (a "datum") paired
|
||||
* with a mutex. The only way to access the datum is while the mutex
|
||||
* is locked, and Synchronized makes it virtually impossible to do
|
||||
* otherwise. The code that would access the datum in unsafe ways
|
||||
* would look odd and convoluted, thus readily alerting the human
|
||||
* reviewer. In contrast, the code that uses Synchronized<T> correctly
|
||||
* looks simple and intuitive.
|
||||
*
|
||||
* The second parameter must be a mutex type. Supported mutexes are
|
||||
* std::mutex, std::recursive_mutex, std::timed_mutex,
|
||||
* std::recursive_timed_mutex, boost::mutex, boost::recursive_mutex,
|
||||
* boost::shared_mutex, boost::timed_mutex,
|
||||
* boost::recursive_timed_mutex, and the folly/RWSpinLock.h
|
||||
* classes.
|
||||
*
|
||||
* You may define Synchronized support by defining 4-6 primitives in
|
||||
* the same namespace as the mutex class (found via ADL). The
|
||||
* primitives are: acquireRead, acquireReadWrite, releaseRead, and
|
||||
* releaseReadWrite. Two optional primitives for timout operations are
|
||||
* overloads of acquireRead and acquireReadWrite. For signatures,
|
||||
* refer to the namespace detail below, which implements the
|
||||
* primitives for mutexes in std and boost.
|
||||
*/
|
||||
template <class T, class Mutex = boost::shared_mutex>
|
||||
struct Synchronized {
|
||||
/**
|
||||
* Default constructor leaves both members call their own default
|
||||
* constructor.
|
||||
*/
|
||||
Synchronized() = default;
|
||||
|
||||
/**
|
||||
* Copy constructor copies the data (with locking the source and
|
||||
* all) but does NOT copy the mutex. Doing so would result in
|
||||
* deadlocks.
|
||||
*/
|
||||
Synchronized(const Synchronized& rhs) {
|
||||
auto guard = rhs.operator->();
|
||||
datum_ = rhs.datum_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move constructor moves the data (with locking the source and all)
|
||||
* but does not move the mutex.
|
||||
*/
|
||||
Synchronized(Synchronized&& rhs) {
|
||||
auto guard = rhs.operator->();
|
||||
datum_ = std::move(rhs.datum_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor taking a datum as argument copies it. There is no
|
||||
* need to lock the constructing object.
|
||||
*/
|
||||
explicit Synchronized(const T& rhs) : datum_(rhs) {}
|
||||
|
||||
/**
|
||||
* Constructor taking a datum rvalue as argument moves it. Again,
|
||||
* there is no need to lock the constructing object.
|
||||
*/
|
||||
explicit Synchronized(T&& rhs) : datum_(std::move(rhs)) {}
|
||||
|
||||
/**
|
||||
* The canonical assignment operator only assigns the data, NOT the
|
||||
* mutex. It locks the two objects in ascending order of their
|
||||
* addresses.
|
||||
*/
|
||||
Synchronized& operator=(const Synchronized& rhs) {
|
||||
if (this == &rhs) {
|
||||
// Self-assignment, pass.
|
||||
} else if (this < &rhs) {
|
||||
auto guard1 = operator->();
|
||||
auto guard2 = rhs.operator->();
|
||||
datum_ = rhs.datum_;
|
||||
} else {
|
||||
auto guard1 = rhs.operator->();
|
||||
auto guard2 = operator->();
|
||||
datum_ = rhs.datum_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move assignment operator, only assigns the data, NOT the
|
||||
* mutex. It locks the two objects in ascending order of their
|
||||
* addresses.
|
||||
*/
|
||||
Synchronized& operator=(Synchronized&& rhs) {
|
||||
if (this == &rhs) {
|
||||
// Self-assignment, pass.
|
||||
} else if (this < &rhs) {
|
||||
auto guard1 = operator->();
|
||||
auto guard2 = rhs.operator->();
|
||||
datum_ = std::move(rhs.datum_);
|
||||
} else {
|
||||
auto guard1 = rhs.operator->();
|
||||
auto guard2 = operator->();
|
||||
datum_ = std::move(rhs.datum_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock object, assign datum.
|
||||
*/
|
||||
Synchronized& operator=(const T& rhs) {
|
||||
auto guard = operator->();
|
||||
datum_ = rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock object, move-assign datum.
|
||||
*/
|
||||
Synchronized& operator=(T&& rhs) {
|
||||
auto guard = operator->();
|
||||
datum_ = std::move(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A LockedPtr lp keeps a modifiable (i.e. non-const)
|
||||
* Synchronized<T> object locked for the duration of lp's
|
||||
* existence. Because of this, you get to access the datum's methods
|
||||
* directly by using lp->fun().
|
||||
*/
|
||||
struct LockedPtr {
|
||||
/**
|
||||
* Found no reason to leave this hanging.
|
||||
*/
|
||||
LockedPtr() = delete;
|
||||
|
||||
/**
|
||||
* Takes a Synchronized and locks it.
|
||||
*/
|
||||
explicit LockedPtr(Synchronized* parent) : parent_(parent) {
|
||||
acquire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a Synchronized and attempts to lock it for some
|
||||
* milliseconds. If not, the LockedPtr will be subsequently null.
|
||||
*/
|
||||
LockedPtr(Synchronized* parent, unsigned int milliseconds) {
|
||||
using namespace detail;
|
||||
if (acquireReadWrite(parent->mutex_, milliseconds)) {
|
||||
parent_ = parent;
|
||||
return;
|
||||
}
|
||||
// Could not acquire the resource, pointer is null
|
||||
parent_ = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used ONLY inside SYNCHRONIZED_DUAL. It initializes
|
||||
* everything properly, but does not lock the parent because it
|
||||
* "knows" someone else will lock it. Please do not use.
|
||||
*/
|
||||
LockedPtr(Synchronized* parent, detail::InternalDoNotUse)
|
||||
: parent_(parent) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy ctor adds one lock.
|
||||
*/
|
||||
LockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
|
||||
acquire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigning from another LockedPtr results in freeing the former
|
||||
* lock and acquiring the new one. The method works with
|
||||
* self-assignment (does nothing).
|
||||
*/
|
||||
LockedPtr& operator=(const LockedPtr& rhs) {
|
||||
if (parent_ != rhs.parent_) {
|
||||
if (parent_) parent_->mutex_.unlock();
|
||||
parent_ = rhs.parent_;
|
||||
acquire();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor releases.
|
||||
*/
|
||||
~LockedPtr() {
|
||||
using namespace detail;
|
||||
if (parent_) releaseReadWrite(parent_->mutex_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe to access the data. Don't save the obtained pointer by
|
||||
* invoking lp.operator->() by hand. Also, if the method returns a
|
||||
* handle stored inside the datum, don't use this idiom - use
|
||||
* SYNCHRONIZED below.
|
||||
*/
|
||||
T* operator->() {
|
||||
return parent_ ? &parent_->datum_ : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class temporarily unlocks a LockedPtr in a scoped
|
||||
* manner. It is used inside of the UNSYNCHRONIZED macro.
|
||||
*/
|
||||
struct Unsynchronizer {
|
||||
explicit Unsynchronizer(LockedPtr* p) : parent_(p) {
|
||||
using namespace detail;
|
||||
releaseReadWrite(parent_->parent_->mutex_);
|
||||
}
|
||||
Unsynchronizer(const Unsynchronizer&) = delete;
|
||||
Unsynchronizer& operator=(const Unsynchronizer&) = delete;
|
||||
~Unsynchronizer() {
|
||||
parent_->acquire();
|
||||
}
|
||||
LockedPtr* operator->() const {
|
||||
return parent_;
|
||||
}
|
||||
private:
|
||||
LockedPtr* parent_;
|
||||
};
|
||||
friend struct Unsynchronizer;
|
||||
Unsynchronizer typeHackDoNotUse();
|
||||
|
||||
template <class P1, class P2>
|
||||
friend void lockInOrder(P1& p1, P2& p2);
|
||||
|
||||
private:
|
||||
void acquire() {
|
||||
using namespace detail;
|
||||
if (parent_) acquireReadWrite(parent_->mutex_);
|
||||
}
|
||||
|
||||
// This is the entire state of LockedPtr.
|
||||
Synchronized* parent_;
|
||||
};
|
||||
|
||||
/**
|
||||
* ConstLockedPtr does exactly what LockedPtr does, but for const
|
||||
* Synchronized objects. Of interest is that ConstLockedPtr only
|
||||
* uses a read lock, which is faster but more restrictive - you only
|
||||
* get to call const methods of the datum.
|
||||
*
|
||||
* Much of the code between LockedPtr and
|
||||
* ConstLockedPtr is identical and could be factor out, but there
|
||||
* are enough nagging little differences to not justify the trouble.
|
||||
*/
|
||||
struct ConstLockedPtr {
|
||||
ConstLockedPtr() = delete;
|
||||
explicit ConstLockedPtr(const Synchronized* parent) : parent_(parent) {
|
||||
acquire();
|
||||
}
|
||||
ConstLockedPtr(const Synchronized* parent, detail::InternalDoNotUse)
|
||||
: parent_(parent) {
|
||||
}
|
||||
ConstLockedPtr(const ConstLockedPtr& rhs) : parent_(rhs.parent_) {
|
||||
acquire();
|
||||
}
|
||||
explicit ConstLockedPtr(const LockedPtr& rhs) : parent_(rhs.parent_) {
|
||||
acquire();
|
||||
}
|
||||
ConstLockedPtr(const Synchronized* parent, unsigned int milliseconds) {
|
||||
if (parent->mutex_.timed_lock(
|
||||
boost::posix_time::milliseconds(milliseconds))) {
|
||||
parent_ = parent;
|
||||
return;
|
||||
}
|
||||
// Could not acquire the resource, pointer is null
|
||||
parent_ = NULL;
|
||||
}
|
||||
|
||||
ConstLockedPtr& operator=(const ConstLockedPtr& rhs) {
|
||||
if (parent_ != rhs.parent_) {
|
||||
if (parent_) parent_->mutex_.unlock_shared();
|
||||
parent_ = rhs.parent_;
|
||||
acquire();
|
||||
}
|
||||
}
|
||||
~ConstLockedPtr() {
|
||||
using namespace detail;
|
||||
if (parent_) releaseRead(parent_->mutex_);
|
||||
}
|
||||
|
||||
const T* operator->() const {
|
||||
return parent_ ? &parent_->datum_ : NULL;
|
||||
}
|
||||
|
||||
struct Unsynchronizer {
|
||||
explicit Unsynchronizer(ConstLockedPtr* p) : parent_(p) {
|
||||
using namespace detail;
|
||||
releaseRead(parent_->parent_->mutex_);
|
||||
}
|
||||
Unsynchronizer(const Unsynchronizer&) = delete;
|
||||
Unsynchronizer& operator=(const Unsynchronizer&) = delete;
|
||||
~Unsynchronizer() {
|
||||
using namespace detail;
|
||||
acquireRead(parent_->parent_->mutex_);
|
||||
}
|
||||
ConstLockedPtr* operator->() const {
|
||||
return parent_;
|
||||
}
|
||||
private:
|
||||
ConstLockedPtr* parent_;
|
||||
};
|
||||
friend struct Unsynchronizer;
|
||||
Unsynchronizer typeHackDoNotUse();
|
||||
|
||||
template <class P1, class P2>
|
||||
friend void lockInOrder(P1& p1, P2& p2);
|
||||
|
||||
private:
|
||||
void acquire() {
|
||||
using namespace detail;
|
||||
if (parent_) acquireRead(parent_->mutex_);
|
||||
}
|
||||
|
||||
const Synchronized* parent_;
|
||||
};
|
||||
|
||||
/**
|
||||
* This accessor offers a LockedPtr. In turn. LockedPtr offers
|
||||
* operator-> returning a pointer to T. The operator-> keeps
|
||||
* expanding until it reaches a pointer, so syncobj->foo() will lock
|
||||
* the object and call foo() against it.
|
||||
*/
|
||||
LockedPtr operator->() {
|
||||
return LockedPtr(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same, for constant objects. You will be able to invoke only const
|
||||
* methods.
|
||||
*/
|
||||
ConstLockedPtr operator->() const {
|
||||
return ConstLockedPtr(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to acquire for a given number of milliseconds. If
|
||||
* acquisition is unsuccessful, the returned LockedPtr is NULL.
|
||||
*/
|
||||
LockedPtr timedAcquire(unsigned int milliseconds) {
|
||||
return LockedPtr(this, milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* As above, for a constant object.
|
||||
*/
|
||||
ConstLockedPtr timedAcquire(unsigned int milliseconds) const {
|
||||
return ConstLockedPtr(this, milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by SYNCHRONIZED_DUAL.
|
||||
*/
|
||||
LockedPtr internalDoNotUse() {
|
||||
return LockedPtr(this, detail::InternalDoNotUse());
|
||||
}
|
||||
|
||||
/**
|
||||
* ditto
|
||||
*/
|
||||
ConstLockedPtr internalDoNotUse() const {
|
||||
return ConstLockedPtr(this, detail::InternalDoNotUse());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes, although you have a mutable object, you only want to
|
||||
* call a const method against it. The most efficient way to achieve
|
||||
* that is by using a read lock. You get to do so by using
|
||||
* obj.asConst()->method() instead of obj->method().
|
||||
*/
|
||||
const Synchronized& asConst() const {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps with another Synchronized. Protected against
|
||||
* self-swap. Only data is swapped. Locks are acquired in increasing
|
||||
* address order.
|
||||
*/
|
||||
void swap(Synchronized& rhs) {
|
||||
if (this == &rhs) {
|
||||
return;
|
||||
}
|
||||
if (this > &rhs) {
|
||||
return rhs.swap(*this);
|
||||
}
|
||||
auto guard1 = operator->();
|
||||
auto guard2 = rhs.operator->();
|
||||
|
||||
using std::swap;
|
||||
swap(datum_, rhs.datum_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap with another datum. Recommended because it keeps the mutex
|
||||
* held only briefly.
|
||||
*/
|
||||
void swap(T& rhs) {
|
||||
LockedPtr guard = operator->();
|
||||
|
||||
using std::swap;
|
||||
swap(datum_, rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies datum to a given target.
|
||||
*/
|
||||
void copy(T* target) const {
|
||||
ConstLockedPtr guard = operator->();
|
||||
*target = datum_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fresh copy of the datum.
|
||||
*/
|
||||
T copy() const {
|
||||
ConstLockedPtr guard = operator->();
|
||||
return datum_;
|
||||
}
|
||||
|
||||
private:
|
||||
T datum_;
|
||||
mutable Mutex mutex_;
|
||||
};
|
||||
|
||||
// Non-member swap primitive
|
||||
template <class T, class M>
|
||||
void swap(Synchronized<T, M>& lhs, Synchronized<T, M>& rhs) {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* SYNCHRONIZED is the main facility that makes Synchronized<T>
|
||||
* helpful. It is a pseudo-statement that introduces a scope where the
|
||||
* object is locked. Inside that scope you get to access the unadorned
|
||||
* datum.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Synchronized<vector<int>> svector;
|
||||
* ...
|
||||
* SYNCHRONIZED (svector) { ... use svector as a vector<int> ... }
|
||||
* or
|
||||
* SYNCHRONIZED (v, svector) { ... use v as a vector<int> ... }
|
||||
*
|
||||
* Refer to folly/docs/Synchronized.md for a detailed explanation and more
|
||||
* examples.
|
||||
*/
|
||||
#define SYNCHRONIZED(...) \
|
||||
if (bool SYNCHRONIZED_state = false) {} else \
|
||||
for (auto SYNCHRONIZED_lockedPtr = \
|
||||
(FB_ARG_2_OR_1(__VA_ARGS__)).operator->(); \
|
||||
!SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
|
||||
for (auto& FB_ARG_1(__VA_ARGS__) = \
|
||||
*SYNCHRONIZED_lockedPtr.operator->(); \
|
||||
!SYNCHRONIZED_state; SYNCHRONIZED_state = true)
|
||||
|
||||
#define TIMED_SYNCHRONIZED(timeout, ...) \
|
||||
if (bool SYNCHRONIZED_state = false) {} else \
|
||||
for (auto SYNCHRONIZED_lockedPtr = \
|
||||
(FB_ARG_2_OR_1(__VA_ARGS__)).timedAcquire(timeout); \
|
||||
!SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
|
||||
for (auto FB_ARG_1(__VA_ARGS__) = \
|
||||
SYNCHRONIZED_lockedPtr.operator->(); \
|
||||
!SYNCHRONIZED_state; SYNCHRONIZED_state = true)
|
||||
|
||||
/**
|
||||
* Similar to SYNCHRONIZED, but only uses a read lock.
|
||||
*/
|
||||
#define SYNCHRONIZED_CONST(...) \
|
||||
SYNCHRONIZED(FB_ARG_1(__VA_ARGS__), \
|
||||
(FB_ARG_2_OR_1(__VA_ARGS__)).asConst())
|
||||
|
||||
/**
|
||||
* Similar to TIMED_SYNCHRONIZED, but only uses a read lock.
|
||||
*/
|
||||
#define TIMED_SYNCHRONIZED_CONST(timeout, ...) \
|
||||
TIMED_SYNCHRONIZED(timeout, FB_ARG_1(__VA_ARGS__), \
|
||||
(FB_ARG_2_OR_1(__VA_ARGS__)).asConst())
|
||||
|
||||
/**
|
||||
* Temporarily disables synchronization inside a SYNCHRONIZED block.
|
||||
*/
|
||||
#define UNSYNCHRONIZED(name) \
|
||||
for (decltype(SYNCHRONIZED_lockedPtr.typeHackDoNotUse()) \
|
||||
SYNCHRONIZED_state3(&SYNCHRONIZED_lockedPtr); \
|
||||
!SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
|
||||
for (auto name = *SYNCHRONIZED_state3.operator->(); \
|
||||
!SYNCHRONIZED_state; SYNCHRONIZED_state = true)
|
||||
|
||||
/**
|
||||
* Locks two objects in increasing order of their addresses.
|
||||
*/
|
||||
template <class P1, class P2>
|
||||
void lockInOrder(P1& p1, P2& p2) {
|
||||
if (static_cast<const void*>(p1.operator->()) >
|
||||
static_cast<const void*>(p2.operator->())) {
|
||||
p2.acquire();
|
||||
p1.acquire();
|
||||
} else {
|
||||
p1.acquire();
|
||||
p2.acquire();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes two Synchronized objects (they may encapsulate
|
||||
* different data). Synchronization is done in increasing address of
|
||||
* object order, so there is no deadlock risk.
|
||||
*/
|
||||
#define SYNCHRONIZED_DUAL(n1, e1, n2, e2) \
|
||||
if (bool SYNCHRONIZED_state = false) {} else \
|
||||
for (auto SYNCHRONIZED_lp1 = (e1).internalDoNotUse(); \
|
||||
!SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
|
||||
for (auto& n1 = *SYNCHRONIZED_lp1.operator->(); \
|
||||
!SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
|
||||
for (auto SYNCHRONIZED_lp2 = (e2).internalDoNotUse(); \
|
||||
!SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
|
||||
for (auto& n2 = *SYNCHRONIZED_lp2.operator->(); \
|
||||
!SYNCHRONIZED_state; SYNCHRONIZED_state = true) \
|
||||
if ((::folly::lockInOrder( \
|
||||
SYNCHRONIZED_lp1, SYNCHRONIZED_lp2), \
|
||||
false)) {} \
|
||||
else
|
||||
|
||||
} /* namespace folly */
|
||||
|
||||
#endif // SYNCHRONIZED_H_
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/ThreadCachedArena.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
ThreadCachedArena::ThreadCachedArena(size_t minBlockSize)
|
||||
: minBlockSize_(minBlockSize) {
|
||||
}
|
||||
|
||||
SysArena* ThreadCachedArena::allocateThreadLocalArena() {
|
||||
SysArena* arena = new SysArena(minBlockSize_);
|
||||
auto disposer = [this] (SysArena* t, TLPDestructionMode mode) {
|
||||
std::unique_ptr<SysArena> tp(t); // ensure it gets deleted
|
||||
if (mode == TLPDestructionMode::THIS_THREAD) {
|
||||
zombify(std::move(*t));
|
||||
}
|
||||
};
|
||||
arena_.reset(arena, disposer);
|
||||
return arena;
|
||||
}
|
||||
|
||||
void ThreadCachedArena::zombify(SysArena&& arena) {
|
||||
std::lock_guard<std::mutex> lock(zombiesMutex_);
|
||||
zombies_.merge(std::move(arena));
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_THREADCACHEDARENA_H_
|
||||
#define FOLLY_THREADCACHEDARENA_H_
|
||||
|
||||
#include <utility>
|
||||
#include <mutex>
|
||||
#include <limits>
|
||||
#include <boost/intrusive/slist.hpp>
|
||||
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/Arena.h"
|
||||
#include "folly/ThreadLocal.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Thread-caching arena: allocate memory which gets freed when the arena gets
|
||||
* destroyed.
|
||||
*
|
||||
* The arena itself allocates memory using malloc() in blocks of
|
||||
* at least minBlockSize bytes.
|
||||
*
|
||||
* For speed, each thread gets its own Arena (see Arena.h); when threads
|
||||
* exit, the Arena gets merged into a "zombie" Arena, which will be deallocated
|
||||
* when the ThreadCachedArena object is destroyed.
|
||||
*/
|
||||
class ThreadCachedArena {
|
||||
public:
|
||||
explicit ThreadCachedArena(
|
||||
size_t minBlockSize = SysArena::kDefaultMinBlockSize);
|
||||
|
||||
void* allocate(size_t size) {
|
||||
SysArena* arena = arena_.get();
|
||||
if (UNLIKELY(!arena)) {
|
||||
arena = allocateThreadLocalArena();
|
||||
}
|
||||
|
||||
return arena->allocate(size);
|
||||
}
|
||||
|
||||
void deallocate(void* p) {
|
||||
// Deallocate? Never!
|
||||
}
|
||||
|
||||
private:
|
||||
ThreadCachedArena(const ThreadCachedArena&) = delete;
|
||||
ThreadCachedArena(ThreadCachedArena&&) = delete;
|
||||
ThreadCachedArena& operator=(const ThreadCachedArena&) = delete;
|
||||
ThreadCachedArena& operator=(ThreadCachedArena&&) = delete;
|
||||
|
||||
SysArena* allocateThreadLocalArena();
|
||||
|
||||
// Zombify the blocks in arena, saving them for deallocation until
|
||||
// the ThreadCachedArena is destroyed.
|
||||
void zombify(SysArena&& arena);
|
||||
|
||||
size_t minBlockSize_;
|
||||
SysArena zombies_; // allocated from threads that are now dead
|
||||
std::mutex zombiesMutex_;
|
||||
ThreadLocalPtr<SysArena> arena_; // per-thread arena
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_THREADCACHEDARENA_H_ */
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Higher performance (up to 10x) atomic increment using thread caching.
|
||||
*
|
||||
* @author Spencer Ahrens (sahrens)
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_THREADCACHEDINT_H
|
||||
#define FOLLY_THREADCACHEDINT_H
|
||||
|
||||
#include <atomic>
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/ThreadLocal.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
|
||||
// Note that readFull requires holding a lock and iterating through all of the
|
||||
// thread local objects with the same Tag, so if you have a lot of
|
||||
// ThreadCachedInt's you should considering breaking up the Tag space even
|
||||
// further.
|
||||
template <class IntT, class Tag=IntT>
|
||||
class ThreadCachedInt : boost::noncopyable {
|
||||
struct IntCache;
|
||||
|
||||
public:
|
||||
explicit ThreadCachedInt(IntT initialVal = 0, uint32_t cacheSize = 1000)
|
||||
: target_(initialVal), cacheSize_(cacheSize) {
|
||||
}
|
||||
|
||||
void increment(IntT inc) {
|
||||
auto cache = cache_.get();
|
||||
if (UNLIKELY(cache == NULL || cache->parent_ == NULL)) {
|
||||
cache = new IntCache(*this);
|
||||
cache_.reset(cache);
|
||||
}
|
||||
cache->increment(inc);
|
||||
}
|
||||
|
||||
// Quickly grabs the current value which may not include some cached
|
||||
// increments.
|
||||
IntT readFast() const {
|
||||
return target_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Reads the current value plus all the cached increments. Requires grabbing
|
||||
// a lock, so this is significantly slower than readFast().
|
||||
IntT readFull() const {
|
||||
IntT ret = readFast();
|
||||
for (const auto& cache : cache_.accessAllThreads()) {
|
||||
if (!cache.reset_.load(std::memory_order_acquire)) {
|
||||
ret += cache.val_.load(std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Quickly reads and resets current value (doesn't reset cached increments).
|
||||
IntT readFastAndReset() {
|
||||
return target_.exchange(0, std::memory_order_release);
|
||||
}
|
||||
|
||||
// This function is designed for accumulating into another counter, where you
|
||||
// only want to count each increment once. It can still get the count a
|
||||
// little off, however, but it should be much better than calling readFull()
|
||||
// and set(0) sequentially.
|
||||
IntT readFullAndReset() {
|
||||
IntT ret = readFastAndReset();
|
||||
for (auto& cache : cache_.accessAllThreads()) {
|
||||
if (!cache.reset_.load(std::memory_order_acquire)) {
|
||||
ret += cache.val_.load(std::memory_order_relaxed);
|
||||
cache.reset_.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void setCacheSize(uint32_t newSize) {
|
||||
cacheSize_.store(newSize, std::memory_order_release);
|
||||
}
|
||||
|
||||
uint32_t getCacheSize() const {
|
||||
return cacheSize_.load();
|
||||
}
|
||||
|
||||
ThreadCachedInt& operator+=(IntT inc) { increment(inc); return *this; }
|
||||
ThreadCachedInt& operator-=(IntT inc) { increment(-inc); return *this; }
|
||||
// pre-increment (we don't support post-increment)
|
||||
ThreadCachedInt& operator++() { increment(1); return *this; }
|
||||
ThreadCachedInt& operator--() { increment(-1); return *this; }
|
||||
|
||||
// Thread-safe set function.
|
||||
// This is a best effort implementation. In some edge cases, there could be
|
||||
// data loss (missing counts)
|
||||
void set(IntT newVal) {
|
||||
for (auto& cache : cache_.accessAllThreads()) {
|
||||
cache.reset_.store(true, std::memory_order_release);
|
||||
}
|
||||
target_.store(newVal, std::memory_order_release);
|
||||
}
|
||||
|
||||
// This is a little tricky - it's possible that our IntCaches are still alive
|
||||
// in another thread and will get destroyed after this destructor runs, so we
|
||||
// need to make sure we signal that this parent is dead.
|
||||
~ThreadCachedInt() {
|
||||
for (auto& cache : cache_.accessAllThreads()) {
|
||||
cache.parent_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<IntT> target_;
|
||||
std::atomic<uint32_t> cacheSize_;
|
||||
ThreadLocalPtr<IntCache,Tag> cache_; // Must be last for dtor ordering
|
||||
|
||||
// This should only ever be modified by one thread
|
||||
struct IntCache {
|
||||
ThreadCachedInt* parent_;
|
||||
mutable std::atomic<IntT> val_;
|
||||
mutable uint32_t numUpdates_;
|
||||
std::atomic<bool> reset_;
|
||||
|
||||
explicit IntCache(ThreadCachedInt& parent)
|
||||
: parent_(&parent), val_(0), numUpdates_(0), reset_(false) {}
|
||||
|
||||
void increment(IntT inc) {
|
||||
if (LIKELY(!reset_.load(std::memory_order_acquire))) {
|
||||
// This thread is the only writer to val_, so it's fine do do
|
||||
// a relaxed load and do the addition non-atomically.
|
||||
val_.store(
|
||||
val_.load(std::memory_order_relaxed) + inc,
|
||||
std::memory_order_release
|
||||
);
|
||||
} else {
|
||||
val_.store(inc, std::memory_order_relaxed);
|
||||
reset_.store(false, std::memory_order_release);
|
||||
}
|
||||
++numUpdates_;
|
||||
if (UNLIKELY(numUpdates_ >
|
||||
parent_->cacheSize_.load(std::memory_order_acquire))) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
void flush() const {
|
||||
parent_->target_.fetch_add(val_, std::memory_order_release);
|
||||
val_.store(0, std::memory_order_release);
|
||||
numUpdates_ = 0;
|
||||
}
|
||||
|
||||
~IntCache() {
|
||||
if (parent_) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
-339
@@ -1,339 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Improved thread local storage for non-trivial types (similar speed as
|
||||
* pthread_getspecific but only consumes a single pthread_key_t, and 4x faster
|
||||
* than boost::thread_specific_ptr).
|
||||
*
|
||||
* Also includes an accessor interface to walk all the thread local child
|
||||
* objects of a parent. accessAllThreads() initializes an accessor which holds
|
||||
* a global lock *that blocks all creation and destruction of ThreadLocal
|
||||
* objects with the same Tag* and can be used as an iterable container.
|
||||
*
|
||||
* Intended use is for frequent write, infrequent read data access patterns such
|
||||
* as counters.
|
||||
*
|
||||
* There are two classes here - ThreadLocal and ThreadLocalPtr. ThreadLocalPtr
|
||||
* has semantics similar to boost::thread_specific_ptr. ThreadLocal is a thin
|
||||
* wrapper around ThreadLocalPtr that manages allocation automatically.
|
||||
*
|
||||
* @author Spencer Ahrens (sahrens)
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_THREADLOCAL_H_
|
||||
#define FOLLY_THREADLOCAL_H_
|
||||
|
||||
#include "folly/Portability.h"
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
#include "folly/Likely.h"
|
||||
#include <type_traits>
|
||||
|
||||
|
||||
namespace folly {
|
||||
enum class TLPDestructionMode {
|
||||
THIS_THREAD,
|
||||
ALL_THREADS
|
||||
};
|
||||
} // namespace
|
||||
|
||||
#include "folly/detail/ThreadLocalDetail.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
template<class T, class Tag> class ThreadLocalPtr;
|
||||
|
||||
template<class T, class Tag=void>
|
||||
class ThreadLocal {
|
||||
public:
|
||||
ThreadLocal() { }
|
||||
|
||||
T* get() const {
|
||||
T* ptr = tlp_.get();
|
||||
if (LIKELY(ptr != nullptr)) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// separated new item creation out to speed up the fast path.
|
||||
return makeTlp();
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
T& operator*() const {
|
||||
return *get();
|
||||
}
|
||||
|
||||
void reset(T* newPtr = nullptr) {
|
||||
tlp_.reset(newPtr);
|
||||
}
|
||||
|
||||
typedef typename ThreadLocalPtr<T,Tag>::Accessor Accessor;
|
||||
Accessor accessAllThreads() const {
|
||||
return tlp_.accessAllThreads();
|
||||
}
|
||||
|
||||
// movable
|
||||
ThreadLocal(ThreadLocal&&) = default;
|
||||
ThreadLocal& operator=(ThreadLocal&&) = default;
|
||||
|
||||
private:
|
||||
// non-copyable
|
||||
ThreadLocal(const ThreadLocal&) = delete;
|
||||
ThreadLocal& operator=(const ThreadLocal&) = delete;
|
||||
|
||||
T* makeTlp() const {
|
||||
T* ptr = new T();
|
||||
tlp_.reset(ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
mutable ThreadLocalPtr<T,Tag> tlp_;
|
||||
};
|
||||
|
||||
/*
|
||||
* The idea here is that __thread is faster than pthread_getspecific, so we
|
||||
* keep a __thread array of pointers to objects (ThreadEntry::elements) where
|
||||
* each array has an index for each unique instance of the ThreadLocalPtr
|
||||
* object. Each ThreadLocalPtr object has a unique id that is an index into
|
||||
* these arrays so we can fetch the correct object from thread local storage
|
||||
* very efficiently.
|
||||
*
|
||||
* In order to prevent unbounded growth of the id space and thus huge
|
||||
* ThreadEntry::elements, arrays, for example due to continuous creation and
|
||||
* destruction of ThreadLocalPtr objects, we keep a set of all active
|
||||
* instances. When an instance is destroyed we remove it from the active
|
||||
* set and insert the id into freeIds_ for reuse. These operations require a
|
||||
* global mutex, but only happen at construction and destruction time.
|
||||
*
|
||||
* We use a single global pthread_key_t per Tag to manage object destruction and
|
||||
* memory cleanup upon thread exit because there is a finite number of
|
||||
* pthread_key_t's available per machine.
|
||||
*/
|
||||
|
||||
template<class T, class Tag=void>
|
||||
class ThreadLocalPtr {
|
||||
public:
|
||||
ThreadLocalPtr() : id_(threadlocal_detail::StaticMeta<Tag>::create()) { }
|
||||
|
||||
ThreadLocalPtr(ThreadLocalPtr&& other) : id_(other.id_) {
|
||||
other.id_ = 0;
|
||||
}
|
||||
|
||||
ThreadLocalPtr& operator=(ThreadLocalPtr&& other) {
|
||||
assert(this != &other);
|
||||
destroy();
|
||||
id_ = other.id_;
|
||||
other.id_ = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~ThreadLocalPtr() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
T* get() const {
|
||||
return static_cast<T*>(threadlocal_detail::StaticMeta<Tag>::get(id_).ptr);
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
T& operator*() const {
|
||||
return *get();
|
||||
}
|
||||
|
||||
void reset(T* newPtr = nullptr) {
|
||||
threadlocal_detail::ElementWrapper& w =
|
||||
threadlocal_detail::StaticMeta<Tag>::get(id_);
|
||||
if (w.ptr != newPtr) {
|
||||
w.dispose(TLPDestructionMode::THIS_THREAD);
|
||||
w.set(newPtr);
|
||||
}
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return get() != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* reset() with a custom deleter:
|
||||
* deleter(T* ptr, TLPDestructionMode mode)
|
||||
* "mode" is ALL_THREADS if we're destructing this ThreadLocalPtr (and thus
|
||||
* deleting pointers for all threads), and THIS_THREAD if we're only deleting
|
||||
* the member for one thread (because of thread exit or reset())
|
||||
*/
|
||||
template <class Deleter>
|
||||
void reset(T* newPtr, Deleter deleter) {
|
||||
threadlocal_detail::ElementWrapper& w =
|
||||
threadlocal_detail::StaticMeta<Tag>::get(id_);
|
||||
if (w.ptr != newPtr) {
|
||||
w.dispose(TLPDestructionMode::THIS_THREAD);
|
||||
w.set(newPtr, deleter);
|
||||
}
|
||||
}
|
||||
|
||||
// Holds a global lock for iteration through all thread local child objects.
|
||||
// Can be used as an iterable container.
|
||||
// Use accessAllThreads() to obtain one.
|
||||
class Accessor {
|
||||
friend class ThreadLocalPtr<T,Tag>;
|
||||
|
||||
threadlocal_detail::StaticMeta<Tag>& meta_;
|
||||
boost::mutex* lock_;
|
||||
int id_;
|
||||
|
||||
public:
|
||||
class Iterator;
|
||||
friend class Iterator;
|
||||
|
||||
// The iterators obtained from Accessor are bidirectional iterators.
|
||||
class Iterator : public boost::iterator_facade<
|
||||
Iterator, // Derived
|
||||
T, // value_type
|
||||
boost::bidirectional_traversal_tag> { // traversal
|
||||
friend class Accessor;
|
||||
friend class boost::iterator_core_access;
|
||||
const Accessor* const accessor_;
|
||||
threadlocal_detail::ThreadEntry* e_;
|
||||
|
||||
void increment() {
|
||||
e_ = e_->next;
|
||||
incrementToValid();
|
||||
}
|
||||
|
||||
void decrement() {
|
||||
e_ = e_->prev;
|
||||
decrementToValid();
|
||||
}
|
||||
|
||||
T& dereference() const {
|
||||
return *static_cast<T*>(e_->elements[accessor_->id_].ptr);
|
||||
}
|
||||
|
||||
bool equal(const Iterator& other) const {
|
||||
return (accessor_->id_ == other.accessor_->id_ &&
|
||||
e_ == other.e_);
|
||||
}
|
||||
|
||||
explicit Iterator(const Accessor* accessor)
|
||||
: accessor_(accessor),
|
||||
e_(&accessor_->meta_.head_) {
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return (e_->elements &&
|
||||
accessor_->id_ < e_->elementsCapacity &&
|
||||
e_->elements[accessor_->id_].ptr);
|
||||
}
|
||||
|
||||
void incrementToValid() {
|
||||
for (; e_ != &accessor_->meta_.head_ && !valid(); e_ = e_->next) { }
|
||||
}
|
||||
|
||||
void decrementToValid() {
|
||||
for (; e_ != &accessor_->meta_.head_ && !valid(); e_ = e_->prev) { }
|
||||
}
|
||||
};
|
||||
|
||||
~Accessor() {
|
||||
release();
|
||||
}
|
||||
|
||||
Iterator begin() const {
|
||||
return ++Iterator(this);
|
||||
}
|
||||
|
||||
Iterator end() const {
|
||||
return Iterator(this);
|
||||
}
|
||||
|
||||
Accessor(const Accessor&) = delete;
|
||||
Accessor& operator=(const Accessor&) = delete;
|
||||
|
||||
Accessor(Accessor&& other) noexcept
|
||||
: meta_(other.meta_),
|
||||
lock_(other.lock_),
|
||||
id_(other.id_) {
|
||||
other.id_ = 0;
|
||||
other.lock_ = nullptr;
|
||||
}
|
||||
|
||||
Accessor& operator=(Accessor&& other) noexcept {
|
||||
// Each Tag has its own unique meta, and accessors with different Tags
|
||||
// have different types. So either *this is empty, or this and other
|
||||
// have the same tag. But if they have the same tag, they have the same
|
||||
// meta (and lock), so they'd both hold the lock at the same time,
|
||||
// which is impossible, which leaves only one possible scenario --
|
||||
// *this is empty. Assert it.
|
||||
assert(&meta_ == &other.meta_);
|
||||
assert(lock_ == nullptr);
|
||||
using std::swap;
|
||||
swap(lock_, other.lock_);
|
||||
swap(id_, other.id_);
|
||||
}
|
||||
|
||||
Accessor()
|
||||
: meta_(threadlocal_detail::StaticMeta<Tag>::instance()),
|
||||
lock_(nullptr),
|
||||
id_(0) {
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Accessor(int id)
|
||||
: meta_(threadlocal_detail::StaticMeta<Tag>::instance()),
|
||||
lock_(&meta_.lock_) {
|
||||
lock_->lock();
|
||||
id_ = id;
|
||||
}
|
||||
|
||||
void release() {
|
||||
if (lock_) {
|
||||
lock_->unlock();
|
||||
id_ = 0;
|
||||
lock_ = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// accessor allows a client to iterate through all thread local child
|
||||
// elements of this ThreadLocal instance. Holds a global lock for each <Tag>
|
||||
Accessor accessAllThreads() const {
|
||||
static_assert(!std::is_same<Tag, void>::value,
|
||||
"Must use a unique Tag to use the accessAllThreads feature");
|
||||
return Accessor(id_);
|
||||
}
|
||||
|
||||
private:
|
||||
void destroy() {
|
||||
if (id_) {
|
||||
threadlocal_detail::StaticMeta<Tag>::destroy(id_);
|
||||
}
|
||||
}
|
||||
|
||||
// non-copyable
|
||||
ThreadLocalPtr(const ThreadLocalPtr&) = delete;
|
||||
ThreadLocalPtr& operator=(const ThreadLocalPtr&) = delete;
|
||||
|
||||
int id_; // every instantiation has a unique id
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_THREADLOCAL_H_ */
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/TimeoutQueue.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace folly {
|
||||
|
||||
TimeoutQueue::Id TimeoutQueue::add(
|
||||
int64_t now,
|
||||
int64_t delay,
|
||||
Callback callback) {
|
||||
Id id = nextId_++;
|
||||
timeouts_.insert({id, now + delay, -1, std::move(callback)});
|
||||
return id;
|
||||
}
|
||||
|
||||
TimeoutQueue::Id TimeoutQueue::addRepeating(
|
||||
int64_t now,
|
||||
int64_t interval,
|
||||
Callback callback) {
|
||||
Id id = nextId_++;
|
||||
timeouts_.insert({id, now + interval, interval, std::move(callback)});
|
||||
return id;
|
||||
}
|
||||
|
||||
int64_t TimeoutQueue::nextExpiration() const {
|
||||
return (timeouts_.empty() ? std::numeric_limits<int64_t>::max() :
|
||||
timeouts_.get<BY_EXPIRATION>().begin()->expiration);
|
||||
}
|
||||
|
||||
bool TimeoutQueue::erase(Id id) {
|
||||
return timeouts_.get<BY_ID>().erase(id);
|
||||
}
|
||||
|
||||
int64_t TimeoutQueue::runInternal(int64_t now, bool onceOnly) {
|
||||
auto& byExpiration = timeouts_.get<BY_EXPIRATION>();
|
||||
int64_t nextExp;
|
||||
do {
|
||||
auto end = byExpiration.upper_bound(now);
|
||||
std::vector<Event> expired;
|
||||
std::move(byExpiration.begin(), end, std::back_inserter(expired));
|
||||
byExpiration.erase(byExpiration.begin(), end);
|
||||
for (auto& event : expired) {
|
||||
// Reinsert if repeating, do this before executing callbacks
|
||||
// so the callbacks have a chance to call erase
|
||||
if (event.repeatInterval >= 0) {
|
||||
timeouts_.insert({event.id, now + event.repeatInterval,
|
||||
event.repeatInterval, event.callback});
|
||||
}
|
||||
}
|
||||
|
||||
// Call callbacks
|
||||
for (auto& event : expired) {
|
||||
event.callback(event.id, now);
|
||||
}
|
||||
nextExp = nextExpiration();
|
||||
} while (!onceOnly && nextExp <= now);
|
||||
return nextExp;
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simple timeout queue. Call user-specified callbacks when their timeouts
|
||||
* expire.
|
||||
*
|
||||
* This class assumes that "time" is an int64_t and doesn't care about time
|
||||
* units (seconds, milliseconds, etc). You call runOnce() / runLoop() using
|
||||
* the same time units that you use to specify callbacks.
|
||||
*
|
||||
* @author Tudor Bosman (tudorb@fb.com)
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_TIMEOUTQUEUE_H_
|
||||
#define FOLLY_TIMEOUTQUEUE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <functional>
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/indexed_by.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
|
||||
namespace folly {
|
||||
|
||||
class TimeoutQueue {
|
||||
public:
|
||||
typedef int64_t Id;
|
||||
typedef std::function<void(Id, int64_t)> Callback;
|
||||
|
||||
TimeoutQueue() : nextId_(1) { }
|
||||
|
||||
/**
|
||||
* Add a one-time timeout event that will fire "delay" time units from "now"
|
||||
* (that is, the first time that run*() is called with a time value >= now
|
||||
* + delay).
|
||||
*/
|
||||
Id add(int64_t now, int64_t delay, Callback callback);
|
||||
|
||||
/**
|
||||
* Add a repeating timeout event that will fire every "interval" time units
|
||||
* (it will first fire when run*() is called with a time value >=
|
||||
* now + interval).
|
||||
*
|
||||
* run*() will always invoke each repeating event at most once, even if
|
||||
* more than one "interval" period has passed.
|
||||
*/
|
||||
Id addRepeating(int64_t now, int64_t interval, Callback callback);
|
||||
|
||||
/**
|
||||
* Erase a given timeout event, returns true if the event was actually
|
||||
* erased and false if it didn't exist in our queue.
|
||||
*/
|
||||
bool erase(Id id);
|
||||
|
||||
/**
|
||||
* Process all events that are due at times <= "now" by calling their
|
||||
* callbacks.
|
||||
*
|
||||
* Callbacks are allowed to call back into the queue and add / erase events;
|
||||
* they might create more events that are already due. In this case,
|
||||
* runOnce() will only go through the queue once, and return a "next
|
||||
* expiration" time in the past or present (<= now); runLoop()
|
||||
* will process the queue again, until there are no events already due.
|
||||
*
|
||||
* Note that it is then possible for runLoop to never return if
|
||||
* callbacks re-add themselves to the queue (or if you have repeating
|
||||
* callbacks with an interval of 0).
|
||||
*
|
||||
* Return the time that the next event will be due (same as
|
||||
* nextExpiration(), below)
|
||||
*/
|
||||
int64_t runOnce(int64_t now) { return runInternal(now, true); }
|
||||
int64_t runLoop(int64_t now) { return runInternal(now, false); }
|
||||
|
||||
/**
|
||||
* Return the time that the next event will be due.
|
||||
*/
|
||||
int64_t nextExpiration() const;
|
||||
|
||||
private:
|
||||
int64_t runInternal(int64_t now, bool runOnce);
|
||||
// noncopyable
|
||||
TimeoutQueue(const TimeoutQueue&) = delete;
|
||||
TimeoutQueue& operator=(const TimeoutQueue&) = delete;
|
||||
|
||||
struct Event {
|
||||
Id id;
|
||||
int64_t expiration;
|
||||
int64_t repeatInterval;
|
||||
Callback callback;
|
||||
};
|
||||
|
||||
typedef boost::multi_index_container<
|
||||
Event,
|
||||
boost::multi_index::indexed_by<
|
||||
boost::multi_index::ordered_unique<boost::multi_index::member<
|
||||
Event, Id, &Event::id
|
||||
>>,
|
||||
boost::multi_index::ordered_non_unique<boost::multi_index::member<
|
||||
Event, int64_t, &Event::expiration
|
||||
>>
|
||||
>
|
||||
> Set;
|
||||
|
||||
enum {
|
||||
BY_ID=0,
|
||||
BY_EXPIRATION=1
|
||||
};
|
||||
|
||||
Set timeouts_;
|
||||
Id nextId_;
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_TIMEOUTQUEUE_H_ */
|
||||
|
||||
-502
@@ -1,502 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @author: Andrei Alexandrescu
|
||||
|
||||
#ifndef FOLLY_BASE_TRAITS_H_
|
||||
#define FOLLY_BASE_TRAITS_H_
|
||||
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <bits/c++config.h>
|
||||
|
||||
#include <boost/type_traits.hpp>
|
||||
#include <boost/mpl/and.hpp>
|
||||
#include <boost/mpl/has_xxx.hpp>
|
||||
#include <boost/mpl/not.hpp>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* IsRelocatable<T>::value describes the ability of moving around
|
||||
* memory a value of type T by using memcpy (as opposed to the
|
||||
* conservative approach of calling the copy constructor and then
|
||||
* destroying the old temporary. Essentially for a relocatable type,
|
||||
* the following two sequences of code should be semantically
|
||||
* equivalent:
|
||||
*
|
||||
* void move1(T * from, T * to) {
|
||||
* new(to) T(from);
|
||||
* (*from).~T();
|
||||
* }
|
||||
*
|
||||
* void move2(T * from, T * to) {
|
||||
* memcpy(to, from, sizeof(T));
|
||||
* }
|
||||
*
|
||||
* Most C++ types are relocatable; the ones that aren't would include
|
||||
* internal pointers or (very rarely) would need to update remote
|
||||
* pointers to pointers tracking them. All C++ primitive types and
|
||||
* type constructors are relocatable.
|
||||
*
|
||||
* This property can be used in a variety of optimizations. Currently
|
||||
* fbvector uses this property intensively.
|
||||
*
|
||||
* The default conservatively assumes the type is not
|
||||
* relocatable. Several specializations are defined for known
|
||||
* types. You may want to add your own specializations. Do so in
|
||||
* namespace folly and make sure you keep the specialization of
|
||||
* IsRelocatable<SomeStruct> in the same header as SomeStruct.
|
||||
*
|
||||
* You may also declare a type to be relocatable by including
|
||||
* `typedef std::true_type IsRelocatable;`
|
||||
* in the class header.
|
||||
*
|
||||
* It may be unset in a base class by overriding the typedef to false_type.
|
||||
*/
|
||||
/*
|
||||
* IsTriviallyCopyable describes the value semantics property. C++11 contains
|
||||
* the type trait is_trivially_copyable; however, it is not yet implemented
|
||||
* in gcc (as of 4.7.1), and the user may wish to specify otherwise.
|
||||
*/
|
||||
/*
|
||||
* IsZeroInitializable describes the property that default construction is the
|
||||
* same as memset(dst, 0, sizeof(T)).
|
||||
*/
|
||||
|
||||
namespace traits_detail {
|
||||
|
||||
#define FOLLY_HAS_TRUE_XXX(name) \
|
||||
BOOST_MPL_HAS_XXX_TRAIT_DEF(name); \
|
||||
template <class T> struct name ## _is_true \
|
||||
: std::is_same<typename T::name, std::true_type> {}; \
|
||||
template <class T> struct has_true_ ## name \
|
||||
: std::conditional< \
|
||||
has_ ## name <T>::value, \
|
||||
name ## _is_true<T>, \
|
||||
std::false_type \
|
||||
>:: type {};
|
||||
|
||||
FOLLY_HAS_TRUE_XXX(IsRelocatable)
|
||||
FOLLY_HAS_TRUE_XXX(IsZeroInitializable)
|
||||
FOLLY_HAS_TRUE_XXX(IsTriviallyCopyable)
|
||||
|
||||
#undef FOLLY_HAS_TRUE_XXX
|
||||
}
|
||||
|
||||
template <class T> struct IsTriviallyCopyable
|
||||
: std::integral_constant<bool,
|
||||
!std::is_class<T>::value ||
|
||||
// TODO: add alternate clause is_trivially_copyable, when available
|
||||
traits_detail::has_true_IsTriviallyCopyable<T>::value
|
||||
> {};
|
||||
|
||||
template <class T> struct IsRelocatable
|
||||
: std::integral_constant<bool,
|
||||
!std::is_class<T>::value ||
|
||||
// TODO add this line (and some tests for it) when we upgrade to gcc 4.7
|
||||
//std::is_trivially_move_constructible<T>::value ||
|
||||
IsTriviallyCopyable<T>::value ||
|
||||
traits_detail::has_true_IsRelocatable<T>::value
|
||||
> {};
|
||||
|
||||
template <class T> struct IsZeroInitializable
|
||||
: std::integral_constant<bool,
|
||||
!std::is_class<T>::value ||
|
||||
traits_detail::has_true_IsZeroInitializable<T>::value
|
||||
> {};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
/**
|
||||
* Use this macro ONLY inside namespace folly. When using it with a
|
||||
* regular type, use it like this:
|
||||
*
|
||||
* // Make sure you're at namespace ::folly scope
|
||||
* template<> FOLLY_ASSUME_RELOCATABLE(MyType)
|
||||
*
|
||||
* When using it with a template type, use it like this:
|
||||
*
|
||||
* // Make sure you're at namespace ::folly scope
|
||||
* template<class T1, class T2>
|
||||
* FOLLY_ASSUME_RELOCATABLE(MyType<T1, T2>)
|
||||
*/
|
||||
#define FOLLY_ASSUME_RELOCATABLE(...) \
|
||||
struct IsRelocatable< __VA_ARGS__ > : std::true_type {};
|
||||
|
||||
/**
|
||||
* Use this macro ONLY inside namespace boost. When using it with a
|
||||
* regular type, use it like this:
|
||||
*
|
||||
* // Make sure you're at namespace ::boost scope
|
||||
* template<> FOLLY_ASSUME_HAS_NOTHROW_CONSTRUCTOR(MyType)
|
||||
*
|
||||
* When using it with a template type, use it like this:
|
||||
*
|
||||
* // Make sure you're at namespace ::boost scope
|
||||
* template<class T1, class T2>
|
||||
* FOLLY_ASSUME_HAS_NOTHROW_CONSTRUCTOR(MyType<T1, T2>)
|
||||
*/
|
||||
#define FOLLY_ASSUME_HAS_NOTHROW_CONSTRUCTOR(...) \
|
||||
struct has_nothrow_constructor< __VA_ARGS__ > : ::boost::true_type {};
|
||||
|
||||
/**
|
||||
* The FOLLY_ASSUME_FBVECTOR_COMPATIBLE* macros below encode two
|
||||
* assumptions: first, that the type is relocatable per IsRelocatable
|
||||
* above, and that it has a nothrow constructor. Most types can be
|
||||
* assumed to satisfy both conditions, but it is the responsibility of
|
||||
* the user to state that assumption. User-defined classes will not
|
||||
* work with fbvector (see FBVector.h) unless they state this
|
||||
* combination of properties.
|
||||
*
|
||||
* Use FOLLY_ASSUME_FBVECTOR_COMPATIBLE with regular types like this:
|
||||
*
|
||||
* FOLLY_ASSUME_FBVECTOR_COMPATIBLE(MyType)
|
||||
*
|
||||
* The versions FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1, _2, _3, and _4
|
||||
* allow using the macro for describing templatized classes with 1, 2,
|
||||
* 3, and 4 template parameters respectively. For template classes
|
||||
* just use the macro with the appropriate number and pass the name of
|
||||
* the template to it. Example:
|
||||
*
|
||||
* template <class T1, class T2> class MyType { ... };
|
||||
* ...
|
||||
* // Make sure you're at global scope
|
||||
* FOLLY_ASSUME_FBVECTOR_COMPATIBLE_2(MyType)
|
||||
*/
|
||||
|
||||
// Use this macro ONLY at global level (no namespace)
|
||||
#define FOLLY_ASSUME_FBVECTOR_COMPATIBLE(...) \
|
||||
namespace folly { template<> FOLLY_ASSUME_RELOCATABLE(__VA_ARGS__) } \
|
||||
namespace boost { \
|
||||
template<> FOLLY_ASSUME_HAS_NOTHROW_CONSTRUCTOR(__VA_ARGS__) }
|
||||
// Use this macro ONLY at global level (no namespace)
|
||||
#define FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(...) \
|
||||
namespace folly { \
|
||||
template <class T1> FOLLY_ASSUME_RELOCATABLE(__VA_ARGS__<T1>) } \
|
||||
namespace boost { \
|
||||
template <class T1> FOLLY_ASSUME_HAS_NOTHROW_CONSTRUCTOR(__VA_ARGS__<T1>) }
|
||||
// Use this macro ONLY at global level (no namespace)
|
||||
#define FOLLY_ASSUME_FBVECTOR_COMPATIBLE_2(...) \
|
||||
namespace folly { \
|
||||
template <class T1, class T2> \
|
||||
FOLLY_ASSUME_RELOCATABLE(__VA_ARGS__<T1, T2>) } \
|
||||
namespace boost { \
|
||||
template <class T1, class T2> \
|
||||
FOLLY_ASSUME_HAS_NOTHROW_CONSTRUCTOR(__VA_ARGS__<T1, T2>) }
|
||||
// Use this macro ONLY at global level (no namespace)
|
||||
#define FOLLY_ASSUME_FBVECTOR_COMPATIBLE_3(...) \
|
||||
namespace folly { \
|
||||
template <class T1, class T2, class T3> \
|
||||
FOLLY_ASSUME_RELOCATABLE(__VA_ARGS__<T1, T2, T3>) } \
|
||||
namespace boost { \
|
||||
template <class T1, class T2, class T3> \
|
||||
FOLLY_ASSUME_HAS_NOTHROW_CONSTRUCTOR(__VA_ARGS__<T1, T2, T3>) }
|
||||
// Use this macro ONLY at global level (no namespace)
|
||||
#define FOLLY_ASSUME_FBVECTOR_COMPATIBLE_4(...) \
|
||||
namespace folly { \
|
||||
template <class T1, class T2, class T3, class T4> \
|
||||
FOLLY_ASSUME_RELOCATABLE(__VA_ARGS__<T1, T2, T3, T4>) } \
|
||||
namespace boost { \
|
||||
template <class T1, class T2, class T3, class T4> \
|
||||
FOLLY_ASSUME_HAS_NOTHROW_CONSTRUCTOR(__VA_ARGS__<T1, T2, T3, T4>) }
|
||||
|
||||
/**
|
||||
* Instantiate FOLLY_ASSUME_FBVECTOR_COMPATIBLE for a few types. It is
|
||||
* safe to assume that pair is compatible if both of its components
|
||||
* are. Furthermore, all STL containers can be assumed to comply,
|
||||
* although that is not guaranteed by the standard.
|
||||
*/
|
||||
|
||||
namespace std {
|
||||
|
||||
template <class T, class U>
|
||||
struct pair;
|
||||
#ifndef _GLIBCXX_USE_FB
|
||||
template <class T, class R, class A>
|
||||
class basic_string;
|
||||
#else
|
||||
template <class T, class R, class A, class S>
|
||||
class basic_string;
|
||||
#endif
|
||||
template <class T, class A>
|
||||
class vector;
|
||||
template <class T, class A>
|
||||
class deque;
|
||||
template <class T, class A>
|
||||
class list;
|
||||
template <class T, class C, class A>
|
||||
class set;
|
||||
template <class K, class V, class C, class A>
|
||||
class map;
|
||||
template <class T>
|
||||
class shared_ptr;
|
||||
|
||||
}
|
||||
|
||||
namespace boost {
|
||||
|
||||
template <class T> class shared_ptr;
|
||||
|
||||
template <class T, class U>
|
||||
struct has_nothrow_constructor< std::pair<T, U> >
|
||||
: ::boost::mpl::and_< has_nothrow_constructor<T>,
|
||||
has_nothrow_constructor<U> > {};
|
||||
|
||||
} // namespace boost
|
||||
|
||||
namespace folly {
|
||||
|
||||
// STL commonly-used types
|
||||
template <class T, class U>
|
||||
struct IsRelocatable< std::pair<T, U> >
|
||||
: ::boost::mpl::and_< IsRelocatable<T>, IsRelocatable<U> > {};
|
||||
|
||||
// Is T one of T1, T2, ..., Tn?
|
||||
template <class T, class... Ts>
|
||||
struct IsOneOf {
|
||||
enum { value = false };
|
||||
};
|
||||
|
||||
template <class T, class T1, class... Ts>
|
||||
struct IsOneOf<T, T1, Ts...> {
|
||||
enum { value = std::is_same<T, T1>::value || IsOneOf<T, Ts...>::value };
|
||||
};
|
||||
|
||||
/**
|
||||
* A traits class to check for incomplete types.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* struct FullyDeclared {}; // complete type
|
||||
* struct ForwardDeclared; // incomplete type
|
||||
*
|
||||
* is_complete<int>::value // evaluates to true
|
||||
* is_complete<FullyDeclared>::value // evaluates to true
|
||||
* is_complete<ForwardDeclared>::value // evaluates to false
|
||||
*
|
||||
* struct ForwardDeclared {}; // declared, at last
|
||||
*
|
||||
* is_complete<ForwardDeclared>::value // now it evaluates to true
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
template <typename T>
|
||||
class is_complete {
|
||||
template <unsigned long long> struct sfinae {};
|
||||
template <typename U>
|
||||
constexpr static bool test(sfinae<sizeof(U)>*) { return true; }
|
||||
template <typename> constexpr static bool test(...) { return false; }
|
||||
public:
|
||||
constexpr static bool value = test<T>(nullptr);
|
||||
};
|
||||
|
||||
/*
|
||||
* Complementary type traits for integral comparisons.
|
||||
*
|
||||
* For instance, `if(x < 0)` yields an error in clang for unsigned types
|
||||
* when -Werror is used due to -Wtautological-compare
|
||||
*
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, bool>
|
||||
struct is_negative_impl {
|
||||
constexpr static bool check(T x) { return x < 0; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_negative_impl<T, false> {
|
||||
constexpr static bool check(T x) { return false; }
|
||||
};
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool less_than_impl(
|
||||
typename std::enable_if<
|
||||
(rhs <= std::numeric_limits<LHS>::max()
|
||||
&& rhs > std::numeric_limits<LHS>::min()),
|
||||
LHS
|
||||
>::type const lhs
|
||||
) {
|
||||
return lhs < rhs;
|
||||
}
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool less_than_impl(
|
||||
typename std::enable_if<
|
||||
(rhs > std::numeric_limits<LHS>::max()),
|
||||
LHS
|
||||
>::type const
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool less_than_impl(
|
||||
typename std::enable_if<
|
||||
(rhs <= std::numeric_limits<LHS>::min()),
|
||||
LHS
|
||||
>::type const
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool greater_than_impl(
|
||||
typename std::enable_if<
|
||||
(rhs <= std::numeric_limits<LHS>::max()
|
||||
&& rhs >= std::numeric_limits<LHS>::min()),
|
||||
LHS
|
||||
>::type const lhs
|
||||
) {
|
||||
return lhs > rhs;
|
||||
}
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool greater_than_impl(
|
||||
typename std::enable_if<
|
||||
(rhs > std::numeric_limits<LHS>::max()),
|
||||
LHS
|
||||
>::type const
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool greater_than_impl(
|
||||
typename std::enable_if<
|
||||
(rhs < std::numeric_limits<LHS>::min()),
|
||||
LHS
|
||||
>::type const
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace detail {
|
||||
|
||||
// same as `x < 0`
|
||||
template <typename T>
|
||||
constexpr bool is_negative(T x) {
|
||||
return folly::detail::is_negative_impl<T, std::is_signed<T>::value>::check(x);
|
||||
}
|
||||
|
||||
// same as `x <= 0`
|
||||
template <typename T>
|
||||
constexpr bool is_non_positive(T x) { return !x || folly::is_negative(x); }
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool less_than(LHS const lhs) {
|
||||
return detail::less_than_impl<
|
||||
RHS, rhs, typename std::remove_reference<LHS>::type
|
||||
>(lhs);
|
||||
}
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool greater_than(LHS const lhs) {
|
||||
return detail::greater_than_impl<
|
||||
RHS, rhs, typename std::remove_reference<LHS>::type
|
||||
>(lhs);
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
FOLLY_ASSUME_FBVECTOR_COMPATIBLE_3(std::basic_string);
|
||||
FOLLY_ASSUME_FBVECTOR_COMPATIBLE_2(std::vector);
|
||||
FOLLY_ASSUME_FBVECTOR_COMPATIBLE_2(std::list);
|
||||
FOLLY_ASSUME_FBVECTOR_COMPATIBLE_2(std::deque);
|
||||
FOLLY_ASSUME_FBVECTOR_COMPATIBLE_2(std::unique_ptr);
|
||||
FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(std::shared_ptr);
|
||||
FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(std::function);
|
||||
|
||||
// Boost
|
||||
FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(boost::shared_ptr);
|
||||
|
||||
#define FOLLY_CREATE_HAS_MEMBER_FN_TRAITS_IMPL(classname, func_name, cv_qual) \
|
||||
template <typename TTheClass_, typename RTheReturn_, typename... TTheArgs_> \
|
||||
class classname<TTheClass_, RTheReturn_(TTheArgs_...) cv_qual> { \
|
||||
template < \
|
||||
typename UTheClass_, RTheReturn_ (UTheClass_::*)(TTheArgs_...) cv_qual \
|
||||
> struct sfinae {}; \
|
||||
template <typename UTheClass_> \
|
||||
constexpr static bool test(sfinae<UTheClass_, &UTheClass_::func_name>*) \
|
||||
{ return true; } \
|
||||
template <typename> \
|
||||
constexpr static bool test(...) { return false; } \
|
||||
public: \
|
||||
constexpr static bool value = test<TTheClass_>(nullptr); \
|
||||
}
|
||||
|
||||
/*
|
||||
* The FOLLY_CREATE_HAS_MEMBER_FN_TRAITS is used to create traits
|
||||
* classes that check for the existence of a member function with
|
||||
* a given name and signature. It currently does not support
|
||||
* checking for inherited members.
|
||||
*
|
||||
* Such classes receive two template parameters: the class to be checked
|
||||
* and the signature of the member function. A static boolean field
|
||||
* named `value` (which is also constexpr) tells whether such member
|
||||
* function exists.
|
||||
*
|
||||
* Each traits class created is bound only to the member name, not to
|
||||
* its signature nor to the type of the class containing it.
|
||||
*
|
||||
* Say you need to know if a given class has a member function named
|
||||
* `test` with the following signature:
|
||||
*
|
||||
* int test() const;
|
||||
*
|
||||
* You'd need this macro to create a traits class to check for a member
|
||||
* named `test`, and then use this traits class to check for the signature:
|
||||
*
|
||||
* namespace {
|
||||
*
|
||||
* FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
|
||||
*
|
||||
* } // unnamed-namespace
|
||||
*
|
||||
* void some_func() {
|
||||
* cout << "Does class Foo have a member int test() const? "
|
||||
* << boolalpha << has_test_traits<Foo, int() const>::value;
|
||||
* }
|
||||
*
|
||||
* You can use the same traits class to test for a completely different
|
||||
* signature, on a completely different class, as long as the member name
|
||||
* is the same:
|
||||
*
|
||||
* void some_func() {
|
||||
* cout << "Does class Foo have a member int test()? "
|
||||
* << boolalpha << has_test_traits<Foo, int()>::value;
|
||||
* cout << "Does class Foo have a member int test() const? "
|
||||
* << boolalpha << has_test_traits<Foo, int() const>::value;
|
||||
* cout << "Does class Bar have a member double test(const string&, long)? "
|
||||
* << boolalpha << has_test_traits<Bar, double(const string&, long)>::value;
|
||||
* }
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
#define FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(classname, func_name) \
|
||||
template <typename, typename> class classname; \
|
||||
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS_IMPL(classname, func_name, ); \
|
||||
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS_IMPL(classname, func_name, const); \
|
||||
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS_IMPL(classname, func_name, volatile); \
|
||||
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS_IMPL(classname, func_name, volatile const)
|
||||
|
||||
#endif //FOLLY_BASE_TRAITS_H_
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/Unicode.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
fbstring codePointToUtf8(char32_t cp) {
|
||||
fbstring result;
|
||||
|
||||
// Based on description from http://en.wikipedia.org/wiki/UTF-8.
|
||||
|
||||
if (cp <= 0x7f) {
|
||||
result.resize(1);
|
||||
result[0] = static_cast<char>(cp);
|
||||
} else if (cp <= 0x7FF) {
|
||||
result.resize(2);
|
||||
result[1] = static_cast<char>(0x80 | (0x3f & cp));
|
||||
result[0] = static_cast<char>(0xC0 | (cp >> 6));
|
||||
} else if (cp <= 0xFFFF) {
|
||||
result.resize(3);
|
||||
result[2] = static_cast<char>(0x80 | (0x3f & cp));
|
||||
result[1] = (0x80 | static_cast<char>((0x3f & (cp >> 6))));
|
||||
result[0] = (0xE0 | static_cast<char>(cp >> 12));
|
||||
} else if (cp <= 0x10FFFF) {
|
||||
result.resize(4);
|
||||
result[3] = static_cast<char>(0x80 | (0x3f & cp));
|
||||
result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
|
||||
result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12)));
|
||||
result[0] = static_cast<char>(0xF0 | (cp >> 18));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Some utility routines relating to unicode.
|
||||
|
||||
#ifndef FOLLY_UNICODE_H_
|
||||
#define FOLLY_UNICODE_H_
|
||||
|
||||
#include "folly/FBString.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* Encode a single unicode code point into a UTF-8 byte sequence.
|
||||
*
|
||||
* Return value is undefined if `cp' is an invalid code point.
|
||||
*/
|
||||
fbstring codePointToUtf8(char32_t cp);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_URI_H_
|
||||
#error This file may only be included from folly/Uri.h
|
||||
#endif
|
||||
|
||||
#include "folly/Conv.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
template <class String>
|
||||
String Uri::toString() const {
|
||||
String str;
|
||||
toAppend(scheme_, "://", &str);
|
||||
if (!password_.empty()) {
|
||||
toAppend(username_, ":", password_, "@", &str);
|
||||
} else if (!username_.empty()) {
|
||||
toAppend(username_, "@", &str);
|
||||
}
|
||||
toAppend(host_, &str);
|
||||
if (port_ != 0) {
|
||||
toAppend(":", port_, &str);
|
||||
}
|
||||
toAppend(path_, &str);
|
||||
if (!query_.empty()) {
|
||||
toAppend("?", query_, &str);
|
||||
}
|
||||
if (!fragment_.empty()) {
|
||||
toAppend("#", fragment_, &str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "folly/Uri.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace {
|
||||
|
||||
fbstring submatch(const boost::cmatch& m, size_t idx) {
|
||||
auto& sub = m[idx];
|
||||
return fbstring(sub.first, sub.second);
|
||||
}
|
||||
|
||||
template <class String>
|
||||
void toLower(String& s) {
|
||||
for (auto& c : s) {
|
||||
c = tolower(c);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Uri::Uri(StringPiece str) : port_(0) {
|
||||
static const boost::regex uriRegex(
|
||||
"([a-zA-Z][a-zA-Z0-9+.-]*):" // scheme:
|
||||
"([^?#]*)" // authority and path
|
||||
"(?:\\?([^#]*))?" // ?query
|
||||
"(?:#(.*))?"); // #fragment
|
||||
static const boost::regex authorityAndPathRegex("//([^/]*)(/.*)?");
|
||||
|
||||
boost::cmatch match;
|
||||
if (UNLIKELY(!boost::regex_match(str.begin(), str.end(), match, uriRegex))) {
|
||||
throw std::invalid_argument("invalid URI");
|
||||
}
|
||||
|
||||
scheme_ = submatch(match, 1);
|
||||
toLower(scheme_);
|
||||
|
||||
StringPiece authorityAndPath(match[2].first, match[2].second);
|
||||
boost::cmatch authorityAndPathMatch;
|
||||
if (!boost::regex_match(authorityAndPath.begin(),
|
||||
authorityAndPath.end(),
|
||||
authorityAndPathMatch,
|
||||
authorityAndPathRegex)) {
|
||||
// Does not start with //, doesn't have authority
|
||||
path_ = authorityAndPath.fbstr();
|
||||
} else {
|
||||
static const boost::regex authorityRegex(
|
||||
"(?:([^@:]*)(?::([^@]*))?@)?" // username, password
|
||||
"(\\[[^\\]]*\\]|[^\\[:]*)" // host (IP-literal, dotted-IPv4, or
|
||||
// named host)
|
||||
"(?::(\\d*))?"); // port
|
||||
|
||||
auto authority = authorityAndPathMatch[1];
|
||||
boost::cmatch authorityMatch;
|
||||
if (!boost::regex_match(authority.first,
|
||||
authority.second,
|
||||
authorityMatch,
|
||||
authorityRegex)) {
|
||||
throw std::invalid_argument("invalid URI authority");
|
||||
}
|
||||
|
||||
StringPiece port(authorityMatch[4].first, authorityMatch[4].second);
|
||||
if (!port.empty()) {
|
||||
port_ = to<uint32_t>(port);
|
||||
}
|
||||
|
||||
username_ = submatch(authorityMatch, 1);
|
||||
password_ = submatch(authorityMatch, 2);
|
||||
host_ = submatch(authorityMatch, 3);
|
||||
path_ = submatch(authorityAndPathMatch, 2);
|
||||
}
|
||||
|
||||
query_ = submatch(match, 3);
|
||||
fragment_ = submatch(match, 4);
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_URI_H_
|
||||
#define FOLLY_URI_H_
|
||||
|
||||
#include "folly/String.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Class representing a URI.
|
||||
*
|
||||
* Consider http://www.facebook.com/foo/bar?key=foo#anchor
|
||||
*
|
||||
* The URI is broken down into its parts: scheme ("http"), authority
|
||||
* (ie. host and port, in most cases: "www.facebook.com"), path
|
||||
* ("/foo/bar"), query ("key=foo") and fragment ("anchor"). The scheme is
|
||||
* lower-cased.
|
||||
*
|
||||
* If this Uri represents a URL, note that, to prevent ambiguity, the component
|
||||
* parts are NOT percent-decoded; you should do this yourself with
|
||||
* uriUnescape() (for the authority and path) and uriUnescape(...,
|
||||
* UriEscapeMode::QUERY) (for the query, but probably only after splitting at
|
||||
* '&' to identify the individual parameters).
|
||||
*/
|
||||
class Uri {
|
||||
public:
|
||||
/**
|
||||
* Parse a Uri from a string. Throws std::invalid_argument on parse error.
|
||||
*/
|
||||
explicit Uri(StringPiece str);
|
||||
|
||||
const fbstring& scheme() const { return scheme_; }
|
||||
const fbstring& username() const { return username_; }
|
||||
const fbstring& password() const { return password_; }
|
||||
const fbstring& host() const { return host_; }
|
||||
uint32_t port() const { return port_; }
|
||||
const fbstring& path() const { return path_; }
|
||||
const fbstring& query() const { return query_; }
|
||||
const fbstring& fragment() const { return fragment_; }
|
||||
|
||||
template <class String>
|
||||
String toString() const;
|
||||
|
||||
std::string str() const { return toString<std::string>(); }
|
||||
fbstring fbstr() const { return toString<fbstring>(); }
|
||||
|
||||
private:
|
||||
fbstring scheme_;
|
||||
fbstring username_;
|
||||
fbstring password_;
|
||||
fbstring host_;
|
||||
uint32_t port_;
|
||||
fbstring path_;
|
||||
fbstring query_;
|
||||
fbstring fragment_;
|
||||
};
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#include "folly/Uri-inl.h"
|
||||
|
||||
#endif /* FOLLY_URI_H_ */
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_VARINT_H_
|
||||
#define FOLLY_VARINT_H_
|
||||
|
||||
#include "folly/Range.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Variable-length integer encoding, using a little-endian, base-128
|
||||
* representation.
|
||||
*
|
||||
* The MSb is set on all bytes except the last.
|
||||
*
|
||||
* Details:
|
||||
* https://developers.google.com/protocol-buffers/docs/encoding#varints
|
||||
*
|
||||
* If you want to encode multiple values, GroupVarint (in GroupVarint.h)
|
||||
* is faster and likely smaller.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Maximum length (in bytes) of the varint encoding of a 32-bit value.
|
||||
*/
|
||||
constexpr size_t kMaxVarintLength32 = 5;
|
||||
|
||||
/**
|
||||
* Maximum length (in bytes) of the varint encoding of a 64-bit value.
|
||||
*/
|
||||
constexpr size_t kMaxVarintLength64 = 10;
|
||||
|
||||
/**
|
||||
* Encode a value in the given buffer, returning the number of bytes used
|
||||
* for encoding.
|
||||
* buf must have enough space to represent the value (at least
|
||||
* kMaxVarintLength64 bytes to encode arbitrary 64-bit values)
|
||||
*/
|
||||
size_t encodeVarint(uint64_t val, uint8_t* buf);
|
||||
|
||||
/**
|
||||
* Decode a value from a given buffer, advances data past the returned value.
|
||||
*/
|
||||
uint64_t decodeVarint(ByteRange& data);
|
||||
|
||||
/**
|
||||
* ZigZag encoding that maps signed integers with a small absolute value
|
||||
* to unsigned integers with a small (positive) values. Without this,
|
||||
* encoding negative values using Varint would use up 9 or 10 bytes.
|
||||
*
|
||||
* if x >= 0, encodeZigZag(x) == 2*x
|
||||
* if x < 0, encodeZigZag(x) == -2*x + 1
|
||||
*/
|
||||
|
||||
inline uint64_t encodeZigZag(int64_t val) {
|
||||
// Bit-twiddling magic stolen from the Google protocol buffer document;
|
||||
// val >> 63 is an arithmetic shift because val is signed
|
||||
return static_cast<uint64_t>((val << 1) ^ (val >> 63));
|
||||
}
|
||||
|
||||
inline int64_t decodeZigZag(uint64_t val) {
|
||||
return static_cast<int64_t>((val >> 1) ^ -(val & 1));
|
||||
}
|
||||
|
||||
// Implementation below
|
||||
|
||||
inline size_t encodeVarint(uint64_t val, uint8_t* buf) {
|
||||
uint8_t* p = buf;
|
||||
while (val >= 128) {
|
||||
*p++ = 0x80 | (val & 0x7f);
|
||||
val >>= 7;
|
||||
}
|
||||
*p++ = val;
|
||||
return p - buf;
|
||||
}
|
||||
|
||||
inline uint64_t decodeVarint(ByteRange& data) {
|
||||
const int8_t* begin = reinterpret_cast<const int8_t*>(data.begin());
|
||||
const int8_t* end = reinterpret_cast<const int8_t*>(data.end());
|
||||
const int8_t* p = begin;
|
||||
uint64_t val = 0;
|
||||
|
||||
if (LIKELY(end - begin >= kMaxVarintLength64)) { // fast path
|
||||
int64_t b;
|
||||
do {
|
||||
b = *p++; val = (b & 0x7f) ; if (b >= 0) break;
|
||||
b = *p++; val |= (b & 0x7f) << 7; if (b >= 0) break;
|
||||
b = *p++; val |= (b & 0x7f) << 14; if (b >= 0) break;
|
||||
b = *p++; val |= (b & 0x7f) << 21; if (b >= 0) break;
|
||||
b = *p++; val |= (b & 0x7f) << 28; if (b >= 0) break;
|
||||
b = *p++; val |= (b & 0x7f) << 35; if (b >= 0) break;
|
||||
b = *p++; val |= (b & 0x7f) << 42; if (b >= 0) break;
|
||||
b = *p++; val |= (b & 0x7f) << 49; if (b >= 0) break;
|
||||
b = *p++; val |= (b & 0x7f) << 56; if (b >= 0) break;
|
||||
b = *p++; val |= (b & 0x7f) << 63; if (b >= 0) break;
|
||||
throw std::invalid_argument("Invalid varint value"); // too big
|
||||
} while (false);
|
||||
} else {
|
||||
int shift = 0;
|
||||
while (p != end && *p < 0) {
|
||||
val |= static_cast<uint64_t>(*p++ & 0x7f) << shift;
|
||||
shift += 7;
|
||||
}
|
||||
if (p == end) throw std::invalid_argument("Invalid varint value");
|
||||
val |= static_cast<uint64_t>(*p++) << shift;
|
||||
}
|
||||
|
||||
data.advance(p - begin);
|
||||
return val;
|
||||
}
|
||||
|
||||
} // namespaces
|
||||
|
||||
#endif /* FOLLY_VARINT_H_ */
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Some utilities used by AtomicHashArray and AtomicHashMap
|
||||
//
|
||||
// Note: no include guard; different -inl.h files include this and
|
||||
// undef it more than once in a translation unit.
|
||||
|
||||
#if !(defined(__x86__) || defined(__i386__) || defined(__x86_64__))
|
||||
#define FOLLY_SPIN_WAIT(condition) \
|
||||
for (int counter = 0; condition; ++counter) { \
|
||||
if (counter < 10000) continue; \
|
||||
pthread_yield(); \
|
||||
}
|
||||
#else
|
||||
#define FOLLY_SPIN_WAIT(condition) \
|
||||
for (int counter = 0; condition; ++counter) { \
|
||||
if (counter < 10000) { \
|
||||
asm volatile("pause"); \
|
||||
continue; \
|
||||
} \
|
||||
pthread_yield(); \
|
||||
}
|
||||
#endif
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_DETAIL_BITITERATORDETAIL_H_
|
||||
#define FOLLY_DETAIL_BITITERATORDETAIL_H_
|
||||
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <boost/iterator/iterator_adaptor.hpp>
|
||||
|
||||
namespace folly {
|
||||
|
||||
template <class BaseIter> class BitIterator;
|
||||
|
||||
namespace bititerator_detail {
|
||||
|
||||
// Reference to a bit.
|
||||
// Templatize on both parent reference and value types to capture
|
||||
// const-ness correctly and to work with the case where Ref is a
|
||||
// reference-like type (not T&), just like our BitReference here.
|
||||
template <class Ref, class Value>
|
||||
class BitReference {
|
||||
public:
|
||||
BitReference(Ref r, size_t bit) : ref_(r), bit_(bit) { }
|
||||
|
||||
operator bool() const {
|
||||
return ref_ & (one_ << bit_);
|
||||
}
|
||||
|
||||
BitReference& operator=(bool b) {
|
||||
if (b) {
|
||||
set();
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void set() {
|
||||
ref_ |= (one_ << bit_);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
ref_ &= ~(one_ << bit_);
|
||||
}
|
||||
|
||||
void flip() {
|
||||
ref_ ^= (one_ << bit_);
|
||||
}
|
||||
|
||||
private:
|
||||
// shortcut to avoid writing static_cast everywhere
|
||||
const static Value one_ = 1;
|
||||
|
||||
Ref ref_;
|
||||
size_t bit_;
|
||||
};
|
||||
|
||||
template <class BaseIter>
|
||||
struct BitIteratorBase {
|
||||
static_assert(std::is_integral<typename BaseIter::value_type>::value,
|
||||
"BitIterator may only be used with integral types");
|
||||
typedef boost::iterator_adaptor<
|
||||
BitIterator<BaseIter>, // Derived
|
||||
BaseIter, // Base
|
||||
bool, // Value
|
||||
boost::use_default, // CategoryOrTraversal
|
||||
bititerator_detail::BitReference<
|
||||
typename std::iterator_traits<BaseIter>::reference,
|
||||
typename std::iterator_traits<BaseIter>::value_type
|
||||
>, // Reference
|
||||
ssize_t> type;
|
||||
};
|
||||
|
||||
|
||||
} // namespace bititerator_detail
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_DETAIL_BITITERATORDETAIL_H_ */
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_DETAIL_BITSDETAIL_H_
|
||||
#define FOLLY_DETAIL_BITSDETAIL_H_
|
||||
|
||||
namespace folly {
|
||||
namespace detail {
|
||||
|
||||
// If we're targeting an architecture with popcnt support, use
|
||||
// __builtin_popcount directly, as it's presumably inlined.
|
||||
// If not, use runtime detection using __attribute__((ifunc))
|
||||
// (see Bits.cpp)
|
||||
#ifdef __POPCNT__
|
||||
|
||||
inline int popcount(unsigned int x) {
|
||||
return __builtin_popcount(x);
|
||||
}
|
||||
inline int popcountll(unsigned long long x) {
|
||||
return __builtin_popcountll(x);
|
||||
}
|
||||
|
||||
#else /* !__POPCNT__ */
|
||||
|
||||
int popcount(unsigned int x);
|
||||
int popcountll(unsigned long long x);
|
||||
|
||||
#endif /* !__POPCNT__ */
|
||||
|
||||
} // namespace detail
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_DETAIL_BITSDETAIL_H_ */
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_DETAIL_DISCRIMINATEDPTRDETAIL_H_
|
||||
#define FOLLY_DETAIL_DISCRIMINATEDPTRDETAIL_H_
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace folly {
|
||||
namespace dptr_detail {
|
||||
|
||||
/**
|
||||
* Given a target type and a list of types, return the 1-based index of the
|
||||
* type in the list of types. Fail to compile if the target type doesn't
|
||||
* appear in the list.
|
||||
*
|
||||
* GetIndex<int, void, char, int>::value == 3
|
||||
* GetIndex<int, void, char>::value -> fails to compile
|
||||
*/
|
||||
template <typename... Types> struct GetTypeIndex;
|
||||
|
||||
// When recursing, we never reach the 0- or 1- template argument base case
|
||||
// unless the target type is not in the list. If the target type is in the
|
||||
// list, we stop recursing when it is at the head of the remaining type
|
||||
// list via the GetTypeIndex<T, T, Types...> partial specialization.
|
||||
template <typename T, typename... Types>
|
||||
struct GetTypeIndex<T, T, Types...> {
|
||||
static const size_t value = 1;
|
||||
};
|
||||
|
||||
template <typename T, typename U, typename... Types>
|
||||
struct GetTypeIndex<T, U, Types...> {
|
||||
static const size_t value = 1 + GetTypeIndex<T, Types...>::value;
|
||||
};
|
||||
|
||||
// Generalize std::is_same for variable number of type arguments
|
||||
template <typename... Types>
|
||||
struct IsSameType;
|
||||
|
||||
template <>
|
||||
struct IsSameType<> {
|
||||
static const bool value = true;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct IsSameType<T> {
|
||||
static const bool value = true;
|
||||
};
|
||||
|
||||
template <typename T, typename U, typename... Types>
|
||||
struct IsSameType<T, U, Types...> {
|
||||
static const bool value =
|
||||
std::is_same<T,U>::value && IsSameType<U, Types...>::value;
|
||||
};
|
||||
|
||||
// Define type as the type of all T in (non-empty) Types..., asserting that
|
||||
// all types in Types... are the same.
|
||||
template <typename... Types>
|
||||
struct SameType;
|
||||
|
||||
template <typename T, typename... Types>
|
||||
struct SameType<T, Types...> {
|
||||
typedef T type;
|
||||
static_assert(IsSameType<T, Types...>::value,
|
||||
"Not all types in pack are the same");
|
||||
};
|
||||
|
||||
// Determine the result type of applying a visitor of type V on a pointer
|
||||
// to type T.
|
||||
template <typename V, typename T>
|
||||
struct VisitorResult1 {
|
||||
typedef typename std::result_of<V (T*)>::type type;
|
||||
};
|
||||
|
||||
// Determine the result type of applying a visitor of type V on a const pointer
|
||||
// to type T.
|
||||
template <typename V, typename T>
|
||||
struct ConstVisitorResult1 {
|
||||
typedef typename std::result_of<V (const T*)>::type type;
|
||||
};
|
||||
|
||||
// Determine the result type of applying a visitor of type V on pointers of
|
||||
// all types in Types..., asserting that the type is the same for all types
|
||||
// in Types...
|
||||
template <typename V, typename... Types>
|
||||
struct VisitorResult {
|
||||
typedef typename SameType<
|
||||
typename VisitorResult1<V,Types>::type...>::type type;
|
||||
};
|
||||
|
||||
// Determine the result type of applying a visitor of type V on const pointers
|
||||
// of all types in Types..., asserting that the type is the same for all types
|
||||
// in Types...
|
||||
template <typename V, typename... Types>
|
||||
struct ConstVisitorResult {
|
||||
typedef typename SameType<
|
||||
typename ConstVisitorResult1<V,Types>::type...>::type type;
|
||||
};
|
||||
|
||||
template <typename V, typename R, typename... Types> struct ApplyVisitor1;
|
||||
|
||||
template <typename V, typename R>
|
||||
struct ApplyVisitor1<V, R> {
|
||||
R operator()(size_t index, V&& visitor, void* ptr) const {
|
||||
CHECK(false); // NOTREACHED
|
||||
}
|
||||
};
|
||||
|
||||
template <typename V, typename R, typename T, typename... Types>
|
||||
struct ApplyVisitor1<V, R, T, Types...> {
|
||||
R operator()(size_t index, V&& visitor, void* ptr) const {
|
||||
return (index == 1 ? visitor(static_cast<T*>(ptr)) :
|
||||
ApplyVisitor1<V, R, Types...>()(
|
||||
index - 1, std::forward<V>(visitor), ptr));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename V, typename R, typename... Types> struct ApplyConstVisitor1;
|
||||
|
||||
template <typename V, typename R>
|
||||
struct ApplyConstVisitor1<V, R> {
|
||||
R operator()(size_t index, V&& visitor, void* ptr) const {
|
||||
CHECK(false); // NOTREACHED
|
||||
}
|
||||
};
|
||||
|
||||
template <typename V, typename R, typename T, typename... Types>
|
||||
struct ApplyConstVisitor1<V, R, T, Types...> {
|
||||
R operator()(size_t index, V&& visitor, void* ptr) const {
|
||||
return (index == 1 ? visitor(static_cast<const T*>(ptr)) :
|
||||
ApplyConstVisitor1<V, R, Types...>()(
|
||||
index - 1, std::forward<V>(visitor), ptr));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename V, typename... Types>
|
||||
struct ApplyVisitor
|
||||
: ApplyVisitor1<
|
||||
V, typename VisitorResult<V, Types...>::type, Types...> {
|
||||
};
|
||||
|
||||
template <typename V, typename... Types>
|
||||
struct ApplyConstVisitor
|
||||
: ApplyConstVisitor1<
|
||||
V, typename ConstVisitorResult<V, Types...>::type, Types...> {
|
||||
};
|
||||
|
||||
} // namespace dptr_detail
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_DETAIL_DISCRIMINATEDPTRDETAIL_H_ */
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_DETAIL_FILEUTILDETAIL_H_
|
||||
#define FOLLY_DETAIL_FILEUTILDETAIL_H_
|
||||
|
||||
#include <cerrno>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/uio.h>
|
||||
|
||||
/**
|
||||
* Helper functions and templates for FileUtil.cpp. Declared here so
|
||||
* they can be unittested.
|
||||
*/
|
||||
namespace folly { namespace fileutil_detail {
|
||||
|
||||
// Wrap call to f(args) in loop to retry on EINTR
|
||||
template<class F, class... Args>
|
||||
ssize_t wrapNoInt(F f, Args... args) {
|
||||
ssize_t r;
|
||||
do {
|
||||
r = f(args...);
|
||||
} while (r == -1 && errno == EINTR);
|
||||
return r;
|
||||
}
|
||||
|
||||
inline void incr(ssize_t n) { }
|
||||
inline void incr(ssize_t n, off_t& offset) { offset += n; }
|
||||
|
||||
// Wrap call to read/pread/write/pwrite(fd, buf, count, offset?) to retry on
|
||||
// incomplete reads / writes. The variadic argument magic is there to support
|
||||
// an additional argument (offset) for pread / pwrite; see the incr() functions
|
||||
// above which do nothing if the offset is not present and increment it if it
|
||||
// is.
|
||||
template <class F, class... Offset>
|
||||
ssize_t wrapFull(F f, int fd, void* buf, size_t count, Offset... offset) {
|
||||
char* b = static_cast<char*>(buf);
|
||||
ssize_t totalBytes = 0;
|
||||
ssize_t r;
|
||||
do {
|
||||
r = f(fd, b, count, offset...);
|
||||
if (r == -1) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
totalBytes += r;
|
||||
b += r;
|
||||
count -= r;
|
||||
incr(r, offset...);
|
||||
} while (r != 0 && count); // 0 means EOF
|
||||
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
// Wrap call to readv/preadv/writev/pwritev(fd, iov, count, offset?) to
|
||||
// retry on incomplete reads / writes.
|
||||
template <class F, class... Offset>
|
||||
ssize_t wrapvFull(F f, int fd, iovec* iov, int count, Offset... offset) {
|
||||
ssize_t totalBytes = 0;
|
||||
ssize_t r;
|
||||
do {
|
||||
r = f(fd, iov, count, offset...);
|
||||
if (r == -1) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
if (r == 0) {
|
||||
break; // EOF
|
||||
}
|
||||
|
||||
totalBytes += r;
|
||||
incr(r, offset...);
|
||||
while (r != 0 && count != 0) {
|
||||
if (r >= iov->iov_len) {
|
||||
r -= iov->iov_len;
|
||||
++iov;
|
||||
--count;
|
||||
} else {
|
||||
iov->iov_base = static_cast<char*>(iov->iov_base) + r;
|
||||
iov->iov_len -= r;
|
||||
r = 0;
|
||||
}
|
||||
}
|
||||
} while (count);
|
||||
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
}} // namespaces
|
||||
|
||||
#endif /* FOLLY_DETAIL_FILEUTILDETAIL_H_ */
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_BUILD_FINGERPRINTPOLYNOMIAL_H_
|
||||
#define FOLLY_BUILD_FINGERPRINTPOLYNOMIAL_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace folly {
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Representation of a polynomial of degree DEG over GF(2) (that is,
|
||||
* with binary coefficients).
|
||||
*
|
||||
* Probably of no use outside of Fingerprint code; used by
|
||||
* GenerateFingerprintTables and the unittest.
|
||||
*/
|
||||
template <int DEG>
|
||||
class FingerprintPolynomial {
|
||||
public:
|
||||
FingerprintPolynomial() {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
val_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
explicit FingerprintPolynomial(const uint64_t* vals) {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
val_[i] = vals[i];
|
||||
}
|
||||
}
|
||||
|
||||
void write(uint64_t* out) const {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
out[i] = val_[i];
|
||||
}
|
||||
}
|
||||
|
||||
void add(const FingerprintPolynomial<DEG>& other) {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
val_[i] ^= other.val_[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply by X. The actual degree must be < DEG.
|
||||
void mulX() {
|
||||
CHECK_EQ(0, val_[0] & (1UL<<63));
|
||||
uint64_t b = 0;
|
||||
for (int i = size()-1; i >= 0; i--) {
|
||||
uint64_t nb = val_[i] >> 63;
|
||||
val_[i] = (val_[i] << 1) | b;
|
||||
b = nb;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute (this * X) mod P(X), where P(X) is a monic polynomial of degree
|
||||
// DEG+1 (represented as a FingerprintPolynomial<DEG> object, with the
|
||||
// implicit coefficient of X^(DEG+1)==1)
|
||||
//
|
||||
// This is a bit tricky. If k=DEG+1:
|
||||
// Let P(X) = X^k + p_(k-1) * X^(k-1) + ... + p_1 * X + p_0
|
||||
// Let this = A(X) = a_(k-1) * X^(k-1) + ... + a_1 * X + a_0
|
||||
// Then:
|
||||
// A(X) * X
|
||||
// = a_(k-1) * X^k + (a_(k-2) * X^(k-1) + ... + a_1 * X^2 + a_0 * X)
|
||||
// = a_(k-1) * X^k + (the binary representation of A, left shift by 1)
|
||||
//
|
||||
// if a_(k-1) = 0, we can ignore the first term.
|
||||
// if a_(k-1) = 1, then:
|
||||
// X^k mod P(X)
|
||||
// = X^k - P(X)
|
||||
// = P(X) - X^k
|
||||
// = p_(k-1) * X^(k-1) + ... + p_1 * X + p_0
|
||||
// = exactly the binary representation passed in as an argument to this
|
||||
// function!
|
||||
//
|
||||
// So A(X) * X mod P(X) is:
|
||||
// the binary representation of A, left shift by 1,
|
||||
// XOR p if a_(k-1) == 1
|
||||
void mulXmod(const FingerprintPolynomial<DEG>& p) {
|
||||
bool needXOR = (val_[0] & (1UL<<63));
|
||||
val_[0] &= ~(1UL<<63);
|
||||
mulX();
|
||||
if (needXOR) {
|
||||
add(p);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute (this * X^k) mod P(X) by repeatedly multiplying by X (see above)
|
||||
void mulXkmod(int k, const FingerprintPolynomial<DEG>& p) {
|
||||
for (int i = 0; i < k; i++) {
|
||||
mulXmod(p);
|
||||
}
|
||||
}
|
||||
|
||||
// add X^k, where k <= DEG
|
||||
void addXk(int k) {
|
||||
DCHECK_GE(k, 0);
|
||||
DCHECK_LE(k, DEG);
|
||||
int word_offset = (DEG - k) / 64;
|
||||
int bit_offset = 63 - (DEG - k) % 64;
|
||||
val_[word_offset] ^= (1UL << bit_offset);
|
||||
}
|
||||
|
||||
// Set the highest 8 bits to val.
|
||||
// If val is interpreted as polynomial of degree 7, then this sets *this
|
||||
// to val * X^(DEG-7)
|
||||
void setHigh8Bits(uint8_t val) {
|
||||
val_[0] = ((uint64_t)val) << (64-8);
|
||||
for (int i = 1; i < size(); i++) {
|
||||
val_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int size() {
|
||||
return 1 + DEG/64;
|
||||
}
|
||||
private:
|
||||
// Internal representation: big endian
|
||||
// val_[0] contains the highest order coefficients, with bit 63 as the
|
||||
// highest order coefficient
|
||||
//
|
||||
// If DEG+1 is not a multiple of 64, val_[size()-1] only uses the highest
|
||||
// order (DEG+1)%64 bits (the others are always 0)
|
||||
uint64_t val_[1 + DEG/64];
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_BUILD_FINGERPRINTPOLYNOMIAL_H_ */
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <linux/futex.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace folly { namespace detail {
|
||||
|
||||
/**
|
||||
* Futex is an atomic 32 bit unsigned integer that provides access to the
|
||||
* futex() syscall on that value. It is templated in such a way that it
|
||||
* can interact properly with DeterministicSchedule testing.
|
||||
*
|
||||
* If you don't know how to use futex(), you probably shouldn't be using
|
||||
* this class. Even if you do know how, you should have a good reason
|
||||
* (and benchmarks to back you up).
|
||||
*/
|
||||
template <template <typename> class Atom = std::atomic>
|
||||
struct Futex : Atom<uint32_t>, boost::noncopyable {
|
||||
|
||||
explicit Futex(uint32_t init = 0) : Atom<uint32_t>(init) {}
|
||||
|
||||
/** Puts the thread to sleep if this->load() == expected. Returns true when
|
||||
* it is returning because it has consumed a wake() event, false for any
|
||||
* other return (signal, this->load() != expected, or spurious wakeup). */
|
||||
bool futexWait(uint32_t expected, uint32_t waitMask = -1);
|
||||
|
||||
/** Wakens up to count waiters where (waitMask & wakeMask) != 0,
|
||||
* returning the number of awoken threads. */
|
||||
int futexWake(int count = std::numeric_limits<int>::max(),
|
||||
uint32_t wakeMask = -1);
|
||||
};
|
||||
|
||||
template <>
|
||||
inline bool Futex<std::atomic>::futexWait(uint32_t expected,
|
||||
uint32_t waitMask) {
|
||||
assert(sizeof(*this) == sizeof(int));
|
||||
int rv = syscall(SYS_futex,
|
||||
this, /* addr1 */
|
||||
FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG, /* op */
|
||||
expected, /* val */
|
||||
nullptr, /* timeout */
|
||||
nullptr, /* addr2 */
|
||||
waitMask); /* val3 */
|
||||
assert(rv == 0 || (errno == EWOULDBLOCK || errno == EINTR));
|
||||
return rv == 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int Futex<std::atomic>::futexWake(int count, uint32_t wakeMask) {
|
||||
assert(sizeof(*this) == sizeof(int));
|
||||
int rv = syscall(SYS_futex,
|
||||
this, /* addr1 */
|
||||
FUTEX_WAKE_BITSET | FUTEX_PRIVATE_FLAG, /* op */
|
||||
count, /* val */
|
||||
nullptr, /* timeout */
|
||||
nullptr, /* addr2 */
|
||||
wakeMask); /* val3 */
|
||||
assert(rv >= 0);
|
||||
return rv;
|
||||
}
|
||||
|
||||
}}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_DETAIL_GROUPVARINTDETAIL_H_
|
||||
#define FOLLY_DETAIL_GROUPVARINTDETAIL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
template <typename T>
|
||||
class GroupVarint;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
struct GroupVarintTraits;
|
||||
|
||||
template <>
|
||||
struct GroupVarintTraits<uint32_t> {
|
||||
enum {
|
||||
kGroupSize = 4,
|
||||
kHeaderSize = 1,
|
||||
};
|
||||
};
|
||||
|
||||
template <>
|
||||
struct GroupVarintTraits<uint64_t> {
|
||||
enum {
|
||||
kGroupSize = 5,
|
||||
kHeaderSize = 2,
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class GroupVarintBase {
|
||||
protected:
|
||||
typedef GroupVarintTraits<T> Traits;
|
||||
enum { kHeaderSize = Traits::kHeaderSize };
|
||||
|
||||
public:
|
||||
typedef T type;
|
||||
|
||||
/**
|
||||
* Number of integers encoded / decoded in one pass.
|
||||
*/
|
||||
enum { kGroupSize = Traits::kGroupSize };
|
||||
|
||||
/**
|
||||
* Maximum encoded size.
|
||||
*/
|
||||
enum { kMaxSize = kHeaderSize + sizeof(type) * kGroupSize };
|
||||
|
||||
/**
|
||||
* Maximum size for n values.
|
||||
*/
|
||||
static size_t maxSize(size_t n) {
|
||||
// Full groups
|
||||
size_t total = (n / kGroupSize) * kFullGroupSize;
|
||||
// Incomplete last group, if any
|
||||
n %= kGroupSize;
|
||||
if (n) {
|
||||
total += kHeaderSize + n * sizeof(type);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Size of n values starting at p.
|
||||
*/
|
||||
static size_t totalSize(const T* p, size_t n) {
|
||||
size_t size = 0;
|
||||
for (; n >= kGroupSize; n -= kGroupSize, p += kGroupSize) {
|
||||
size += Derived::size(p);
|
||||
}
|
||||
if (n) {
|
||||
size += Derived::partialSize(p, n);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef GroupVarint<T> Derived;
|
||||
enum { kFullGroupSize = kHeaderSize + kGroupSize * sizeof(type) };
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_DETAIL_GROUPVARINTDETAIL_H_ */
|
||||
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário