我正在编写一个应用程序,其中两个应用程序(比如服务器和客户端)通过localhost上的基于TCP的连接进行通信。
代码对性能至关重要,所以我尽量优化。
以下代码来自服务器应用程序。要发送消息,我的天真方法是从TcpClient的流创建BinaryWriter,并通过BinaryWriter写入消息的每个值。 所以我们假设消息由4个值组成;一个长的,然后是一个bolean值,然后再多2个;天真的做法是:
TcpClient client = ...;
var writer = new BinaryWriter(client.GetStream());
// The following takes ca. 0.55ms:
writer.Write((long)123);
writer.Write(true);
writer.Write((long)456);
writer.Write((long)2);
执行时间为0.55ms,这让我感觉相当慢。 然后,我尝试了以下内容:
TcpClient client = ...;
// The following takes ca. 0.15ms:
var b1 = BitConverter.GetBytes((long)123);
var b2 = BitConverter.GetBytes(true);
var b3 = BitConverter.GetBytes((long)456);
var b4 = BitConverter.GetBytes((long)2);
var result = new byte[b1.Length + b2.Length + b3.Length + b4.Length];
Array.Copy(b1, 0, result, 0, b1.Length);
Array.Copy(b2, 0, result, b1.Length, b2.Length);
Array.Copy(b3, 0, result, b1.Length + b2.Length, b3.Length);
Array.Copy(b4, 0, result, b1.Length + b2.Length + b3.Length, b4.Length);
client.GetStream().Write(result, 0, result.Length);
后者运行时间约为0.15毫秒,而第一种方法约需0.55毫秒,因此慢了3-4倍。
我想知道......为什么? 更重要的是,尽可能快地编写消息的最佳方式是什么(同时保持至少最低代码可读性)?
我现在想到的唯一方法是创建一个类似于BinaryWriter的自定义类; 但是不是直接将每个值写入流,而是缓冲一定数量的数据(比如10,000个字节等),并且只在内部缓冲区已满时将其发送到流,或者明确表示某些.Flush()方法。被叫(例如,当写完信息时)。
这应该有用,但是我想知道我是否过于复杂化并且有更简单的方法来获得良好的性能? 如果这确实是最好的方法 - 任何建议理想情况下内部缓冲区应该有多大?将它与Winsock的发送和接收缓冲区对齐是否有意义,或者最好使它尽可能大(或者说在合理的内存限制下如此大)?
谢谢!
答案 0 :(得分:0)
第一个代码执行四个阻止网络IO操作,而第二个代码只执行一个操作。通常,大多数类型的IO操作都会产生相当大的开销,因此您可能希望避免小的写入/读取和批处理。
您应该始终序列化您的数据,如果可行,则将其批量处理为单个消息。这样就可以避免尽可能多的IO开销。
答案 1 :(得分:0)
问题可能更多是关于进程间通信(IPC)而不是TCP协议。 IPC有多种选择(请参阅Microsoft Dev Center上的Interprocess Communications页面)。首先,您需要定义系统要求(系统应如何执行/扩展),而不是选择使用性能指标在您的特定方案中最佳选择的最简单选项。
Joe Duffy的Performance Culture文章的相关摘录:
体面的工程师直觉。好的工程师测量。伟大的工程师同时做到这两点。
衡量什么呢?
我将指标分为两个不同的类别:
- 消费指标。这些直接测量运行测试所消耗的资源。
- 观察指标。这些测量使用系统“外部”的度量来衡量运行测试的结果。
消费度量的示例是硬件性能计数器,例如退出的指令,数据高速缓存未命中,指令高速缓存未命中,TLB未命中和/或上下文切换。软件性能计数器也是很好的候选者,例如I / O数量,分配(和收集)的内存,中断和/或系统调用的数量。观察指标的示例包括由云提供商计费的运行时间和运行测试的成本。由于不同的原因,两者显然都很重要。
至于TCP,当你可以一次写入数据时,我没有看到写小数据的重点。您可以使用BufferedStream
来装饰TCP客户端流实例并使用相同的BinaryWriter
。只是确保不要以强制BufferedStream
尝试将内部缓冲区写回流的方式混合读取和写入,因为NetworkStream
不支持该操作。请参阅有关StackOverflow的Is it better to send 1 large chunk or lots of small ones when using TCP?和Why would BufferedStream.Write throw “This stream does not support seek operations”?讨论。
有关详情,请查看有关StackOverflow的Example of Named Pipes,C# Sockets vs Pipes,IPC Mechanisms in C# - Usage and Best Practices,When to use .NET BufferedStream class?和When is optimisation premature?讨论。