first commit
This commit is contained in:
commit
e1f1e71f9a
17
.clang-format
Normal file
17
.clang-format
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# .clang-format
|
||||||
|
|
||||||
|
# 风格格式化
|
||||||
|
BasedOnStyle: Google
|
||||||
|
# 4 空格缩进
|
||||||
|
IndentWidth: 4
|
||||||
|
# 连续对齐变量的声明
|
||||||
|
AlignConsecutiveDeclarations: true
|
||||||
|
# 指针左侧对齐
|
||||||
|
PointerAlignment: Left
|
||||||
|
# 访问说明符(public、private等)的偏移
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
# 大括号
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BraceWrapping:
|
||||||
|
# 函数定义后面大括号在新行
|
||||||
|
AfterFunction: true
|
7
.clangd
Normal file
7
.clangd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Hover:
|
||||||
|
ShowAKA: Yes
|
||||||
|
Diagnostics:
|
||||||
|
UnusedIncludes: None
|
||||||
|
Suppress: [anon_type_definition]
|
||||||
|
ClangTidy:
|
||||||
|
Remove: misc-unused-alias-decls
|
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
*.smod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
cmake-*
|
||||||
|
build
|
||||||
|
.vs
|
||||||
|
.idea
|
||||||
|
out
|
35
.vscode/settings.json
vendored
Normal file
35
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"files.autoSave": "onFocusChange",
|
||||||
|
"editor.fontSize": 17,
|
||||||
|
"editor.fontFamily": "'Operator Mono Lig Light', 'Operator Mono Lig Light', 'Operator Mono Lig Light'",
|
||||||
|
"cmake.configureOnOpen": true,
|
||||||
|
"cmake.debugConfig": {
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
"cmake.options.statusBarVisibility": "visible",
|
||||||
|
"cmake.generator": "Ninja",
|
||||||
|
"C_Cpp.intelliSenseEngine": "disabled",
|
||||||
|
"clangd.arguments": [
|
||||||
|
"--header-insertion=never",
|
||||||
|
"--all-scopes-completion",
|
||||||
|
"--completion-style=detailed",
|
||||||
|
"--clang-tidy",
|
||||||
|
"-j=4",
|
||||||
|
"--pch-storage=memory",
|
||||||
|
"--compile-commands-dir=build",
|
||||||
|
"--background-index",
|
||||||
|
"--ranking-model=heuristics",
|
||||||
|
"--query-driver=/usr/bin/g++"
|
||||||
|
],
|
||||||
|
"editor.inlayHints.enabled": "off",
|
||||||
|
"editor.unicodeHighlight.allowedLocales": {
|
||||||
|
"ja": true,
|
||||||
|
"zh-hans": true
|
||||||
|
},
|
||||||
|
"files.associations": {
|
||||||
|
"iostream": "cpp"
|
||||||
|
},
|
||||||
|
"workbench.colorCustomizations": {
|
||||||
|
//"editor.background": "#C0C0C0"
|
||||||
|
}
|
||||||
|
}
|
12
CMakeLists.txt
Normal file
12
CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.8)
|
||||||
|
project(skip_use)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
add_compile_options(/source-charset:utf-8)
|
||||||
|
add_compile_options(/EHsc)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_executable(skip_use main.cpp MSkipList.hpp)
|
||||||
|
add_executable(skip_test stress.cpp MSkipList.hpp)
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 taynpg
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
243
MSkipList.hpp
Normal file
243
MSkipList.hpp
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
#ifndef MSKIPLIST_HEADER
|
||||||
|
#define MSKIPLIST_HEADER
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include <random>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
class SkipList {
|
||||||
|
public:
|
||||||
|
// 插入数据
|
||||||
|
void insert(const T& key, const P& value);
|
||||||
|
// 删除数据
|
||||||
|
void remove(const T& key);
|
||||||
|
// 查询数据
|
||||||
|
bool search(const T& key, P& value);
|
||||||
|
// 是否包含某个数据
|
||||||
|
bool contains(const T& key);
|
||||||
|
// 当前表节点个数
|
||||||
|
std::size_t count() const;
|
||||||
|
// 清除表
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
public:
|
||||||
|
SkipList(int max_level = 12);
|
||||||
|
~SkipList();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct SkipNode;
|
||||||
|
std::size_t count_{};
|
||||||
|
SkipNode** pre_{};
|
||||||
|
SkipNode* header_{};
|
||||||
|
const int max_level_{}; // 限定的最大高度
|
||||||
|
std::atomic_int cur_max_height_{}; // 当前使用的最大高度
|
||||||
|
std::random_device rd_{};
|
||||||
|
std::mt19937 gen_{};
|
||||||
|
std::uniform_int_distribution<int> dis_{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
int random_level();
|
||||||
|
SkipNode* find_node(const T& key);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
SkipList<T, P>::~SkipList()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
delete[] pre_;
|
||||||
|
delete header_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
void SkipList<T, P>::clear()
|
||||||
|
{
|
||||||
|
SkipNode* start = header_->get_no_bar(0);
|
||||||
|
while (start) {
|
||||||
|
SkipNode* n = start;
|
||||||
|
start = start->get_no_bar(0);
|
||||||
|
delete n;
|
||||||
|
--count_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
inline int SkipList<T, P>::random_level()
|
||||||
|
{
|
||||||
|
static const int base_ = 2;
|
||||||
|
int height = 1;
|
||||||
|
while (height < max_level_ && (dis_(gen_) % base_) == 0) {
|
||||||
|
++height;
|
||||||
|
}
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
struct SkipList<T, P>::SkipNode {
|
||||||
|
T key_{};
|
||||||
|
P value_{};
|
||||||
|
int level_{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SkipNode(int level = 1) { alloc(level); }
|
||||||
|
~SkipNode() { release(); }
|
||||||
|
SkipNode(const T& key, const P& value, int level)
|
||||||
|
{
|
||||||
|
key_ = key;
|
||||||
|
value_ = value;
|
||||||
|
alloc(level);
|
||||||
|
}
|
||||||
|
void alloc(int max_level)
|
||||||
|
{
|
||||||
|
if (max_level < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
release();
|
||||||
|
next_ = new std::atomic<SkipNode*>[max_level] {};
|
||||||
|
}
|
||||||
|
void release() { delete[] next_; }
|
||||||
|
SkipNode* get(int n)
|
||||||
|
{
|
||||||
|
assert(n >= 0);
|
||||||
|
return next_[n].load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
SkipNode* get_no_bar(int n)
|
||||||
|
{
|
||||||
|
assert(n >= 0);
|
||||||
|
return next_[n].load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
void set(int n, SkipNode* node)
|
||||||
|
{
|
||||||
|
assert(n >= 0);
|
||||||
|
next_[n].store(node, std::memory_order_release);
|
||||||
|
}
|
||||||
|
void set_no_bar(int n, SkipNode* node)
|
||||||
|
{
|
||||||
|
assert(n >= 0);
|
||||||
|
next_[n].store(node, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<SkipNode*>* next_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
inline SkipList<T, P>::SkipList(int max_level)
|
||||||
|
: max_level_(max_level),
|
||||||
|
cur_max_height_(1),
|
||||||
|
gen_(rd_()),
|
||||||
|
dis_(0, std::numeric_limits<int>::max())
|
||||||
|
{
|
||||||
|
assert(max_level_ > 0);
|
||||||
|
header_ = new SkipNode(max_level_);
|
||||||
|
pre_ = new SkipNode*[max_level_];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
typename SkipList<T, P>::SkipNode* SkipList<T, P>::find_node(const T& key)
|
||||||
|
{
|
||||||
|
memset(pre_, 0x0, sizeof(SkipNode*) * max_level_);
|
||||||
|
SkipNode* x = header_;
|
||||||
|
int level = cur_max_height_.load() - 1;
|
||||||
|
while (true) {
|
||||||
|
SkipNode* next = x->get(level);
|
||||||
|
if (next && next->key_ < key) {
|
||||||
|
x = next;
|
||||||
|
} else {
|
||||||
|
pre_[level] = x;
|
||||||
|
if (level == 0) {
|
||||||
|
return next;
|
||||||
|
} else {
|
||||||
|
--level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
inline bool SkipList<T, P>::contains(const T& key)
|
||||||
|
{
|
||||||
|
SkipNode* x = find_node(key);
|
||||||
|
if (x && x->key_ == key) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
inline std::size_t SkipList<T, P>::count() const
|
||||||
|
{
|
||||||
|
return count_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
inline void SkipList<T, P>::insert(const T& key, const P& value)
|
||||||
|
{
|
||||||
|
SkipNode* x = find_node(key);
|
||||||
|
|
||||||
|
if (x && x->key_ == key) {
|
||||||
|
x->value_ = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int height = random_level();
|
||||||
|
if (height > cur_max_height_) {
|
||||||
|
for (int i = cur_max_height_; i < height; ++i) {
|
||||||
|
pre_[i] = header_;
|
||||||
|
}
|
||||||
|
cur_max_height_.store(height, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
x = new SkipNode(key, value, height);
|
||||||
|
for (int i = 0; i < height; ++i) {
|
||||||
|
x->set_no_bar(i, pre_[i]->get_no_bar(i));
|
||||||
|
pre_[i]->set(i, x);
|
||||||
|
}
|
||||||
|
++count_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
inline void SkipList<T, P>::remove(const T& key)
|
||||||
|
{
|
||||||
|
memset(pre_, 0x0, sizeof(SkipNode*) * max_level_);
|
||||||
|
SkipNode* x = header_;
|
||||||
|
SkipNode* purpose = nullptr;
|
||||||
|
int level = cur_max_height_.load() - 1;
|
||||||
|
while (true) {
|
||||||
|
if (level < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SkipNode* next = x->get(level);
|
||||||
|
if (!next) {
|
||||||
|
--level;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next->key_ < key) {
|
||||||
|
x = next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next->key_ == key) {
|
||||||
|
SkipNode* nx = next->get_no_bar(level);
|
||||||
|
x->set_no_bar(level, nx);
|
||||||
|
purpose = next;
|
||||||
|
}
|
||||||
|
--level;
|
||||||
|
}
|
||||||
|
delete purpose;
|
||||||
|
--count_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename P>
|
||||||
|
inline bool SkipList<T, P>::search(const T& key, P& value)
|
||||||
|
{
|
||||||
|
SkipNode* x = find_node(key);
|
||||||
|
if (x && x->key_ == key) {
|
||||||
|
value = x->value_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
17
README.md
Normal file
17
README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# MSkipList
|
||||||
|
|
||||||
|
一个根据LevelDb源码修改的直接可用的跳表,纯C++11标准库,跨平台。
|
||||||
|
|
||||||
|
# 压力测试
|
||||||
|
|
||||||
|
参考[Skiplist-CPP](https://github.com/youngyangyang04/Skiplist-CPP)的压力测试代码:
|
||||||
|
|
||||||
|
测得在 Visual Studio 2022 x64 release环境下(Windows 10 Pro):
|
||||||
|
|
||||||
|
- 随机写(10次均值):0.05002037,即约20w条/s。
|
||||||
|
- 随机读(10次均值):0.0369919,即约27w条/s。
|
||||||
|
|
||||||
|
# 说明
|
||||||
|
|
||||||
|
- 非线程安全,使用自行加锁。
|
||||||
|
- 仅使用`MSkipList.hpp`即可。
|
36
main.cpp
Normal file
36
main.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include "MSkipList.hpp"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
// 基本使用
|
||||||
|
SkipList<int, std::string> skip;
|
||||||
|
std::string tmp;
|
||||||
|
|
||||||
|
skip.insert(101, "医用外科口罩");
|
||||||
|
skip.insert(98, "C++标准库");
|
||||||
|
skip.insert(12, "Hello World.");
|
||||||
|
|
||||||
|
std::cout << "skip 元素个数:" << skip.count() << "\n";
|
||||||
|
if (skip.search(12, tmp)) {
|
||||||
|
std::cout << "12: " << tmp << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skip.search(98, tmp)) {
|
||||||
|
std::cout << "98: " << tmp << "\n";
|
||||||
|
}
|
||||||
|
skip.remove(98);
|
||||||
|
std::cout << "skip 元素个数:" << skip.count() << "\n";
|
||||||
|
if (!skip.search(98, tmp)) {
|
||||||
|
std::cout << "98: 未找到\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 析构测试
|
||||||
|
auto* pSkip = new SkipList<int, std::string>(12);
|
||||||
|
pSkip->insert(101, "医用外科口罩");
|
||||||
|
pSkip->insert(98, "C++标准库");
|
||||||
|
pSkip->insert(12, "Hello World.");
|
||||||
|
delete pSkip;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
75
stress.cpp
Normal file
75
stress.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
|
#include <mutex>
|
||||||
|
#include <random>
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
|
#include "MSkipList.hpp"
|
||||||
|
|
||||||
|
#define THREAD_COUNT 1
|
||||||
|
#define TEST_COUNT 100000
|
||||||
|
|
||||||
|
std::random_device g_rd{};
|
||||||
|
std::mt19937 g_gen(g_rd());
|
||||||
|
std::uniform_int_distribution<int> g_dis(0, std::numeric_limits<int>::max());
|
||||||
|
|
||||||
|
std::mutex g_mutex{};
|
||||||
|
SkipList<int, std::string> skip(18);
|
||||||
|
|
||||||
|
void insertFunc()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < TEST_COUNT; ++i) {
|
||||||
|
g_mutex.lock();
|
||||||
|
skip.insert(g_dis(g_gen), "Hello World");
|
||||||
|
g_mutex.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void searchFunc()
|
||||||
|
{
|
||||||
|
std::string tmp{};
|
||||||
|
for (int i = 0; i < TEST_COUNT; ++i) {
|
||||||
|
g_mutex.lock();
|
||||||
|
skip.search(g_dis(g_gen), tmp);
|
||||||
|
g_mutex.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertTime()
|
||||||
|
{
|
||||||
|
std::thread thread_insert[THREAD_COUNT];
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
for (int i = 0; i < THREAD_COUNT; ++i) {
|
||||||
|
thread_insert[i] = std::thread(insertFunc);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < THREAD_COUNT; ++i) {
|
||||||
|
thread_insert[i].join();
|
||||||
|
}
|
||||||
|
auto finish = std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> elapsed = finish - start;
|
||||||
|
std::cout << "insert elapsed:" << elapsed.count() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void getTime()
|
||||||
|
{
|
||||||
|
std::thread thread_search[THREAD_COUNT];
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
for (int i = 0; i < THREAD_COUNT; ++i) {
|
||||||
|
thread_search[i] = std::thread(searchFunc);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < THREAD_COUNT; ++i) {
|
||||||
|
thread_search[i].join();
|
||||||
|
}
|
||||||
|
auto finish = std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> elapsed = finish - start;
|
||||||
|
std::cout << "get elapsed:" << elapsed.count() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
insertTime();
|
||||||
|
getTime();
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user