c#中最有效的循环是什么

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15247247/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 14:39:22  来源:igfitidea点击:

What is the most efficient loop in c#

c#loopsfor-loopforeachwhile-loop

提问by TheAlbear

There are a number of different way to accomplish the same simple loop though the items of an object in c#.

通过 C# 中的对象项,有许多不同的方法可以完成相同的简单循环。

This has made me wonder if there is any reason be it performance or ease of use, as to use on over the other. Or is it just down to personal preference.

这让我想知道是否有任何理由是性能或易用性,而不是使用另一个。或者这只是取决于个人喜好。

Take a simple object

拿一个简单的物体

var myList = List<MyObject>; 

Lets assume the object is filled and we want to iterate over the items.

让我们假设对象已填充并且我们想要迭代这些项目。

Method 1.

方法一。

foreach(var item in myList) 
{
   //Do stuff
}

Method 2

方法二

myList.Foreach(ml => 
{
   //Do stuff
});

Method 3

方法三

while (myList.MoveNext()) 
{
  //Do stuff
}

Method 4

方法四

for (int i = 0; i < myList.Count; i++)
{
  //Do stuff   
}

What I was wondering is do each of these compiled down to the same thing? is there a clear performance advantage for using one over the others?

我想知道的是,这些编译器中的每一个都编译成相同的东西吗?使用一个比其他的有明显的性能优势吗?

or is this just down to personal preference when coding?

或者这只是取决于编码时的个人喜好?

Have I missed any?

我错过了什么吗?

采纳答案by casperOne

The answer the majority of the time is it does not matter.The number of items in the loop (even what one might consider a "large" number of items, say in the thousands) isn't going to have an impact on the code.

大多数时候的答案是没关系。循环中的项目数量(即使人们可能认为是“大量”项目,比如数千个)不会对代码产生影响。

Of course, if you identify this as a bottleneck in your situation, by all means, address it, but you have to identify the bottleneck first.

当然,如果您认为这是您的情况的瓶颈,请务必解决它,但您必须首先确定瓶颈。

That said, there are a number of things to take into consideration with each approach, which I'll outline here.

也就是说,每种方法都需要考虑许多因素,我将在此处概述。

Let's define a few things first:

让我们先定义几件事:

  • All of the tests were run on .NET 4.0 on a 32-bit processor.
  • TimeSpan.TicksPerSecondon my machine = 10,000,000
  • All tests were performed in separate unit test sessions, not in the same one (so as not to possibly interfere with garbage collections, etc.)
  • 所有测试均在 32 位处理器上的 .NET 4.0 上运行。
  • TimeSpan.TicksPerSecond在我的机器上 = 10,000,000
  • 所有测试都在单独的单元测试会话中执行,而不是在同一个(以免干扰垃圾收集等)

Here's some helpers that are needed for each test:

以下是每次测试所需的一些帮助程序:

The MyObjectclass:

MyObject类:

public class MyObject
{
    public int IntValue { get; set; }
    public double DoubleValue { get; set; }
}

A method to create a List<T>of any length of MyClassinstances:

创建List<T>任意长度MyClass实例的方法:

public static List<MyObject> CreateList(int items)
{
    // Validate parmaeters.
    if (items < 0) 
        throw new ArgumentOutOfRangeException("items", items, 
            "The items parameter must be a non-negative value.");

    // Return the items in a list.
    return Enumerable.Range(0, items).
        Select(i => new MyObject { IntValue = i, DoubleValue = i }).
        ToList();
}

An action to perform for each item in the list (needed because Method 2 uses a delegate, and a call needs to be made to somethingto measure impact):

为列表中的每个项目执行的操作(需要,因为方法 2 使用委托,并且需要调用某些东西来衡量影响):

public static void MyObjectAction(MyObject obj, TextWriter writer)
{
    // Validate parameters.
    Debug.Assert(obj != null);
    Debug.Assert(writer != null);

    // Write.
    writer.WriteLine("MyObject.IntValue: {0}, MyObject.DoubleValue: {1}", 
        obj.IntValue, obj.DoubleValue);
}

A method to create a TextWriterwhich writes to a nullStream(basically a data sink):

创建一个TextWriter写入空值的方法Stream(基本上是一个数据接收器):

public static TextWriter CreateNullTextWriter()
{
    // Create a stream writer off a null stream.
    return new StreamWriter(Stream.Null);
}

And let's fix the number of items at one million (1,000,000, which should be sufficiently high to enforce that generally, these all have about the same performance impact):

让我们将项目数量固定为 100 万(1,000,000,这应该足够高以强制执行,通常,这些都具有大致相同的性能影响):

// The number of items to test.
public const int ItemsToTest = 1000000;

Let's get into the methods:

让我们进入方法:

Method 1: foreach

方法一: foreach

The following code:

以下代码:

