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

public Int64 ReturnDifferenceA() { User[] arrayList; Int64 firstTicks; IList 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左右。

顺便说一句,在Array上使用IEnumerable.Count()比Array.Length慢几百倍……虽然这根本不回答这个问题。

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

如果要重复进行某些操作,那么您会经常注意到第一次测量的运行时间较长,其余的应该调整到较小的量。

我在MATLAB代码中执行此操作,并且看到我第一次运行基准测试循环时,需要五秒钟,后续时间需要五分之一秒。 这是一个巨大的差异,因为它是一种需要某种forms的编译的解释性语言,但实际上,它并不影响你的表现,因为绝大多数将是“第二次在任何生产应用程序中。

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

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

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

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

您是否保留了应用程序而没有在后续运行中终止? 然后,第二个原因也是由于VM。 (VM:1 =虚拟机; VM:2 =虚拟内存)。 所有现代通用操作系统都在虚拟内存上运行其进程,虚拟内存是实际内存的映射,以允许操作系统管理和优化系统资源的使用。 较少使用的进程经常被扫描到磁盘缓存中,以便让其他进程充分利用资源。

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

此外,操作系统会根据需要将资源发送到您的进程。 因此,对于第一轮,您的流程必须经历与OS一起推动信封争用以扩展其资源边界的痛苦。

虚拟机允许.NET和Java将大多数编程function抽象为独立于机器的层,隔离并因此为机器相关软件工程师处理更小的混乱。 即使Microsoft Windows在相当统一的x86后代硬件上运行,但是与不同的操作系统版本和CPU模型存在足够的差异,以保证抽象的虚拟机能够为.NET程序员和用户提供一致的视图。

你说你没有做到3次,第2次和第3次相对接近。 在我看来,它是第一次通过循环,事情是缓慢的。

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

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

您的测试仅与您可以将其与盒子上运行的所有其他软件隔离的程度一样有用。 在尝试测量运行之前,请关闭所有可能的软件。

但那我知道什么? – 也许你的测量方法也由.net(或其他)运行时管理,只考虑运行时’虚拟周期’。