我正在使用一些通过串口控制设备的代码(通过usb)。该应用程序需要能够在后台运行,然后通过端口连续推送大量数据,因此我需要数据编写代码相当快。我的代码每秒多次大量推送其数据("帧")。目前,我有一个Queue,用于生成需要为当前帧发送的命令。使用SerialPort.BaseStream.WriteByte(byte)一次遍历Queue并一次一个地推送我的命令会更快,或者使用Queue构建一个字节数组并使用SerialPort.Write(byte)一次性发送它[],int,int)?
一些示例代码,如果我的描述令人困惑: 哪个更快,这个:
public void PushData(List<KeyValuePair<Address, byte>> data) {
Queue<DataPushAction> actions = BuildActionQueue(data);
foreach(var item in actions) {
port.BaseStream.WriteByte(item.Value);
}
}
或者这个:
public void PushData(List<KeyValuePair<Address, byte>> data) {
Queue<DataPushAction> actions = BuildActionQueue(data);
byte[] buffer = actions.Select(x => x.Value).ToArray();
port.Write(buffer, 0, buffer.Length);
}
更新
仔细检查源代码后,似乎两个方法都是相同的(WriteByte只使用带有一个元素的临时数组,否则与Write相同)。然而,这实际上并没有回答这个问题,只是改写它:编写许多小字节数组或一个大字节数字会更快吗?
答案 0 :(得分:1)
通过修改下面Rion的答案中的代码,我能够测试这个并得到一些令人惊讶的结果。我使用了以下代码(从Rion修改,谢谢):
class Program {
static void Main(string[] args) {
// Create a stopwatch for performance testing
var stopwatch = new Stopwatch();
// Test content
var data = GetTestingBytes();
var ports = SerialPort.GetPortNames();
using (SerialPort port = new SerialPort(ports[0], 115200, Parity.None, 8, StopBits.One)) {
port.Open();
// Looping Test
stopwatch.Start();
foreach (var item in data) {
port.BaseStream.WriteByte(item);
}
stopwatch.Stop();
Console.WriteLine($"Loop Test: {stopwatch.Elapsed}");
stopwatch.Start();
port.Write(data, 0, data.Length);
stopwatch.Stop();
Console.WriteLine($"All At Once Test: {stopwatch.Elapsed}");
}
Console.Read();
}
static byte[] GetTestingBytes() {
var str = String.Join(",", Enumerable.Range(0, 1000).Select(x => Guid.NewGuid()).ToArray());
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
}
结果非常令人惊讶;使用带有字节数组的方法几乎完全是12.5818728秒的两倍,相比之下,反复调用WriteByte只需要6.2935748秒。这与我预期的结果相反。无论哪种方式,我都没想到一种方法的速度是另一种方法的两倍!
如果有人能弄清楚为什么会这样,我很想知道!
答案 1 :(得分:0)
基于quick glance at the source,看起来SerialPort.Write()
方法实际上只指向基础流的Write()
方法:
public void Write(byte[] buffer, int offset, int count)
{
if (!IsOpen)
throw new InvalidOperationException(SR.GetString(SR.Port_not_open));
if (buffer==null)
throw new ArgumentNullException("buffer", SR.GetString(SR.ArgumentNull_Buffer));
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
if (count < 0)
throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
if (buffer.Length - offset < count)
throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
if (buffer.Length == 0) return;
internalSerialStream.Write(buffer, offset, count, writeTimeout);
}
如果我不得不下注,我会认为两种表现之间的差异可以忽略不计。我想如果你有一些测试数据,你可以创建一个StopWatch
来实际计算两种方法之间的差异。
使用效果测试进行更新
我没有使用SerialPort
对象对此进行测试,而是选择了基本MemoryStream
对象,因为问题的真正核心似乎是如果在循环中写入字节比编写它们更有效率使用byte[]
。
对于测试数据,我只生成了1000个随机Guid
个对象并将它们连接成一个字符串:
static byte[] GetTestingBytes()
{
var str = String.Join(",", Enumerable.Range(0, 1000).Select(x => Guid.NewGuid()).ToArray());
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
就测试本身而言:
// Create a stopwatch for performance testing
var stopwatch = new Stopwatch();
// Test content
var data = GetTestingBytes();
// Looping Test
using (var loop = new MemoryStream())
{
stopwatch.Start();
foreach (var item in data)
{
loop.WriteByte(item);
}
stopwatch.Stop();
Console.WriteLine($"Loop Test: {stopwatch.Elapsed}");
}
// Buffered Test
using (var buffer = new MemoryStream())
{
stopwatch.Start();
buffer.Write(data, 0, data.Length);
stopwatch.Stop();
Console.WriteLine($"Buffer Test: {stopwatch.Elapsed}");
}
经过几次测试后,1000次测试后的平均值如下:
LOOP: 00:00:00.0976584
BUFFER: 00:00:00.0976629
因此,至少在使用MemoryStream
对象的情况下,循环方法似乎是赢家。
如果您想自己运行,可以see the entire testing code here。