commit e1f1e71f9ad0d23cf55e4c5bbc63d71eff0ca2e5 Author: taynpg Date: Thu Apr 11 09:27:21 2024 +0800 first commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4ed25d6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,17 @@ +# .clang-format + +# 风格格式化 +BasedOnStyle: Google +# 4 空格缩进 +IndentWidth: 4 +# 连续对齐变量的声明 +AlignConsecutiveDeclarations: true +# 指针左侧对齐 +PointerAlignment: Left +# 访问说明符(public、private等)的偏移 +AccessModifierOffset: -4 +# 大括号 +BreakBeforeBraces: Custom +BraceWrapping: + # 函数定义后面大括号在新行 + AfterFunction: true diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..a0851df --- /dev/null +++ b/.clangd @@ -0,0 +1,7 @@ +Hover: + ShowAKA: Yes +Diagnostics: + UnusedIncludes: None + Suppress: [anon_type_definition] + ClangTidy: + Remove: misc-unused-alias-decls diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..424542a --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e0cfb5f --- /dev/null +++ b/.vscode/settings.json @@ -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" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b6de43e --- /dev/null +++ b/CMakeLists.txt @@ -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) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6cfc697 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/MSkipList.hpp b/MSkipList.hpp new file mode 100644 index 0000000..09abcc5 --- /dev/null +++ b/MSkipList.hpp @@ -0,0 +1,243 @@ +#ifndef MSKIPLIST_HEADER +#define MSKIPLIST_HEADER + +#include +#include +#include +#include + +template +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 dis_{}; + +private: + int random_level(); + SkipNode* find_node(const T& key); +}; + +template +SkipList::~SkipList() +{ + clear(); + delete[] pre_; + delete header_; +} + +template +void SkipList::clear() +{ + SkipNode* start = header_->get_no_bar(0); + while (start) { + SkipNode* n = start; + start = start->get_no_bar(0); + delete n; + --count_; + } +} + +template +inline int SkipList::random_level() +{ + static const int base_ = 2; + int height = 1; + while (height < max_level_ && (dis_(gen_) % base_) == 0) { + ++height; + } + return height; +} + +template +struct SkipList::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[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* next_{}; +}; + +template +inline SkipList::SkipList(int max_level) + : max_level_(max_level), + cur_max_height_(1), + gen_(rd_()), + dis_(0, std::numeric_limits::max()) +{ + assert(max_level_ > 0); + header_ = new SkipNode(max_level_); + pre_ = new SkipNode*[max_level_]; +} + +template +typename SkipList::SkipNode* SkipList::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 +inline bool SkipList::contains(const T& key) +{ + SkipNode* x = find_node(key); + if (x && x->key_ == key) { + return true; + } + return false; +} + +template +inline std::size_t SkipList::count() const +{ + return count_; +} + +template +inline void SkipList::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 +inline void SkipList::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 +inline bool SkipList::search(const T& key, P& value) +{ + SkipNode* x = find_node(key); + if (x && x->key_ == key) { + value = x->value_; + return true; + } + return false; +} + +#endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..b9fb27c --- /dev/null +++ b/README.md @@ -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`即可。 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..cd2c73e --- /dev/null +++ b/main.cpp @@ -0,0 +1,36 @@ +#include +#include "MSkipList.hpp" + +int main() { + + // 基本使用 + SkipList 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(12); + pSkip->insert(101, "医用外科口罩"); + pSkip->insert(98, "C++标准库"); + pSkip->insert(12, "Hello World."); + delete pSkip; + + return 0; +} diff --git a/stress.cpp b/stress.cpp new file mode 100644 index 0000000..f2c4229 --- /dev/null +++ b/stress.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include +#include + +#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 g_dis(0, std::numeric_limits::max()); + +std::mutex g_mutex{}; +SkipList 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 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 elapsed = finish - start; + std::cout << "get elapsed:" << elapsed.count() << std::endl; +} + +int main() +{ + insertTime(); + getTime(); + return 0; +} \ No newline at end of file