foreach(var item in myList) 
{
   //Do stuff
}

Compiles down into the following:

编译成以下内容:

using (var enumerable = myList.GetEnumerable())
while (enumerable.MoveNext())
{
    var item = enumerable.Current;

    // Do stuff.
}

There's quite a bit going on there. You have the method calls (and it may or may not be against the IEnumerator<T>or IEnumeratorinterfaces, as the compiler respects duck-typing in this case) and your // Do stuffis hoisted into that while structure.

那里有很多事情要做。您有方法调用(并且它可能会或可能不会针对IEnumerator<T>IEnumerator接口,因为在这种情况下编译器尊重鸭子类型)并且您// Do stuff被提升到 while 结构中。

Here's the test to measure the performance:

这是衡量性能的测试:

[TestMethod]
public void TestForEachKeyword()
{
    // Create the list.
    List<MyObject> list = CreateList(ItemsToTest);

    // Create the writer.
    using (TextWriter writer = CreateNullTextWriter())
    {
        // Create the stopwatch.
        Stopwatch s = Stopwatch.StartNew();

        // Cycle through the items.
        foreach (var item in list)
        {
            // Write the values.
            MyObjectAction(item, writer);
        }

        // Write out the number of ticks.
        Debug.WriteLine("Foreach loop ticks: {0}", s.ElapsedTicks);
    }
}

The output:

输出:

Foreach loop ticks: 3210872841

Foreach 循环滴答:3210872841

Method 2: .ForEachmethod on List<T>

方法2:.ForEach方法上List<T>

The code for the .ForEachmethod on List<T>looks something like this:

.ForEach方法的代码List<T>如下所示:

public void ForEach(Action<T> action)
{
    // Error handling omitted

    // Cycle through the items, perform action.
    for (int index = 0; index < Count; ++index)
    {
        // Perform action.
        action(this[index]);
    }
}

Note that this is functionally equivalent to Method 4, with one exception, the code that is hoisted into the forloop is passed as a delegate. This requires a dereference to get to the code that needs to be executed. While the performance of delegates has improved from .NET 3.0 on, that overhead isthere.

请注意,这在功能上等同于方法 4,但有一个例外,即提升到for循环中的代码作为委托传递。这需要取消引用以获取需要执行的代码。虽然从 .NET 3.0 开始,委托的性能有所提高,但开销仍然存在。

However, it's negligible. The test to measure the performance:

然而,这是微不足道的。衡量性能的测试:

[TestMethod]
public void TestForEachMethod()
{
    // Create the list.
    List<MyObject> list = CreateList(ItemsToTest);

    // Create the writer.
    using (TextWriter writer = CreateNullTextWriter())
    {
        // Create the stopwatch.
        Stopwatch s = Stopwatch.StartNew();

        // Cycle through the items.
        list.ForEach(i => MyObjectAction(i, writer));

        // Write out the number of ticks.
        Debug.WriteLine("ForEach method ticks: {0}", s.ElapsedTicks);
    }
}

The output:

输出:

ForEach method ticks: 3135132204

ForEach 方法滴答:3135132204

That's actually~7.5 seconds fasterthan using the foreachloop. Not completely surprising, given that it uses direct array access instead of using IEnumerable<T>.

实际上比使用循环约 7.5 秒foreach。考虑到它使用直接数组访问而不是使用IEnumerable<T>.

Remember though, this translates to 0.0000075740637 seconds per item being saved. That's notworth it for small lists of items.

但请记住,这意味着每保存一个项目需要 0.0000075740637 秒。对于小项目清单来说,这是值得的。

Method 3: while (myList.MoveNext())

方法三: while (myList.MoveNext())

As shown in Method 1, this is exactlywhat the compiler does (with the addition of the usingstatement, which is good practice). You're not gaining anything here by unwinding the code yourself that the compiler would otherwise generate.

如方法 1 所示,这正是编译器所做的(添加了using语句,这是一种很好的做法)。通过自己展开编译器本来会生成的代码,您不会在这里获得任何东西。

For kicks, let's do it anyways:

无论如何,让我们这样做:

[TestMethod]
public void TestEnumerator()
{
    // Create the list.
    List<MyObject> list = CreateList(ItemsToTest);

    // Create the writer.
    using (TextWriter writer = CreateNullTextWriter())
    // Get the enumerator.
    using (IEnumerator<MyObject> enumerator = list.GetEnumerator())
    {
        // Create the stopwatch.
        Stopwatch s = Stopwatch.StartNew();

        // Cycle through the items.
        while (enumerator.MoveNext())
        {
            // Write.
            MyObjectAction(enumerator.Current, writer);
        }

        // Write out the number of ticks.
        Debug.WriteLine("Enumerator loop ticks: {0}", s.ElapsedTicks);
    }
}

The output:

输出:

Enumerator loop ticks: 3241289895

