first commit

This commit is contained in:
taynpg 2024-04-11 09:27:21 +08:00
commit e1f1e71f9a
10 changed files with 500 additions and 0 deletions

17
.clang-format Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}