最近在写高并发服务器用到了完成端口: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;   我觉的这样申请的内存有一个很大的问题:由于这些一块一块的内存不是一起申请的,所以地址是不连续的,也就是这些内存会分散在不同的页中,这样就导致被锁定在物理内存中的页无形中多了很多,这应该很好理解的。   所以我认为应该一次性申请地址空间连续的内存,这样整齐的内存会减少很多被锁定的页面。------------------------------------------------不一定成熟的想法。

解决方案 »

  1.   

    你可以调用VirtualAlloc来分配内存
      

  2.   

    对,申请一个足够大的内存,对效率是很有帮助的,但是也非常容易申请失败,另外如果优化内存效率,也可以使用VirtualAlloc
      

  3.   

    还有一个疑问:完成端口如何管理那些超时的socket连接?
    比较通常的思路是:建立一个链表来把所有连接保存起来,然后定时遍历连接,超时的断开,同时为每一个链表节点建立一个互斥量以同步线程:
      PLink =^ TLink;
      TLink = record
        Socket: TSocket;
        RemoteIP: string[30];
        RemotePort: DWord;
        //最后收到数据时的系统节拍
        TickCountActive: DWord;
        //处理该连接的当前线程的信息
        Section: TRTLCriticalSection;
        Next: PLink;
        Prior: PLink;
      end;
      

  4.   

    完成端口申请的Overlapped Buffer的大小可以为0,这样就不占用non-paged pool了,当一个0字节的Overlapped完成后进行异步操作直到返回WSAEWOULDBLOCK.
      

  5.   

    loki库里有一个smallobjectalloc,对于小型对象的分配管理的非常好,可以直接拿来用,也有很多借鉴的地方。
    另外,既然程序使用IOCP,是服务,是长时间运行的程序,这一类程序还是一次从系统中分配足够的内存,然后自己管理比较好。系统默认的分配方式,能够适应各种情况,同时也意味着会有更多的薄记,更低的效率,这对于一个以效率为诉求的服务来说是一种伤害。
      

  6.   

    实际效果这真的是这样吗?不太见得。
    换一个角度看,计算机管理的内存就是我们的内存池。不是吗? 能作为服务器的机子,一般都是专门的跑一个服务程序。操作系统管理的内存本身就是我们的内存池了。
    其次,在应用程序唯一的情况下,内存的碎片跟你的程序中的new/delete模块尺寸才是最主要的。比如游戏服务器中,最主要的对象产生就是物品和人物。那么在设计上往往可以规定好尺寸的种类,或者静态分配一个数组,来模拟一些链表的实现。
    所以,在一些必须的new/delete模块上,把系统的内存管理看成内存池即可;更重要的是在你的应用程序中掌握好对象的尺寸。
      

  7.   

    就像gunsand所说的,完成端口和内存池其实没有什么联系。如果考虑使用内存池的话,确实可以一些内存优化的动作。
    比如说复用内存,避免频繁的分配和释放。至于LZ所说的一下分配一块巨大的内存,然后在这个内存中进行处理;
    以避免频繁的内存锁定问题。这个效果如何,其实应该测试一下。不过对于计算机操作系统而言,内存的管理分配模块是一个
    经过高度优化之后的产品(包含了非常多的内存分配算法),
    以尽可能的避免内存碎片以及快速的分配。自己做的内存池的管理
    模块并不一定会有着明显的优势(除非你经过测试证明了);
    相反可能会对你的程序造成一些意外的影响。
    而且,从整个程序的角度来看,无论你使用哪种内存分配管理
    手段,都不可避免的会造成内存碎片。(详细的讨论可以参考
    计算机程序设计艺术一书,上面有详细的介绍)。IOCP的优势是I/O操作的多路复用,减少线程等待I/O操作的时间
    ,提高CPU的利用率。把它和Windows提供的线程池结合起来,
    对于程序的效率有着显著的提高。
      

  8.   

    只能UPUPUPUPUPUPUPUPUPUPUPUPUPUPUPUPUPUPUPUPUPUPUPUP
      

  9.   

    理论上是这样, 但实际效果如何, 还是需要通过测试程序跑 bench
    一个好的内存池要做出来很花功夫, 如果对性能提升不大就没什么意义了
      

  10.   

      控制与捕捉输入法的实现单元两种SQL语句的执行速度比较  改变你生活的50种方式   11种脸色反映出你的健康女程序员转哪行比较好?  话说程序员职业生涯  放文件系统里还是数据库里快?
      

  11.   

    还有一个疑问:完成端口如何管理那些超时的socket连接?
    比较通常的思路是:建立一个链表来把所有连接保存起来,然后定时遍历连接,超时的断开,同时为每一个链表节点建立一个互斥量以同步线程:
    PLink =^ TLink;
    TLink = record
    Socket: TSocket;
    RemoteIP: string[30];
    RemotePort: DWord;
    //最后收到数据时的系统节拍
    TickCountActive: DWord;
    //处理该连接的当前线程的信息
    Section: TRTLCriticalSection;
    Next: PLink;
    Prior: PLink;
    end;
      

  12.   

    很久不玩delphi了。现在玩的人还多吗?都用来做些什么?谢谢!
      

  13.   

    我一般都是开辟一个空的内存池,随着使用内存块的增加逐步扩大内存池,因为是基于客户端或者小型服务器,大规模高并发的内存存取操作基本上没有,感觉这么做也足够了,比频繁的new、delete效率高。但是如果做大型服务器的话,就不能用这种方法了,最好的办法是程序运行时就申请出“足够大”的内存区块,之后所有的数据操作都在这个“池”中进行。难的地方就是不知道这个“足够大”到底是多大,如果内存池的容量远远大于使用所需的容量,会造成浪费;如果内存池容量不够用,还需要进行扩张,又变成了不连续的内存区块。
      

  14.   

    内存碎片产生的根源是程序无序的申请任意大小的内存,最后导致系统堆上的内存不贯。系统内存有字节对齐
    问题。我们先申请一块大内存(当然要考虑申请失败的情况),然后规划我们需要每次申请的内存块,内存块
    可以按照最小内存块倍数分配。程序运行期内只向大内存块申请但不释放。大内存块内部全部复用。提高运行
    效率,同时打印内存分配日志用于debug。
      

  15.   

    问题是:
    1. 足够大是多大?
    2. 申请失败如何调整,特别是对申请nonpaged内存?