首页 > 编程知识 正文

限制python内存上限,python小数据池

时间:2023-05-06 20:09:37 阅读:163178 作者:3814

在实际项目中,很多地方都有IO。

IO需要时间。 例如,new,malloc。

对现代计算机来说,往往是GHz量级的主频,似乎没有感觉到影响。 但是,在大型项目中,重复new和delete不仅会花费很多时间,还会导致内存碎片。

new是非常麻烦的东西。

有new的地方,就是你有可能种坑的地方。

new发出的内存无法正确释放,会导致内存泄漏。 细微的内存泄漏会积累很长时间,使程序崩溃。

掌握内存管理是C程序员必备的能力。

其次,在这里实现简单的内存池。

这个内存池不能与STL的内存池相媲美。 STL的内存池实现更为复杂,当然提供了更强大的功能。 但是,其本质是一样的。 这个简单的内存池用于模板。

所需的知识点:

c基本语法: reinterpret_cast(para ) ) ) ) ) ) ) ) ) )。

数据结构:链表

C通用编程:模板

操作系统:临界区域,锁定

placement new (这需要自己去看C Primer ) )。

那么,现在开始:

实践中的一个基本点: reinterpret_cast(para ) ) ) ) ) ) ) ) )。

查看reinterpret_cast(para )的字面,重新解释。 深奥的话,怎么解释? 在《C++ Primer》这本书中,对此的说明也很少。

我以前也不知道,但后来看多了,有点感觉。 我的理解是使用新的expr结构重新解释。

在以下代码中,您将看到以下内容

phead=*(reinterpret_cast ) (phead ) ) ) ) )。

实际上,pHead是T*型。 在reinterpret_cast中将T*结构的pHead强制转换为T**结构,然后对T**结构执行*运算符。 怎么样,很暴力吧。 之后,结合代码进行说明。

实践要点之二:数据结构:链表

对所有程序员来说,熟悉基本的数据结构必须是一项必备技能。 我经常听鲜艳的莲花们说话。 基础决定了你能爬多高。 想想看。 万丈高楼从平地开始。 我在大二学习数据结构的时候,完全不知道老师在讲台上一个人唾沫四溅是什么意思,完全不知道他在说什么。 临近考试,拼命啃书,对数据结构到底在说什么,有点眉目了。 好了,回到正题吧。

所谓链表,是指有头部的节点h,该节点指向下一个节点NodeA,NodeA指向自己的下一个节点nodeb到最后的节点为止指向空值。 这样,就成了一系列的俗称链接列表。

举个例子:

类型结构节点

{

int _content;

Node* next;

}

节点*头部; //define a head of a LinkList

Node* pNode; //define another node

pHead-next=pNode; //the head point to another node。

上面的代码是一个简单的例子,省略了初始化部分。 实际操作的时候不能这样写。

实践要点之三: c通用编程:模板

基于模板的APP应用的典型示例是STL。 因此,可以轻松使用vector、list、map等容器。 STL已成为C的重要库之一,在工程中得到广泛应用。 忽略此类型的定义可以提高代码的可重用性。 模板在网络编程中的作用是必不可少的。

模板在《C++ Primer》这本书里做了非常详细的说明。 模板函数,模板类。 如果你不认识,请仔细阅读这本书的通用编程部分。

实践要点之四:操作系统:临界区、摇滚

现代计算机的硬件已经很抢手了。 多核,多线程。 为了充分发挥处理器的作用,在实际工程中多线程技术已经相当成熟。 但是,在多线程操作的情况下,线程a对某个共享数据进行写入,同时线程b也对相同的共享数据进行多少写入,如果没有好好控制,共享数据就会成为脏数据。 这个时候该怎么办? 解决方法各种各样,锁定、互斥变量、信号量、临界区。 Windows提供了一系列用于处理这些内容的api。 我们需要用

