微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么有些迭代器比C#中的其他迭代器更快?

一些迭代器更快.我发现这一点是因为我在 Channel 9从Bob Tabor那里听说永远不会复制和粘贴.

我习惯做这样的事情来设置数组值:

testArray[0] = 0;
testArray[1] = 1;

这是一个简化的例子,但是为了不复制和粘贴,或者不再输入东西,我想我应该使用循环.但我有这种唠叨的感觉,循环比简单地列出命令要慢,看起来我是对的:列出的东西要快得多.在我的大多数试验中,速度,最快到最慢,是列表,do循环,for循环,然后是while循环.

为什么列出事物比使用迭代器更快,为什么迭代器的速度不同?

如果我没有以最有效的方式使用这些迭代器,请帮助我.

这是我的结果(对于2个int数组),我的代码在下面(对于4个int数组).我在Windows 7 64位上尝试了几次.

要么我不擅长迭代,要么使用迭代器并不像它的那样伟大.请告诉我它是哪个.非常感谢.

int trials = 0;

TimeSpan listTimer = new TimeSpan(0,0);
TimeSpan forTimer = new TimeSpan(0,0);
TimeSpan doTimer = new TimeSpan(0,0);
TimeSpan whileTimer = new TimeSpan(0,0);
Stopwatch stopWatch = new Stopwatch();
long numberOfIterations = 100000000;

int numElements = 4;
int[] testArray = new int[numElements];
testArray[0] = 0;
testArray[1] = 1;
testArray[2] = 2;
testArray[3] = 3;

// List them
stopWatch.Start();
for (int x = 0; x < numberOfIterations; x++)
{
    testArray[0] = 0;
    testArray[1] = 1;
    testArray[2] = 2;
    testArray[3] = 3;
}
stopWatch.Stop();
listTimer += stopWatch.Elapsed;
Console.WriteLine(stopWatch.Elapsed);
stopWatch.Reset();

// for them
stopWatch.Start();
int q;
for (int x = 0; x < numberOfIterations; x++)
{
    for (q = 0; q < numElements; q++)
        testArray[q] = q;
}
stopWatch.Stop();
forTimer += stopWatch.Elapsed;
Console.WriteLine(stopWatch.Elapsed);
stopWatch.Reset();

// do them
stopWatch.Start();
int r;
for (int x = 0; x < numberOfIterations; x++)
{
    r = 0;
    do
    {
        testArray[r] = r;
        r++;
    } while (r < numElements);
}
stopWatch.Stop();
doTimer += stopWatch.Elapsed;
Console.WriteLine(stopWatch.Elapsed);
stopWatch.Reset();

// while
stopWatch.Start();
int s;
for (int x = 0; x < numberOfIterations; x++)
{
    s = 0;
    while (s < numElements)
    {
        testArray[s] = s;
        s++;
    }
}
stopWatch.Stop();
whileTimer += stopWatch.Elapsed;
Console.WriteLine(stopWatch.Elapsed);
stopWatch.Reset();
Console.WriteLine("listTimer");
Console.WriteLine(listTimer);
Console.WriteLine("forTimer");
Console.WriteLine(forTimer);
Console.WriteLine("doTimer");
Console.WriteLine(doTimer);
Console.WriteLine("whileTimer");
Console.WriteLine(whileTimer);

Console.WriteLine("Enter any key to try again the program");
Console.ReadLine();
trials++;

当我尝试4元素数组时,结果似乎变得更加明显.

我认为只有通过像其他试验一样通过变量分配listThem组的值才会公平.它确实使listThem组稍慢,但它仍然是最快的.以下是几次尝试后的结果:

这是我实现列表的方式:

int w = 0;
for (int x = 0; x < numberOfIterations; x++)
{
    testArray[w] = w;
    w++;
    testArray[w] = w;
    w++;
    testArray[w] = w;
    w++;
    testArray[w] = w;
    w = 0;
}

我知道这些结果可能是特定于实现的,但你会认为微软会告诉我们每个循环在速度方面的优缺点.你怎么看?谢谢.

更新:根据我发布的注释代码和列表仍然比循环更快,但循环看起来更接近性能.循环从最快到最慢:for,while,然后执行.这有点不同,所以我的猜测是,虽然基本上是相同的速度,for循环比do和while循环快约半个百分点,至少在我的机器上.以下是一些试验的结果:

解决方法

Some iterators are faster.

当然,一些迭代器会做不同的事情.执行不同操作的不同代码将以不同的速度运行.

I was in the habit of doing something like this to set array values:

首先,这真的是你需要节省的时间吗?根据您的测量结果(如果它是调试版本,这是毫无意义的),您的额外代码似乎可以节省大约10纳秒.如果世界上的每个人都使用过您的应用程序一次,那么您保存所有用户的总时间仍然会少于刚刚输入的额外时间.他们中的任何一个都不会想到“好吧,有十纳秒我永远不会回来”.

but you would think Microsoft would warn us of the advantages and disadvantages of each loop when it comes to speed

不,我真的不会.

特别是当你进一步概括时.首先,使用更大的循环,等效的展开代码很可能会更慢,因为循环可能适合指令行缓存,而展开的代码则不会.

