高并发内存池项目:内存池性能分析及其优化

前面我们对内存池进行了一定程度上的优化,本期我们就来学习着如何分析性能瓶颈并优化

工程代码:高并发内存池: 个人学习的项目------高并发内存池

目录

性能分析

常见的性能分析工具介绍(Windows下的VS2022和Linux中)

[Windows 平台 (Visual Studio 2022 及系统工具)](#Windows 平台 (Visual Studio 2022 及系统工具))

[Linux 平台](#Linux 平台)

总结与选型建议

工具性能分析

基数树

利用基数树优化

性能测试


性能分析

常见的性能分析工具介绍(Windows下的VS2022和Linux中)

Windows 平台 (Visual Studio 2022 及系统工具)

1. Visual Studio 2022 内置工具

这是你在IDE里写代码时最顺手、最直观的分析工具。

  • 性能分析器 (Debug > Performance Profiler)

    1. CPU 使用量:最常用的工具。可以精准定位是哪个函数占用了大量CPU时间,通过"调用树/调用方/被调用方"视图看清函数调用关系。

    2. 检测 (Instrumentation) :如果你的程序在等待 (比如网络、锁、磁盘I/O)而不是计算,用这个。它能告诉你函数被调用了多少次,以及实际耗时(挂钟时间),帮你揪出那些因阻塞而慢的函数。

    3. WindowsPerf 扩展 (针对 ARM 设备):如果你在 ARM 设备(如骁龙本)上开发,安装这个扩展后可以采样 CPU 事件(如缓存未命中),结果会直接关联到 VS 里的代码行。

  • 进程浏览器 (Process Explorer)

    • 这是微软官方出品的高级版"任务管理器"。它用树状视图清晰地显示进程父子关系,还能查看每个进程打开了哪些文件、注册表项,甚至可以把进程提交到 VirusTotal 检查安全性。如果你发现电脑卡顿但任务管理器看不出问题,试试这个。

2. 系统级通用工具

  • 任务管理器:快速查看哪个进程占用了高的CPU、内存或磁盘。

  • 资源监视器:比任务管理器更细,可以看每个进程具体的磁盘读写速度和网络连接情况。

  • 性能监视器 (Perfmon):面向资深玩家。可以添加超过2000个性能计数器(比如".NET CLR Memory"下的"GC 时间"),记录成日志文件用于长期追踪内存泄漏。

  • Windows 性能记录器/分析器 (WPR/WPA) :微软官方的"核武器"。通过 wpr 命令录制系统活动(如CPU采样、磁盘I/O),生成 .etl 文件,然后用 WPA 打开进行深度分析,适合排查间歇性卡顿。

3. 硬件与系统诊断工具

  • CrystalDiskMark / CrystalDiskInfo:前者测硬盘读写速度,后者看硬盘健康度(SMART信息)。

  • Windows 内存诊断 / MemTest86:检查内存条是否有硬件故障。

  • 3DMark / Cinebench:压测显卡和CPU,看看性能是否跑满

Linux 平台

1. 系统级通用工具

这些是排查问题时第一时间想到的"瑞士军刀"。

  • top / htop :实时看进程CPU、内存占用。htop 界面更友好,支持鼠标操作。

  • vmstat / iostatvmstat 看整体CPU、内存、系统负载;iostat 看磁盘读写速度和I/O队列长度,判断是不是磁盘太慢。

  • mpstat:查看每个CPU核心的使用率,对于诊断多线程程序在哪个核上"打架"很有用。

2. 深入分析工具

当你想知道代码内部为什么慢时,这些是主力。

  • perf (Linux 内核自带):这是 Linux 上最强大的性能分析工具,没有之一。它可以采样 CPU 周期、缓存未命中、分支预测失败等硬件事件,并精确到代码行。用它生成火焰图(Flame Graph)可以直观地看到程序的调用栈和热点路径。

  • Valgrind (Callgrind)慢但精确 。它会在虚拟 CPU 上运行你的程序,精确记录每行代码的调用次数和消耗,适合调试逻辑复杂的程序。配合 KCachegrind 可以图形化分析。

  • gprof :GCC 编译时加上 -pg 参数,程序运行结束后会生成报告。优点是简单,缺点是精确度一般,且不太适合分析多线程程序。

3. 高级追踪工具

  • bpftrace / SystemTap :内核级的动态追踪。可以写脚本实时监控内核函数调用、系统调用,非常适合排查生产环境的诡异问题(比如为什么 open 系统调用这么慢)。

  • dool:整合了 vmstat、iostat、netstat 的功能,能一次性输出系统概览。

4. 综合测试与基准测试工具

  • Phoronix Test Suite:Linux 上最全面的自动化测试套件,可以一键跑分对比硬件性能。

  • fio:强大的磁盘性能测试工具,可以模拟各种读写场景(随机读写、顺序读写等),测 SSD 性能必用。

  • iperf3:测试网络带宽吞吐量,看两台机器之间的网速能跑多快。

总结与选型建议

你的需求 Windows (VS2022) 首选工具 Linux 首选工具
快速看一眼谁在占CPU 任务管理器 / Process Explorer htop
分析代码函数级耗时 VS 性能分析器 (CPU Usage) perf + 火焰图
排查 I/O 或锁等待问题 VS 性能分析器 (Instrumentation) perf (带 -g) / bpftrace
查找内存泄漏 VS 诊断工具 / WinDbg Valgrind (Memcheck)
压测硬盘速度 CrystalDiskMark fio
压测 CPU/GPU 极限 3DMark / Cinebench stress / 7z b (跑分)

工具性能分析

在debug条件下使用性能探查器即可

我们会发现主要的性能竞争在于锁。

基数树

基数树(Radix Tree),又称紧凑前缀树(Compact Prefix Tree)或 Patricia Trie,是一种用于存储和检索字符串键的数据结构。它通过压缩路径来优化空间------当某个节点只有一个子节点时,将该节点与其子节点合并,从而减少节点数量。这种结构特别适合存储大量共享公共前缀的键,例如路由表、自动补全、IP 路由查找等场景。

主要特性

  • 空间高效:通过合并单子节点路径,显著减少了节点数量,节省内存。

  • 查找快速:查找时间与键长度相关,且由于节点数少,通常比普通 Trie 更快。

  • 动态操作:支持插入、查找、删除等动态操作(删除实现较复杂)。

  • 前缀压缩:每个节点存储一段字符串(标签),而非单个字符,实现了路径压缩。

更详细的内容可以参考这个:https://blog.csdn.net/2401_89119815/article/details/159395823?fromshare=blogdetail&sharetype=blogdetail&sharerId=159395823&sharerefer=PC&sharesource=2401_89119815&sharefrom=from_link

利用基数树优化

利用基数树,就要考虑具体的环境,对于32位系统用一层足够了,但是对于64位系统来说必须要用3层的基数树

PageMap.h

cpp 复制代码
#pragma once

#include"Common.h"
#include "ConcurrentMemoryPool.h"
#include<cstring>
// Single-level array
// 单层数组映射,用于管理固定大小的内存映射
template<int BITS> // 32-13 = 19 , 32 位可以存2^19页
class TCMalloc_PageMap1 {
private:
    static const int LENGTH = 1 << BITS;  // 数组长度 2^19页,即页号
    void **array_;                       // 数组指针,存储void*类型的数据

public:
    typedef uintptr_t Number;            // 定义Number类型为uintptr_t(无符号整数类型)

    // 构造函数,初始化数组
    explicit TCMalloc_PageMap1() {
        size_t size = sizeof(void *) << BITS;  // 计算数组所需的内存大小
        size_t alignSize = Alignment::_RoundUP(size, 1 << PAGE_SHIFT); // 对齐内存大小
        array_ = (void **) SystemAlloc(alignSize >> PAGE_SHIFT); // 调用SystemAlloc分配内存
        memset(array_, 0, sizeof(void *) << BITS); // 初始化数组为0
    }

    // 获取键k对应的值,如果k超出范围或未设置,返回NULL
    void *get(Number k) const {
        if ((k >> BITS) > 0) {  // 检查k是否超出范围
            return nullptr;
        }
        return array_[k];       // 返回数组中k对应的值
    }

    // 设置键k的值为v,要求k必须在[0, 2^BITS-1]范围内
    void set(Number k, void *v) {
        array_[k] = v;          // 将数组中k对应的值设置为v
    }
};


// Two-level radix tree
// 两层基数树,用于管理更大的内存映射
template<int BITS> // 32 - 12 = 20
class TCMalloc_PageMap2 {
private:
    static const int ROOT_BITS = 5;              // 根节点的位数
    static const int ROOT_LENGTH = 1 << ROOT_BITS; // 根节点的长度(32)

    static const int LEAF_BITS = BITS - ROOT_BITS; // 叶子节点的位数 20 - 5 = 15
    static const int LEAF_LENGTH = 1 << LEAF_BITS; // 叶子节点的长度 2^15

    // 叶子节点结构
    struct Leaf {
        void *values[LEAF_LENGTH]; // 存储实际数据的数组
    };

    Leaf *root_[ROOT_LENGTH];      // 根节点数组,指向叶子节点

public:
    typedef uintptr_t Number;      // 定义Number类型为uintptr_t

    // 构造函数,初始化根节点
    explicit TCMalloc_PageMap2() {
        memset(root_, 0, sizeof(root_)); // 初始化根节点为NULL
        PreallocateMoreMemory();        // 预分配内存
    }

    // 获取键k对应的值,如果k超出范围或未设置,返回NULL
    void *get(Number k) const {
        const Number i1 = k >> LEAF_BITS; // 计算根节点索引
        const Number i2 = k & (LEAF_LENGTH - 1); // 计算叶子节点索引
        if ((k >> BITS) > 0 || root_[i1] == nullptr) { // 检查k是否超出范围或叶子节点未分配
            return nullptr;
        }
        return root_[i1]->values[i2]; // 返回叶子节点中对应的值
    }

    // 设置键k的值为v,要求k必须在[0, 2^BITS-1]范围内
    void set(Number k, void *v) {
        const Number i1 = k >> LEAF_BITS; // 计算根节点索引
        const Number i2 = k & (LEAF_LENGTH - 1); // 计算叶子节点索引
        assert(i1 < ROOT_LENGTH);         // 确保根节点索引有效
        root_[i1]->values[i2] = v;        // 设置叶子节点中对应的值
    }

    // 确保从start开始的n个键都有对应的叶子节点
    bool Ensure(Number start, size_t n) {
        for (Number key = start; key <= start + n - 1;) {
            const Number i1 = key >> LEAF_BITS; // 计算根节点索引

            // 检查是否溢出
            if (i1 >= ROOT_LENGTH)
                return false;

            // 如果叶子节点未分配,则分配
            if (root_[i1] == nullptr) {
                static ConcurrentMemoryPool<Leaf> leafPool; // 使用对象池分配叶子节点
                Leaf *leaf = (Leaf *) leafPool.New(); // 从对象池中获取叶子节点
                memset(leaf, 0, sizeof(*leaf));  // 初始化叶子节点
                root_[i1] = leaf;                // 将叶子节点挂载到根节点
            }

            // 跳过当前叶子节点覆盖的范围
            key = ((key >> LEAF_BITS) + 1) << LEAF_BITS;
        }
        return true;
    }

    // 预分配内存,确保所有可能的键都有对应的叶子节点
    void PreallocateMoreMemory() {
        Ensure(0, 1 << BITS); // 确保所有键都有叶子节点
    }
};

// 三级基数树
template<int BITS> // 64 - 12 = 52
class TCMalloc_PageMap3 {
private:
    // 在每一层内部节点消耗的位数
    static const int INTERIOR_BITS = (BITS + 2) / 3; // 向上取整 (54)/3 = 18
    static const int INTERIOR_LENGTH = 1 << INTERIOR_BITS; // 内部节点的长度 2^18

    // 在叶子节点消耗的位数
    static const int LEAF_BITS = BITS - 2 * INTERIOR_BITS; // 叶子节点的位数 52 - 2 * 18 = 52 - 36 = 16,就是说有两层18的
    static const int LEAF_LENGTH = 1 << LEAF_BITS; // 叶子节点的长度 2^16

    // 内部节点结构
    struct Node {
        Node* ptrs[INTERIOR_LENGTH]; // 指向子节点的指针数组
    };

    // 叶子节点结构
    struct Leaf {
        void* values[LEAF_LENGTH]; // 存储值的数组
    };

    Node* root_;                          // 基数树的根节点

    // 创建一个新的内部节点
    Node* NewNode() 
    {
        static ConcurrentMemoryPool<Node> newPool;
        Node *result = newPool.New();
        if (result != nullptr) {
            memset(result, 0, sizeof(Node)); // 初始化内存为0
        }
        assert(result);
        return result;
    }

public:
    typedef uintptr_t Number; // 定义Number类型为uintptr_t

    // 构造函数,初始化内存分配器和根节点
    explicit TCMalloc_PageMap3() 
    {
        root_ = NewNode();
    }

    // 获取键k对应的值
    void* get(Number k) const {
        // LEAF_BITS-->16  INTERIOR_BITS-->18
        const Number i1 = k >> (LEAF_BITS + INTERIOR_BITS); // 第一层索引
        const Number i2 = (k >> LEAF_BITS) & (INTERIOR_LENGTH - 1); // 第二层索引
        const Number i3 = k & (LEAF_LENGTH - 1); // 第三层索引

        if ((k >> BITS) > 0 ||
            root_->ptrs[i1] == nullptr || root_->ptrs[i1]->ptrs[i2] == nullptr) {
            return nullptr; // 如果键超出范围或节点不存在,返回NULL
        }
        return reinterpret_cast<Leaf *>(root_->ptrs[i1]->ptrs[i2])->values[i3]; // 返回叶子节点中的值
    }

    // 设置键k对应的值
    void set(Number k, void *v) 
    {
        assert(k >> BITS == 0); // 确保键k在有效范围内

        // LEAF_BITS-->16  INTERIOR_BITS-->18
        const Number i1 = k >> (LEAF_BITS + INTERIOR_BITS); // 第一层索引
        if (root_->ptrs[i1] == nullptr)
        {
            //bool Ensure(Number start, size_t  n)
            Ensure(k, 1);
        }
        const Number i2 = (k >> LEAF_BITS) & (INTERIOR_LENGTH - 1); // 第二层索引

        if (root_->ptrs[i1]->ptrs[i2] == nullptr)
        {
            Ensure(k, 1);
        }
        const Number i3 = k & (LEAF_LENGTH - 1); // 第三层索引



        reinterpret_cast<Leaf *>(root_->ptrs[i1]->ptrs[i2])->values[i3] = v; // 设置叶子节点中的值
    }

    // 确保从start开始的n个键对应的节点存在
    bool Ensure(Number start,  size_t  n) 
    {
        for (Number key = start; key <= start + n - 1;) 
        {
            const Number i1 = key >> (LEAF_BITS + INTERIOR_BITS); // 第一层索引
            const Number i2 = (key >> LEAF_BITS) & (INTERIOR_LENGTH - 1); // 第二层索引

            // 检查是否溢出
            if (i1 >= INTERIOR_LENGTH || i2 >= INTERIOR_LENGTH)
                return false;

            // 如果第二层节点不存在,则创建
            if (root_->ptrs[i1] == nullptr) {
                Node *node = NewNode();
                if (node == nullptr) return false;
                root_->ptrs[i1] = node;
            }

            // 如果叶子节点不存在,则创建
            if (root_->ptrs[i1]->ptrs[i2] == nullptr) 
            {
                static ConcurrentMemoryPool<Leaf> leafPool; // 使用对象池分配叶子节点
                Leaf *leaf = (Leaf *) leafPool.New(); // 从对象池中获取叶子节点
                if (leaf == nullptr) return false;
                memset(leaf, 0, sizeof(*leaf)); // 初始化内存为0
                root_->ptrs[i1]->ptrs[i2] = reinterpret_cast<Node *>(leaf);
            }

            // 跳过当前叶子节点覆盖的键范围
            // LEAF_BITS-->16  INTERIOR_BITS-->18
            key = ((key >> LEAF_BITS) + 1) << LEAF_BITS;

        }
        return true;
    }
};

PageCache.h

cpp 复制代码
#pragma once
#include"Common.h"
#include "ConcurrentMemoryPool.h"
#include"PageMap.h"
#include<unordered_map>
class PageCache
{
private:
	// 页号映射对应的centralcache的Span位置
#ifdef _WIN64
	TCMalloc_PageMap3<64 - PAGE_SHIFT> IdSpanMap_; 
#elif _WIN32
	TCMalloc_PageMap1<32 - PAGE_SHIFT> IdSpanMap_;
#elif __linux__
	TCMalloc_PageMap3<64 - PAGE_SHIFT> IdSpanMap_;
#endif
	SpanList spanlist_[NFRESSLISTS];
	static PageCache sInstan_;
	ConcurrentMemoryPool<Span> spanPool_;
	
	PageCache() = default;
	PageCache(const PageCache&) = delete;
public:
	static PageCache* Instance()
	{
		return &sInstan_;
	}
	// 获取从对象到span的映射
	Span* MapObjectToSpan(void* obj);
	// 释放空闲span回到Pagecache,并合并相邻的span
	void ReleaseSpanToPageCache(Span* span);
	std::mutex pagemtx_;
	//获取K页的span
	Span* GetSpan(size_t K);
};

PageCache.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include "PageCache.h"
PageCache PageCache::sInstan_;

// 获取一个K页的span
Span* PageCache::GetSpan(size_t k)
{
	assert(k > 0);

	// 大于128 page的直接向堆申请
	if (k > NPAGES - 1)
	{
		void* ptr = SystemAlloc(k);
		//Span* span = new Span;
		Span* span = spanPool_.New();

		span->PageNum = (PageID)ptr >> PAGE_SHIFT;
		span->n_ = k;
		IdSpanMap_.set(span->PageNum, span);
		return span;
	}

	// 先检查第k个桶里面有没有span
	if (!spanlist_[k].Empty())
	{
		Span* kSpan = spanlist_[k].PopFront();

		// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
		for (size_t i = 0; i < kSpan->n_; ++i)
		{
			IdSpanMap_.set(kSpan->PageNum + i, kSpan);
		}

		return kSpan;
	}

	// 检查一下后面的桶里面有没有span,如果有可以把他它进行切分
	for (size_t i = k + 1; i < NPAGES; ++i)
	{
		if (!spanlist_[i].Empty())
		{
			Span* nSpan = spanlist_[i].PopFront();
			//Span* kSpan = new Span;
			Span* kSpan = spanPool_.New();

			// 在nSpan的头部切一个k页下来
			// k页span返回
			// nSpan再挂到对应映射的位置
			kSpan->PageNum = nSpan->PageNum;
			kSpan->n_ = k;

			nSpan->PageNum += k;
			nSpan->n_ -= k;

			spanlist_[nSpan->n_].PushFront(nSpan);
			// 存储nSpan的首位页号跟nSpan映射,方便page cache回收内存时
			// 进行的合并查找
			IdSpanMap_.set(nSpan->PageNum, nSpan);
			IdSpanMap_.set(nSpan->PageNum + nSpan->n_ - 1, nSpan);


			// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
			for (PageID i = 0; i < kSpan->n_; ++i)
			{
				IdSpanMap_.set(kSpan->PageNum + i, kSpan);
			}

			return kSpan;
		}
	}

	// 走到这个位置就说明后面没有大页的span了
	// 这时就去找堆要一个128页的span
	//Span* bigSpan = new Span;
	Span* bigSpan = spanPool_.New();
	void* ptr = SystemAlloc(NPAGES - 1);
	bigSpan->PageNum = (PageID)ptr >> PAGE_SHIFT;
	bigSpan->n_ = NPAGES - 1;

	spanlist_[bigSpan->n_].PushFront(bigSpan);

	return GetSpan(k);
}

Span* PageCache::MapObjectToSpan(void* obj)
{
	PageID id = ((PageID)obj >> PAGE_SHIFT);

	auto ret = (Span*)IdSpanMap_.get(id);
	assert(ret != nullptr);
	return ret;
}

void PageCache::ReleaseSpanToPageCache(Span* span)
{
	// 大于128 page的直接还给堆
	if (span->n_ > NPAGES - 1)
	{
		void* ptr = (void*)(span->PageNum << PAGE_SHIFT);
		SystemFree(ptr);
		//delete span;
		spanPool_.Delete(span);

		return;
	}

	// 对span前后的页,尝试进行合并,缓解内存碎片问题
	while (1)
	{
		PageID prevId = span->PageNum - 1;
		//auto ret = _idSpanMap.find(prevId);
		//// 前面的页号没有,不合并了
		//if (ret == _idSpanMap.end())
		//{
		//	break;
		//}

		auto ret = (Span*)IdSpanMap_.get(prevId);
		if (ret == nullptr)
		{
			break;
		}

		// 前面相邻页的span在使用,不合并了
		Span* prevSpan = ret;
		if (prevSpan->IsUse == true)
		{
			break;
		}

		// 合并出超过128页的span没办法管理,不合并了
		if (prevSpan->n_ + span->n_ > NPAGES - 1)
		{
			break;
		}

		span->PageNum = prevSpan->PageNum;
		span->n_ += prevSpan->n_;

		spanlist_[prevSpan->n_].Erase(prevSpan);
		//delete prevSpan;
		spanPool_.Delete(prevSpan);
	}

	// 向后合并
	while (1)
	{
		PageID nextId = span->PageNum + span->n_;
		/*auto ret = _idSpanMap.find(nextId);
		if (ret == _idSpanMap.end())
		{
			break;
		}*/

		auto ret = (Span*)IdSpanMap_.get(nextId);
		if (ret == nullptr)
		{
			break;
		}

		Span* nextSpan = ret;
		if (nextSpan->IsUse == true)
		{
			break;
		}

		if (nextSpan->n_ + span->n_ > NPAGES - 1)
		{
			break;
		}

		span->n_ += nextSpan->n_;

		spanlist_[nextSpan->n_].Erase(nextSpan);
		//delete nextSpan;
		spanPool_.Delete(nextSpan);
	}

	spanlist_[span->n_].PushFront(span);
	span->IsUse = false;
	IdSpanMap_.set(span->PageNum + span->n_ - 1, span);
	IdSpanMap_.set(span->PageNum, span);
}

性能测试

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
//多线程下与malloc的性能测试
#include"ConcurrentAlloc.h"
#include<thread>
#include<vector>
#include<atomic>
#include<ctime>
#include<cstdio>
// ntimes 一轮申请和释放内存的次数
// rounds 轮次
void BenchmarkMalloc(size_t ntimes, size_t nworks, size_t rounds)
{
	std::vector<std::thread> vthread(nworks);
	std::atomic<size_t> malloc_costtime = 0;
	std::atomic<size_t> free_costtime = 0;

	for (size_t k = 0; k < nworks; ++k)
	{
		vthread[k] = std::thread([&, k]()
			{
				std::vector<void*> v;
				v.reserve(ntimes);

				for (size_t j = 0; j < rounds; ++j)
				{
					size_t begin1 = clock();
					for (size_t i = 0; i < ntimes; i++)
					{
						v.push_back(malloc(16));
						//v.push_back(malloc((16 + i) % 8192 + 1));
					}
					size_t end1 = clock();

					size_t begin2 = clock();
					for (size_t i = 0; i < ntimes; i++)
					{
						free(v[i]);
					}
					size_t end2 = clock();
					v.clear();

					malloc_costtime += (end1 - begin1);
					free_costtime += (end2 - begin2);
				}
			});
	}

	for (auto& t : vthread)
	{
		t.join();
	}

	printf("%u个线程并发执行%u轮次,每轮次malloc %u次: 花费:%u ms\n",
		nworks, rounds, ntimes, malloc_costtime.load());

	printf("%u个线程并发执行%u轮次,每轮次free %u次: 花费:%u ms\n",
		nworks, rounds, ntimes, free_costtime.load());

	printf("%u个线程并发malloc&free %u次,总计花费:%u ms\n",
		nworks, nworks * rounds * ntimes, malloc_costtime.load() + free_costtime.load());
}


// 单轮次申请释放次数 线程数 轮次
void BenchmarkConcurrentMalloc(size_t ntimes, size_t nworks, size_t rounds)
{
	std::vector<std::thread> vthread(nworks);
	std::atomic<size_t> malloc_costtime = 0;
	std::atomic<size_t> free_costtime = 0;

	for (size_t k = 0; k < nworks; ++k)
	{
		vthread[k] = std::thread([&]() {
			std::vector<void*> v;
			v.reserve(ntimes);

			for (size_t j = 0; j < rounds; ++j)
			{
				size_t begin1 = clock();
				for (size_t i = 0; i < ntimes; i++)
				{
					v.push_back(ConcurrentAlloc(16));
					//v.push_back(ConcurrentAlloc((16 + i) % 8192 + 1));
				}
				size_t end1 = clock();

				size_t begin2 = clock();
				for (size_t i = 0; i < ntimes; i++)
				{ 
					ConcurrentFree(v[i]);
				}
				size_t end2 = clock();
				v.clear();

				malloc_costtime += (end1 - begin1);
				free_costtime += (end2 - begin2);
			}
			});
	}

	for (auto& t : vthread)
	{
		t.join();
	}

	printf("%u个线程并发执行%u轮次,每轮次concurrent alloc %u次: 花费:%u ms\n",
		nworks, rounds, ntimes, malloc_costtime.load());

	printf("%u个线程并发执行%u轮次,每轮次concurrent dealloc %u次: 花费:%u ms\n",
		nworks, rounds, ntimes, free_costtime.load());

	printf("%u个线程并发concurrent alloc&dealloc %u次,总计花费:%u ms\n",
		nworks, nworks * rounds * ntimes, malloc_costtime.load() + free_costtime.load());
}

struct Node {
	Node* ptrs[262144]; // 指向子节点的指针数组
};

int main()
{
	size_t n = 10000;
	cout << "==========================================================" << endl;
	BenchmarkConcurrentMalloc(n, 10, 10);
	cout << endl << endl;

	BenchmarkMalloc(n, 10, 10);
	cout << "==========================================================" << endl;

	return 0;
}

结果为:

本期项目相关的博客到这里就结束了,喜欢请点个赞谢谢

封面图自取:

相关推荐
重庆小透明3 分钟前
【搞定面试之mysql】第三篇 mysql的锁
java·后端·mysql·面试·职场和发展
大尚来也10 分钟前
PHP 反序列化漏洞深度解析:从原理利用到 allowed_classes 防御实战
android·开发语言·php
雕刻刀13 分钟前
ERROR: Failed to build ‘natten‘ when getting requirements to build wheel
开发语言·python
qq_4160187213 分钟前
高性能密码学库
开发语言·c++·算法
吃杠碰小鸡16 分钟前
前端 IndexedDB 完全指南
学习
小碗羊肉22 分钟前
【从零开始学Java | 第十八篇】BigInteger
java·开发语言·新手入门
宵时待雨25 分钟前
C++笔记归纳14:AVL树
开发语言·数据结构·c++·笔记·算法
执笔画流年呀39 分钟前
PriorityQueue(堆)续集
java·开发语言
山川行42 分钟前
关于《项目C语言》专栏的总结
c语言·开发语言·数据结构·vscode·python·算法·visual studio code
呜喵王阿尔萨斯44 分钟前
C and C++ code
c语言·开发语言·c++