【问题标题】:Named pipes performance issues命名管道性能问题
【发布时间】:2012-10-16 11:42:05
【问题描述】:

我正在使用命名管道在 C# 和 Delphi 之间进行过程间通信。 C# 使用System.IO.Pipes 包,而Delphi 使用Libby's pipes.pas。不幸的是,通信几乎是高性能的:分析显示通信占用了整个运行时间的 72%,其余用于计算。
我能够找到一个可能占用资源的问题:如果我没有在 Delphi 中显式断开发送客户端的连接,C# 根本不会收到任何数据

德尔福(发送)

FClient1.Write(msg[1], Length(msg));
FClient1.FlushPipeBuffers;
FClient1.WaitForReply(20);
FClient1.Disconnect;   // disconnect to signalize C# that the writing is finished
FClient1.Connect;      // connect again to prevent synchronization problems

C#(接收)

// Wait for a client to connect
stc.pipeServer.WaitForConnection();
while (reconnect_attempts < MAX_RECONNECT_ATTEMPTS) // 
{
   string tmp = sr.ReadLine();

   // if result is empty, try again for <MAX_RECONNECT_ATTEMPTS> times
   // so you can eliminate the chance that there's just a single empty request
   while (tmp != null)// && result != tmp)
   {
      tmp = sr.ReadLine();
      result += tmp;
   }
   // sleep, increment reconnect, write debugging...
}
stc.pipeServer.Close();

尽管我猜重新连接很昂贵,但我并不完全确定。一个数据流(大约 1 / 11 kb)总共需要 130(11kb 分别为 270ms)(发送和接收)。

我的问题是
是否有必要强制断开管道以表示客户端已完成写入?就我的观察而言,只有在使用 libby 发送时才有必要。性能不佳还有其他可能的原因吗?提前致谢。

另外,这里的发送和接收反之亦然:

C#(发送)

 stc.pipeClient.Connect();
 StreamWriter sw = new StreamWriter(stc.pipeClient);
 //sw.AutoFlush = true;
 sw.WriteLine(msg);
 sw.Flush();
 stc.pipeClient.WaitForPipeDrain();  // waits for the other end to read all bytes 
 // neither disconnect nor dispose

Delphi(接收)

 SetLength(S, Stream.Size);   Stream.Read(S[1], Length(S));  
 FPipeBuffer := FPipeBuffer + S;   { TODO 2 : switch case ID }   
// if the XML is complete, i.e. ends with the closing checksum   
if (IsFullMessage()) then
begin
   // end reading, set flag
   FIsPipeReady := true;
end

【问题讨论】:

  • 也许不是断开连接而是尝试发送换行符,以便 C# 端可以处理其Readline(); 调用。
  • 在字符串中添加 #13#10 形式的换行符没有帮助。从外观上看,C# 部分在断开连接之前无法识别任何数据。
  • 究竟是什么吃掉了这 72%? C#/Delphi 发送/接收?
  • 两边的全管道通信,即C# -> Delphi,反之亦然。

标签: c# delphi io named-pipes


【解决方案1】:

经过大量(手动)分析后,我对这个问题提出了两个见解:

  1. Libby 的管道是一个复杂的野兽。由于它似乎使用了多个线程并且在其使用方面表现出奇怪的行为,因此手动使用 WinApi 毕竟更加方便。此外,实际通信的性能有所提高。换句话说:在这样一个相对简单的 IPC 场景中,libby 的管道似乎比 WinApi 慢。
  2. 匿名管道/使用标准输出和标准输入似乎比命名管道更快。

但是,我必须补充一点,我仍然有点困惑,无法判断这是真的还是我在这里计算了错误的数字。

下面是一个简单的例子,展示了在 Delphi 中 WinApi 实现的样子:

// setup pipes, you'll need one for each direction
// init handles with 0
    CreatePipe(ReadPipe1,       // hReadpipe
               WritePipe1,      // hWritePIpe
               @SecurityAttributes,        // Security
               PIPE_SIZE)                  // Size

    // setup Startupinfo
    FillChar(StartupInfo, Sizeof(StartupInfo), 0);
    StartupInfo.cb := Sizeof(StartupInfo);
    StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
    StartupInfo.hStdInput := ReadPipe1;
    StartupInfo.hStdOutput := WritePipe2;
    StartupInfo.wShowWindow :=  SW_HIDE; 

    // CreateProcess [...]

    // read
    Win32Check(
            ReadFile(
                  ReadPipe1,  // source
                  (@outputBuffer[1])^,               // buffer-pointer
                  PIPE_BUFFER_SIZE,                 // size
                  bytesRead,                       // returns bytes actually read
                  nil                             // overlapped on default
                  ));
    // send           
    Win32Check(
            WriteFile(
                WritePipe2,
                (@msg[1])^,         // lpBuffer - workarround to avoid type cast
                NumberOfBytesToWrite,
                bytesWritten,       // lpNumberOfBytesWritten
                nil                 // Overlapped   
                ));                          

【讨论】:

    【解决方案2】:

    也许您可以将命名事件用于 IPC 信令。 当这些是本地的 (TEvent.Create('local\myserver'); 当您需要在不同会话之间进行 IPC(例如客户端应用程序和后台 windows 服务)时,您需要更多权限等(由于 UAC,win7 中无法使用默认全局\?)。 http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/devwin32/threadswaitingforatasktobecompleted_xml.html

    例如:为每个连接创建一个事件(每个连接生成一个名称)。

    或者看看一个不同的 IPC 命名管道 + 事件实现: https://micksmix.wordpress.com/2011/06/27/named-pipes-unit-for-delphi/

    顺便说一句:你提到你使用了分析,但你不能说什么最耗时? 你使用了什么样的分析?不是“分析器”之类的 AQtime (http://smartbear.com/products/free-tools/aqtime-standard) 还是 AsmProfiler (http://code.google.com/p/asmprofiler/)?

    【讨论】:

    • 注意:提到的“其他”实现使用特殊的开始+结束标记来指示传输的开始或结束(MB_END等)。但是这个实现只是 delphi...
    • 感谢您的快速回复。我已经在使用这个“改进的”pipes.pas,因为 libby 的原始网站甚至不再在线(archive.org 的最后一个条目是从 2010 年开始的)。你有一个如何使用这些令牌的例子吗?源代码并没有太大帮助 - 我无法弄清楚如何使用 TPipeThread(它实现了 MB_*-constants)。
    • 顺便说一下,我使用了 AQtime 和自定义时间测量,在代码中的重要点对时间(毫秒)和 CPU 滴答声进行了协议。 AQtime 无法逐行分析代码。
    • 我对Delphi和C#之间的命名管道做的不多,也许你可以发布一个delphi和C#的小演示应用程序? (pastebin.com)
    【解决方案3】:

    简单的改进可能是:首先发送要发送的字节数(以便接收方知道它可以预期多少数据)然后发送数据

    【讨论】:

    • 这将如何提高性能?如果我在实际消息之前发送消息的长度,我需要另一个不连接来表示 C# 来读取它。我很确定 System-API 已经使用了缓冲结构。
    • 不,您通常应该需要断开连接以发出信号!只需先发送字节数,在 C# 中,您读取此数量并读取一个字节数组,直到您拥有相同的数量,因此 C# 知道它何时准备就绪。
    猜你喜欢
    • 1970-01-01
    • 2016-03-30
    • 2010-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-31
    • 1970-01-01
    相关资源
    最近更新 更多