C#Foreach循环比RaspberryPI上的for循环慢得可笑 [英] C# foreach loop comically slower than for loop on a RaspberryPi

查看:52
本文介绍了C#Foreach循环比RaspberryPI上的for循环慢得可笑的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在RaspberryPI上测试.NET应用程序,该程序的每次迭代在Windows笔记本电脑上需要500毫秒,而在RaspberryPI上同样需要5秒。经过一些调试后,我发现大部分时间都花在了foreach连接字符串的循环上。

编辑1:澄清一下,我提到的500 ms零5 s时间是整个循环的时间。我在循环之前放置了一个计时器,并在循环结束后停止计时器。而且,两者的迭代次数相同,为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循环快得多的场景?

推荐答案

实际问题是连接字符串,而不是forforeach之间的区别。报告的计时非常慢,即使在覆盆子PI上也是如此。1000个项目是如此之少的数据,以至于可以放入两台机器的CPU缓存中。RPI拥有1+GHz的CPU,这意味着每个级联至少需要1000个周期。

问题在于串联。字符串是不可变的。修改或连接字符串将创建一个新字符串。您的循环创建了2000个需要垃圾收集的临时对象。该过程昂贵。请改用StringBuilder,其capacity最好与预期字符串的大小大致相同。

    [Benchmark]
    public string StringBuilder()
    {
        var sb = new StringBuilder(registers.Count * 3);
        foreach (list_of_bytes register in registers)
        {
            sb.AppendFormat("{0}",register.RegisterValue);
        }
        return sb.ToString();
    }
简单地测量一次执行,甚至平均10次执行,都不会产生有效的数字。在一次测试中,GC很可能会收集这2000个对象。也很可能是因为JIT编译或任何其他原因而延迟了其中一个测试。测试应运行足够长的时间以生成稳定的数字。

.NET基准测试的事实标准是BenchmarkDotNet。该库将运行每个基准测试足够长的时间,以消除启动和冷却影响,并考虑内存分配和GC收集。您不仅会看到每次测试需要多少内存,而且还会看到使用了多少内存以及导致了多少GC

要实际测量您的代码,请尝试使用BenchmarkDotNet:

使用此基准
[MemoryDiagnoser]
[MarkdownExporterAttribute.StackOverflow]
public class ConcatTest
{

    private readonly List<list_of_bytes> registers;


    public ConcatTest()
    {
        registers = Enumerable.Range(0,1000).Select(i=>new list_of_bytes(i)).ToList();
    }

    [Benchmark]
    public string StringBuilder()
    {
        var sb = new StringBuilder(registers.Count*3);
        foreach (var register in registers)
        {
            sb.AppendFormat("{0}",register.RegisterValue);
        }
        return sb.ToString();
    }

    [Benchmark]
    public string ForEach()
    {
        string ret = string.Empty;
        foreach (list_of_bytes register in registers)
        {
            ret += Convert.ToString(register.RegisterValue) + ",";
        }
        return ret;
    }

    [Benchmark]
    public string For()
    {
        string ret = string.Empty;
        for (UInt16 i = 0; i < registers.Count; i++)
        {
            ret += Convert.ToString(registers[i].RegisterValue) + ",";
        }
        return ret;
    }

}

测试通过调用BenchmarkRunner.Run<ConcatTest>()

运行
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Linq;

public class Program
{
    public static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<ConcatTest>();
        Console.WriteLine(summary);
    }
}

结果

在Macbook上运行它会产生以下结果。请注意,BenchmarkDotNet生成的结果可以在StackOverflow中使用,并且运行时信息包含在结果中:

BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.5.2 (20G95) [Darwin 20.6.0]
Intel Core i7-8750H CPU 2.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK=6.0.100
  [Host]     : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
  DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT


        Method |      Mean |    Error |   StdDev |    Gen 0 |   Gen 1 | Allocated |
-------------- |----------:|---------:|---------:|---------:|--------:|----------:|
 StringBuilder |  34.56 μs | 0.682 μs | 0.729 μs |   7.5684 |  0.3052 |     35 KB |
       ForEach | 278.36 μs | 5.509 μs | 5.894 μs | 818.8477 | 24.4141 |  3,763 KB |
           For | 268.72 μs | 3.611 μs | 3.015 μs | 818.8477 | 24.4141 |  3,763 KB |

ForForEach占用的内存几乎是StringBuilder的10倍,使用的内存是StringBuilder的100倍

这篇关于C#Foreach循环比RaspberryPI上的for循环慢得可笑的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