【发布时间】:2019-11-20 16:47:28
【问题描述】:
我编写了以下代码来将字节数组 data 转换为字符串数组 hex,每个条目包含 32 个字节作为十六进制字符串,以将它们写入文件。
byte[] data = new byte[4*1024*1024];
string[] hex = data.Select((b) => b.ToString("X2")).ToArray();
hex = Enumerable.Range(0, data.Length / 32).Select((r) => String.Join(" ", hex.Skip(r * 32).Take(32))).ToArray(); // <= This line takes forever
问题是尽管生成的文件小于 20MB,但它需要几分钟(!)才能完成。于是我尝试对其进行优化,得出以下结论:
byte[] data = new byte[4*1024*1024];
string[] hex = new string[4*1024*1024/32];
for (var i = 0; i <= hex.Length - 1; i++)
{
var sb = new System.Text.StringBuilder();
sb.Append(data[i * 32].ToString("X2"));
for (var k = 1; k <= 32 - 1; k++)
{
sb.Append(' ');
sb.Append(data[i * 32 + k].ToString("X2"));
}
hex[i] = sb.ToString();
}
此版本的功能相同,但速度要快几个数量级(133 毫秒对 8 分钟)。
我的问题是我真的不明白为什么原来的版本这么慢。我查看了String.Join() 的source,它看起来与我的改进版本非常相似。
我喜欢将 LINQ 用于这些想法,因为你可以很容易地解决各种问题,而且我认为它在大多数情况下都很有效,因为它是惰性求值。所以我想知道我在这里缺少什么来改进我未来对 LINQ 的使用。
另一方面,我不知道它可能会写得更快,但这真的不是重点,因为第二个版本对于仅用于调试目的的函数来说已经足够快了。
【问题讨论】:
-
您不能运行性能查看器来查看占用时间的地方吗?乍一看,这可能是字符串和字符串生成器之间的区别,我认为在第一个示例中,您还有更多循环,但如果不进行深入分析,则很难说
-
由于嵌入了
Skip().Take(),Linq 查询将一遍又一遍地迭代data。另外我认为您打算改用hex。另一种方法是使用Select,其中包括索引和索引上的组除以 32 以获得每个 32 块,然后遍历这些结果并使用字符串生成器。 -
@MarkDavies 目前我怀疑我对 LINQ 的理解,并正在寻找某种一般性的答案。除此之外,我确实进行了一些测试。我很确定大部分时间都花在了
String.Join(),但我不知道为什么在这种情况下它会这么慢。这对我来说很重要,因为我经常使用String.Join(),并且想知道何时避免使用它。 -
我的猜测是字符串是不可变的,string.join() 创建了新的字符串实例。时间浪费在创建新对象上,甚至可能正在清理它。看看在这两种情况下创建了多少对象会很有趣。
-
啊,对;您在第二个示例中使用了 StringBuilder。
标签: c# .net performance linq