【问题标题】:Will the Compiler Optimize this out编译器会优化这个吗
【发布时间】:2011-10-30 15:58:15
【问题描述】:

假设我在 C 代码中有类似的东西。我知道您可以改用#define 来使编译器不编译它,但出于好奇,我问编译器是否也会解决这个问题。

我认为这对 Java Compiler 更为重要,因为它不支持 #define

const int CONDITION = 0;
........
// Will the compiler compile this?
if ( CONDITION )
{

}
.......

【问题讨论】:

  • 你为什么不试试看,看看生成的二进制文件?
  • 具体来说,您可以在您的 Class 上使用 javap -c 命令打印出字节码,这实际上很容易阅读。 download.oracle.com/javase/1,5.0/docs/tooldocs/windows/…
  • 我记得看到 if (false) { ... } 是在 Java 中排除代码的推荐方式,就像 C 预处理器中的 #if。那是几年前的事了。
  • @Joe 我从不理解这种吸引力。在任何现代 IDE 上,注释掉几行代码都比编写 if() 更快,而且更清晰。可怕的习惯 imo - 只有在手头没有好的 IDE 时才需要。
  • @Voo - 如果您有很多应该一起禁用的块,可能分布在所有类中怎么办?仅仅改变一个常数的值肯定会更快,更不容易出错。 (但我也不喜欢这个主意)

标签: java c optimization


【解决方案1】:

首先,Java 不允许在 C 等条件中使用非布尔值(ifwhile 等)。此外,如果您在 if 检查中有一个“常量”表达式,编译器会警告您正在比较相同的表达式,所以我确定它已被优化。例如

    final int i = 1;
    if (1 == i) { // warning
        System.out.println("HI");
    }

【讨论】:

  • 此代码仅在i 不是1 时生成警告(“死代码”,Java 6)!
【解决方案2】:

在 Java 中,if 中的代码甚至不会成为编译代码的一部分。它必须编译,但不会写入已编译的字节码。它实际上取决于编译器,但我不知道没有优化它的编译器。规则定义在the JLS:

优化编译器可能会意识到语句 x=3;永不 被执行并且可以选择省略该语句的代码 生成的类文件,但语句 x=3;不被视为 此处指定的技术意义上的“无法访问”。

这种不同处理的基本原理是允许程序员 定义“标志变量”,例如:

static final boolean DEBUG = false;

然后写代码如:

if (DEBUG) { x=3; }

这个想法是应该可以改变 DEBUG 的值 从假到真或从真到假然后编译代码 正确,无需对程序文本进行其他更改。

不了解 C。

【讨论】:

【解决方案3】:

我可以回忆起在我的 Java 和 C# 程序中出现的场景(优化它)。但我也知道这在很大程度上取决于编译器设置 - 因此场景太不具体了。

在 Java 场景中,我们在一个 Java 源文件中拥有 const 值,而在另一个类(文件)中使用它们。发生的事情是,当我们使用 const 值更改并重新编译文件时,使用部分的流程没有任何变化。我们不得不重新编译整个项目(这是优化出来的证明)。

【讨论】:

    【解决方案4】:

    与其问这么简单的问题(唯一正确的答案是“用你的编译器试试看”)——为什么不试试呢?

    public class Test {
        public static void main(String[] args) {
            if (true) {
                System.out.println("Yep");
            }
            boolean var = false;
            if (var) {
                System.out.println("Nope");
            }
            final boolean var2 = false;
            if (var2) {
                System.out.println("Nope");
            }
        }
    }
    
    javac .\Test.java 
    javap -c Test
    Compiled from "Test.java"
    public class Test {
      public Test();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #3                  // String Yep
           5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: iconst_0
           9: istore_1
          10: iload_1
          11: ifeq          22
          14: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
          17: ldc           #3                  // String Yep
          19: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          22: return
    }
    

    您无需了解太多有关 java/c# 字节码或程序集的知识即可了解正在发生的事情。现在去为 C# 尝试同样的方法..

    【讨论】:

      【解决方案5】:

      以下是特定于 C 语言的。我不知道 Java 是如何处理它的。

      由于int 被定义为const,因此if (i) 在此处变为no-op 指令。一个聪明的编译器应该能够优化掉那个空的if 语句。

      示例:VC 2008

      带有if 声明的非空{}

      const int i = 1;
      // mov dword ptr [i], 1
      if (i)
      // mov eax, 1
      // test eax, eax
      // je wmain+35h
      {
         int j = 2;
         // move dword ptr [j], 2
      }
      // ..
      

      if 声明空{}

      const int i = 1;
      // mov dword ptr [i], 1
      if (i)
      {
      }
      // ..
      

      【讨论】:

        【解决方案6】:

        我刚刚对以下代码进行了快速检查

        public class Test {
            private static final boolean flag = true;
        
            public static void main(String[] args) throws InterruptedException {
        
                if(flag){
                    System.out.println("1");
                    System.out.println("1");
                    System.out.println("1");
                    System.out.println("1");
                    System.out.println("1");
                    System.out.println("1");
                    System.out.println("1");
                    System.out.println("1");
                    System.out.println("1");        
                }
        
            }
        
        }
        

        当 flag = true 时,生成的类文件大小为 708

        当标志 = 假时。生成的类文件大小为 462

        这意味着编译肯定会对静态最终值进行优化

        【讨论】:

        • 使用标准 JDK 1.6 做到这一点
        • 在 GCC 4.4.5(C 代码)上复制了这个,结果相似。
        【解决方案7】:

        Java 编译器必须检测明显无法访问的代码,这是语言要求。所以下面的代码编译不会出错:

        static final boolean flag = true; 
        
        public static void main(String[] args) {
            final String msg;
            if (flag)
                msg = "true";
            if (!flag)
                msg = "false";
            System.out.println(msg);
        }
        

        请注意,msg 是最终的,但编译器既不会抱怨 msg 未初始化,也不会抱怨它被初始化了两次。大多数编译器不会将死代码写入类文件。但即便如此,JIT 也会将其优化掉。

        C++ 也有编译时常量的概念。 const int 是编译时常量,因此它可以用作例如非类型模板参数。所以每个理智的 C++ 编译器都会检测并优化掉这种类型的明显死代码,即使你在编译时没有指定优化选项。

        【讨论】:

          猜你喜欢
          • 2017-01-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-31
          • 1970-01-01
          • 1970-01-01
          • 2015-01-11
          • 2013-09-11
          相关资源
          最近更新 更多