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

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

问题描述

有许多不同的方式来完成相同的简单循环虽然在c#对象的项目。



这使我不知道是否有任何理由无论是性能还是易用性,以比其他使用上。或者只是个人喜好。



举一个简单对象

  VAR myList中=列表<为MyObject> ; 



让我们假设该对象被填补,我们要遍历的项目。



方法1。

 的foreach(在myList中VAR项)
{
//做的东西
}

方法2

  myList.Foreach(毫升=> 
{
//做的东西
});



方法3



 而(myList.MoveNext())
{
//做的东西
}

方法4

 的for(int i = 0; I< myList.Count;我++)
{
//做的东西
}

什么我在想,是每个做这些编译为同样的事情?有一个明显的性能优势,用一个比其他人呢?



或此编码时,只是个人喜好?



我错过了没有?


解决方案

答案大部分的时间没关系。 的项目在环(甚至什么人可能会考虑项目的大量的,说,在数千名)的数量不会对代码的影响。



当然,如果您发现本作中,你的情况的瓶颈,通过各种手段,解决它,但你必须先找出瓶颈。



< 。p>这是说,有一些事情要考虑到与每一种方法,我会在这里概述



让我们首先定义一个几件事情:




  • 所有的测试都是在.NET 4.0在32位处理器上运行。

  • TimeSpan.TicksPerSecond 我机器= 10,000,000

  • 所有测试均在单独的单元测试会话进行,而不是在相同的一(以免可能与垃圾收集干扰等)



下面是一些需要为每个测试助手:



MyObject的类:

 公共类MyObject的
{
公众诠释INTVALUE {搞定;组; }
公共双{的doubleValue获得;组; }
}

