Add missing files.
GitOrigin-RevId: 8a8503246a51483927b683ba5cd74f758e21a538
This commit is contained in:
parent
a0f6616ad3
commit
e300208960
@ -105,7 +105,6 @@ set(TDUTILS_SOURCE
|
||||
td/utils/tests.cpp
|
||||
td/utils/Time.cpp
|
||||
td/utils/Timer.cpp
|
||||
td/utils/tests.cpp
|
||||
td/utils/tl_parsers.cpp
|
||||
td/utils/translit.cpp
|
||||
td/utils/unicode.cpp
|
||||
@ -185,6 +184,9 @@ set(TDUTILS_SOURCE
|
||||
td/utils/format.h
|
||||
td/utils/Gzip.h
|
||||
td/utils/GzipByteFlow.h
|
||||
td/utils/Hash.h
|
||||
td/utils/HashMap.h
|
||||
td/utils/HashSet.h
|
||||
td/utils/HazardPointers.h
|
||||
td/utils/Heap.h
|
||||
td/utils/Hints.h
|
||||
@ -298,8 +300,7 @@ if (CRC32C_FOUND)
|
||||
target_link_libraries(tdutils PRIVATE crc32c)
|
||||
endif()
|
||||
if (ABSL_FOUND)
|
||||
#target_link_libraries(tdutils PRIVATE absl::base absl::time)
|
||||
target_link_libraries_system(tdutils absl::base absl::container absl::hash )
|
||||
target_link_libraries(tdutils absl::base absl::container absl::hash)
|
||||
endif()
|
||||
|
||||
if (WIN32 AND WINGETOPT_FOUND)
|
||||
|
315
tdutils/td/utils/ConcurrentHashTable.h
Normal file
315
tdutils/td/utils/ConcurrentHashTable.h
Normal file
@ -0,0 +1,315 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/HazardPointers.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <vector>
|
||||
// AtomicHashArray<KeyT, ValueT>
|
||||
// Building block for other conurrent hash maps
|
||||
//
|
||||
// Support one operation:
|
||||
// template <class F>
|
||||
// bool with_value(KeyT key, bool should_create, F &&func);
|
||||
//
|
||||
// Finds slot for key, and call func(value)
|
||||
// Creates slot if shoul_create is true.
|
||||
// Returns true if func was called.
|
||||
//
|
||||
// Concurrent calls with same key may result in concurrent calls of func(value)
|
||||
// It is resposibility of caller to handle such races.
|
||||
//
|
||||
// Key should already be random
|
||||
// It is resposibility of caller to provide unique random key.
|
||||
// One may use injective hash function, or handle collisions on some other way.
|
||||
|
||||
namespace td {
|
||||
template <class KeyT, class ValueT>
|
||||
class AtomicHashArray {
|
||||
public:
|
||||
AtomicHashArray(size_t n) : nodes_(n) {
|
||||
}
|
||||
struct Node {
|
||||
std::atomic<KeyT> key{KeyT{}};
|
||||
ValueT value{};
|
||||
};
|
||||
size_t size() const {
|
||||
return nodes_.size();
|
||||
}
|
||||
Node &node_at(size_t i) {
|
||||
return nodes_[i];
|
||||
}
|
||||
static KeyT empty_key() {
|
||||
return KeyT{};
|
||||
}
|
||||
|
||||
template <class F>
|
||||
bool with_value(KeyT key, bool should_create, F &&f) {
|
||||
DCHECK(key != empty_key());
|
||||
size_t pos = static_cast<size_t>(key) % nodes_.size();
|
||||
size_t n = std::min(std::max(size_t(300), nodes_.size() / 16 + 2), nodes_.size());
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
pos++;
|
||||
if (pos >= nodes_.size()) {
|
||||
pos = 0;
|
||||
}
|
||||
auto &node = nodes_[pos];
|
||||
while (true) {
|
||||
auto node_key = node.key.load(std::memory_order_acquire);
|
||||
if (node_key == empty_key()) {
|
||||
if (!should_create) {
|
||||
return false;
|
||||
}
|
||||
KeyT expected_key = empty_key();
|
||||
if (node.key.compare_exchange_strong(expected_key, key, std::memory_order_relaxed,
|
||||
std::memory_order_relaxed)) {
|
||||
f(node.value);
|
||||
return true;
|
||||
}
|
||||
} else if (node_key == key) {
|
||||
f(node.value);
|
||||
return true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Node> nodes_;
|
||||
};
|
||||
|
||||
// Simple concurrent hash map with multiple limitations
|
||||
template <class KeyT, class ValueT>
|
||||
class ConcurrentHashMap {
|
||||
using HashMap = AtomicHashArray<KeyT, std::atomic<ValueT>>;
|
||||
static td::HazardPointers<HashMap> hp_;
|
||||
|
||||
public:
|
||||
ConcurrentHashMap(size_t n = 32) {
|
||||
n = 1;
|
||||
hash_map_.store(make_unique<HashMap>(n).release());
|
||||
}
|
||||
ConcurrentHashMap(const ConcurrentHashMap &) = delete;
|
||||
ConcurrentHashMap &operator=(const ConcurrentHashMap &) = delete;
|
||||
ConcurrentHashMap(ConcurrentHashMap &&) = delete;
|
||||
ConcurrentHashMap &operator=(ConcurrentHashMap &&) = delete;
|
||||
~ConcurrentHashMap() {
|
||||
td::unique_ptr<HashMap>(hash_map_.load());
|
||||
}
|
||||
|
||||
static std::string get_name() {
|
||||
return "ConcurrrentHashMap";
|
||||
}
|
||||
|
||||
static KeyT empty_key() {
|
||||
return KeyT{};
|
||||
}
|
||||
static ValueT empty_value() {
|
||||
return ValueT{};
|
||||
}
|
||||
static ValueT migrate_value() {
|
||||
return (ValueT)(1); // c-style convertion because reinterpret_cast<int>(1) is CE in MSVC
|
||||
}
|
||||
|
||||
ValueT insert(KeyT key, ValueT value) {
|
||||
CHECK(key != empty_key());
|
||||
CHECK(value != migrate_value());
|
||||
typename HazardPointers<HashMap>::Holder holder(hp_, get_thread_id(), 0);
|
||||
while (true) {
|
||||
auto hash_map = holder.protect(hash_map_);
|
||||
if (!hash_map) {
|
||||
do_migrate(nullptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
ValueT inserted_value;
|
||||
hash_map->with_value(key, true, [&](auto &node_value) {
|
||||
ValueT expected_value = this->empty_value();
|
||||
if (node_value.compare_exchange_strong(expected_value, value, std::memory_order_release,
|
||||
std::memory_order_acquire)) {
|
||||
ok = true;
|
||||
inserted_value = value;
|
||||
} else {
|
||||
if (expected_value == this->migrate_value()) {
|
||||
ok = false;
|
||||
} else {
|
||||
ok = true;
|
||||
inserted_value = expected_value;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (ok) {
|
||||
return inserted_value;
|
||||
}
|
||||
do_migrate(hash_map);
|
||||
}
|
||||
}
|
||||
ValueT find(KeyT key, ValueT value) {
|
||||
typename HazardPointers<HashMap>::Holder holder(hp_, get_thread_id(), 0);
|
||||
while (true) {
|
||||
auto hash_map = holder.protect(hash_map_);
|
||||
if (!hash_map) {
|
||||
do_migrate(nullptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool has_value = hash_map->with_value(
|
||||
key, false, [&](auto &node_value) { value = node_value.load(std::memory_order_acquire); });
|
||||
if (!has_value || value != migrate_value()) {
|
||||
return value;
|
||||
}
|
||||
do_migrate(hash_map);
|
||||
}
|
||||
}
|
||||
template <class F>
|
||||
void for_each(F &&f) {
|
||||
auto hash_map = hash_map_.load();
|
||||
CHECK(hash_map);
|
||||
auto size = hash_map->size();
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
auto &node = hash_map->node_at(i);
|
||||
auto key = node.key.load(std::memory_order_relaxed);
|
||||
auto value = node.value.load(std::memory_order_relaxed);
|
||||
|
||||
if (key != empty_key()) {
|
||||
CHECK(value != migrate_value());
|
||||
if (value != empty_value()) {
|
||||
f(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// use no padding intentionally
|
||||
std::atomic<HashMap *> hash_map_{nullptr};
|
||||
|
||||
std::mutex migrate_mutex_;
|
||||
std::condition_variable migrate_cv_;
|
||||
|
||||
int migrate_cnt_{0};
|
||||
int migrate_generation_{0};
|
||||
HashMap *migrate_from_hash_map_{nullptr};
|
||||
HashMap *migrate_to_hash_map_{nullptr};
|
||||
struct Task {
|
||||
size_t begin;
|
||||
size_t end;
|
||||
bool empty() const {
|
||||
return begin >= end;
|
||||
}
|
||||
size_t size() const {
|
||||
if (empty()) {
|
||||
return 0;
|
||||
}
|
||||
return end - begin;
|
||||
}
|
||||
};
|
||||
|
||||
struct TaskCreator {
|
||||
size_t chunk_size;
|
||||
size_t size;
|
||||
std::atomic<size_t> pos{0};
|
||||
Task create() {
|
||||
auto i = pos++;
|
||||
auto begin = i * chunk_size;
|
||||
auto end = begin + chunk_size;
|
||||
if (end > size) {
|
||||
end = size;
|
||||
}
|
||||
return {begin, end};
|
||||
}
|
||||
};
|
||||
TaskCreator task_creator;
|
||||
|
||||
void do_migrate(HashMap *ptr) {
|
||||
//LOG(ERROR) << "do migrate: " << ptr;
|
||||
std::unique_lock<std::mutex> lock(migrate_mutex_);
|
||||
if (hash_map_.load() != ptr) {
|
||||
return;
|
||||
}
|
||||
init_migrate();
|
||||
CHECK(!ptr || migrate_from_hash_map_ == ptr);
|
||||
migrate_cnt_++;
|
||||
auto migrate_generation = migrate_generation_;
|
||||
lock.unlock();
|
||||
|
||||
run_migrate();
|
||||
|
||||
lock.lock();
|
||||
migrate_cnt_--;
|
||||
if (migrate_cnt_ == 0) {
|
||||
finish_migrate();
|
||||
}
|
||||
migrate_cv_.wait(lock, [&] { return migrate_generation_ != migrate_generation; });
|
||||
}
|
||||
|
||||
void finish_migrate() {
|
||||
//LOG(ERROR) << "finish_migrate";
|
||||
hash_map_.store(migrate_to_hash_map_);
|
||||
hp_.retire(get_thread_id(), migrate_from_hash_map_);
|
||||
migrate_from_hash_map_ = nullptr;
|
||||
migrate_to_hash_map_ = nullptr;
|
||||
migrate_generation_++;
|
||||
migrate_cv_.notify_all();
|
||||
}
|
||||
|
||||
void init_migrate() {
|
||||
if (migrate_from_hash_map_ != nullptr) {
|
||||
return;
|
||||
}
|
||||
//LOG(ERROR) << "init_migrate";
|
||||
CHECK(migrate_cnt_ == 0);
|
||||
migrate_generation_++;
|
||||
migrate_from_hash_map_ = hash_map_.exchange(nullptr);
|
||||
auto new_size = migrate_from_hash_map_->size() * 2;
|
||||
migrate_to_hash_map_ = make_unique<HashMap>(new_size).release();
|
||||
task_creator.chunk_size = 100;
|
||||
task_creator.size = migrate_from_hash_map_->size();
|
||||
task_creator.pos = 0;
|
||||
}
|
||||
|
||||
void run_migrate() {
|
||||
//LOG(ERROR) << "run_migrate";
|
||||
size_t cnt = 0;
|
||||
while (true) {
|
||||
auto task = task_creator.create();
|
||||
cnt += task.size();
|
||||
if (task.empty()) {
|
||||
break;
|
||||
}
|
||||
run_task(task);
|
||||
}
|
||||
//LOG(ERROR) << "run_migrate " << cnt;
|
||||
}
|
||||
|
||||
void run_task(Task task) {
|
||||
for (auto i = task.begin; i < task.end; i++) {
|
||||
auto &node = migrate_from_hash_map_->node_at(i);
|
||||
auto old_value = node.value.exchange(migrate_value(), std::memory_order_acq_rel);
|
||||
if (old_value == 0) {
|
||||
continue;
|
||||
}
|
||||
auto node_key = node.key.load(std::memory_order_relaxed);
|
||||
//LOG(ERROR) << node_key << " " << node_key;
|
||||
auto ok = migrate_to_hash_map_->with_value(
|
||||
node_key, true, [&](auto &node_value) { node_value.store(old_value, std::memory_order_relaxed); });
|
||||
LOG_CHECK(ok) << "migration overflow";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class KeyT, class ValueT>
|
||||
td::HazardPointers<typename ConcurrentHashMap<KeyT, ValueT>::HashMap> ConcurrentHashMap<KeyT, ValueT>::hp_(64);
|
||||
} // namespace td
|
196
tdutils/td/utils/EpochBasedMemoryReclamation.h
Normal file
196
tdutils/td/utils/EpochBasedMemoryReclamation.h
Normal file
@ -0,0 +1,196 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/port/sleep.h"
|
||||
#include "td/utils/port/thread.h"
|
||||
|
||||
namespace td {
|
||||
template <class T>
|
||||
class EpochBasedMemoryReclamation {
|
||||
public:
|
||||
EpochBasedMemoryReclamation(const EpochBasedMemoryReclamation &other) = delete;
|
||||
EpochBasedMemoryReclamation &operator=(const EpochBasedMemoryReclamation &other) = delete;
|
||||
EpochBasedMemoryReclamation(EpochBasedMemoryReclamation &&other) = delete;
|
||||
EpochBasedMemoryReclamation &operator=(EpochBasedMemoryReclamation &&other) = delete;
|
||||
|
||||
class Locker {
|
||||
public:
|
||||
Locker(size_t thread_id, EpochBasedMemoryReclamation *ebmr) : thread_id_(thread_id), ebmr_(ebmr) {
|
||||
}
|
||||
Locker(const Locker &other) = delete;
|
||||
Locker &operator=(const Locker &other) = delete;
|
||||
Locker(Locker &&other) = default;
|
||||
Locker &operator=(Locker &&other) = delete;
|
||||
|
||||
~Locker() {
|
||||
if (ebmr_) {
|
||||
retire_sync();
|
||||
unlock();
|
||||
ebmr_.release();
|
||||
}
|
||||
}
|
||||
void lock() {
|
||||
DCHECK(ebmr_);
|
||||
ebmr_->lock(thread_id_);
|
||||
}
|
||||
void unlock() {
|
||||
DCHECK(ebmr_);
|
||||
ebmr_->unlock(thread_id_);
|
||||
}
|
||||
|
||||
void retire_sync() {
|
||||
ebmr_->retire_sync(thread_id_);
|
||||
}
|
||||
|
||||
void retire() {
|
||||
ebmr_->retire(thread_id_);
|
||||
}
|
||||
|
||||
void retire(T *ptr) {
|
||||
ebmr_->retire(thread_id_, ptr);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t thread_id_;
|
||||
struct Never {
|
||||
template <class S>
|
||||
void operator()(S *) const {
|
||||
UNREACHABLE();
|
||||
}
|
||||
};
|
||||
std::unique_ptr<EpochBasedMemoryReclamation, Never> ebmr_;
|
||||
};
|
||||
|
||||
explicit EpochBasedMemoryReclamation(size_t threads_n) : threads_(threads_n) {
|
||||
}
|
||||
|
||||
Locker get_locker(size_t thread_id) {
|
||||
return Locker{thread_id, this};
|
||||
}
|
||||
|
||||
size_t to_delete_size_unsafe() const {
|
||||
size_t res = 0;
|
||||
for (auto &thread : threads_) {
|
||||
LOG(ERROR) << "---" << thread.epoch.load() / 2;
|
||||
for (size_t i = 0; i < MAX_BAGS; i++) {
|
||||
res += thread.to_delete[i].size();
|
||||
LOG(ERROR) << thread.to_delete[i].size();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Locker;
|
||||
static constexpr size_t MAX_BAGS = 3;
|
||||
struct ThreadData {
|
||||
std::atomic<int64> epoch{1};
|
||||
char pad[TD_CONCURRENCY_PAD - sizeof(epoch)];
|
||||
|
||||
size_t to_skip{0};
|
||||
size_t checked_thread_i{0};
|
||||
size_t bag_i{0};
|
||||
std::vector<unique_ptr<T>> to_delete[MAX_BAGS];
|
||||
char pad2[TD_CONCURRENCY_PAD - sizeof(to_delete)];
|
||||
|
||||
void rotate_bags() {
|
||||
bag_i = (bag_i + 1) % MAX_BAGS;
|
||||
to_delete[bag_i].clear();
|
||||
}
|
||||
|
||||
void set_epoch(int64 new_epoch) {
|
||||
//LOG(ERROR) << new_epoch;
|
||||
if (epoch.load(std::memory_order_relaxed) / 2 != new_epoch) {
|
||||
checked_thread_i = 0;
|
||||
to_skip = 0;
|
||||
rotate_bags();
|
||||
}
|
||||
epoch = new_epoch * 2;
|
||||
}
|
||||
|
||||
void idle() {
|
||||
epoch.store(epoch.load(std::memory_order_relaxed) | 1);
|
||||
}
|
||||
|
||||
size_t undeleted() const {
|
||||
size_t res = 0;
|
||||
for (size_t i = 0; i < MAX_BAGS; i++) {
|
||||
res += to_delete[i].size();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
std::vector<ThreadData> threads_;
|
||||
char pad[TD_CONCURRENCY_PAD - sizeof(threads_)];
|
||||
|
||||
std::atomic<int64> epoch_{1};
|
||||
char pad2[TD_CONCURRENCY_PAD - sizeof(epoch_)];
|
||||
|
||||
void lock(size_t thread_id) {
|
||||
auto &data = threads_[thread_id];
|
||||
auto epoch = epoch_.load();
|
||||
data.set_epoch(epoch);
|
||||
|
||||
if (data.to_skip == 0) {
|
||||
data.to_skip = 30;
|
||||
step_check(data);
|
||||
} else {
|
||||
data.to_skip--;
|
||||
}
|
||||
}
|
||||
|
||||
void unlock(size_t thread_id) {
|
||||
//LOG(ERROR) << "UNLOCK";
|
||||
auto &data = threads_[thread_id];
|
||||
data.idle();
|
||||
}
|
||||
|
||||
bool step_check(ThreadData &data) {
|
||||
auto epoch = data.epoch.load(std::memory_order_relaxed) / 2;
|
||||
auto checked_thread_epoch = threads_[data.checked_thread_i].epoch.load();
|
||||
if (checked_thread_epoch % 2 == 1 || checked_thread_epoch / 2 == epoch) {
|
||||
data.checked_thread_i++;
|
||||
if (data.checked_thread_i == threads_.size()) {
|
||||
if (epoch_.compare_exchange_strong(epoch, epoch + 1)) {
|
||||
data.set_epoch(epoch + 1);
|
||||
} else {
|
||||
data.set_epoch(epoch);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void retire_sync(size_t thread_id) {
|
||||
auto &data = threads_[thread_id];
|
||||
|
||||
while (true) {
|
||||
retire(thread_id);
|
||||
data.idle();
|
||||
if (data.undeleted() == 0) {
|
||||
break;
|
||||
}
|
||||
usleep_for(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void retire(size_t thread_id) {
|
||||
auto &data = threads_[thread_id];
|
||||
data.set_epoch(epoch_.load());
|
||||
while (step_check(data) && data.undeleted() != 0) {
|
||||
}
|
||||
}
|
||||
|
||||
void retire(size_t thread_id, T *ptr) {
|
||||
auto &data = threads_[thread_id];
|
||||
data.to_delete[data.bag_i].push_back(unique_ptr<T>{ptr});
|
||||
}
|
||||
};
|
||||
} // namespace td
|
73
tdutils/td/utils/Hash.h
Normal file
73
tdutils/td/utils/Hash.h
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/common.h"
|
||||
|
||||
#if TD_HAVE_ABSL
|
||||
#include <absl/hash/hash.h>
|
||||
#endif
|
||||
|
||||
namespace td {
|
||||
// A simple wrapper for absl::flat_hash_map, std::unordered_map and probably some our implementaion of hash map in
|
||||
// the future
|
||||
|
||||
// We will introduce out own Hashing utility like an absl one.
|
||||
class Hasher {
|
||||
public:
|
||||
Hasher() = default;
|
||||
Hasher(size_t init_value) : hash_(init_value) {
|
||||
}
|
||||
std::size_t finalize() {
|
||||
return hash_;
|
||||
}
|
||||
|
||||
static Hasher combine(Hasher hasher, size_t value) {
|
||||
hasher.hash_ ^= value;
|
||||
return hasher;
|
||||
}
|
||||
|
||||
template <class A, class B>
|
||||
static Hasher combine(Hasher hasher, const std::pair<A, B> &value) {
|
||||
hasher = AbslHashValue(std::move(hasher), value.first);
|
||||
hasher = AbslHashValue(std::move(hasher), value.first);
|
||||
return hasher;
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t hash_{0};
|
||||
};
|
||||
|
||||
template <class IgnoreT>
|
||||
class TdHash {
|
||||
public:
|
||||
template <class T>
|
||||
std::size_t operator()(const T &value) const noexcept {
|
||||
return AbslHashValue(Hasher(), value).finalize();
|
||||
}
|
||||
};
|
||||
|
||||
#if TD_HAVE_ABSL
|
||||
template <class T>
|
||||
using AbslHash = absl::Hash<T>;
|
||||
#endif
|
||||
|
||||
// default hash implementations
|
||||
template <class H, class T>
|
||||
decltype(H::combine(std::declval<H>(), std::declval<T>())) AbslHashValue(H hasher, const T &value) {
|
||||
return H::combine(std::move(hasher), value);
|
||||
}
|
||||
|
||||
#if TD_HAVE_ABSL
|
||||
template <class T>
|
||||
using Hash = AbslHash<T>;
|
||||
#else
|
||||
template <class T>
|
||||
using Hash = TdHash<T>;
|
||||
#endif
|
||||
|
||||
} // namespace td
|
24
tdutils/td/utils/HashMap.h
Normal file
24
tdutils/td/utils/HashMap.h
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/Hash.h"
|
||||
|
||||
#if TD_HAVE_ABSL
|
||||
#include <absl/container/flat_hash_map.h>
|
||||
#else
|
||||
#include <unordered_map>
|
||||
#endif
|
||||
namespace td {
|
||||
#if TD_HAVE_ABSL
|
||||
template <class Key, class Value, class H = Hash<Key>>
|
||||
using HashMap = absl::flat_hash_map<Key, Value, H>;
|
||||
#else
|
||||
template <class Key, class Value, class H = Hash<Key>>
|
||||
using HashMap = std::unordered_map<Key, Value, H>;
|
||||
#endif
|
||||
} // namespace td
|
24
tdutils/td/utils/HashSet.h
Normal file
24
tdutils/td/utils/HashSet.h
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/Hash.h"
|
||||
|
||||
#if TD_HAVE_ABSL
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
#else
|
||||
#include <unordered_set>
|
||||
#endif
|
||||
namespace td {
|
||||
#if TD_HAVE_ABSL
|
||||
template <class Key, class H = Hash<Key>>
|
||||
using HashSet = absl::flat_hash_set<Key, H>;
|
||||
#else
|
||||
template <class Key, class H = Hash<Key>>
|
||||
using HashSet = std::unordered_set<Key, H>;
|
||||
#endif
|
||||
} // namespace td
|
10
tdutils/td/utils/MpmcQueue.cpp
Normal file
10
tdutils/td/utils/MpmcQueue.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/utils/MpmcQueue.h"
|
||||
namespace td {
|
||||
detail::MpmcStat stat_;
|
||||
}
|
129
tdutils/td/utils/OptionsParser.cpp
Normal file
129
tdutils/td/utils/OptionsParser.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/utils/OptionsParser.h"
|
||||
|
||||
#if TD_HAVE_GETOPT
|
||||
#include "getopt.h"
|
||||
#endif
|
||||
|
||||
#if !TD_WINDOWS
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace td {
|
||||
void OptionsParser::set_description(std::string description) {
|
||||
description_ = std::move(description);
|
||||
}
|
||||
|
||||
void OptionsParser::add_option(Option::Type type, char short_key, Slice long_key, Slice description,
|
||||
std::function<Status(Slice)> callback) {
|
||||
options_.push_back(Option{type, short_key, long_key.str(), description.str(), std::move(callback)});
|
||||
}
|
||||
|
||||
void OptionsParser::add_option(char short_key, Slice long_key, Slice description,
|
||||
std::function<Status(Slice)> callback) {
|
||||
add_option(Option::Type::Arg, short_key, long_key, description, std::move(callback));
|
||||
}
|
||||
|
||||
void OptionsParser::add_option(char short_key, Slice long_key, Slice description,
|
||||
std::function<Status(void)> callback) {
|
||||
// Ouch. There must be some better way
|
||||
add_option(Option::Type::NoArg, short_key, long_key, description,
|
||||
std::bind([](std::function<Status(void)> &func, Slice) { return func(); }, std::move(callback),
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
Result<int> OptionsParser::run(int argc, char *argv[]) {
|
||||
#if TD_HAVE_GETOPT
|
||||
char buff[1024];
|
||||
StringBuilder sb(MutableSlice{buff, sizeof(buff)});
|
||||
for (auto &opt : options_) {
|
||||
CHECK(opt.type != Option::OptionalArg);
|
||||
sb << opt.short_key;
|
||||
if (opt.type == Option::Arg) {
|
||||
sb << ":";
|
||||
}
|
||||
}
|
||||
if (sb.is_error()) {
|
||||
return Status::Error("Can't parse options");
|
||||
}
|
||||
CSlice short_options = sb.as_cslice();
|
||||
|
||||
vector<option> long_options;
|
||||
for (auto &opt : options_) {
|
||||
if (opt.long_key.empty()) {
|
||||
continue;
|
||||
}
|
||||
option o;
|
||||
o.flag = nullptr;
|
||||
o.val = opt.short_key;
|
||||
o.has_arg = opt.type == Option::Arg ? required_argument : no_argument;
|
||||
o.name = opt.long_key.c_str();
|
||||
long_options.push_back(o);
|
||||
}
|
||||
long_options.push_back({nullptr, 0, nullptr, 0});
|
||||
|
||||
while (true) {
|
||||
int opt_i = getopt_long(argc, argv, short_options.c_str(), &long_options[0], nullptr);
|
||||
if (opt_i == ':') {
|
||||
return Status::Error("Missing argument");
|
||||
}
|
||||
if (opt_i == '?') {
|
||||
return Status::Error("Unrecognized option");
|
||||
}
|
||||
if (opt_i == -1) {
|
||||
break;
|
||||
}
|
||||
bool found = false;
|
||||
for (auto &opt : options_) {
|
||||
if (opt.short_key == opt_i) {
|
||||
Slice arg;
|
||||
if (opt.type == Option::Arg) {
|
||||
arg = Slice(optarg);
|
||||
}
|
||||
auto status = opt.arg_callback(arg);
|
||||
if (status.is_error()) {
|
||||
return std::move(status);
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return Status::Error("Unknown argument");
|
||||
}
|
||||
}
|
||||
return optind;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
StringBuilder &operator<<(StringBuilder &sb, const OptionsParser &o) {
|
||||
sb << o.description_ << "\n";
|
||||
for (auto &opt : o.options_) {
|
||||
sb << "-" << opt.short_key;
|
||||
if (!opt.long_key.empty()) {
|
||||
sb << "|--" << opt.long_key;
|
||||
}
|
||||
if (opt.type == OptionsParser::Option::OptionalArg) {
|
||||
sb << "[";
|
||||
}
|
||||
if (opt.type != OptionsParser::Option::NoArg) {
|
||||
sb << "<arg>";
|
||||
}
|
||||
if (opt.type == OptionsParser::Option::OptionalArg) {
|
||||
sb << "]";
|
||||
}
|
||||
sb << "\t" << opt.description;
|
||||
sb << "\n";
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
} // namespace td
|
47
tdutils/td/utils/ThreadSafeCounter.h
Normal file
47
tdutils/td/utils/ThreadSafeCounter.h
Normal file
@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
#include "td/utils/port/thread.h"
|
||||
#include "td/utils/port/thread_local.h"
|
||||
#include "td/utils/int_types.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
|
||||
namespace td {
|
||||
class ThreadSafeCounter {
|
||||
public:
|
||||
void add(int64 diff) {
|
||||
auto &node = thread_local_node();
|
||||
node.count_.store(node.count_.load(std::memory_order_relaxed) + diff, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int64 sum() const {
|
||||
int n = max_thread_id_.load();
|
||||
int64 res = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
res += nodes_[i].count_.load();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
std::atomic<int64> count_{0};
|
||||
char padding[128];
|
||||
};
|
||||
static constexpr int MAX_THREAD_ID = 128;
|
||||
std::atomic<int> max_thread_id_{MAX_THREAD_ID};
|
||||
std::array<Node, MAX_THREAD_ID> nodes_;
|
||||
|
||||
Node &thread_local_node() {
|
||||
auto thread_id = get_thread_id();
|
||||
CHECK(0 <= thread_id && static_cast<size_t>(thread_id) < nodes_.size());
|
||||
return nodes_[thread_id];
|
||||
}
|
||||
};
|
||||
} // namespace td
|
34
tdutils/td/utils/port/IoSlice.h
Normal file
34
tdutils/td/utils/port/IoSlice.h
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Span.h"
|
||||
|
||||
#if TD_PORT_POSIX
|
||||
#include <sys/uio.h>
|
||||
#endif
|
||||
|
||||
namespace td {
|
||||
#if TD_PORT_POSIX
|
||||
using IoSlice = struct iovec;
|
||||
inline IoSlice as_io_slice(Slice slice) {
|
||||
IoSlice res;
|
||||
res.iov_len = slice.size();
|
||||
res.iov_base = const_cast<char *>(slice.data());
|
||||
return res;
|
||||
}
|
||||
inline Slice as_slice(const IoSlice io_slice) {
|
||||
return Slice(reinterpret_cast<const char *>(io_slice.iov_base), io_slice.iov_len);
|
||||
}
|
||||
#else
|
||||
using IoSlice = Slice;
|
||||
inline IoSlice as_io_slice(Slice slice) {
|
||||
return slice;
|
||||
}
|
||||
#endif
|
||||
} // namespace td
|
109
tdutils/td/utils/port/MemoryMapping.cpp
Normal file
109
tdutils/td/utils/port/MemoryMapping.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/utils/port/MemoryMapping.h"
|
||||
|
||||
#include "td/utils/misc.h"
|
||||
|
||||
// TODO:
|
||||
// windows,
|
||||
// anonymous map
|
||||
// huge pages?
|
||||
#if TD_WINDOWS
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace td {
|
||||
namespace detail {
|
||||
class MemoryMappingImpl {
|
||||
public:
|
||||
MemoryMappingImpl(MutableSlice data, int64 offset) : data_(data), offset_(offset) {
|
||||
}
|
||||
Slice as_slice() const {
|
||||
return data_.substr(narrow_cast<size_t>(offset_));
|
||||
}
|
||||
MutableSlice as_mutable_slice() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
MutableSlice data_;
|
||||
int64 offset_;
|
||||
};
|
||||
|
||||
Result<int64> get_page_size() {
|
||||
#if TD_WINDOWS
|
||||
return Status::Error("Unimplemented");
|
||||
#else
|
||||
static Result<int64> page_size = []() -> Result<int64> {
|
||||
auto page_size = sysconf(_SC_PAGESIZE);
|
||||
if (page_size < 0) {
|
||||
return OS_ERROR("Can't load page size from sysconf");
|
||||
}
|
||||
return page_size;
|
||||
}();
|
||||
return page_size.clone();
|
||||
#endif
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
Result<MemoryMapping> MemoryMapping::create_anonymous(const MemoryMapping::Options &options) {
|
||||
return Status::Error("Unsupported yet");
|
||||
}
|
||||
Result<MemoryMapping> MemoryMapping::create_from_file(const FileFd &file_fd, const MemoryMapping::Options &options) {
|
||||
#if TD_WINDOWS
|
||||
return Status::Error("Unsupported yet");
|
||||
#else
|
||||
if (file_fd.empty()) {
|
||||
return Status::Error("Can't create memory mapping: file is empty");
|
||||
}
|
||||
TRY_RESULT(stat, file_fd.stat());
|
||||
auto fd = file_fd.get_native_fd().fd();
|
||||
auto begin = options.offset;
|
||||
if (begin < 0) {
|
||||
return Status::Error(PSLICE() << "Can't create memory mapping: negative offset " << options.offset);
|
||||
}
|
||||
|
||||
int64 end;
|
||||
if (options.size < 0) {
|
||||
end = stat.size_;
|
||||
} else {
|
||||
end = begin + stat.size_;
|
||||
}
|
||||
|
||||
TRY_RESULT(page_size, detail::get_page_size());
|
||||
auto fixed_begin = begin / page_size * page_size;
|
||||
|
||||
auto data_offset = begin - fixed_begin;
|
||||
auto data_size = end - fixed_begin;
|
||||
|
||||
void *data = mmap(nullptr, data_size, PROT_READ, MAP_PRIVATE, fd, fixed_begin);
|
||||
if (data == MAP_FAILED) {
|
||||
return OS_ERROR("mmap call failed");
|
||||
}
|
||||
|
||||
return MemoryMapping(std::make_unique<detail::MemoryMappingImpl>(
|
||||
MutableSlice(reinterpret_cast<char *>(data), data_size), data_offset));
|
||||
#endif
|
||||
}
|
||||
|
||||
MemoryMapping::MemoryMapping(MemoryMapping &&other) = default;
|
||||
MemoryMapping &MemoryMapping::operator=(MemoryMapping &&other) = default;
|
||||
MemoryMapping::~MemoryMapping() = default;
|
||||
|
||||
MemoryMapping::MemoryMapping(std::unique_ptr<detail::MemoryMappingImpl> impl) : impl_(std::move(impl)) {
|
||||
}
|
||||
Slice MemoryMapping::as_slice() const {
|
||||
return impl_->as_slice();
|
||||
}
|
||||
MutableSlice MemoryMapping::as_mutable_slice() {
|
||||
return impl_->as_mutable_slice();
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
|
53
tdutils/td/utils/port/MemoryMapping.h
Normal file
53
tdutils/td/utils/port/MemoryMapping.h
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/port/FileFd.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
namespace td {
|
||||
namespace detail {
|
||||
class MemoryMappingImpl;
|
||||
}
|
||||
|
||||
class MemoryMapping {
|
||||
public:
|
||||
struct Options {
|
||||
int64 offset{0};
|
||||
int64 size{-1};
|
||||
|
||||
Options() {
|
||||
}
|
||||
Options &with_offset(int64 new_offset) {
|
||||
offset = new_offset;
|
||||
return *this;
|
||||
}
|
||||
Options &with_size(int64 new_size) {
|
||||
size = new_size;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
static Result<MemoryMapping> create_anonymous(const Options &options = {});
|
||||
static Result<MemoryMapping> create_from_file(const FileFd &file, const Options &options = {});
|
||||
|
||||
Slice as_slice() const;
|
||||
MutableSlice as_mutable_slice(); // returns empty slice if memory is read-only
|
||||
|
||||
MemoryMapping(const MemoryMapping &other) = delete;
|
||||
const MemoryMapping &operator=(const MemoryMapping &other) = delete;
|
||||
MemoryMapping(MemoryMapping &&other);
|
||||
MemoryMapping &operator=(MemoryMapping &&other);
|
||||
~MemoryMapping();
|
||||
|
||||
private:
|
||||
std::unique_ptr<detail::MemoryMappingImpl> impl_;
|
||||
explicit MemoryMapping(std::unique_ptr<detail::MemoryMappingImpl> impl);
|
||||
};
|
||||
} // namespace td
|
105
tdutils/td/utils/port/stacktrace.cpp
Normal file
105
tdutils/td/utils/port/stacktrace.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/utils/port/stacktrace.h"
|
||||
#include "td/utils/port/signals.h"
|
||||
|
||||
#if !TD_WINDOWS && !TD_ANDROID && !TD_FREEBSD
|
||||
#include <execinfo.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if TD_LINUX
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
namespace td {
|
||||
namespace {
|
||||
void print_backtrace(void) {
|
||||
#if !TD_WINDOWS && !TD_ANDROID && !TD_FREEBSD
|
||||
void *buffer[128];
|
||||
int nptrs = backtrace(buffer, 128);
|
||||
td::signal_safe_write("------- Stack Backtrace -------\n", false);
|
||||
backtrace_symbols_fd(buffer, nptrs, 2);
|
||||
td::signal_safe_write("-------------------------------\n", false);
|
||||
#endif
|
||||
}
|
||||
void print_backtrace_gdb(void) {
|
||||
#if !TD_WINDOWS && !TD_DARWIN && !TD_ANDROID
|
||||
char pid_buf[30], *pid_buf_begin = pid_buf + sizeof(pid_buf);
|
||||
pid_t pid = getpid();
|
||||
*--pid_buf_begin = '\0';
|
||||
do {
|
||||
*--pid_buf_begin = static_cast<char>(pid % 10 + '0');
|
||||
pid /= 10;
|
||||
} while (pid > 0);
|
||||
|
||||
char name_buf[512];
|
||||
ssize_t res = readlink("/proc/self/exe", name_buf, 511); // TODO works only under Linux
|
||||
if (res >= 0) {
|
||||
name_buf[res] = 0;
|
||||
|
||||
#if TD_LINUX
|
||||
if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) {
|
||||
td::signal_safe_write("Can't set dumpable\n");
|
||||
return;
|
||||
}
|
||||
#if defined(PR_SET_PTRACER)
|
||||
// We can't use td::EventFd because we are in a signal handler
|
||||
int fds[2];
|
||||
bool need_set_ptracer = true;
|
||||
if (pipe(fds) < 0) {
|
||||
need_set_ptracer = false;
|
||||
td::signal_safe_write("Can't create a pipe\n");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int child_pid = fork();
|
||||
if (child_pid < 0) {
|
||||
td::signal_safe_write("Can't fork() to run gdb\n");
|
||||
return;
|
||||
}
|
||||
if (!child_pid) {
|
||||
#if TD_LINUX && defined(PR_SET_PTRACER)
|
||||
if (need_set_ptracer) {
|
||||
char c;
|
||||
read(fds[0], &c, 1);
|
||||
}
|
||||
#endif
|
||||
dup2(2, 1); // redirect output to stderr
|
||||
execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "thread apply all bt full", name_buf, pid_buf_begin,
|
||||
NULL);
|
||||
return;
|
||||
} else {
|
||||
#if TD_LINUX && defined(PR_SET_PTRACER)
|
||||
if (need_set_ptracer) {
|
||||
if (prctl(PR_SET_PTRACER, child_pid, 0, 0, 0) < 0) {
|
||||
td::signal_safe_write("Can't set ptracer\n");
|
||||
}
|
||||
if (write(fds[1], "a", 1) != 1) {
|
||||
td::signal_safe_write("Can't write to pipe\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
waitpid(child_pid, nullptr, 0);
|
||||
}
|
||||
} else {
|
||||
td::signal_safe_write("Can't get name of executable file to pass to gdb\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Stacktrace::print_to_stderr(const PrintOptions &options) {
|
||||
if (options.use_gdb) {
|
||||
print_backtrace_gdb();
|
||||
}
|
||||
print_backtrace();
|
||||
}
|
||||
} // namespace td
|
19
tdutils/td/utils/port/stacktrace.h
Normal file
19
tdutils/td/utils/port/stacktrace.h
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
namespace td {
|
||||
class Stacktrace {
|
||||
public:
|
||||
struct PrintOptions {
|
||||
bool use_gdb = false;
|
||||
PrintOptions() {
|
||||
}
|
||||
};
|
||||
static void print_to_stderr(const PrintOptions &options = PrintOptions());
|
||||
};
|
||||
|
||||
} // namespace td
|
270
tdutils/td/utils/uint128.h
Normal file
270
tdutils/td/utils/uint128.h
Normal file
@ -0,0 +1,270 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/bits.h"
|
||||
namespace td {
|
||||
|
||||
class uint128_emulated {
|
||||
public:
|
||||
using uint128 = uint128_emulated;
|
||||
uint128_emulated(uint64 hi, uint64 lo) : hi_(hi), lo_(lo) {
|
||||
}
|
||||
uint128_emulated(uint64 lo) : uint128_emulated(0, lo) {
|
||||
}
|
||||
uint128_emulated() = default;
|
||||
uint64 hi() const {
|
||||
return hi_;
|
||||
}
|
||||
uint64 lo() const {
|
||||
return lo_;
|
||||
}
|
||||
static uint128 from_signed(int64 x) {
|
||||
if (x >= 0) {
|
||||
return uint128(0, x);
|
||||
}
|
||||
return uint128(std::numeric_limits<uint64>::max(), static_cast<uint64>(x));
|
||||
}
|
||||
|
||||
uint128 add(uint128 other) const {
|
||||
uint128 res(other.hi() + hi(), other.lo() + lo());
|
||||
if (res.lo() < lo()) {
|
||||
res.hi_++;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
uint128 shl(int cnt) const {
|
||||
if (cnt >= 128) {
|
||||
return uint128();
|
||||
}
|
||||
if (cnt == 0) {
|
||||
return *this;
|
||||
}
|
||||
if (cnt < 64) {
|
||||
return uint128((hi() << cnt) | (lo() >> (64 - cnt)), lo() << cnt);
|
||||
}
|
||||
return uint128(lo() << (cnt - 64), 0);
|
||||
}
|
||||
uint128 shr(int cnt) const {
|
||||
if (cnt >= 128) {
|
||||
return uint128();
|
||||
}
|
||||
if (cnt == 0) {
|
||||
return *this;
|
||||
}
|
||||
if (cnt < 64) {
|
||||
return uint128(hi() >> cnt, (lo() >> cnt) | (hi() << (64 - cnt)));
|
||||
}
|
||||
return uint128(0, hi() >> (cnt - 64));
|
||||
}
|
||||
|
||||
uint128 mult(uint128 other) const {
|
||||
uint64 a_lo = lo() & 0xffffffff;
|
||||
uint64 a_hi = lo() >> 32;
|
||||
uint64 b_lo = other.lo() & 0xffffffff;
|
||||
uint64 b_hi = other.lo() >> 32;
|
||||
uint128 res(lo() * other.hi() + hi() * other.lo() + a_hi * b_hi, a_lo * b_lo);
|
||||
uint128 add1(0, a_lo * b_hi);
|
||||
uint128 add2(0, a_hi * b_lo);
|
||||
return res.add(add1.shl(32)).add(add2.shl(32));
|
||||
}
|
||||
uint128 mult(uint64 other) const {
|
||||
return mult(uint128(0, other));
|
||||
}
|
||||
uint128 mult_signed(int64 other) const {
|
||||
return mult(uint128::from_signed(other));
|
||||
}
|
||||
bool is_zero() const {
|
||||
return lo() == 0 && hi() == 0;
|
||||
}
|
||||
uint128 sub(uint128 other) const {
|
||||
uint32 carry = 0;
|
||||
if (other.lo() > lo()) {
|
||||
carry = 1;
|
||||
}
|
||||
return uint128(hi() - other.hi() - carry, lo() - other.lo());
|
||||
}
|
||||
void divmod(uint128 other, uint128 *div_res, uint128 *mod_res) const {
|
||||
CHECK(!other.is_zero());
|
||||
|
||||
auto from = *this;
|
||||
auto ctz = from.count_leading_zeroes();
|
||||
auto other_ctz = other.count_leading_zeroes();
|
||||
if (ctz > other_ctz) {
|
||||
*div_res = uint128();
|
||||
*mod_res = from;
|
||||
return;
|
||||
}
|
||||
auto shift = other_ctz - ctz;
|
||||
auto res = uint128();
|
||||
for (int i = shift; i >= 0; i--) {
|
||||
auto sub = other.shl(i);
|
||||
res = res.shl(1);
|
||||
if (from.greater_or_equal(sub)) {
|
||||
from = from.sub(sub);
|
||||
res = res.set_lower_bit();
|
||||
}
|
||||
}
|
||||
|
||||
*div_res = res;
|
||||
*mod_res = from;
|
||||
}
|
||||
uint128 div(uint128 other) const {
|
||||
uint128 a, b;
|
||||
divmod(other, &a, &b);
|
||||
return a;
|
||||
}
|
||||
uint128 mod(uint128 other) const {
|
||||
uint128 a, b;
|
||||
divmod(other, &a, &b);
|
||||
return b;
|
||||
}
|
||||
|
||||
void divmod_signed(int64 y, int64 *quot, int64 *rem) const {
|
||||
CHECK(y != 0);
|
||||
auto x = *this;
|
||||
int x_sgn = x.is_negative();
|
||||
int y_sgn = y < 0;
|
||||
if (x_sgn) {
|
||||
x = x.negate();
|
||||
}
|
||||
uint128 uy = from_signed(y);
|
||||
if (uy.is_negative()) {
|
||||
uy = uy.negate();
|
||||
}
|
||||
|
||||
uint128 t_quot, t_mod;
|
||||
x.divmod(uy, &t_quot, &t_mod);
|
||||
*quot = t_quot.lo();
|
||||
*rem = t_mod.lo();
|
||||
if (x_sgn != y_sgn) {
|
||||
*quot = -*quot;
|
||||
}
|
||||
if (x_sgn) {
|
||||
*rem = -*rem;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint64 hi_{0};
|
||||
uint64 lo_{0};
|
||||
|
||||
bool is_negative() const {
|
||||
return static_cast<int64>(hi_) < 0;
|
||||
}
|
||||
|
||||
int32 count_leading_zeroes() const {
|
||||
if (hi() == 0) {
|
||||
return 64 + count_leading_zeroes64(lo());
|
||||
}
|
||||
return count_leading_zeroes64(hi());
|
||||
}
|
||||
uint128 set_lower_bit() const {
|
||||
return uint128(hi(), lo() | 1);
|
||||
}
|
||||
bool greater_or_equal(uint128 other) const {
|
||||
return hi() > other.hi() || (hi() == other.hi() && lo() >= other.lo());
|
||||
}
|
||||
uint128 negate() const {
|
||||
uint128 res(~hi(), ~lo() + 1);
|
||||
if (res.lo() == 0) {
|
||||
return uint128(res.hi() + 1, 0);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
#if TD_HAVE_INT128
|
||||
class uint128_intrinsic {
|
||||
public:
|
||||
using ValueT = unsigned __int128;
|
||||
using uint128 = uint128_intrinsic;
|
||||
explicit uint128_intrinsic(ValueT value) : value_(value) {
|
||||
}
|
||||
uint128_intrinsic(uint64 hi, uint64 lo) : value_((ValueT(hi) << 64) | lo) {
|
||||
}
|
||||
uint128_intrinsic() = default;
|
||||
|
||||
static uint128 from_signed(int64 x) {
|
||||
return uint128(static_cast<ValueT>(x));
|
||||
}
|
||||
uint64 hi() const {
|
||||
return uint64(value() >> 64);
|
||||
}
|
||||
uint64 lo() const {
|
||||
return uint64(value() & std::numeric_limits<uint64>::max());
|
||||
}
|
||||
|
||||
uint128 add(uint128 other) const {
|
||||
return uint128(value() + other.value());
|
||||
}
|
||||
uint128 sub(uint128 other) const {
|
||||
return uint128(value() - other.value());
|
||||
}
|
||||
|
||||
uint128 shl(int cnt) const {
|
||||
if (cnt >= 128) {
|
||||
return uint128();
|
||||
}
|
||||
return uint128(value() << cnt);
|
||||
}
|
||||
|
||||
uint128 shr(int cnt) const {
|
||||
if (cnt >= 128) {
|
||||
return uint128();
|
||||
}
|
||||
return uint128(value() >> cnt);
|
||||
}
|
||||
|
||||
uint128 mult(uint128 other) const {
|
||||
return uint128(value() * other.value());
|
||||
}
|
||||
uint128 mult(uint64 other) const {
|
||||
return uint128(value() * other);
|
||||
}
|
||||
uint128 mult_signed(int64 other) const {
|
||||
return uint128(value() * other);
|
||||
}
|
||||
bool is_zero() const {
|
||||
return value() == 0;
|
||||
}
|
||||
void divmod(uint128 other, uint128 *div_res, uint128 *mod_res) const {
|
||||
CHECK(!other.is_zero());
|
||||
*div_res = uint128(value() / other.value());
|
||||
*mod_res = uint128(value() % other.value());
|
||||
}
|
||||
uint128 div(uint128 other) const {
|
||||
CHECK(!other.is_zero());
|
||||
return uint128(value() / other.value());
|
||||
}
|
||||
uint128 mod(uint128 other) const {
|
||||
CHECK(!other.is_zero());
|
||||
return uint128(value() % other.value());
|
||||
}
|
||||
void divmod_signed(int64 y, int64 *quot, int64 *rem) const {
|
||||
CHECK(y != 0);
|
||||
*quot = (int64)(signed_value() / y);
|
||||
*rem = (int64)(signed_value() % y);
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned __int128 value_{0};
|
||||
ValueT value() const {
|
||||
return value_;
|
||||
}
|
||||
__int128 signed_value() const {
|
||||
return static_cast<__int128>(value());
|
||||
}
|
||||
};
|
||||
#endif
|
||||
#if TD_HAVE_INT128
|
||||
using uint128 = uint128_intrinsic;
|
||||
#else
|
||||
using uint128 = uint128_emulated;
|
||||
#endif
|
||||
} // namespace td
|
242
tdutils/test/ConcurrentHashMap.cpp
Normal file
242
tdutils/test/ConcurrentHashMap.cpp
Normal file
@ -0,0 +1,242 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include <cstdio>
|
||||
#include "td/utils/tests.h"
|
||||
#include "td/utils/benchmark.h"
|
||||
#include "td/utils/SpinLock.h"
|
||||
#include "td/utils/HazardPointers.h"
|
||||
#include "td/utils/ConcurrentHashTable.h"
|
||||
#include <algorithm>
|
||||
|
||||
#if TD_HAVE_ABSL
|
||||
#include <absl/container/flat_hash_map.h>
|
||||
#else
|
||||
#include <unordered_map>
|
||||
#endif
|
||||
|
||||
#if TD_WITH_JUNCTION
|
||||
#include <third-party/libcuckoo/libcuckoo/cuckoohash_map.hh>
|
||||
#endif
|
||||
|
||||
#if TD_WITH_JUNCTION
|
||||
#include <junction/ConcurrentMap_Grampa.h>
|
||||
#include <junction/ConcurrentMap_Linear.h>
|
||||
#include <junction/ConcurrentMap_Leapfrog.h>
|
||||
#endif
|
||||
namespace td {
|
||||
|
||||
// Non resizable HashMap. Just an example
|
||||
template <class KeyT, class ValueT>
|
||||
class ArrayHashMap {
|
||||
public:
|
||||
ArrayHashMap(size_t n) : array_(n) {
|
||||
}
|
||||
struct Node {
|
||||
std::atomic<KeyT> key{KeyT{}};
|
||||
std::atomic<ValueT> value{};
|
||||
};
|
||||
static std::string get_name() {
|
||||
return "ArrayHashMap";
|
||||
}
|
||||
KeyT empty_key() const {
|
||||
return KeyT{};
|
||||
}
|
||||
|
||||
void insert(KeyT key, ValueT value) {
|
||||
array_.with_value(key, true, [&](auto &node_value) { node_value.store(value, std::memory_order_release); });
|
||||
}
|
||||
ValueT find(KeyT key, ValueT value) {
|
||||
array_.with_value(key, false, [&](auto &node_value) { value = node_value.load(std::memory_order_acquire); });
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
AtomicHashArray<KeyT, std::atomic<ValueT>> array_;
|
||||
};
|
||||
|
||||
template <class KeyT, class ValueT>
|
||||
class ConcurrentHashMapMutex {
|
||||
public:
|
||||
ConcurrentHashMapMutex(size_t) {
|
||||
}
|
||||
static std::string get_name() {
|
||||
return "ConcurrentHashMapMutex";
|
||||
}
|
||||
void insert(KeyT key, ValueT value) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
hash_map_.emplace(key, value);
|
||||
}
|
||||
ValueT find(KeyT key, ValueT default_value) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
auto it = hash_map_.find(key);
|
||||
if (it == hash_map_.end()) {
|
||||
return default_value;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
#if TD_HAVE_ABSL
|
||||
absl::flat_hash_map<KeyT, ValueT> hash_map_;
|
||||
#else
|
||||
std::unordered_map<KeyT, ValueT> hash_map_;
|
||||
#endif
|
||||
};
|
||||
template <class KeyT, class ValueT>
|
||||
class ConcurrentHashMapSpinlock {
|
||||
public:
|
||||
ConcurrentHashMapSpinlock(size_t) {
|
||||
}
|
||||
static std::string get_name() {
|
||||
return "ConcurrentHashMapSpinlock";
|
||||
}
|
||||
void insert(KeyT key, ValueT value) {
|
||||
auto guard = spinlock_.lock();
|
||||
hash_map_.emplace(key, value);
|
||||
}
|
||||
ValueT find(KeyT key, ValueT default_value) {
|
||||
auto guard = spinlock_.lock();
|
||||
auto it = hash_map_.find(key);
|
||||
if (it == hash_map_.end()) {
|
||||
return default_value;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
private:
|
||||
td::SpinLock spinlock_;
|
||||
#if TD_HAVE_ABSL
|
||||
absl::flat_hash_map<KeyT, ValueT> hash_map_;
|
||||
#else
|
||||
std::unordered_map<KeyT, ValueT> hash_map_;
|
||||
#endif
|
||||
};
|
||||
#if TD_WITH_LIBCUCKOO
|
||||
template <class KeyT, class ValueT>
|
||||
class ConcurrentHashMapLibcuckoo {
|
||||
public:
|
||||
ConcurrentHashMapLibcuckoo(size_t) {
|
||||
}
|
||||
static std::string get_name() {
|
||||
return "ConcurrentHashMapLibcuckoo";
|
||||
}
|
||||
void insert(KeyT key, ValueT value) {
|
||||
hash_map_.insert(key, value);
|
||||
}
|
||||
ValueT find(KeyT key, ValueT default_value) {
|
||||
hash_map_.find(key, default_value);
|
||||
return default_value;
|
||||
}
|
||||
|
||||
private:
|
||||
cuckoohash_map<KeyT, ValueT> hash_map_;
|
||||
};
|
||||
#endif
|
||||
#if TD_WITH_JUNCTION
|
||||
template <class KeyT, class ValueT>
|
||||
class ConcurrentHashMapJunction {
|
||||
public:
|
||||
ConcurrentHashMapJunction(size_t size) : hash_map_() {
|
||||
}
|
||||
static std::string get_name() {
|
||||
return "ConcurrentHashMapJunction";
|
||||
}
|
||||
void insert(KeyT key, ValueT value) {
|
||||
hash_map_.assign(key, value);
|
||||
}
|
||||
ValueT find(KeyT key, ValueT default_value) {
|
||||
return hash_map_.get(key);
|
||||
}
|
||||
~ConcurrentHashMapJunction() {
|
||||
junction::DefaultQSBR.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
junction::ConcurrentMap_Leapfrog<KeyT, ValueT> hash_map_;
|
||||
};
|
||||
#endif
|
||||
} // namespace td
|
||||
|
||||
template <class HashMap>
|
||||
class HashMapBenchmark : public td::Benchmark {
|
||||
struct Query {
|
||||
int key;
|
||||
int value;
|
||||
};
|
||||
std::vector<Query> queries;
|
||||
std::unique_ptr<HashMap> hash_map;
|
||||
|
||||
size_t threads_n = 16;
|
||||
int mod_;
|
||||
constexpr static size_t mul_ = 7273; //1000000000 + 7;
|
||||
int n_;
|
||||
|
||||
public:
|
||||
HashMapBenchmark(size_t threads_n) : threads_n(threads_n) {
|
||||
}
|
||||
std::string get_description() const override {
|
||||
return hash_map->get_name();
|
||||
}
|
||||
void start_up_n(int n) override {
|
||||
n *= (int)threads_n;
|
||||
n_ = n;
|
||||
hash_map = std::make_unique<HashMap>(n * 2);
|
||||
}
|
||||
|
||||
void run(int n) override {
|
||||
n = n_;
|
||||
std::vector<td::thread> threads;
|
||||
|
||||
for (size_t i = 0; i < threads_n; i++) {
|
||||
size_t l = n * i / threads_n;
|
||||
size_t r = n * (i + 1) / threads_n;
|
||||
threads.emplace_back([l, r, this] {
|
||||
for (size_t i = l; i < r; i++) {
|
||||
auto x = int((i + 1) * mul_ % n_) + 3;
|
||||
auto y = int(i + 2);
|
||||
hash_map->insert(x, y);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (auto &thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void tear_down() override {
|
||||
for (int i = 0; i < n_; i++) {
|
||||
auto x = int((i + 1) * mul_ % n_) + 3;
|
||||
auto y = int(i + 2);
|
||||
ASSERT_EQ(y, hash_map->find(x, -1));
|
||||
}
|
||||
queries.clear();
|
||||
hash_map.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
template <class HashMap>
|
||||
void bench_hash_map() {
|
||||
td::bench(HashMapBenchmark<HashMap>(16));
|
||||
td::bench(HashMapBenchmark<HashMap>(1));
|
||||
}
|
||||
|
||||
TEST(ConcurrentHashMap, Benchmark) {
|
||||
bench_hash_map<td::ConcurrentHashMap<int, int>>();
|
||||
bench_hash_map<td::ArrayHashMap<int, int>>();
|
||||
bench_hash_map<td::ConcurrentHashMapSpinlock<int, int>>();
|
||||
bench_hash_map<td::ConcurrentHashMapMutex<int, int>>();
|
||||
#if TD_WITH_LIBCUCKOO
|
||||
bench_hash_map<td::ConcurrentHashMapLibcuckoo<int, int>>();
|
||||
#endif
|
||||
#if TD_WITH_JUNCTION
|
||||
bench_hash_map<td::ConcurrentHashMapJunction<int, int>>();
|
||||
#endif
|
||||
}
|
||||
|
67
tdutils/test/EpochBasedMemoryReclamation.cpp
Normal file
67
tdutils/test/EpochBasedMemoryReclamation.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/utils/EpochBasedMemoryReclamation.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/port/thread.h"
|
||||
#include "td/utils/Random.h"
|
||||
#include "td/utils/tests.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#if !TD_THREAD_UNSUPPORTED
|
||||
TEST(EpochBaseMemoryReclamation, stress) {
|
||||
struct Node {
|
||||
std::atomic<std::string *> name_;
|
||||
char pad[64];
|
||||
};
|
||||
|
||||
int threads_n = 10;
|
||||
std::vector<Node> nodes(threads_n);
|
||||
td::EpochBasedMemoryReclamation<std::string> ebmr(threads_n + 1);
|
||||
auto locker = ebmr.get_locker(threads_n);
|
||||
locker.lock();
|
||||
locker.unlock();
|
||||
std::vector<td::thread> threads(threads_n);
|
||||
int thread_id = 0;
|
||||
for (auto &thread : threads) {
|
||||
thread = td::thread([&, thread_id] {
|
||||
auto locker = ebmr.get_locker(thread_id);
|
||||
locker.lock();
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
auto &node = nodes[td::Random::fast(0, threads_n - 1)];
|
||||
auto *str = node.name_.load(std::memory_order_acquire);
|
||||
if (str) {
|
||||
CHECK(*str == "one" || *str == "twotwo");
|
||||
}
|
||||
if ((i + 1) % 100 == 0) {
|
||||
locker.retire();
|
||||
}
|
||||
if (td::Random::fast(0, 5) == 0) {
|
||||
std::string *new_str = new std::string(td::Random::fast(0, 1) == 0 ? "one" : "twotwo");
|
||||
if (node.name_.compare_exchange_strong(str, new_str, std::memory_order_acq_rel)) {
|
||||
locker.retire(str);
|
||||
} else {
|
||||
delete new_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
locker.retire_sync();
|
||||
locker.unlock();
|
||||
});
|
||||
thread_id++;
|
||||
}
|
||||
for (auto &thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
LOG(ERROR) << "Undeleted pointers: " << ebmr.to_delete_size_unsafe();
|
||||
//CHECK(static_cast<int>(ebmr.to_delete_size_unsafe()) <= threads_n * threads_n);
|
||||
for (int i = 0; i < threads_n; i++) {
|
||||
ebmr.get_locker(i).retire_sync();
|
||||
}
|
||||
CHECK(ebmr.to_delete_size_unsafe() == 0);
|
||||
}
|
||||
#endif //!TD_THREAD_UNSUPPORTED
|
Loading…
Reference in New Issue
Block a user