nig*_*elg 3 java performance mockito
为什么执行 Mockito 模拟所花费的时间如此不稳定?我能想到的最简单的 SSCCE 如下:
import static org.mockito.Mockito.mock;
public class TestSimpleMockTiming
{
public static final void main (final String args [])
{
final Runnable theMock = mock (Runnable.class);
int tookShort = 0;
int tookMedium = 0;
int tookLong = 0;
int tookRidiculouslyLong = 0;
long longest = 0;
for (int n = 0; n < 2000000; n++)
{
final long startTime = System.nanoTime ();
theMock.run ();
final long duration = System.nanoTime () - startTime;
if (duration < 1000000) // 0.001 seconds
tookShort++;
else if (duration < 100000000) // 0.1 seconds
tookMedium++;
else if (duration < 1000000000) // 1 second !!!
tookLong++;
else
tookRidiculouslyLong++;
longest = Math.max (longest, duration);
}
System.out.println (tookShort + ", " + tookMedium + ", " + tookLong + ", " + tookRidiculouslyLong);
System.out.println ("Longest duration was " + longest + " ns");
}
}
Run Code Online (Sandbox Code Playgroud)
如果我运行它(在 Eclipse 中,在 Win 7 x64 上使用 JDK 1.7.45)典型输出如下:
1999983, 4, 9, 4
Longest duration was 5227445252 ns
Run Code Online (Sandbox Code Playgroud)
因此,虽然在大多数情况下模拟执行速度非常快,但有几个执行时间甚至超过 1 秒。对于什么都不做的方法来说,这是永恒的。从我的实验来看,我不认为问题在于 System.nanoTime() 的准确性,我认为模拟确实需要那么长时间来执行。我可以做些什么来改进这一点并使时间表现得更一致?
(仅供参考,为什么这是一个问题,因为我有一个包含各种框架的 Swing 应用程序,我尝试为框架编写 JUnit 测试,以便我可以测试 layoutManagers 的行为是否正确,而无需启动整个应用程序并导航到正确的屏幕。在这样的一个测试中,屏幕使用 javax.swing.Timer 来实现滚动,因此当鼠标靠近帧的末尾时,显示将围绕一个区域平移。我注意到这种行为是非常不稳定,滚动虽然通常很好,但会周期性地冻结长达一秒钟,看起来很糟糕。我围绕这个写了一个 SSCCE,认为问题是不能依赖 Swing Timers 以一致的速率触发,并且在 SSCCE 中,它运行良好。
经过几个小时的撕扯,然后试图找出我的真实代码和滚动演示 SSCCE 之间的差异,我开始在重复运行的代码块周围放置纳米计时器,注意到我的paintComponent 方法所花费的时间非常不稳定,最终变窄了它归结为模拟电话。通过运行真实的应用程序测试屏幕,滚动行为流畅,这只是 JUnit 测试中的一个问题,因为模拟调用,这导致我与上面发布的 SSCCE 隔离测试了一个简单的模拟。)
非常感谢!
该测试在多个方面存在缺陷。如果你想正确地进行基准测试,我强烈建议使用JMH,它是由比我们更聪明的人Alexey Shipilev完成的,并且在 JVM 方面肯定比我们心爱的星球上大多数使用 Java 的人更了解。
这是测试存在缺陷的最显着方式。
该测试忽略了 JVM 正在做的事情,例如预热阶段、编译 C1 和 C2 线程、GC、线程问题(即使此代码不是多线程的,JVM/OS 可能还需要做其他事情)等...
该测试似乎确实忽略了实际的 OS/JVM/CPU 组合是否提供了高达纳秒的正确分辨率。
即使System.nanoTime()您确定 JVM 和操作系统具有正确的分辨率。例如,在 Windows 上,JVM 无法访问真正的纳秒,而是访问某个计数器,而不是挂钟时间。在javadoc中指出这一点,这里的片段:
此方法只能用于测量经过的时间,与系统或挂钟时间的任何其他概念无关。返回的值表示自某个固定但任意的原始时间以来的纳秒(可能在未来,因此值可能为负)。在 Java 虚拟机的实例中,此方法的所有调用都使用相同的来源;其他虚拟机实例可能使用不同的来源。
此方法提供纳秒精度,但不一定提供纳秒分辨率(即值更改的频率) -除了分辨率至少与 currentTimeMillis() 一样好外,不做任何保证。
该测试还忽略了 Mockito 的工作原理。
Mockito将每个调用存储在自己的模型中,以便能够在执行场景后验证这些调用。因此,在循环的每次迭代中,Mockito 都会存储另一个调用,最多可达 2M 次,这将影响 JVM(可能模拟实例将保存几代并提升为年老,这对于 GC 来说肯定是更昂贵的)。这意味着迭代次数越多,这段代码就越强调 JVM 而不是 Mockito。
我相信它没有发布(但是在 jcentral 上有开发二进制文件),但是 Mockito 将提供一个设置来允许 mockito 存根,因此它不会存储调用,这可能使 Mockito 非常适合这样的场景。
该测试缺乏适当的统计分析。
有趣的是,测试代码有一个伪百分位方法。哪个好!虽然它不能那样工作,在这种情况下它无法解决大问题。相反,它应该记录每一个度量,以便提取随着迭代计数的增加所花费的时间的演变趋势。
如果您愿意,最好存储每个记录的度量,这样就可以将它们提供给适当的统计分析工具,如R,以便提取图形、百分位数据等。
在这个统计问题上,使用HDRHistogram肯定会很有趣。当然,在微基准测试之外,因为它会影响内存并改变微基准测试的结果。让我们为 JMH 保留它。
如果您将代码更改为使用 JMH,则可以解决第 1 点和第 2 点。
希望有帮助。
| 归档时间: |
|
| 查看次数: |
2721 次 |
| 最近记录: |