Java中的代码注入/汇编内联?

2022-09-03 09:32:40

我知道Java是一种安全的语言,但是当需要矩阵计算时,我可以尝试更快的方法吗?

我正在C++,Digital-Mars编译器和FASM中学习__asm{}。我想在Java中做同样的事情。如何在函数中内联装配代码?这有可能吗?

类似如下(一个矢量化循环,用于将数组的所有元素钳制为一个值而不进行分支,使用AVX支持的CPU):

JavaAsmBlock(
   # get pointers into registers somehow
   # and tell Java which registers the asm clobbers somehow
     vbroadcastss  twenty_five(%rip), %ymm0
     xor   %edx,%edx
.Lloop:                            # do {
    vmovups   (%rsi, %rdx, 4), %ymm1
    vcmpltps   %ymm1, %ymm0, %ymm2
    vblendvps  %ymm2, %ymm0, %ymm1, %ymm1  # TODO: use vminps instead
    vmovups    %ymm1, (%rdi, %rdx, 4)
    # TODO: unroll the loop a bit, and maybe handle unaligned output specially if that's common
    add         $32, %rdx
    cmp         %rcx, %rdx
    jb     .Lloop                  # } while(idx < count)
    vzeroupper
);

System.out.println(var[0]);

我不想使用代码注入器。我想查看英特尔或AT&T风格的x86说明。


答案 1

在Java代码和底层硬件之间有一层抽象,这使得这种事情在原则上是不可能的。从技术上讲,您无法知道代码在底层机器上的表示方式,因为相同的字节码可以在不同的处理器和不同的体系结构上运行。

您正式可以做的是使用 Java 本机接口 (JNI) 从 Java 代码中调用本机代码。调用开销很大,与Java共享数据相当昂贵,因此这应该仅用于大小适中的本机代码块。

不过,从理论上讲,这种扩展应该是可能的。可以想象一个针对特定平台并允许程序集转义的Java编译器。编译器必须发布其 ABI,这样您才能知道调用约定。但是,我不知道有任何东西可以这样做。但是有几种编译器可以将Java直接编译为本机代码;其中一个可能在我不知情的情况下支持这样的事情,或者可以扩展到这样做。

最后,在完全不同的层面上,有JVM的字节码汇编器,比如Jasmin。字节码汇编器允许您编写直接面向 JVM 的“机器代码”,有时您可以编写比编译器可以生成的更好的代码。无论如何,玩起来都很有趣。javac


答案 2

不能在 Java 代码中直接内联程序集。然而,与其他一些答案所声称的相反,方便地调用汇编而不经过任何中间C(或C++)层是可能的。

快速演练

请考虑以下 Java 类:

public class MyJNIClass {

    public native void printVersion();

}

主要思想是使用 JNI 命名约定声明符号。在本例中,要在程序集代码中使用的残缺名称是 。此符号必须从其他转换单元中可见,例如,可以使用 FASM 中的指令或 NASM 中的指令来实现此符号。如果您使用的是 macOS,请在名称前面附加一个额外的下划线。Java_MyJNIClass_printVersionpublicglobal

使用目标体系结构的调用约定编写汇编代码(参数可以在寄存器、堆栈、其他内存结构等中传递)。传递给程序集函数的第一个参数是指向 的指针,它本身就是指向 JNI 函数表的指针。使用它来调用 JNI 函数。例如,使用 NASM 和定位x86_64:JNIEnv

global Java_MyJNIClass_printVersion

section .text

Java_MyJNIClass_printVersion:
    mov rax, [rdi]
    call [rax + 8*4]  ; pointer size in x86_64 * index of GetVersion
    ...

JNI 函数的索引可以在 Java 文档中找到。由于 JNI 函数表基本上是一个指针数组,因此不要忘记将这些索引乘以目标体系结构中指针的大小。

传递给程序集函数的第二个参数是对调用 Java 类或对象的引用。所有后续参数都是本机 Java 方法的参数。

最后,组装代码以生成对象文件,然后从该对象文件创建共享库。GCC 和 Clang 可以使用类似于 的命令执行最后一步。gcc/clang -shared -o ...

其他资源

此 DZone 文章中提供了更全面的演练。我还在GitHub上创建了一个完全可运行的示例,请随时查看并使用它以获得更好的理解。


推荐