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

15

我知道Java是一种安全的语言,但在需要进行矩阵计算时,我可以尝试一些更快的东西吗?

我正在学习C++中的__asm{}、Digital-Mars编译器和FASM。我想在Java中做同样的事情。如何在函数中嵌入汇编代码?这是否可能?

类似于以下内容(使用CPU的AVX支持,对数组的所有元素进行值限制的向量化循环,而不使用分支):

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]);

我不想使用代码注入器,我想查看Intel或AT&T风格的x86指令。


3
如果你写汇编代码的方式是这样的(使用16位寄存器并使用div除以4而不是shr al,2),那么它绝对不会比C编译器生成的代码更快。,所以你应该使用带有C或C ++的JNI。只有在你知道如何针对当前CPU微体系结构进行调整时,汇编语言才有用于性能优化。这是一个有用的问题,但是示例是为什么大多数人不应该使用汇编语言的例子。 - Peter Cordes
你说得对。同时处理两件事情。如果我当时有足够的经验,我会添加类似于AVX点积的东西,并正确排序指令。 - huseyin tugrul buyukisik
你可以编辑问题,使用一些现代的东西。比如BMI2 pdep,它没有Java内在函数。理想情况下,你可以想出一些C编译器无法轻易生成的内容。 - Peter Cordes
我修复了你的汇编代码,包括实际循环,而不仅仅是循环开销但没有分支。并且为了高性能进行了优化,使其成为你可能真正想要使用的内容。你在使用带符号32位循环计数器的方式强制编译器在每次迭代中对其进行符号扩展。 - Peter Cordes
1
就像你可能不会在商业应用程序中使用C++(或者并非全部),也不会使用Python编写操作系统一样,似乎Java不是此处手头任务的正确工具。 - IceFire
显示剩余7条评论
6个回答

20

您的Java代码和底层硬件之间存在一层抽象,原则上使这种操作不可能。您无法确切地知道您的代码在底层机器上的表示方式,因为相同的字节码可以在不同的处理器和不同的架构上运行。

官方上,您可以使用Java Native Interface(JNI)从Java代码中调用本地代码。调用开销非常大,并且与Java共享数据相当昂贵,因此这仅应用于较大的本地代码块。

理论上,这样的扩展应该是可能的。可以想象一个针对特定平台并允许汇编转义的Java编译器。编译器必须发布其ABI,以便您了解调用约定。然而,我不知道有任何一个编译器这样做。但是,有几个直接将Java编译为本机代码的编译器 可用;可能其中一个支持类似这样的功能而我不知道,或者可以扩展它来实现。

最后,在完全不同的层面上,有一些针对JVM的字节码汇编器,比如Jasmin。字节码汇编器允许您编写直接针对JVM的“机器码”,有时您可以编写比javac编译器生成的更好的代码。总之,玩一下很有趣。


2
在现有的Java到本地代码编译器中,Excelsior JET只实现了JNI,而GCJ支持JNI和它自己的接口CNI - Dmitry Leskov
2
要明确的是,如果您认为几十个周期是“重大的”(JNI调用的典型开销),那么开销只是“重大的”。对于像上面这样操作合理大小数组的方法,只要数据传递正确(例如,通过Get*Critical函数直接操作基础数组),JNI开销就应该在噪音中消失。 - BeeOnRope

12

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

快速指南

考虑以下Java类:

public class MyJNIClass {

    public native void printVersion();

}

主要思想是使用JNI命名约定声明符号。在这种情况下,在汇编代码中要使用的名称为Java_MyJNIClass_printVersion。此符号必须对其他翻译单元可见,例如可以使用FASM中的public指令或NASM中的global指令来实现。如果您在macOS上,请在名称前面添加额外的下划线。

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

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上完全可运行的示例,随意查看并尝试操作以获得更好的理解。


这比JNI C++还要深入吗? - huseyin tugrul buyukisik
它使用与C或C++相同的JNI实现,但是确实从更低层次开始。;-) - Pyves
1
你本可以写成 mov rax, [rdi] / call [rax + 8*4]。x86寻址模式比额外的指令更有效率。内存间接调用并不比加载+调用更快,但也不会更慢,并且可以节省代码大小和解码带宽。(嗯,实际上根据http://agner.org/optimize/的说法,在AMD上可能会更慢,因为它超过了2个微操作,这意味着使用了VectorPath(微码),而不是DirectPath。如果针对AMD进行优化,也许可以使用`mov rax,[rdi]/mov rax,[rax + 8 * 4]/call rax`。仍然没有ADD指令,那总是更糟糕的) - Peter Cordes
@PeterCordes 感谢您提供这些见解,我已相应修改了我的答案。我还将前往修改存储库上的代码,除非您有兴趣提交拉取请求? - Pyves
1
我没有设置好Java开发环境来检查是否出了问题,所以你可以自行更改。 - Peter Cordes
没问题,已经完成了这里 - Pyves

2
使用机器级Java技术,可以从Java调用汇编语言。它将您用Java编写的汇编代码(非常类似于最常用的汇编语法)透明地打包到本地库中。接下来,您只需要调用一个本地方法,该方法在您编写汇编的同一类中定义。因此,您始终处于Java环境中,并且无需从Java IDE切换到某些汇编工具,然后再切回Java。

看起来你提供的API缺乏文档说明。你能提供更多细节吗? - Nicola Ferraro
比JNI更低的API/接口延迟? - huseyin tugrul buyukisik

1

非常好。我会尝试一下。我正在使用Digital Mars编译器。你认为用__asm可以实现吗?没关系,我会自己尝试。谢谢。 - huseyin tugrul buyukisik
据我所记,您可以使用任何喜欢的C编译器。Java只是使用平台ABI。 - andrew cooke
2
你可以用汇编写一个遵循C ABI的函数,因此可以像调用C函数一样调用它。基本上,无论你在C函数中做什么使其与JNI兼容,你都可以在汇编中完成。 - Peter Cordes

1

您可以使用JNI或JNA从Java中调用本地函数。或者,作为替代方案,您可以将字节码作为InputStream,并将其转换为Java类。


1

Aparapi 不是用于 GPU 并行编程的吗? - huseyin tugrul buyukisik
4
是的。难道您不是询问如何更快地进行矩阵计算吗? - Dmitry Leskov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接