c#中最有效的循环是什么 [英] What is the most efficient loop in c#

查看:24
本文介绍了c#中最有效的循环是什么的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

There are a number of different way to accomplish the same simple loop though the items of an object in 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.

取一个简单的对象

var myList = List<MyObject>; 

让我们假设对象已填充,并且我们要遍历项目.

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

方法一.

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

方法二

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

方法三

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

方法四

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?

我错过了什么吗?

推荐答案

大多数时候的答案是没关系. 循环中的项目数量(即使人们可能会考虑大量"项目,比如数千)不会对代码产生影响.

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:

  • 所有测试均在 32 位处理器上的 .NET 4.0 上运行.
  • TimeSpan.TicksPerSecond 在我的机器上 = 10,000,000
  • 所有测试都在单独的单元测试会话中执行,而不是在同一个单元测试会话中执行(以免干扰垃圾收集等)

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

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

MyObject 类:

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

一种创建List的方法 任意长度的 MyClass 实例:

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

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();
}

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

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 something to measure impact):

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);
}

创建TextWriter 写入 null Stream(基本上数据接收器):

A method to create a TextWriter which writes to a null Stream (basically a data sink):

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

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

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):

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

让我们进入方法:

以下代码:

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

编译如下:

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

    // Do stuff.
}

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

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 IEnumerator interfaces, as the compiler respects duck-typing in this case) and your // Do stuff is hoisted into that while structure.

这是衡量性能的测试:

[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);
    }
}

输出:

Foreach 循环滴答:3210872841

Foreach loop ticks: 3210872841

方法二:.ForEach方法在List

List 上的 .ForEach 方法的代码如下所示:

Method 2: .ForEach method on List<T>

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

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]);
    }
}

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

Note that this is functionally equivalent to Method 4, with one exception, the code that is hoisted into the for loop 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 is there.

然而,它可以忽略不计.衡量性能的测试:

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);
    }
}

输出:

ForEach 方法滴答:3135132204

ForEach method ticks: 3135132204

这比使用 foreach 循环实际上约 7.5 秒.并不完全令人惊讶,因为它使用直接数组访问而不是使用 IEnumerable<T>.

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

不过请记住,这意味着每保存一个项目需要 0.0000075740637 秒.对于小项目列表,这值得.

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

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

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

为了踢球,我们无论如何都要这样做:

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);
    }
}

输出:

枚举器循环刻度:3241289895

Enumerator loop ticks: 3241289895

方法四:for

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

[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);
    }
}

输出:

列出索引器循环刻度:3039649305

List indexer loop ticks: 3039649305

然而这个可以产生影响的地方是数组.编译器可以展开数组以一次处理多个项目.

However the place where this can make 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.

然而,我并不肯定这是否真的发生了(我必须查看 IL 和编译后的 IL 的输出).

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).

测试如下:

[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);
    }
}

输出:

数组循环滴答:3102911316

Array loop ticks: 3102911316

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

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

TL;DR

您真的不应该关心这些东西的性能,除非在您的情况下进行测试表明您有真正的瓶颈(并且您必须拥有大量项目才能产生影响).

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).

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

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

这篇关于c#中最有效的循环是什么的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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