另一方面,迭代和枚举(平均而言往往比迭代更慢,但也不是很多)更灵活.它们将导致更小,更惯用的代码.适用于许多情况下,您所处的那种情况不适用或不适用(因此,您因为不得不做一些令人费解的事情而失去您期望的节省).它们的误差范围较小,因为它们的范围较小.

所以首先MS或其他任何人都不能建议总是用重复的复制粘贴语句填充你的代码以节省几纳秒,因为它总是不是最快的方法,其次他们不会这样做所以,由于其他所有方式,其他代码都是优越的.

现在,确实存在节省几纳秒非常重要的情况,这就是我们做几十亿次的事情.如果一个芯片制造商敲了一个基本指令所需的几纳秒的时间,它就会真正赢得胜利.

就我们在​​C#中可能做的代码而言,我们可能会进行一次展开的优化,尽管我们很少关注运行时间.

假设我需要做x次.

首先,我做了显而易见的事:

for(int i = 0; i != x; ++i)
  DoSomething();

假设我的应用程序整体上没有我需要的那么快.我做的第一件事就是考虑“我需要多快”的意思,因为除非这是为了乐趣而编码(嘿,追求速度的荒谬努力可能很有趣),这是我想知道的第一件事.我得到了答案,或者更可能是几个答案(最低可接受,最低目标,理想和市场营销 – 以吹嘘 – 如何 – 快 – 这可能是不同的水平).

然后我发现花费了实际代码间的哪些部分.在应用程序的生命周期中,当用户单击按钮时外部循环调用一个需要400ms的部分1000次时,无需优化需要10ns的内容.,导致4秒延迟.

然后我重新考虑我的整个方法 – 是“做X次”(这本身就是O(x)的时间复杂度),实现我的实际目标的唯一方法,或者我可以做一些完全不同的事情,也许是O(ln x )(即,不是花时间与X成比例,而是需要时间与X的对数成正比).我是否可以缓存一些结果,以便在更长的初始运行时间内保存几毫秒数千次?

然后我会看看我是否可以提高DoSomething()的速度. 99.9%的时间,我在那里做得比在改变循环方面做得更好,因为它可能花费的时间比循环本身所花费的几纳秒多.

而且我可能会在DoSomething()中做一些非常可怕的单一和令人困惑的事情,我通常会认为它们是错误代码,因为我知道这是值得它的地方(我会评论不仅仅是解释这个更令人困惑的代码如何工作,但正是为什么这样做的方式).我将测量这些变化,并且可能在几年内我会再次测量它们,因为当前cpu上使用当前框架的最快方法可能不是.NET 6.5上最快的方法,因为我们已经移动了使用英特尔于2017年推出的最新芯片应用于酷炫的新服务器上.

很可能我会直接将DoSomething()直接插入到循环中,因为调用函数的成本几乎肯定大于循环方法的成本(但不完全可以肯定,可能会有什么惊喜由抖动和它有什么影响内联.

也许,也许,我可能会用以下内容替换实际的循环:

if(x > 0)
  switch(x & 7)
  {
    case 0:
      DoSomething();
      goto case 7;
    case 7:
      DoSomething();
      goto case 6;
    case 6:
      DoSomething();
      goto case 5;
    case 5:
      DoSomething();
      goto case 4;
    case 4:
      DoSomething();
      goto case 3;
    case 3:
      DoSomething();
      goto case 2;
    case 2:
      DoSomething();
      goto case 1;
    case 1:
      DoSomething();
      if((x -= 8) > 0)
        goto case 0;
      break;
  }

因为这是一种结合循环所带来的性能优势而不占用大量指令内存的方法,所以您发现手动展开循环会带来短循环的性能优势;它几乎将你的方法用于8个项目的组,并循环通过8个块.

为什么8?因为这是一个合理的起点;如果这在我的代码中非常重要,那么我实际上会测量不同的大小.我唯一一次在真实(不仅仅是为了好玩)的.NET代码中完成了这个,我最终做了16块.

而且只有时间,每次迭代调用的指令都非常短(12条IL指令与C#代码* x = * y相对应)并且代码设计的目的是为了让其他代码快速执行某些操作.整个代码路径是我在大多数情况下避免遇到的问题,有更多的工作要弄清楚我何时更好地使用或避免它,而不是尽可能快地制作那个位.

其余的时间,要么放松要么节省不多(如果有的话),要么它没有保存在重要的地方,或者在考虑它之前还有其他更迫切的优化要做.

我当然不会从这样的代码开始;这将是过早优化的定义.

通常,迭代很快.其他编码员也知道.抖动是已知的(在某些情况下可以应用一些优化).这是可以理解的.它很短.它很灵活.通常使用foreach也很快,尽管没有迭代那么快,而且它更加灵活(有各种方式可以高效地使用IEnumerable实现).

重复代码更脆弱,更容易隐藏一个愚蠢的错误(我们都写错误让我们认为“这太愚蠢了,几乎不足以算作一个bug”,这些很容易修复,只要你可以找到他们).维护起来比较困难,而且随着项目的进行,更有可能变成更难维护的东西.从大局来看,更难以看到可以实现最大的性能提升.

总而言之,9频道节目的那个人并没有警告你,某些事情可能会让你的节目慢10ns,在某些情况下,他会被嘲笑.

原文地址:https://www.jb51.cc/csharp/243785.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