python减少内存占用,Python的浮点类型没有长度限制,只受限于内存的大小
在实际项目中,很多地方都有IO。
木卫一需要时间。比如新malloc。
对于现代电脑来说,往往是GHz主频,似乎没有什么影响。但是在大型项目中,重复new和delete不仅会耗费大量时间,还会导致内存碎片。
新是一件很麻烦的事情。
哪里有新,哪里就是你可能埋坑的地方。
new发出的内存无法正确释放,会导致内存泄漏。一个细微的内存泄漏,日积月累就会让程序崩溃。
掌握内存管理是C程序员必备的能力。
其次,这里实现了一个简单的内存池。
这个内存池和STL的内存池没法比。STL的内存池实现更复杂,当然也提供了更强大的功能。但其本质是一样的。这个简单的内存池用于模板。
必备知识点:
c基本语法:reinterpret_cast(para))))))))))))))。
结构:链表
c通用编程:模板
操作系统:关键区域,锁定
放置新的(需要自己看C Primer))。
所以,我们开始吧:
实践中的一个基本点:reinterpret_cast(para))))))))))。
看一下reinterpret_cast(para)的字面意思,重新解释一下。你如何解释深刻的话?在《C++ Primer》这本书里,对此的解释很少。
以前不知道,看多了就有感觉了。我的理解是用新的expr结构重新解读。
在下面的代码中,您将看到以下内容
phead=*(reinterpret _ cast)(phead))))).
其实pHead是T*型的。在reinterpret_cast中,T*结构的pHead被强制转换成T**结构,然后在T**结构上执行*运算符。怎么样?很暴力。之后用代码解释。
练习点2:数据结构:链表
熟悉基本的数据结构必须是所有程序员的必备技能。我常听鲜艳的荷花。基础决定了你能爬多高。想想吧。一座高楼从平地上拔地而起。高二学数据结构的时候,完全不知道老师在讲台上一个人吐槽是什么意思,也不知道他在说什么。随着考试的临近,我在努力看书,对数据结构在说什么有了一些想法。好吧,让我们回到正题。
所谓链表是指有头的节点H,指向下一个节点NodeA,NodeA指向它的下一个节点nodeb,直到最后一个节点指向空值。这样就成了一系列俗称的链表。
例如:
类型节点
{
int _ content
节点*下一个;
}
节点*头;//定义一个链表头
Node * pNode//定义另一个节点
pHead-next=pNode;//头指向另一个节点.
上面的代码是一个简单的例子,省略了初始化部分。这个在实践中是写不出来的。
实践要点3: C通用编程:模板
基于模板的APP应用的一个典型例子就是STL。所以使用vector、list、map等容器很容易。STL已经成为C语言的重要库之一,并在工程中得到广泛应用。忽略此类型的定义可以提高代码的可重用性。模板在网络编程中的作用至关重要。
模板在书《C++ Primer》中有非常详细的解释。模板函数,模板类。如果你不知道,请仔细阅读这本书的通用编程部分。
练习点4:操作系统:临界区,摇滚
现代计算机的硬件已经非常普及。多核多线程。为了充分发挥处理器的作用,多线程技术在实际工程中已经相当成熟。但是在多线程操作的情况下,线程A写入一些共享数据,线程B也写入相同的共享数据。如果控制不好,共享的数据就会变成脏数据。这时候我该怎么办?有各种各样的解决方案,如锁定、互斥变量、信号量和临界区。Windows提供了一系列API来处理这些内容。我们需要使用
初始化安全(critical _ section *;//初始化限制区域
deletecriticalsection(critical _ section *;//解放边界地区
企业安全(critical _ section *;//进入临界区
leavecriticalsection(critical _ section *;//离开临界区
其中,CRITICAL_SECTION是Windows中定义的关键区域结构。我们不必深究,只需知道它的作用。
处理共用面积的整个过程应该如下:
共享数据在初始化期间声明并初始化关键部分结构。当线程A操作共享数据时,它
等等,让数据进入临界区,然后操作。这样,线程b就无法写入数据。完成后,让数据离开关键区域。b线程此时只能写数据。当数据被破坏时,关键区域被释放。
介绍完这些知识点,下面就是编码的时间了。在命名中,我们给关键区域一个单独的名字:锁。
定义临界区(因为你要对Windows使用API函数,记得包括Windows.h)
CBaseLock类
{
公共:
CBaseLock()
{
InitializeCriticalSection(m _ Sect);
}
~CBaseLock()
{
DeleteCriticalSection(m _ Sect);
}
无效锁()
{
enter critical section(m _ Sect);
}
无效解锁()
{
LeaveCriticalSection(m _ Sect);
}
私人:
临界_截面m _截面;
};
定义锁
模板
类别CLockImpl
{
公共:
CLockImpl(T* l) : m_lock(l)
{
m _ Lock-Lock();
}
~CLockImpl()
{
m _ lock-Unlock();
}
私人:
T * m _ lock
};
定义内存池
模板
MyPool类
{
公共:
我的池()
{
allocated count=0;
ElemSize=sizeof(T) sizeof(T*)?sizeof(T):sizeof(T *);
pHead=NULL
}
~我的池()
{
while(pHead)
{
T * ret=pHead
pHead=*(reinterpret _ cast(pHead));
免费(退休);
}
}
int GetCount()常量
{
返回numAllocated
}
t *分配()
{
CLockImpl锁(m _ lock);
numAllocated
if(pHead==NULL)
return new(malloc(ElemSize))T;
T * ret=freeListHead
pHead=*(reinterpret _ cast(pHead));
返回新的(ret)T;
}
模板
T *alloc(常数T1 p)
{
CLockImpl锁(m _ lock);
numAllocated
if(pHead==NULL)
返回new(malloc(ElemSize))T(p);
T * ret=pHead
pHead=*(reinterpret _ cast(pHead));
返回新的(ret)T(p);
}
模板
模板
…//这些情况我就不写了。
无效dealloc(T* elem)
{
elem-~ T();
如果(真)
{
CLockImpl锁(m _ lock);
memset(elem,0xfe,elementSize);
-allocated count;
*(reinterpret _ cast(elem))=pHead;
pHead=elem
}
}
私人:
int AllocatedCount//分配的内存节点数
size _ t ElemSize//内存节点的大小
T * pHead//空闲内存的链表头
LockMode m _ lock
}
上面的代码基本上完成了一个简单的内存池。
下面,对内存池中的一些关键语句进行解释。
构造器
当内存初始化时,什么都没有。因此,指向空闲内存区域的指针应该指向NULL。分配的存储器节点的数量当然必须是0。最深刻的,应该是这句话:
ElemSize=sizeof(T) sizeof(T*)?sizeof(T):sizeof(T *);
这意味着ElemSize的大小应该是两个参数T和T*中较大的一个。(为什么要这样设计?往下看。)
接口PublicAlloc()
对于内存池中的element对象,如果需要多个参数进行初始化,则需要传入多个参数。这样,alloc()接口显然需要满足很多情况,所以需要将alloc()定义为模板函数,并提供相应的结构参数。形式参数是在内存池中用模板参数指定的,因此通过调用alloc()并传入实际参数,可以初始化元素并为它们分配内存。
仔细看alloc()的实现,发现当pHead指向null时,需要一个ElemSize大小的新空间。当pHead不为空时,返回pHead指向的空间。
pHead=*(reinterpret _ cast(pHead));
pHead指向T* type,然后用reinterpret_cast重新解释PHead。此时,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**类型。也就是说我删除了ELM的内存的内容,然后把地址分成几个4字节的连续块(32位系统下),把pHead指向的第一个空闲地址块的地址下放到电阻块的第一个4字节保存,那么pHead=elemPHead指向elem的地址,那么第一个空闲地址就是新释放的elem的地址,elem地址中的前4个字节保存下一个空闲地址块的地址.所以链接成一个链表。
然后回到alloc,alloc首先检查是否有空闲地址,也就是pHead是否指向NULL,如果指向NULL,就会新建一个ElemSize大小的内存。在这个内存中,它将使用放置新技术在请求的内存中构造所需的元素对象。如果pHead没有指向NULL,说明已经有应用的内存了(可能是某个对象被析构了,留下了,没有返回给系统)。获取pHead指向的第一个空闲内存块的地址,然后pHead指向这个空闲地址指向的空闲地址块(即pHead=*(reinterpret _ cast(pHead));这句话的意思),然后构造元素对象。这样就减少了思考新系统的次数,节省了系统寻找合适大小内存块的时间,提高了效率。
锁定的原因是防止多个线程同时在一个对象内存池上操作。
再次解释这个简单的内存池。池子的运行机制大致如下。有些内存池会比这个复杂得多。但是了解了分配的关键点之后,任何内存池都可以玩的游刃有余。
总结:
内存池的存在减少了系统IO次数和系统寻找合适大小的内存块的时间。提高了程序的运行效率,有效减少了内存碎片的产生。
=====结尾=====
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。