12

我正在处理需要对图像进行非常高级操作的 Java 项目。事实上,我正在使用 OpenCV 进行大部分操作,并且我正在使用 JNI 来包装我需要的 OpenCV 函数。我对 OpenCV 提供的性能非常满意,编写 OpenCV 代码的人应该对代码给予极大的赞誉。与我对 Java 开发人员编写的代码的体验形成鲜明对比。

我开始对我的编程语言的选择持乐观态度,我的项目的第一次工作迭代运行良好,但它的性能远不接近实时(每 2 秒获得大约 1 帧。)我已经对我的代码和它帮助很大。我已经能够将帧速率提高到大约每秒 10-20 帧,这很棒,但我发现要进行任何进一步的优化,我必须重写 Java 代码来做同样的事情,但 10 -20 倍更高效。

我对 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 的“优雅”和缓慢。那里的课程数量似乎有点过头了。

4

9 回答 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 循环。

如果您需要刻录 RAM,您可以为所有可能的 24 位像素像素颜色创建一个静态的、最终的输出灰度字节查找表。这将在 RAM 中约为 16 MB,但是您不必执行任何浮点运算,只需访问单个数组即可。这可能会更快,具体取决于您使用的 JVM,以及它是否可以优化数组边界检查。

可以找到类似的、更快的图像处理代码的地方:

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

为什么 Java2D 库如此间接:

大多数“类”是由于支持多种颜色模型和存储格式(IE HSB 图像、基于浮点的颜色模型、索引颜色模型)。间接存在是有原因的,有时实际上会提高性能 - BufferedImage 类(例如)直接连接到最近 VM 中的图形内存中,以使某些操作更快。间接允许它在很多时候向用户掩盖这一点。

于 2009-05-22T15:38:41.693 回答
6

我的问题是,在开发的这个后期(我已经进行了第一次迭代,而不是我正在研究实时执行更多的第二次)我是否应该硬着头皮切换到我可以很好的 C/C++更多地调整事情,或者我应该坚持使用 Java 并希望事情变得更加实时友好,这样我就不必重写已经实现的 Java 代码来获得加速。

你问我应该

  1. 切换到我可以满足我的性能要求的语言。
  2. 坚持使用 Java,希望情况有所改善。

可能还有其他选项....但是选项 2 似乎不现实,您不能只是“希望”代码变得更快:p

需要注意的几点:

  1. OpenJDK 不一定具有与 Sun JDK 相同的性能,您是否尝试过 Sun JDK?
  2. 如果您需要通过几种方法进行性能优化,那么可能值得重新编写它们并坚持使用 Java...
于 2009-05-21T23:25:27.073 回答
3

我的建议是这取决于图像操作与整个项目相比的重要性,以及相对于 java 带来的任何优势。显然,如果需要,您可以在 java 中编写快速代码(如您所演示的)。但是,如果您的项目的 80% 都包含这种优化,我肯定会重新考虑 Java 作为这里的语言选择。

另一方面,如果这代表了应用程序的 20%,而另外 80% 是围绕提供这种转换的用户功能,那么也许必须做一些工作来完成操作是一个值得的权衡,而不是必须处理使用您自己的内存管理,并拥有 java 为您提供的任何其他 API 用于用户交互(Web、Swing、SWT,无论您使用什么)。

由于垃圾收集器,Java 的实时能力并不为人所知。那也可能会咬你,所以要小心。

于 2009-05-21T23:19:56.727 回答
1

我不知道你会得到多少性能提升,但如果你有一个长时间运行的进程在做重复的事情,你应该尝试使用java -server. 它的性能比 Windows 上的默认客户端 VM好得多,后者针对快速启动时间进行了优化。

于 2009-05-22T05:38:43.297 回答
1

目前尚不清楚您是否真的在询问实时。实时和真正快速之间是有区别的。对于真正的快速,考虑平均案例行为就足够了。吞吐量是主要问题。实时意味着每次都能够在固定的时间内完成某些任务。或者,当然,有些应用程序需要两者。

在传统的 Java 实现中,例如 OpenJDK,垃圾收集器是实现实时行为的最大问题。这是因为垃圾收集器可以在任何时候中断程序来完成它的工作。我的公司 aicas 实现了不需要单独的垃圾收集线程的 Java。相反,在分配时会完成一些 GC 工作。实际上,分配是通过为每个释放的块标记或清除几个块来支付的。这需要完全重新实现虚拟机。

编译是实时 Java 与传统 Java 实现不同的另一点。实时 Java 技术倾向于使用静态或提前 (AoT) 编译而不是 JIT 编译。JiT 可能适合您的应用程序,因为您可能能够容忍传统 VM 编译最常用的类所需的“预热”时间。如果是这样,那么您可能没有实时要求,只有吞吐量要求。

如果您有兴趣确保帧解码不会被垃圾收集中断,那么使用 Java 的实时实现以及 AoT 编译也是有意义的。Java 实时规范 (RTSJ) 还为实时和嵌入式编程提供其他支持,例如 RelatimeThread、AsyncEventHandler 和 RawMemoryAccess。

当然,要获得良好的性能,无论是实时还是真正快速,都需要注意细节。过度使用临时对象没有帮助。分配总是需要额外的成本,所以应该尽量减少。这对于不允许更改对象状态的函数式语言来说是一个重大挑战。但是,应该注意理解正在编写的代码的关键路径,以避免不必要的优化。分析对于了解优化工作的最佳使用位置至关重要。

于 2012-06-23T14:59:12.337 回答
0

过早的优化是万恶之源。

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

参考实现的要点是制作可理解、可维护的代码——它必须如此。我认为总是期望供应商在必要时分析这个可理解的版本并重新实现部件以提高速度。

于 2009-05-21T23:30:41.963 回答
0

除了其他人所说的之外,您还可以为 JDK 贡献优化。如果您可以提供一个不牺牲通用性或可读性的强大优化,我希望您能够在未来的 JDK 版本中包含您的补丁。

因此,您不必希望JDK 会变得更好。你可以帮助实现它。

于 2009-05-21T23:52:34.587 回答
0

据我了解,最新版本的 Java(或者可能是 JavaFX)具有允许您访问系统视频硬件中的高级功能的方法。很抱歉我这么笼统,我相信我在 Java Posse 上听说过它,而且由于我被困在 Java 1.3 领域,我从来没有真正有机会检查它——但我确实记得听到过类似的话。

这是关于它的一些事情:但它看起来只会在 Java 7 中 :(

看起来它最初只支持播放流和基本的流操作——但也许“等一下,Java 会改进”的方法可能真的有效。

于 2009-05-21T23:56:10.087 回答
0

是什么阻止您编写您希望使用的方法的优化版本而不是使用内置方法?如果这不可能,为什么不使用更本地的语言编写您的对象,并将其导入您现有的应用程序?

于 2009-05-22T00:04:30.863 回答