初始化安全性(critical _ section *; //初始化极限区域

deletecriticalsection (critical _ section *; //解放界限区

企业安全(critical _ section *; //进入临界区

leavecriticalsection (critical _ section *; //离开临界区

其中CRITICAL_SECTION是在Windows中定义的临界区域结构。 我们不用深入,只要知道其作用就可以了。

处理共享区域的整个过程应该如下所示:

共享数据Data在初始化时声明并初始化关键节结构,在线程a操作共享数据Data时

候,让Data进入临界区域,然后操作。这样,线程B就无法对Data进行写操作了。操作完成,让Data离开临界区域。线程B这个时候才可以对Data进行写操作。在Data析构的时候,释放临界区域。

介绍完这些知识点,下面是应该上代码的时候了。在命名上,我们把临界区取个别名:锁。

定义临界区(因为要使用到Windows的API函数,记得包含Windows.h)

class CBaseLock

{

public:

CBaseLock()

{

InitializeCriticalSection(&m_sect);

}

~CBaseLock()

{

DeleteCriticalSection(&m_sect);

}

void Lock()

{

EnterCriticalSection(&m_sect);

}

void Unlock()

{

LeaveCriticalSection(&m_sect);

}

private:

CRITICAL_SECTION m_sect;

};

定义锁

template

class CLockImpl

{

public:

CLockImpl(T* l) : m_lock(l)

{

m_lock->Lock();

}

~CLockImpl()

{

m_lock->Unlock();

}

private:

T* m_lock;

};

定义内存池

template

class MyPool

{

public:

MyPool()

{

AllocatedCount = 0;

ElemSize = sizeof(T) > sizeof(T*) ? sizeof(T) : sizeof(T*);

pHead = NULL;

}

~MyPool()

{

while(pHead)

{

T* ret = pHead;

pHead = *(reinterpret_cast(pHead));

::free(ret);

}

}

int GetCount()const

{

return numAllocated;

}

T *alloc()

{

CLockImpl lock(&m_lock);

numAllocated++;

if(pHead == NULL)

return new(malloc(ElemSize))T;

T* ret = freeListHead;

pHead = *(reinterpret_cast(pHead));

return new(ret)T;

}

template

T *alloc(const T1& p)

{

CLockImpl lock(&m_lock);

numAllocated++;

if(pHead == NULL)

return new(malloc(ElemSize))T(p);

T* ret = pHead;

pHead = *(reinterpret_cast(pHead));

return new(ret)T(p);

}

template

template

……//这些情况我就不写了

void dealloc(T* elem)

{

elem->~T();

if(true)

{

CLockImpl lock(&m_lock);

memset(elem, 0xfe, elementSize);

--AllocatedCount;

*(reinterpret_cast(elem)) = pHead;

pHead = elem;

}

}

private:

int AllocatedCount; // 已经分配的内存节点个数

size_t ElemSize; // 内存节点的大小

T* pHead; // 空闲内存的链表表头

LockMode m_lock;

}

以上代码,基本完成了一个简单的内存池。

下面,对内存池中的一些重点语句进行解释。

构造函数

内存池初始化出来,是什么都没有的。因此,指向空闲内存区域的指针应指向NULL。而已分配的内存节点个数,当然必须是0。最最富有深意的,就应该是这句:

ElemSize = sizeof(T) > sizeof(T*) ? sizeof(T) : sizeof(T*);

其含义是,ElemSize的大小,应该是木板参数T与T*二者中较大的一个。(为什么要这么设计?往下看。)

public 接口 alloc()

对于内存池中的元素对象,若需要多个参数来初始化,就需要传入多个参数。这样,alloc()接口显然要满足多种情况,就需要把alloc()定义为模板函数,提供对应构造形参。而形参在内存池中,用模板参数指定,这样,调用alloc(),传入实参,就可以初始化元素,并为之分配内存了。

仔细看alloc()的实现,发现pHead指向空的时候,需要new一个ElemSize大小的空间。而pHead不为空的时候,就把pHead指向的空间返回。

pHead = *(reinterpret_cast(pHead));

pHead指向T*类型,然后把pHead用reinterpret_cast重新解释,这个时候,pHead指向的地址的空间(逻辑)结构就改变了。假设pHead指向ElemA类型,       sizeof(ElemA) = 5,用reinterpret_cast重新解释,pHead指向就是指向指针的指针了。然后*reinterpret_cast就是向重新解释的地址区域取     值(取的是什么?继续往下看)。

析构

析构的时候,有这么一句:

*(reinterpret_cast(elem)) = pHead;

按照之前的解释,是把elem的地址用T**重新解释,然后取值,把pHead指向的地址放到该处。而pHead指向的地址,一定是比T和T*中,较大的一个的。32位操作系统下,T*是4字节,这说明pHead指向的地址,至少是4字节,至少足够容纳一个T**的类型。也就是说,我把elem这块内存的内容删除了,然后把这块地址分成若干4字节(32位系统下)的连续块,把pHead指向的第一个空闲地址块的地址放倒这块抵制块的第一个4字节中保存,然后pHead = elem; pHead指向elem的地址,则第一个空闲地址快就是刚刚释放了的elem的地址,elem地址中的第一个4字节保存了下一个空闲地址块的地址……如此链成链表。

然后又回到alloc上,alloc先检查有没有空闲地址,即pHead是否指向NULL,如果指向NULL,则new一块ElemSize大小的内存,在该内存中,用placement new技术,在申请的内存中,构造需要的元素对象。若pHead不指向NULL,则说明已经有申请好的内存(可能是某个对象析构之后,留下来,没有返还给系统的),取到pHead指向的第一块空闲内存块的地址,然后然pHead指向这块空闲地址质指向的空闲地址块(即 pHead = *(reinterpret_cast(pHead));这句的含义),然后构造元素对象。这样减少了想系统new的次数,节省了系统寻找合适大小内存块的时间,提高了效率。

加锁的原因,是因为防止多个线程,同时对某一对象内存池进行操作。

这样把简单的内存池解释了一遍。内存池的运行机制,大致是这样的。有些内存池会比这个内存池复杂得多。但是,理解了其中分配的要点,什么内存池都能玩得游刃有余。

总结:

内存池的存在,减少了系统IO次数,缩减了系统查找合适大小内存块的时间。提高了程序的运行效率,并有效减少了内存碎片的产生。

=====>THE END<=====

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。