Java实时性能

13

我正在使用Java项目进行高级图像处理。实际上,我大部分的操作都是使用OpenCV完成的,并且我使用JNI来包装我需要的OpenCV函数。我非常满意OpenCV提供的性能,编写OpenCV代码的人们应该得到很大的赞誉。与Java开发者编写的代码形成了鲜明的对比。

我一开始对我的编程语言选择感到乐观,我的第一个工作版本的效果还可以,但其性能远远不足以实现实时性(每2秒钟获得1帧)。我已经对我的代码进行了一些优化,这有很大的帮助。我已经成功将帧速率提高到每秒约10-20帧,这很不错,但我发现要进行进一步的优化,我必须重写Java代码以使其更加高效。

我对Java开发人员在编写与媒体相关类的代码时非常不重视性能感到震惊。我已经下载并探索了我使用的函数的OpenJDK。例如,在Raster类下有一个名为getPixels(...)的函数,它获取图像的像素。我期望这个函数在源代码中是高度优化的函数,使用System.arrayCopy进行多次调用以进一步优化性能。但实际上我发现的是非常“繁琐”的代码,他们调用了5-6个不同的类和10-20个不同的方法,仅仅为了完成我可以用一行代码完成的任务:

for (int i =0; i < n; i++) {
  long p = rawFrame[i];
  p = (p << 32) >>> 32;
  byte red = (byte) ((p >> 16) & 0xff);
  byte green = (byte) ((p >> 8) & 0xff);
  byte blue = (byte) ((p) & 0xff);
  byte val = (byte)(0.212671f * red + 0.715160f * green + 0.072169f * blue);
  data[i] = val;
  grayFrameData[i] = (val & 0x80) + (val & (0x7f)); 
}
上面的代码将图像转换为灰度并获得浮点像素数据,大约需要1-10毫秒。如果我想使用Java内置函数完成相同的操作,转换为灰度本身就需要200-300毫秒,然后获取浮点像素需要大约50-100毫秒。这对于实时性能来说是不可接受的。请注意,为了加速,我大量使用位运算符,而Java开发人员则避之唯恐不及。
我理解他们需要处理一般情况,但即使这样,他们也不能至少提供优化选项或者至少警告这段代码执行起来会有多慢吗。
我的问题是,在开发的这个晚期(我已经有了第一次迭代,现在正在进行第二次,以便更实时地工作),我应该咬紧牙关切换到C/C++,在那里我可以更好地调整细节,还是应该坚持使用Java,并希望事情变得更加实时友好,这样我就不必重新编写已经实现的Java代码以获得加速。
我真的开始对Java的“高雅”和缓慢感到厌倦。似乎有太多的类。

6
请注意,优秀的JIT编译器(Sun提供了优秀的JIT编译器)可以通过内联函数调用来即时将您的代码去除“类别化”。它们还会在使用过程中对代码进行分析,如果发现某个虚拟调用站点总是在同一个类上派发,它会将其替换为快速类型检查和非虚拟调用(或内联代码)。在指责高级代码之前,您必须至少进行分析,因为看起来很“类别化”的东西可能被JIT编译为无分支的机器码。 - Steve Jessop
2
我不理解为什么会有人踩。我点赞是为了补偿。 - Yishai
9个回答

16

我曾使用Java进行计算机视觉工作,我认为它非常适用于计算机视觉和实时处理等任务,只需要知道如何使用它。

潜在的优化:

如果你需要优化你的代码,我很乐意提供帮助 -- 例如,我可以告诉你,通过创建一个方法,你可能会获得更好的性能表现。

`public static final int getGrayScale(final int pixelRGB){
    return (0.212671f * ((pixelRGB >> 16) & 0xff) + 0.715160f * ((pixelRGB >> 8) & 0xff) + 0.072169f * ((pixelRGB) & 0xff));
}`

在你的 for{pixels} 循环中使用此方法调用。通过使用方法调用,JVM 可以更加重度地优化此操作,并且可能也可以更好地优化 for 循环。

如果您有多余内存,可以创建一个静态的、最终的查找表,包含所有可能的 24 位像素颜色的输出灰度字节。 这将占用 ~16 MB 的 RAM,但是您不需要进行任何浮点运算,只需进行单个数组访问。这取决于您使用的 JVM 以及它是否可以优化掉数组边界检查,这种方法 可能 更快。

寻找类似且更快的图像处理代码的地方:

我强烈建议您查看 ImageJ 图像处理应用程序及其库的代码,特别是 ij.process.TypeConverter。 就像您的代码一样,它在直接数组操作和最少额外数组创建方面依赖很大。 Java2D 库(标准 JRE 的一部分)和 Java Advanced Imaging(JAI)库提供了其他直接对图像数据进行图像处理的方法,而无需每次都自己编写操作。对于 Java2D,您只需要小心使用哪些函数。

为什么 Java2D 库如此间接:

大部分“类”的本质是支持多个颜色模型和存储格式(即 HSB 图像、基于浮点数的颜色模型、索引色模型)。 这种间接性是有原因的,有时实际上可以提高性能--例如 BufferedImage 类在最近的 VM 中直接连接到图形存储器,从而使某些操作速度更快。间接性让它很多时候可以把这些对用户隐藏起来。


