zoukankan      html  css  js  c++  java
  • 内存池版本1单线程固定大小专为某类设计的内存池

    先贴代码:

    #include "stdafx.h"
    #include
    <windows.h>
    #include
    <MMSystem.h>
    #include
    <iostream>
    using namespace std;

    #pragma comment(lib, "winmm.lib")

    class Foo
    {
    public:
    Foo(
    int a = 0, int b = 0):m(a), n(b) {}

    private:
    int m;
    int n;
    };

    struct NextFreeList
    {
    struct NextFreeList* next;
    };

    class Foo1
    {
    public:
    Foo1(
    int a = 0, int b = 0):m(a), n(b) {}
    inline
    void* operator new (size_t size);
    inline
    void operator delete(void* doomed, size_t size);

    static void NewMemoryPool();
    static void DeleteMemoryPool();

    private:
    static void ExpandFreeList();

    static NextFreeList* freeList;
    enum{EXPAND_SIZE = 32};
    int m;
    int n;
    };

    NextFreeList
    * Foo1::freeList = 0;

    inline
    void* Foo1::operator new (size_t size)
    {
    if (freeList == 0)
    {
    ExpandFreeList();
    }

    NextFreeList
    * head = freeList;
    freeList
    = freeList->next;
    return head;
    }

    inline
    void Foo1::operator delete(void* doomed, size_t size)
    {
    NextFreeList
    * head = static_cast<NextFreeList*>(doomed);
    head
    ->next = freeList;
    freeList
    = head;
    }

    void Foo1::NewMemoryPool()
    {
    ExpandFreeList();
    }

    void Foo1::DeleteMemoryPool()
    {
    NextFreeList
    * nextPtr = freeList;
    for (; nextPtr != NULL; nextPtr = freeList)
    {
    freeList
    = nextPtr->next;
    delete []nextPtr;
    }
    }

    void Foo1::ExpandFreeList()
    {
    size_t size
    = sizeof(NextFreeList*) > sizeof(Foo1) ? sizeof(NextFreeList*) : sizeof(Foo1);
    NextFreeList
    * ptr = 0;
    ptr
    = (NextFreeList*)(new char[size]);
    freeList
    = ptr;
    for (int i=0; i<EXPAND_SIZE; ++i)
    {
    ptr
    ->next = (NextFreeList*)(new char[size]);
    ptr
    = ptr->next;
    }
    ptr
    ->next = 0;
    }



    int main()
    {
    DWORD time1, time2, deltaTime;
    time1
    = timeGetTime();
    for (int i=0; i<500; ++i)
    {
    Foo
    * ptrArray[1000];
    for (int j=0; j<1000; ++j)
    {
    ptrArray[j]
    = new Foo;
    }

    for (int j=0; j<1000; ++j)
    {
    delete ptrArray[j];
    }
    }

    time2
    = timeGetTime();
    deltaTime
    = time2- time1;
    cout
    <<deltaTime<<endl;

    Foo1::NewMemoryPool();
    time1
    = timeGetTime();
    for (int i=0; i<500; ++i)
    {
    Foo1
    * ptrArray[1000];
    for (int j=0; j<1000; ++j)
    {
    ptrArray[j]
    = new Foo1;
    }

    for (int j=0; j<1000; ++j)
    {
    delete ptrArray[j];
    }
    }

    time2
    = timeGetTime();
    deltaTime
    = time2- time1;
    Foo1::DeleteMemoryPool();
    cout
    <<deltaTime<<endl;

    return 0;
    }


    // operator new
    //(1)只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,则
    //如果有new_handler,则调用new_handler,否则如果没要求不抛出异常(以nothrow参数表达),
    //则执行bad_alloc异常,否则返回0
    //(2)可以被重载
    //(3)重载时,返回类型必须声明为void*
    //(4)重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t
    //(5)重载时,可以带其它参数.
    //void* operator new(size_t size);
    //void operator delete(void *p);
    //void operator delete(void *p,size_t size);

      

    (代码参考自:提高C++编程性能的技术 一书。)

    结果是自己实现的内存池比系统的new,delete要快一个数量级。因为单线程不用考虑并发;不用担心临界区不用对内存管理进行保护,而且分配的是固定大小的内存块,这样需要的计算就很少(系统的new会查找一下个满足需求的足够大的内存块)。

    附上一些关于这个的资料:

    条款8: 写operator new和operator delete时要遵循常规

    自己重写operator new时(条款10解释了为什么有时要重写它),很重要的一点是函数提供的行为要和系统缺省的operator new一致。实际做起来也就是:要有正确的返回值;可用内存不够时要调用出错处理函数(见条款7);处理好0字节内存请求的情况。此外,还要避免不小心隐藏了标准形式的new,不过这是条款9的话题。

    有关返回值的部分很简单。如果内存分配请求成功,就返回指向内存的指针;如果失败,则遵循条款7的规定抛出一个std::bad_alloc类型的异常。

    但事情也不是那么简单。因为operator new实际上会不只一次地尝试着去分配内存,它要在每次失败后调用出错处理函数,还期望出错处理函数能想办法释放别处的内存。只有在指向出错处理函数的指针为空的情况下,operator new才抛出异常。

    另外,c++标准要求,即使在请求分配0字节内存时,operator new也要返回一个合法指针。(实际上,这个听起来怪怪的要求确实给c++语言其它地方带来了简便)

    这样,非类成员形式的operator new的伪代码看起来会象下面这样:
    void * operator new(size_t size)        // operator new还可能有其它参数
    {                                      

      if (size == 0) {                      // 处理0字节请求时,
        size = 1;                           // 把它当作1个字节请求来处理
      }                                     
      while (1) {
        分配size字节内存;

        if (分配成功)
          return (指向内存的指针);

        // 分配不成功,找出当前出错处理函数
        new_handler globalhandler = set_new_handler(0);
        set_new_handler(globalhandler);

        if (globalhandler) (*globalhandler)();
        else throw std::bad_alloc();
      }
    }

    处理零字节请求的技巧在于把它作为请求一个字节来处理。这看起来也很怪,但简单,合法,有效。而且,你又会多久遇到一次零字节请求的情况呢?

    你又会奇怪上面的伪代码中为什么把出错处理函数置为0后又立即恢复。这是因为没有办法可以直接得到出错处理函数的指针,所以必须通过调用set_new_handler来找到。办法很笨但也有效。

    条款7提到operator new内部包含一个无限循环,上面的代码清楚地说明了这一点——while (1)将导致无限循环。跳出循环的唯一办法是内存分配成功或出错处理函数完成了条款7所描述的事件中的一种:得到了更多的可用内存;安装了一个新的new-handler(出错处理函数);卸除了new-handler;抛出了一个std::bad_alloc或其派生类型的异常;或者返回失败。现在明白了为什么new-handler必须做这些工作中的一件。如果不做,operator new里面的循环就不会结束。

    很多人没有认识到的一点是operator new经常会被子类继承。这会导致某些复杂性。上面的伪代码中,函数会去分配size字节的内存(除非size为0)。size很重要,因为它是传递给函数的参数。但是大多数针对类所写的operator new(包括条款10中的那种)都是只为特定的类设计的,不是为所有的类,也不是为它所有的子类设计的。这意味着,对于一个类x的operator new来说,函数内部的行为在涉及到对象的大小时,都是精确的sizeof(x):不会大也不会小。但由于存在继承,基类中的operator new可能会被调用去为一个子类对象分配内存:
    class base {
    public:
      static void * operator new(size_t size);
      ...
    };

    class derived: public base       // derived类没有声明operator new
    { ... };                         //

    derived *p = new derived;        // 调用base::operator new

    如果base类的operator new不想费功夫专门去处理这种情况——这种情况出现的可能性不大——那最简单的办法是把这个“错误”数量的内存分配请求转给标准operator new来处理,象下面这样:
    void * base::operator new(size_t size)
    {
      if (size != sizeof(base))             // 如果数量“错误”,让标准operator new
        return ::operator new(size);        // 去处理这个请求
                                            //

      ...                                   // 否则处理这个请求
    }

    “停!”我听见你在叫,“你忘了检查一种虽然不合理但是有可能出现的一种情况——size有可能为零!”是的,我没检查,但拜托下次再叫出声的时候不要这么文绉绉的。:)但实际上检查还是做了,只不过融合到size != sizeof(base)语句中了。c++标准很怪异,其中之一就是规定所以独立的(freestanding)类的大小都是非零值。所以sizeof(base)永远不可能是零(即使base类没有成员),如果size为零,请求会转到::operator new,由它来以一种合理的方式对请求进行处理。(有趣的是,如果base不是独立的类,sizeof(base)有可能是零,详细说明参见"my article on counting objects")。

    如果想控制基于类的数组的内存分配,必须实现operator new的数组形式——operator new[](这个函数常被称为“数组new”,因为想不出"operator new[]")该怎么发音)。写operator new[]时,要记住你面对的是“原始”内存,不能对数组里还不存在的对象进行任何操作。实际上,你甚至还不知道数组里有多少个对象,因为不知道每个对象有多大。基类的operator new[]会通过继承的方式被用来为子类对象的数组分配内存,而子类对象往往比基类要大。所以,不能想当然认为base::operator new[]里的每个对象的大小都是sizeof(base),也就是说,数组里对象的数量不一定就是(请求字节数)/sizeof(base)。关于operator new[]的详细介绍参见条款m8。

    重写operator new(和operator new[])时所有要遵循的常规就这些。对于operator delete(以及它的伙伴operator delete[]),情况更简单。所要记住的只是,c++保证删除空指针永远是安全的,所以你要充分地应用这一保证。下面是非类成员形式的operator delete的伪代码:
    void operator delete(void *rawmemory)
    {
      if (rawmemory == 0) return;    file://如/果指针为空,返回
                                     //

      释放rawmemory指向的内存;

      return;
    }

    这个函数的类成员版本也简单,只是还必须检查被删除的对象的大小。假设类的operator new将“错误”大小的分配请求转给::operator new,那么也必须将“错误”大小的删除请求转给::operator delete:

    class base {                       // 和前面一样,只是这里声明了
    public:                            // operator delete
      static void * operator new(size_t size);
      static void operator delete(void *rawmemory, size_t size);
      ...
    };

    void base::operator delete(void *rawmemory, size_t size)
    {
      if (rawmemory == 0) return;      // 检查空指针

      if (size != sizeof(base)) {      // 如果size"错误",
        ::operator delete(rawmemory);  // 让标准operator来处理请求
        return;                        
      }

      释放指向rawmemory的内存;

      return;
    }

    可见,有关operator new和operator delete(以及他们的数组形式)的规定不是那么麻烦,重要的是必须遵守它。只要内存分配程序支持new-handler函数并正确地处理了零内存请求,就差不多了;如果内存释放程序又处理了空指针,那就没其他什么要做的了。至于在类成员版本的函数里增加继承支持,那将很快就可以完成。

  • 相关阅读:
    (转)sql server 生成树形菜单
    在Sublime Text 3中配置Python3的开发环境/Build System
    按回车键提交表单 问题
    多页面 返回 到同一页面
    sql 查找出表里所有字段
    sql 创建表变量,临时表
    sql语句中数据类型转换函数:CAST 和 Convert
    sql isnull函数
    判断值是否为整数
    TSQL游标使用
  • 原文地址:https://www.cnblogs.com/kex1n/p/2136704.html
Copyright © 2011-2022 走看看