一个方法来创建的 列表< T> 的任何长度的 MyClass的实例:

 公共静态列表<&MyObject的GT; CreateList(INT项目)
{
//验证parmae​​ters。
如果(项目< 0)(的项目参数必须是一个非负价值,项目,项目,

抛出新ArgumentOutOfRangeException;

//返回一个列表中的项目。
返回Enumerable.Range(0项)。
选择(I =>新建为MyObject {INTVALUE = I,=的doubleValue I})。
了ToL​​ist();
}



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

 公共静态无效MyObjectAction(OBJ为MyObject,TextWriter的作家)
{
//验证参数。
Debug.Assert的(OBJ!= NULL);
Debug.Assert的(作家!= NULL);

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

一个方法来创建的 的TextWriter 其写入到的null (基本数据接收器):

 公共静态的TextWriter CreateNullTextWriter()
{
//创建一个流作家了一个空流。
返回新的StreamWriter(Stream.Null);
}

和我们进行了修复项目的数量达到了一百万(1,000,000,这应该是足够高,以强制执行普遍,这些都对相同的性能影响):

  //项目数进行测试。 
公共const int的ItemsToTest = 1000000;

让我们进入方法:



方法1:的foreach



下面的代码:

 的foreach(在myList中VAR项)
{
//做的东西
}

编译成以下:

 使用(VAR枚举= myList中.GetEnumerable())
,而(enumerable.MoveNext())
{
VAR项目= enumerable.Current;

//做的东西。
}

有相当多的事情出现。你有方法调用(它可能会或可能不会对的IEnumerator< T> 的IEnumerator 接口,编译尊重鸭打字在这种情况下),你的 //做的东西悬挂到,虽然结构。



下面是衡量性能测试:

  [TestMethod的] 
公共无效TestForEachKeyword()
{
//创建列表。
名单,LT;为MyObject>清单= CreateList(ItemsToTest);

//创建作家。使用
(TextWriter的作家= CreateNullTextWriter())
{
//创建秒表。
秒表S = Stopwatch.StartNew();

//通过项目周期。
的foreach(在列表VAR项)
{
//写值。
MyObjectAction(项目,作家);
}

//写出蜱的数量。
的Debug.WriteLine(Foreach循环滴答:{0},s.El​​apsedTicks);
}
}

输出:




Foreach循环滴答:3210872841




方法2: .ForEach 法>列表< T>



有关该代码的 .ForEach 列表< T> 看起来是这样的:

 公共无效的ForEach(动作< T>动作)
{
//错误处理通过项目省略

//循环,执行操作。
为(INT指数= 0;指数 - LT; COUNT ++指数)
{
//执行动作。
动作(这[指数]);
}
}

请注意,这是功能等效的方法4,用只有一个例外,那就是悬挂到循环作为一个代表通过代码。这需要一个解引用到达需要被执行的代码。而代表的表现已经从.NET 3.0,这架空改善的的存在。



然而,这是可以忽略不计。该测试来衡量性能:

  [TestMethod的] 
公共无效TestForEachMethod()
{
//创建列表。
名单,LT;为MyObject>清单= CreateList(ItemsToTest);

//创建作家。使用
(TextWriter的作家= CreateNullTextWriter())
{
//创建秒表。
秒表S = Stopwatch.StartNew();

//通过项目周期。
list.ForEach(I => MyObjectAction(I,作家));

//写出蜱的数量。
的Debug.WriteLine(的ForEach方法滴答:{0},s.El​​apsedTicks);
}
}

输出:




的ForEach方法滴答:3135132204




这是的实际〜7.5秒。更快的比使用的foreach 循环。没有完全不足为奇,因为它使用直接数组访问,而不是使用 的IEnumerable< T>



不过要记住,这相当于每件0.0000075740637秒保存。这是的的值得的物品名单小



方法3:而(myList.MoveNext())



如方法1所示,这是<青霉>准确的编译器做什么(加的使用语句,这是很好的做法)。你不是通过退绕代码自行编译器,否则产生在这里得到什么



有关几脚,让我们做吧,反正:

  [TestMethod的] 
公共无效TestEnumerator()
{
//创建列表。
名单,LT;为MyObject>清单= CreateList(ItemsToTest);

//创建作家。使用(TextWriter的作家= CreateNullTextWriter())
//获取枚举
。使用
(IEnumerator的<&MyObject的GT;枚举= list.GetEnumerator())
{
//创建秒表。
秒表S = Stopwatch.StartNew();

//通过项目周期。
,而(enumerator.MoveNext())
{
//写。
MyObjectAction(enumerator.Current,作家);
}

//写出蜱的数量。
的Debug.WriteLine(枚举循环滴答:{0},s.El​​apsedTicks);
}
}

输出:




枚举循环滴答:3241289895




方法4:



在这种特殊情况下,你会获得一些速度,列表索引器直接进入到底层阵列执行查找(这是一个实现细节,顺便说一句,还有什么可说的,它不能是一个树状结构支持的列表< T> 向上)。

  [TestMethod的] 
公共无效TestListIndexer()
{
//创建列表。
名单,LT;为MyObject>清单= CreateList(ItemsToTest);

//创建作家。使用
(TextWriter的作家= CreateNullTextWriter())
{
//创建秒表。
秒表S = Stopwatch.StartNew();

//通过索引周期。
的for(int i = 0; I< list.Count ++ I)
{
//获取该项目。
MyObject的项目列表= [I]

//执行动作。
MyObjectAction(项目,作家);
}

//写出蜱的数量。
的Debug.WriteLine(目录索引循环滴答:{0},s.El​​apsedTicks);
}
}

输出:




目录索引循环滴答:3039649305




然而其中本的可以的有所作为的地方阵列。数组可以由编译器来同时处理多个项目上展开。



而不是做一个项目的10次重复的十项循环,编译器可以放松到这个五次迭代的十个项目环两个项目。



不过,我并不积极这里,这是实际发生的事情(我有看IL和输出。编译IL的)



下面的测试:

  [ TestMethod的] 
公共无效TestArray()
{
//创建列表。
为MyObject []数组= CreateList(ItemsToTest).ToArray();

//创建作家。使用
(TextWriter的作家= CreateNullTextWriter())
{
//创建秒表。
秒表S = Stopwatch.StartNew();

//通过索引周期。
的for(int i = 0; I< array.Length ++ I)
{
//获取该项目。
MyObject的项目=阵列[我]

//执行动作。
MyObjectAction(项目,作家);
}

//写出蜱的数量。
的Debug.WriteLine(枚举循环滴答:{0},s.El​​apsedTicks);
}
}

输出:




阵列环蜱:3102911316




应当注意的是,外-the中, ReSharper的提供建议与重构改变上述语句的foreach 语句。这并不是说这是正确的,但基础是减少技术债的代码量。






TL; DR



您真的不应该有这些东西的性能而言,除非你的情况的测试表明你有一个真正的瓶颈(并且你必须有一个项目庞大的数字产生影响)。



通常情况下,你应该去什么是最易维护,在这种情况下,方法1(的foreach )是要走的路。


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.

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?

解决方案

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

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

The MyObject class:

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

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

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

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

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;

Let's get into the methods:

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

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

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

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

The output:

ForEach method ticks: 3135132204

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

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

Method 3: while (myList.MoveNext())

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

The output:

Enumerator loop ticks: 3241289895

Method 4: 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).

[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

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.

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

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

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

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

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

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