6
我想问的是,在开发的这个晚期阶段(我已经有了第一个迭代版本,现在正在开发第二个版本,希望能更实时),我应该决定转向C/C++语言,以便更好地进行调优,还是坚持使用Java,并希望事情变得更加实时友好,这样我就不必重写已经实现的Java代码来提高速度。
你在询问是否:
1. 转向一种可以满足性能要求的语言。 2. 坚持使用Java并希望有所改善。
可能还有其他选择...但是选项2似乎不现实,你不能只是“希望”代码变快:p 需要注意以下几点:
1. OpenJDK的性能不一定与Sun JDK相同,您尝试过Sun JDK吗? 2. 如果您需要进行性能优化的方法只有几个,那么重写它们并继续使用Java可能是值得的...

3
我的建议是,这取决于图像处理相对于整个项目的重要性以及与Java带来的任何优势的关系。如果需要快速编写代码,则可以使用Java(如您所演示的)。然而,如果80%的项目将包含此类优化,则我肯定会重新考虑在此处选择Java作为语言。

另一方面,如果这代表应用程序的20%,其余80%是围绕提供此转换的用户功能,那么也许必须做出努力来完成操作比自己处理内存管理更值得,而且还可以使用Java为用户交互提供其他API(Web,Swing,SWT等)。

由于垃圾收集器,Java并不以其实时能力而闻名。这也可能会给你带来麻烦,所以请小心。

从原帖中可以清楚地看出,图像处理是该应用程序非常重要的一部分,并且似乎实时图像处理是一个非功能性需求。 - hhafez
@hhafez,核心重要功能只占整个项目的20%并不罕见,其余部分涉及用户交互和业务逻辑,这些都是为了实现核心目标。 - Yishai

1

我不知道你会得到多少性能提升,但如果你有一个长时间运行的重复处理的进程,你应该尝试使用 java -server 运行服务器热点虚拟机。它比默认的Windows客户端虚拟机表现更好,后者针对快速启动时间进行了优化。


1

您提出的问题并不明确是否涉及实时性。实时性和快速响应之间存在差异。对于快速响应,考虑平均情况即可。吞吐量是主要关注点。实时性意味着每次都能在固定时间内完成某些任务。当然,有些应用程序需要同时满足这两个需求。

在传统的Java实现中,例如OpenJDK,垃圾收集器是实现实时性行为的最大问题。这是因为垃圾收集器可以在任何时候中断程序以执行其工作。我的公司aicas拥有一种Java实现,不需要单独的线程进行垃圾收集。相反,在分配时会完成一些GC工作。有效地说,标记或清除每个释放的块的几个块来支付分配费用。这需要对虚拟机进行全面重新实现。

编译是实时Java与传统Java实现不同的另一个方面。实时Java技术倾向于使用静态或Ahead-of-Time(AoT)编译,而不是JIT编译。如果您的应用程序可以容忍传统VM编译最常用类所需的“热身”时间,则JiT可能适用于您的应用程序。如果是这样,那么您可能没有实时要求,只有吞吐量要求。

如果您有兴趣确保帧解码不受垃圾回收的干扰,那么使用实时Java实现和AoT编译可能是有意义的。Java的实时规范(RTSJ)还提供了其他支持实时和嵌入式编程的功能,例如RelatimeThread、AsyncEventHandler和RawMemoryAccess。

当然,无论是实时还是快速,获得良好的性能都需要注意细节。过度使用临时对象并不有益。分配总是涉及额外的成本,因此应该尽量减少。这对于函数语言来说是一个重大挑战,因为它们不允许更改对象的状态。然而,应该注意理解正在编写的代码的关键路径,以避免不必要的优化。分析是了解最佳优化方向的关键。


0

过早优化是万恶之源。

与其抱怨,不如编写一组经过优化的库并发布它们,但创建一个预先针对不存在目标进行优化的“参考”Java实现只会是错误的。

参考实现的主要目的是制作易于理解、可维护的代码——这是必须的。我认为人们总是期望在必要时供应商会分析这个易于理解的版本并重新实现部分以提高速度。


1
谁说他过早地进行了优化? - hhafez
@hhafez,我认为Bill的观点是关于为什么标准库没有按照帖子作者所期望的方式进行优化,而不是关于这个特定的用例。 - Yishai
@Yisha谢谢,是的。正如我的意思是他应该编写一组优化的库。有一个需要它的地方--但那个地方不是参考实现。 - Bill K

0
除了其他人所说的,您还可以为JDK贡献优化。如果您能提供一个强大的优化,既不牺牲通用性也不影响可读性,我相信您将能够在未来的JDK版本中包含您的补丁。
因此,您不必仅仅希望JDK变得更好。您可以帮助实现它。

0
据我所知,最新版本的Java(或者可能是JavaFX)有一些方法可以让你访问系统中视频硬件的高级功能。很抱歉我表述得太笼统了,我记得在Java Posse上听说过这个,但由于我被困在Java 1.3的世界里,从未真正有机会去尝试它--但我确实记得听到过这样的事情。
关于这个问题,这里有一些信息:但看起来只有在Java 7中才能使用 :( 此外,似乎它一开始只支持播放流和基本的流操作--但也许“等待并且Java会变得更好”的方法实际上可能奏效。

0

是什么阻止你编写一个优化版本的方法来代替使用内置方法?如果不可能,为什么不用更本地化的语言编写你的对象,并将其导入到现有应用程序中呢?


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