【问题标题】:Java Realtime PerformanceJava 实时性能
【发布时间】:2009-05-21 23:10:24
【问题描述】:

我正在处理需要对图像进行非常高级操作的 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 的“优雅”和缓慢。课程的数量似乎有点过头了。

【问题讨论】:

  • 请注意,良好的 JIT(并且 Sun 提供良好的 JIT)将通过内联函数调用动态地去“分类”您的代码。他们还随时分析代码,注意特定的虚拟调用站点是否总是在同一个类上调度,并用快速类型检查和非虚拟调用(或内联代码)替换它。在指责高级代码之前,您至少必须进行概要分析,因为看起来非常“优雅”的东西可能仍然 JIT 编译为无分支机器代码。
  • 我不明白反对意见。我赞成补偿。

标签: java performance real-time


【解决方案1】:

我已经使用 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 库如此间接:

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

【讨论】:

    【解决方案2】:

    我的问题是,在开发的这个后期阶段(我已经进行了第一次迭代,而不是我正在研究实时执行更多的第二次)我是否应该硬着头皮切换到 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 的实时能力并不为人所知。这也可能会咬你,所以要小心。

      【讨论】:

      • 从 OP 中可以清楚地看出,图像处理是应用程序的一个非常重要的部分,实时图像处理似乎是一项非功能性要求
      • @hhafez,核心重要功能占整个项目的 20% 一点也不稀奇,其余的都是关于用户交互和导致核心目的的业务逻辑。
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

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

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

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

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

        【讨论】:

          【解决方案6】:

          过早的优化是万恶之源。

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

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

          【讨论】:

          • 谁说他过早地优化了?
          • @hhafez,我认为 Bill 的观点是关于为什么标准库没有按照发布者希望的方式进行优化,而不是关于这个特定的用例。
          • @Yisha 谢谢你,是的。正如我的观点所指出的,他应该编写一组优化的库。它有一个地方,但只有在需要时——那个地方不是参考实现。
          【解决方案7】:

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

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

          【讨论】:

            【解决方案8】:

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

            这里有一些关于它的东西:But it looks like it will only be in Java 7 :(

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

            【讨论】:

              【解决方案9】:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2018-11-23
                • 2010-12-08
                • 2011-09-14
                • 2018-09-13
                • 2011-06-16
                • 2020-01-17
                • 1970-01-01
                相关资源
                最近更新 更多