【项目日记(九)】项目整体测试,优化以及缺陷分析

简介: 【项目日记(九)】项目整体测试,优化以及缺陷分析

1. 前言

整个项目的代码和框架就已经介绍

完毕了,项目的所有代码在下面的链接:

gitee代码仓库项目源代码

本章重点:

本篇文章着重讲解本项目是如何测试的,
以及本代码的一些效率上限问题,最后会
引入基数树来对项目整体做优化


2. 整体项目测试

对本项目的测试无非就是将自己写的

内存池与C语言的malloc做对比,代码如下:

#include<cstdio>
#include<iostream>
#include<vector>
#include<thread>
#include<mutex>
#include"ConcurrentAlloc.h"
using namespace std;
void BenchmarkMalloc(size_t ntimes, size_t nworks, size_t rounds)//ntime一轮申请和释放内存的次数,round是跑多少轮,nworks是线程数
{
  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());
}
int main()
{
  size_t n = 10000;
  cout << "==========================================================" << endl;
  BenchmarkConcurrentMalloc(n, 10, 10);
  cout << endl << endl;
  BenchmarkMalloc(n, 10, 10);
  cout << "==========================================================" <<endl;
  return 0;
}

本代码是现成的,不用在意细节

当我们运行代码后会发现,为什么我们自己写的内存池的效率比不上C语言中的malloc函数,这一点显然超出了我们的预期,下面就来分析一下项目的效率上限问题


3. 项目的效率上限分析

在vs的调试中有一个性能探测器

我们可以使用这个功能来分析哪个步骤比较用时,当我们完成检测后会发现,在pagecache文件中的函数耗时都比较久,其实我们隐约已经知道问题出现在哪里了,我们知道unordered_map的底层是哈希桶结构,然而find函数会将每一个桶中的链表都遍历一遍,直到找到了对应的key值,很明显这个查找的过程是比较费时的,并且如果不切换一个容器来代替unordered_map的话,在这个基础上不管怎样去优化都不会有质的提升!!!


4. 效率上限问题的解决方法

对于上面的问题显然超出了我们的能力范围,对于一个C++的初学者来说,标准库中的容器已经是很优秀的了,如果要抛弃标准库,我们也不能写出更好的,所以这里直接将TCmalloc开源项目中的解决方法给搬过来,谷歌的团队使用了一个叫基数树的结构来完美的解决此问题

基数树的文档说明: 基数树百度百科

由于基数树属于此项目的拓展内容,所以这里就不详细介绍了,完美直接把代码搬出来用就可以了!

#pragma once
#include"shared.h"
// Single-level array
template <int BITS>
class TCMalloc_PageMap1 {
private:
  static const int LENGTH = 1 << BITS;
  void** array_;
public:
  typedef uintptr_t Number;
  //explicit TCMalloc_PageMap1(void* (*allocator)(size_t)) {
  explicit TCMalloc_PageMap1() {
    //array_ = reinterpret_cast<void**>((*allocator)(sizeof(void*) << BITS));
    size_t size = sizeof(void*) << BITS;
    size_t alignSize = AlignmentRule::_AlignUp(size, 1 << PAGE_SHIFT);
    array_ = (void**)SystemAlloc(alignSize >> PAGE_SHIFT);
    memset(array_, 0, sizeof(void*) << BITS);
  }
  // Return the current value for KEY.  Returns NULL if not yet set,
  // or if k is out of range.
  void* get(Number k) const {
    if ((k >> BITS) > 0) {
      return NULL;
    }
    return array_[k];
  }
  // REQUIRES "k" is in range "[0,2^BITS-1]".
  // REQUIRES "k" has been ensured before.
  // Sets the value 'v' for key 'k'.
  void set(Number k, void* v) {
    array_[k] = v;
  }
};

之后将所有使用unordered_map的地方都替换成基数树的get和set函数即可!现在我们再来测试一下整个项目的性能如何:

使用基数树后,整个效率就比malloc快了!


5. 项目的缺陷分析

本项目看似每一步都做的天衣无缝,申请

和释放内存一层一层不断递进,但是它有

