【发布时间】:2022-01-04 09:57:09
【问题描述】:
我在 RaspberryPi 上测试 .NET 应用程序,而该程序的每次迭代在 Windows 笔记本电脑上需要 500 毫秒,而在 RaspberryPi 上同样需要 5 秒。经过一些调试,我发现大部分时间都花在了foreach 循环连接字符串上。
编辑 1: 澄清一下,我提到的 500 毫秒和 5 秒时间是整个循环的时间。我在循环之前放置了一个计时器,并在循环完成后停止计时器。而且,两者的迭代次数相同,都是 1000。
编辑 2:为了计时循环,我使用了提到的答案here。
private static string ComposeRegs(List<list_of_bytes> registers)
{
string ret = string.Empty;
foreach (list_of_bytes register in registers)
{
ret += Convert.ToString(register.RegisterValue) + ",";
}
return ret;
}
出乎意料的是,我用for 循环替换了foreach,突然之间它开始花费几乎与笔记本电脑相同的时间。 500 到 600 毫秒。
private static string ComposeRegs(List<list_of_bytes> registers)
{
string ret = string.Empty;
for (UInt16 i = 0; i < 1000; i++)
{
ret += Convert.ToString(registers[i].RegisterValue) + ",";
}
return ret;
}
我是否应该始终使用for 循环而不是foreach?或者这只是for 循环比foreach 循环快得多的场景?
【问题讨论】:
-
spent in a foreach loop concatenating strings.是你的问题,而不是for与foreach。字符串是不可变的。修改或连接字符串会创建一个新字符串。您的循环创建了 2000 个需要进行垃圾回收的临时对象。这个过程是昂贵的。改用 StringBuilder,最好使用capacity大致等于预期字符串的大小 -
至于为什么会有这样的差异,你确定有吗?你实际测量了什么?您确定 GC 在测试期间没有运行吗?要获得有意义的数字,请使用 BenchmarkDotNet 将每个代码运行足够多次,以便获得稳定的结果并考虑 GC 和分配
-
两种方法的另一个明显区别是,第二个在 1000 个项目后中止,无论列表中有多少个,如果少于 1000 个则爆炸。第一个总是处理整个列表,因此根据列表中有多少项目,他们可能做的工作量可能大不相同。
-
那么测试是错误的,完全容易受到 GC 延迟的影响。你不是在衡量你认为你是什么。使用 BenchmarkDotNet 获得有意义的结果
-
同时为
StringBuilder添加另一个测试。我怀疑你会感到惊讶。仅 1000 个项目的 500 毫秒 极其缓慢,令人难以置信的慢!!!!!!!一个树莓派有一个1+GHz的核心,怎么格式化1000个项目要花这么多时间?这些数据太少了,它甚至应该放入 RPi 的 CPU 缓存中!没关系 Windows 机器。
标签: c# .net raspberry-pi raspberry-pi4