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



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

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

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

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

对于n=100000,在VISUALIZE作为静态变量但不是最终变量的情况下,插入排序大约需要9800毫秒,而在注释掉的情况下大约需要3100毫秒。因此,性能损失是不可接受的。

将visualize作为静态final,优化器确实会检测到它并将其删除,但既然它是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);  
    }
}

我认为您有几个选项:

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

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

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

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

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

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

如果您这样做:

private static final ENABLED = false;
// Then later...
if(ENABLED){
    call();
}

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

Java之所以没有预处理器,是为了防止程序员做你想做的事情。相反,你应该编写Java代码来实现你在预处理器指令中本来可以实现的功能,并让编译器优化生成的代码。这样,您最终将使用一种语言(而不是Java和预处理器DSL这两种语言)编写代码,从而使您在分析代码时使用的工具链更加简单。

在纯Java代码中解决问题的一种方法是使用Template方法模式。

正如这家伙声称要证明的那样,可以在运行时修改字节码来设置最终变量。我想你可以用同样的方法来设置static final VISUALIZE的打开和关闭。

相关内容

最新更新