Java中的任何机制都提供编译时代码变体?

时间:2013-12-13 05:27:52

标签: java conditional-compilation compile-time

我正在为Java中的数据结构编写一组可视化接口。我们的想法是这些类应该是算法的高性能实现,但是使用嵌入式钩子,这样算法就可以交互显示。

有很多理由要这样做,但是如果你接受这个面值的请求,我想在算法中嵌入调用来识别刚刚完成的特定子部分。例如,一次排序算法。

我更希望图书馆既高效又允许这样做。在C ++中,我会插入两个不同的模板或使用条件编译,并且可以合理地生成两个版本的代码。有没有办法在Java中实现这一点?我希望别人可以提出一个,因为我做不到。

新闻快讯。我试过这个实际的代码。

对于n = 100,000,插入排序大约需要9800毫秒,其中VISUALIZE作为静态变量但不是最终的,而大约3100表示​​已注释掉。因此,性能损失是不可接受的。

使用visualize作为静态final,优化器确实检测到它并将其删除,但鉴于它是最终的,我可以用它做什么?我不能动态打开和关闭可视化!

public class TestSort {
    private static boolean VISUALIZE = false; 
    private static ArrayObserver ao;
    public static void insertionSort(int[] x) {
        for (int i = 1; i < x.length; i++) {
            int temp = x[i];
            int j = i - 1;
            if (x[j] > temp) {
                do {    
                    x[j+1] = x[j];
/*              if (VISUALIZE) {
                        ao.compare(i-1, i);
            ao.copy(i-1, i);
            }*/
                } while (--j >= 0 && x[j] > temp);
                x[j+1] = temp;
            }
        }
    }
    static Random r = new Random();
    static int[] createRandomArray(int n) {
    int[] x = new int[n];
    for (int i = 0; i < x.length; i++)
        x[i] = r.nextInt(100);
        return x;
    }
    static void display(int[] x) {
    for (int i = 0; i < x.length; i++)
        System.out.print(x[i] + " ");
        System.out.println();
    }
    public static void main(String args[]) {
    //int[] x = {9, 8, 7, 6, 5, 4, 3, 2, 1};
    int [] x = createRandomArray(100000);
    ao = new ArrayObserver(x);
    if (x.length < 20) display(x);
    long t0 = System.currentTimeMillis();
    insertionSort(x);
    long t1 = System.currentTimeMillis();
    if (x.length < 20) display(x);
    System.out.println(t1-t0);  
    }
}

5 个答案:

答案 0 :(得分:5)

我认为你有几个选择:

  • 静态最终常量(例如布尔值)进行测试,有条件地执行可视界面代码。将常量设置为“off”时,JVM将消除死代码,并且您的代码将完全优化。当然,缺点是您只能在编译时切换它,但如果您真的想要构建库的两个副本,这可能没问题。
  • 在确定是否调用可视界面的函数中添加额外参数,并在算法中根据需要进行测试。这将增加少量的运行时开销,但可能是可以接受的。我建议你根据我的经验对此进行基准测试,虽然对局部变量的这些测试通常足够便宜,你可以侥幸逃脱(在CPU方面,可能只是一个寄存器测试,可能比成本更便宜)单个内存访问int []数组...)
  • 使用更高级别/元语言来表达算法,并使用代码生成技术生成您需要的实际代码(有或没有)。例如,我在Clojure中做过这样的事情。它也可以直接使用ASM等工具生成字节码(如果你关心的只是执行,而不需要Java源代码)。
  • 使用文字预处理器。它应该适用于Java代码生成(就像它对C / C ++一样),虽然它不是一种常见的方法,你可能会发现它有点脆弱。您可能需要做一些聪明的事情,比如在文件系统等中生成不同的类名。

答案 1 :(得分:2)

与例如相反C ++,对本机机器代码的最终编译是在运行时完成的,在这种情况下,出于性能原因,完全不需要构建两个单独的版本。

如果您传递boolean以启用/禁用额外调用作为参数,以实现您的算法的类的构造函数并将其存储在final类变量(即常量)中,当算法在紧密循环(=热点&#39;)中执行时,Hotspot VM将编译类实例并删除死代码。这种运行时优化不能用C ++完成。

但请注意,boolean测试可能只占整个算法的一小部分。

编辑:您的测试表明这不起作用,但我不确定它们是否正确完成。您没有使用任何基准测试框架。最积极的优化将发生在服务器VM(-server)上,然后代码必须首先正确预热(前10000次左右的迭代将发生未编译,当然要慢得多)。此外,使用模板模式可能比final boolean更有可能进行优化,因为布尔检查无论如何都是便宜的,并且已知编译器进行虚拟调用内联(据我所知)。

EDIT2:如果您不需要在运行时切换(在完成所有条件编译和单独的构建之后也不会帮助您),只需使用您知道已优化的static final boolean。使用命令行参数或配置文件中的值对其进行初始化,您可以在应用程序启动时轻松地在两个版本之间切换。

答案 2 :(得分:1)

如果你这样做:

private static final ENABLED = false;

// Then later...
if(ENABLED){
    call();
}

整个if-block甚至不会包含在生成的字节码中(至少在较新的JVM上)。这会是一种选择吗?

答案 3 :(得分:0)

Java没有预处理器的原因是为了防止程序员完全按照您的要求进行操作。相反,您应该编写Java代码来实现您在预处理器指令中实现的功能,并让编译器优化生成的代码。这样,您将最终得到用单一语言(而不是两种语言,Java和预处理器DSL)编写的代码,这使您在分析代码时使用的工具链变得更加容易。

在纯Java代码中解决问题的一种方法是使用Template method pattern

答案 4 :(得分:0)

this guy声称要证明时,可以修改字节码以在运行时设置最终变量。我想你可以使用相同的方法来设置static final VISUALIZE开启和关闭。