一个致命的缺陷,那就是内存泄漏问题:

bug出现的情景:

假设线程缓存的K号桶中有10个小块儿内存挂在桶上,此时K号桶向中心缓存申请的小块儿内存个数是7个,小于了桶中小块儿内存的个数,此时会将线程缓存中的7个小块儿内存还给中心缓存,那么也就还剩下三个小块儿内存在桶中没有被还回去,此时如果没有线程来这个桶中申请或释放内存,那么这三块儿内存就会一直挂在桶上,既无法释放它,又失去了对它的控制从而造成内存泄漏!

解决bug的方式:

博主本人比较推荐的方式就是在每次使用完内存池后,手动调用一个释放内存的函数对每一个桶进行遍历,来释放还没有被使用的小块儿内存


6. 项目总结

高并发内存池项目到这里就结项了,

三层缓存结构设计的非常之巧妙,做

这个项目为了去解决某个问题,而是

去学习别人的优秀的,先进的思想


相关文章
|
1天前
|
Java jenkins 持续交付
Jenkins是开源CI/CD工具,用于自动化Java项目构建、测试和部署。通过配置源码管理、构建触发器、执行Maven目标,实现代码提交即触发构建和测试
【7月更文挑战第1天】Jenkins是开源CI/CD工具,用于自动化Java项目构建、测试和部署。通过配置源码管理、构建触发器、执行Maven目标,实现代码提交即触发构建和测试。成功后,Jenkins执行部署任务,发布到服务器或云环境。使用Jenkins能提升效率,保证软件质量,加速上线,并需维护其稳定运行。
11 0
|
4天前
|
监控 测试技术 UED
软件测试中的性能瓶颈定位与优化策略
在软件开发的生命周期中,性能测试是确保产品质量的关键步骤之一。本文深入探讨了性能测试的重要性,并提出了一套系统的性能瓶颈定位与优化策略。通过分析现代软件系统中常见的性能问题,结合最新的研究成果和行业最佳实践,文章详细介绍了如何运用科学严谨的方法来识别和解决性能瓶颈。此外,本文还强调了逻辑严密的问题分析框架和数据驱动的决策过程对于提升软件性能的重要性。
|
7天前
|
缓存 算法 Java
Java中如何进行性能测试与优化?
Java中如何进行性能测试与优化?
|
8天前
|
缓存 算法 Java
Java中如何进行性能测试与优化?
Java中如何进行性能测试与优化?
|
8天前
|
运维 Java 测试技术
Spring运维之boo项目表现层测试加载测试的专用配置属性以及在JUnit中启动web服务器发送虚拟请求
Spring运维之boo项目表现层测试加载测试的专用配置属性以及在JUnit中启动web服务器发送虚拟请求
15 3
|
10天前
|
存储 缓存 NoSQL
Redis性能测试实操记录与分析
Redis性能测试实操记录与分析
14 3
|
13天前
|
SQL 监控 中间件
【应急响应】拒绝服务&钓鱼指南&DDOS压力测试&邮件反制分析&应用日志
【应急响应】拒绝服务&钓鱼指南&DDOS压力测试&邮件反制分析&应用日志
|
15天前
|
测试技术 持续交付 API
Airtest脚本的重构与优化:提升测试效率和可读性
通过对Airtest脚本的重构与优化,我们不仅能提升测试效率,还能增强脚本的可读性和可维护性。这些改进将有助于应对不断变化的测试需求,为保证软件质量提供坚实的支持。记住,优化是一个持续的过程,定期回顾和调整测试脚本是保持测试项目健康的关键。希望以上分享能帮助大家在自动化测试的道路上更进一步。
|
25天前
|
Java
JavaSE——集合框架二(6/6)-(案例)补充知识:集合的嵌套(需求与分析、问题解决、运行测试)
JavaSE——集合框架二(6/6)-(案例)补充知识:集合的嵌套(需求与分析、问题解决、运行测试)
59 0
|
1天前
|
JSON JavaScript 测试技术
Postman接口测试工具详解
Postman接口测试工具详解
11 1