最近在写高并发服务器用到了完成端口:1、完成端口对输入输出的处理需要预先申请一个Overlapped缓存空间,并且要保持该缓存所在页面一直呆在物理内存里(一个页面大小根据情况有所不同,我的破电脑页大小是4K,也就是说如果你有一块Overlapped缓存虽然只有100个字节,但是整个4K的页都必须呆在物理内存里),而不是像一般空间一样可以被系统随意调度出物理内存(windows内存管理机制:长时间不用到的页会从物理内存中转出),但是这带来一个问题:如果输入输出请求量过于巨大,则导致被锁定的页太多,从而使的物理空间被占满影响到其他程序的运行,甚至完成端口本身也无法响应请求。(虽然windowsNT会对锁定页面做限制:即大概是不能超过物理内存空间的1/8)。2、每次进行I/O操作时都要申请新的内存,用完后再释放,显然是非常影响运行效率的,而且也会产生大量的内存碎片。所以一般情况下高性能的服务器要求:在程序运行之初先把需要用到的内存全部申请好,每次需要内存的时候直接拿来用就可以了,等到程序退出的时候再把内存一次性释放掉。-----这个就是内存池技术。 我从网上看了很多人的内存池代码,发现他们有一个比较大的缺陷:他们的内存池不是一整块申请,而是一小块一小块申请,比如: for i := 2 to IO_MEM_MAX_COUNT do
begin
PUNode(IOMemLast)^.Next := HeapAlloc(Heap, HEAP_ZERO_MEMORY, sizeof(TIOMem));
IOMemList[i] := PUNode(IOMemLast)^.Next;
IOMemLast := PUNode(IOMemLast)^.Next;
end; 我觉的这样申请的内存有一个很大的问题:由于这些一块一块的内存不是一起申请的,所以地址是不连续的,也就是这些内存会分散在不同的页中,这样就导致被锁定在物理内存中的页无形中多了很多,这应该很好理解的。 所以我认为应该一次性申请地址空间连续的内存,这样整齐的内存会减少很多被锁定的页面。------------------------------------------------不一定成熟的想法。
begin
PUNode(IOMemLast)^.Next := HeapAlloc(Heap, HEAP_ZERO_MEMORY, sizeof(TIOMem));
IOMemList[i] := PUNode(IOMemLast)^.Next;
IOMemLast := PUNode(IOMemLast)^.Next;
end; 我觉的这样申请的内存有一个很大的问题:由于这些一块一块的内存不是一起申请的,所以地址是不连续的,也就是这些内存会分散在不同的页中,这样就导致被锁定在物理内存中的页无形中多了很多,这应该很好理解的。 所以我认为应该一次性申请地址空间连续的内存,这样整齐的内存会减少很多被锁定的页面。------------------------------------------------不一定成熟的想法。
比较通常的思路是:建立一个链表来把所有连接保存起来,然后定时遍历连接,超时的断开,同时为每一个链表节点建立一个互斥量以同步线程:
PLink =^ TLink;
TLink = record
Socket: TSocket;
RemoteIP: string[30];
RemotePort: DWord;
//最后收到数据时的系统节拍
TickCountActive: DWord;
//处理该连接的当前线程的信息
Section: TRTLCriticalSection;
Next: PLink;
Prior: PLink;
end;
另外,既然程序使用IOCP,是服务,是长时间运行的程序,这一类程序还是一次从系统中分配足够的内存,然后自己管理比较好。系统默认的分配方式,能够适应各种情况,同时也意味着会有更多的薄记,更低的效率,这对于一个以效率为诉求的服务来说是一种伤害。
换一个角度看,计算机管理的内存就是我们的内存池。不是吗? 能作为服务器的机子,一般都是专门的跑一个服务程序。操作系统管理的内存本身就是我们的内存池了。
其次,在应用程序唯一的情况下,内存的碎片跟你的程序中的new/delete模块尺寸才是最主要的。比如游戏服务器中,最主要的对象产生就是物品和人物。那么在设计上往往可以规定好尺寸的种类,或者静态分配一个数组,来模拟一些链表的实现。
所以,在一些必须的new/delete模块上,把系统的内存管理看成内存池即可;更重要的是在你的应用程序中掌握好对象的尺寸。
比如说复用内存,避免频繁的分配和释放。至于LZ所说的一下分配一块巨大的内存,然后在这个内存中进行处理;
以避免频繁的内存锁定问题。这个效果如何,其实应该测试一下。不过对于计算机操作系统而言,内存的管理分配模块是一个
经过高度优化之后的产品(包含了非常多的内存分配算法),
以尽可能的避免内存碎片以及快速的分配。自己做的内存池的管理
模块并不一定会有着明显的优势(除非你经过测试证明了);
相反可能会对你的程序造成一些意外的影响。
而且,从整个程序的角度来看,无论你使用哪种内存分配管理
手段,都不可避免的会造成内存碎片。(详细的讨论可以参考
计算机程序设计艺术一书,上面有详细的介绍)。IOCP的优势是I/O操作的多路复用,减少线程等待I/O操作的时间
,提高CPU的利用率。把它和Windows提供的线程池结合起来,
对于程序的效率有着显著的提高。
一个好的内存池要做出来很花功夫, 如果对性能提升不大就没什么意义了
比较通常的思路是:建立一个链表来把所有连接保存起来,然后定时遍历连接,超时的断开,同时为每一个链表节点建立一个互斥量以同步线程:
PLink =^ TLink;
TLink = record
Socket: TSocket;
RemoteIP: string[30];
RemotePort: DWord;
//最后收到数据时的系统节拍
TickCountActive: DWord;
//处理该连接的当前线程的信息
Section: TRTLCriticalSection;
Next: PLink;
Prior: PLink;
end;
问题。我们先申请一块大内存(当然要考虑申请失败的情况),然后规划我们需要每次申请的内存块,内存块
可以按照最小内存块倍数分配。程序运行期内只向大内存块申请但不释放。大内存块内部全部复用。提高运行
效率,同时打印内存分配日志用于debug。
1. 足够大是多大?
2. 申请失败如何调整,特别是对申请nonpaged内存?