C#、For 循环和速度测试... 完全相同的循环第二次更快?

Posted

技术标签:

【中文标题】C#、For 循环和速度测试... 完全相同的循环第二次更快?【英文标题】:C#, For Loops, and speed test... Exact same loop faster second time around? 【发布时间】:2008-11-19 20:58:36 【问题描述】:
public Int64 ReturnDifferenceA()

  User[] arrayList;
  Int64 firstTicks;
  IList<User> userList;
  Int64 secondTicks;
  System.Diagnostics.Stopwatch watch;

  userList = Enumerable
              .Range(0, 1000)
              .Select(currentItem => new User()).ToList();

  arrayList = userList.ToArray();

  watch = new Stopwatch();
  watch.Start();

  for (Int32 loopCounter = 0; loopCounter < arrayList.Count(); loopCounter++)
  
     DoThings(arrayList[loopCounter]);
  

  watch.Stop();
  firstTicks = watch.ElapsedTicks;

  watch.Reset();
  watch.Start();
  for (Int32 loopCounter = 0; loopCounter < arrayList.Count(); loopCounter++)
  
     DoThings(arrayList[loopCounter]);
  
  watch.Stop();
  secondTicks = watch.ElapsedTicks;

  return firstTicks - secondTicks;

如您所见,这非常简单。创建用户列表,强制到数组,启动手表,循环列表并调用方法,停止手表。重复。通过返回第一次和第二次运行的差异来完成。

现在我用这些来打电话:

differenceList = Enumerable
                 .Range(0, 50)
                 .Select(currentItem => ReturnDifferenceA()).ToList();
average = differenceList.Average();

differenceListA = Enumerable
                  .Range(0, 50)
                  .Select(currentItem => ReturnDifferenceA()).ToList();
averageA = differenceListA.Average();

differenceListB = Enumerable
                  .Range(0, 50)
                  .Select(currentItem => ReturnDifferenceA()).ToList();
averageB = differenceListB.Average();

现在有趣的部分是所有的平均值都是相对较大的正数,范围从 150k 到 300k 滴答。

我没有得到的是,我正在通过相同的列表,相同的方式,相同的方法,但有这样的差异。是否正在进行某种缓存?

另一个有趣的事情是,如果我在第一个秒表部分之前遍历列表,平均值约为 5k 左右。

【问题讨论】:

DoThings(User) 做什么?也许它本身使用某种缓存?请记住,数组中的所有第一次都是新用户,第二次是第一次的相同用户。 【参考方案1】:

您在运行时环境中运行高级语言,该环境执行大量缓存和性能优化,这很常见。有时称为预热虚拟机或预热服务器(当它是生产应用程序时)。

如果要重复执行某项操作,那么您会经常注意到第一次的测量运行时间较长,其余时间应趋于平稳。

我在 MATLAB 代码中执行此操作,发现我第一次运行基准循环时需要 5 秒,随后的时间需要 1/5 秒。这是一个巨大的差异,因为它是一种解释性语言,需要某种形式的编译,但实际上,它不会影响您的性能,因为绝大多数将是“第二次”在任何生产应用程序中。

【讨论】:

【参考方案2】:

顺便说一句,在 Array 上使用 IEnumerable.Count() 比 Array.Length 慢数百倍...尽管这根本不能回答问题。

【讨论】:

如果 IEnumerable.Count 总是计算每个项目,它会是,但它足够聪明,首先尝试将 IEnumerable 转换为 ICollection,如果可行,请使用 Count 属性。 其实,如果我明白你在说什么,他是对的。 for (Int32 loopCounter = 0; loopCounter @James:我知道 ICollection 短路径,但它似乎不起作用,我也测试了代码。我想知道这是否是一个错误?【参考方案3】:

DoThings() 很可能在第一次被调用之前没有被 JIT 编译为本机代码。

【讨论】:

这是一种可能的情况。如果在循环处理第一次开始之前 DoThings 没有被 JIT 处理,那么 JIT 编译会受到轻微的性能影响。之后,每次循环都不会命中。 编译器(不是 JITer)也有可能优化了代码,因为您的两个循环完全相同。另一种情况是运行时(和 JIT 编译器)优化了 JIT 代码。【参考方案4】:

因为 .NET 与 Java 平台一样,是一个 JIT 环境。所有高级 .NET 代码都编译为 Microsoft 的中间语言字节码。

要运行您的程序,需要将此字节码编译/翻译为本机机器码。但是,编译后的 .NET 编程文件不是存储在本机机器代码中,而是存储在中间虚拟机字节码中。

第一次运行是 JIT 编译的,所以需要额外的时间。 后续运行不再需要 JIT 编译,而是从 JIT 缓存中提取原生代码,因此应该更快。

您是否在不终止后续运行的情况下保持应用程序正常运行?然后,第二个原因也是由于VM。 (VM:1 = 虚拟机;VM:2 = 虚拟内存)。所有现代通用操作系统都在虚拟内存上运行它们的进程,虚拟内存是真实内存的映射,以使操作系统能够管理和优化系统资源的使用。较少使用的进程经常被清扫到磁盘缓存中,以让其他进程充分利用资源。

您的进程第一次不在虚拟内存中,因此它必须承受被扫入内存的开销。因为随后,您的进程在最近使用的***列表中(也就是在最近最少使用列表的底部),它还没有被清扫到磁盘缓存中。

此外,操作系统会根据需要将资源分配给您的进程。因此,在第一轮中,您的进程必须经历与操作系统竞争信封以扩展其资源边界的痛苦。

虚拟机允许 .NET 和 Java 将大多数编程功能抽象到一个独立于机器的层中,将它们隔离开来,从而为依赖于机器的软件工程师留下更小的混乱。尽管 Microsoft Windows 运行在相当统一的 x86 后代硬件上,但不同的操作系统版本和 CPU 型号存在足够的差异,因此需要一个抽象的虚拟机,以便为 .NET 程序员和用户提供一致的视图。

【讨论】:

【参考方案5】:

你说你不明白这样做 3 次,第 2 次和第 3 次比较接近。在我看来,这只是第一次通过循环,事情变慢了。

【讨论】:

【参考方案6】:

我怀疑您调用的函数在第一次运行之前不是 Just-In-Timed。您可以尝试运行一次,然后停止并再次运行。在没有代码更改的情况下,上一次运行的即时编译应该仍然可以,并且您看到的任何剩余优化都是实际在工作中的缓存效果。

【讨论】:

【参考方案7】:

暂时不考虑预热 VM 或机器、缓存和 JIT 优化的问题:您的计算机还在做什么?是否有任何 3e42 系统服务和任务托盘东西占用了一些 CPU?也许您的 Steam 客户端决定检查更新,或者 IE 需要做一些非常重要的事情,或者您的防病毒程序妨碍了您?

您的测试的有用程度取决于您可以将它与您的机器上运行的所有其他软件隔离开来的程度。在尝试测量跑步之前,尽可能关闭所有软件。

但是我知道什么呢? - 也许您的测量方法也由 .net(或其他)运行时管理,并且仅考虑运行时“虚拟周期”。

【讨论】:

以上是关于C#、For 循环和速度测试... 完全相同的循环第二次更快?的主要内容,如果未能解决你的问题,请参考以下文章

C# - 在for循环中使用相同的列表大小,索引超出了数组的范围[重复]

c# for循环效率问题,遍历list<string>集合;

ASP.NET Razor - C# 循环和数组

C# for和foreach两种循环的效率问题

C# for和foreach两种循环的效率问题

提高嵌套 for 循环速度 R 创建邻接矩阵