枚举器循环刻度:3241289895

Method 4: for

方法四: for

In this particular case, you're going to gain some speed, as the list indexer is going directly to the underlying array to perform the lookup (that's an implementation detail, BTW, there's nothing to say that it can't be a tree structure backing the List<T>up).

在这种特殊情况下,您将获得一些速度,因为列表索引器将直接转到底层数组来执行查找(这是一个实现细节,顺便说一句,没有什么可以说它不能是树结构的备份List<T>)。

[TestMethod]
public void TestListIndexer()
{
    // Create the list.
    List<MyObject> list = CreateList(ItemsToTest);

    // Create the writer.
    using (TextWriter writer = CreateNullTextWriter())
    {
        // Create the stopwatch.
        Stopwatch s = Stopwatch.StartNew();

        // Cycle by index.
        for (int i = 0; i < list.Count; ++i)
        {
            // Get the item.
            MyObject item = list[i];

            // Perform the action.
            MyObjectAction(item, writer);
        }

        // Write out the number of ticks.
        Debug.WriteLine("List indexer loop ticks: {0}", s.ElapsedTicks);
    }
}

The output:

输出:

List indexer loop ticks: 3039649305

列出索引器循环刻度:3039649305

Howeverthe place where this canmake a difference is arrays. Arrays can be unwound by the compiler to process multiple items at a time.

然而,这可以发挥作用的地方是数组。编译器可以展开数组以一次处理多个项目。

Instead of doing ten iterations of one item in a ten item loop, the compiler can unwind this into five iterations of two items in a ten item loop.

与在十项循环中对一项进行十次迭代不同,编译器可以将其展开为在十项循环中对两项进行五次迭代。

However, I'm not positive here that this is actually happening (I have to look at the IL and the output of the compiled IL).

但是,我在这里并不肯定这实际上正在发生(我必须查看 IL 和编译后的 IL 的输出)。

Here's the test:

这是测试:

[TestMethod]
public void TestArray()
{
    // Create the list.
    MyObject[] array = CreateList(ItemsToTest).ToArray();

    // Create the writer.
    using (TextWriter writer = CreateNullTextWriter())
    {
        // Create the stopwatch.
        Stopwatch s = Stopwatch.StartNew();

        // Cycle by index.
        for (int i = 0; i < array.Length; ++i)
        {
            // Get the item.
            MyObject item = array[i];

            // Perform the action.
            MyObjectAction(item, writer);
        }

        // Write out the number of ticks.
        Debug.WriteLine("Enumerator loop ticks: {0}", s.ElapsedTicks);
    }
}

The output:

输出:

Array loop ticks: 3102911316

数组循环滴答:3102911316

It should be noted that out-of-the box, Resharperoffers a suggestion with a refactoring to change the above forstatements to foreachstatements. That's not to say this is right, but the basis is to reduce the amount of technical debt in code.

应该注意的是,开箱即用,Resharper提供了一个重构建议,将上述for语句更改为foreach语句。这并不是说这是对的,但基础是减少代码中的技术债务。



TL;DR

TL; 博士

You really shouldn't be concerned with the performance of these things, unless testing in your situation shows that you have a real bottleneck (and you'll have to have massive numbers of items to have an impact).

你真的不应该关心这些东西的性能,除非在你的情况下测试表明你有一个真正的瓶颈(你必须有大量的项目才能产生影响)。

Generally, you should go for what's most maintainable, in which case, Method 1 (foreach) is the way to go.

通常,您应该选择最易于维护的方法,在这种情况下,方法 1 ( foreach) 是可行的方法。

回答by nickw

In regards to the final bit of the question, "Did I miss any?" yes and I feel i would be remiss to not mention here even though the question is quite old. While those four ways of doing it will execute in relatively the same amount of time their is a way not shown above that runs faster than all of them, quite significantly in fact as the size of the list that is being iterated over increases. It would be the exact same way as the last method but instead of getting .Count in the condition check of the loop you assign this value to variable before setting up the loop and use that instead, leaving you with something like this

关于问题的最后一点,“我错过了什么吗?” 是的,我觉得即使这个问题很老,我也不会在这里提及。虽然这四种方法的执行时间相对相同,但上面没有显示的方法比所有方法都运行得更快,事实上,随着迭代列表的大小增加,这一点非常重要。这将与最后一种方法完全相同,但不是在循环的条件检查中获取 .Count,而是在设置循环之前将此值分配给变量并使用它,从而为您留下这样的东西

var countVar = list.Count;
for(int i = 0; i < countVar; i++)
{
 //loop logic
}

by doing it this way your only looking up a variable value at each iteration, rather than resolving the Count or Length properties, which is considerably less efficient.

通过这样做,您只需在每次迭代时查找变量值,而不是解析 Count 或 Length 属性,这会大大降低效率。