2009-02-13 59 views
8

我并不十分了解编译器和JIT优化的内部知识,但我通常会尝试使用“常识”来猜测什么可以优化,哪些不能。所以我在写一个简单的单元测试方法今天:如何编写(测试)不会被编译器/ JIT优化的代码?

@Test // [Test] in C# 
public void testDefaultConstructor() { 
    new MyObject(); 
} 

这种方法实际上是我所需要的。它检查默认的构造函数是否存在并且运行时没有异常。

但后来我开始考虑编译器/ JIT优化的影响。编译器/ JIT可以通过完全取消new MyObject();语句来优化此方法吗?当然,它需要确定调用图对其他对象没有副作用,这是普通构造函数的典型情况,它只简单地初始化对象的内部状态。

我认为只有JIT可以执行这样的优化。这可能意味着它不是我应该担心的事情,因为测试方法只执行一次。我的假设是否正确?

尽管如此,我正在考虑一般的主题。当我想到如何防止这种方法被优化时,我想我可能assertTrue(new MyObject().toString() != null),但这非常依赖于toString()方法的实际实现,即使如此,JIT也可以确定toString()方法总是返回一个非null字符串(例如,如果实际上调用了Object.toString()),并因此优化整个分支。所以这种方式是行不通的。

我知道在C#中我可以使用[MethodImpl(MethodImplOptions.NoOptimization)],但这不是我真正想要的。我希望找到一种(语言无关)的方式,确保我的代码的某些特定部分能够按照我的预期实际运行,而JIT不会干扰此过程。

此外,是否有任何典型的优化案例,我应该知道创建我的单元测试?

非常感谢!

回答

4

不用担心。不允许优化任何可以改变系统的内容(速度除外)。如果你新建了一个对象,代码被调用,内存被分配,它必须工作。

如果您有它由IF(假),其中虚假的最后,它可以被优化掉了系统完全的,那么它可以检测,该方法不执行任何操作,并优化它的保护(在理论)。

编辑:顺便说一下,也可以足够聪明,以确定这种方法:

newIfTrue(boolean b) { 
    if(b) 
     new ThisClass(); 
} 

将永远做什么,如果b为假,并最终弄清楚,在你的代码B中的一个点始终是错误的,并且完全从该代码编译该例程。

这就是JIT可以做的东西,在任何非托管语言几乎是不可能。

+0

谢谢。我想知道为什么JIT的行为如此?如果一个对象分配是无用的(在某些情况下实际上可以通过静态分析来确定),为什么JIT不会对它进行优化? – 2009-02-13 22:46:21

+0

虽然我现在可以想到一个角落案例,但我认为这很少见。如果为了确保有足够的内存可用于某些其他对象(甚至确保不会发生分页)而完成对象分配,则优化将使该假定失效。 – 2009-02-13 22:48:24

+0

需要将Java虚拟机(JVM)保持在与根据Java内存模型在JVM中执行程序代码一致的状态。在JIT可以证明代码对可观察程序状态没有影响的情况下,并不需要实际执行任何特定代码或分配内存。 – 2013-05-20 20:18:38

5

我想如果你担心它被优化掉了,你可能会做一些测试矫枉过正。

在静态语言中,我倾向于将编译器视为测试。如果它通过编译,那就意味着某些东西在那里(如方法)。如果你没有另外一个测试来执行你的默认构造函数(这将证明它不会抛出异常),你可能想要考虑为什么你要写这个默认构造函数(YAGNI和所有这些)。

我知道有些人不同意我的看法,但是我觉得这种事情只是一些无用的理由,会让你的测试数量膨胀,甚至通过TDD护目镜观察它。

+0

是,测试矫枉过正FTL。 – MichaelGG 2009-02-13 22:38:28

+0

我同意你的意见。当我想到它时(在阅读你的答案之后),如果这个测试更重要的是只是一个“编译”测试,那么必须有其他测试使用默认的构造函数。谢谢! – 2009-02-13 22:43:35

+0

在像Java这样的语言中,编译和执行环境可能差别很大。如果正在构建的类与测试代码分开定位,那么这样的测试可能会有意义。特别有趣的将有一个自定义的类装载器装载上飞从某种形式的动态存储的类 - 这个代码将确保查找实际工作。 – 2013-05-20 20:21:35

0

为什么要重要?如果编译器/ JIT可以静态确定任何断言都将被命中(这可能会导致副作用),那么你很好。

+0

它还需要验证构造函数是否存在,不会影响静态程序状态,并且不能抛出JVM隐式需要的异常,例如`NullPointerException`。 – 2013-05-20 20:16:28

2

想想这样说:

让我们假设编译器能够确定调用图形没有任何副作用(我不认为这是可能的,我依稀记得一些关于P = NP从我的CS课程)。它会优化任何没有副作用的方法。由于大多数测试没有,也不应该有任何副作用,编译器可以将它们全部优化。

1

看来,在C#中我可以这样做:

[Test] 
public void testDefaultConstructor() { 
    GC.KeepAlive(new MyObject()); 
} 

AFAIU,该GC.KeepAlive方法将不会被JIT内联,所以代码将保证按预期运行。但是,我不知道Java中的类似构造。

0

每个I/O是一个副作用,所以你可以把

Object obj = new MyObject(); 
System.out.println(obj.toString()); 

和你的罚款。

2

的JIT仅允许执行不影响语言的语义保证操作。从理论上说,它可以删除分配和调用到MyObject构造如果它可以保证调用没有副作用,不能抛出异常(不包括OutOfMemoryError)。

换句话说,如果JIT优化召唤出你的测试,那么你的测试将反正过去了。

PS:请注意,这也适用,因为你正在做功能测试,而不是性能测试。在性能测试中,确保JIT不会优化您正在测量的操作非常重要,否则您的结果将变得毫无用处。