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