我继承了一段代码,它大量使用 String -> byte[] 转换,对于一些自制的序列化代码反之亦然。本质上,Java 对象知道如何将其组成部分转换为字符串,然后将其转换为 byte[]。然后,所述字节数组通过 JNI 传递到 C++ 代码中,该代码将 byte[] 重新构成 C++ std::strings,并使用它们来引导镜像 Java 对象的 C++ 对象。还有更多内容,但这是这段代码如何工作的高级视图;通信在两个方向上都是这样工作的,因此 C++ -> Java 转换是我上面提到的 Java -> C++ 转换的镜像。

这段代码的一部分——字符串到字节[]的实际转换——意外地在分析器中显示为消耗大量CPU。诚然,有大量数据正在传输,但这是一个意想不到的瓶颈。

代码的基本轮廓如下:

public void convertToByteArray(String convert_me, ByteArrayOutputStream stream)
{
  stream.write(convert_me.getBytes());
}

该功能还有一点,但不多。对于每个 String/Stringified 对象,上面的函数都会被调用一次,并且在将所有组成部分写入 ByteArrayOutputStream 后,ByteArrayOutputStream 将转换为 byte[]。通过提取以下内容将上述内容分解为更适合分析器的版本 convert_me.getBytes() 调用显示该函数 90% 以上的时间都花在 getBytes() 调用上。

有没有办法提高 getBytes() 调用的性能,或者是否有另一种可能更快的方法来实现相同的转换?

正在转换的对象数量相当大。在仅使用生产数据的一小部分的分析运行中,我看到对上述转换函数的调用超过 1000 万次。

由于我们即将将该项目投入生产,因此目前无法采取一些解决方法:

  • 重写序列化接口以仅在 JNI 层传递 String 对象。(对我来说)这是改善这种情况的明显方法,但它需要对序列化层进行重大重新设计。鉴于我们将于本周初进入 UAT,现在进行这种复杂的更改为时已晚。这是我下一个版本的首要任务,所以它会完成;然而,在那之前我确实需要一个解决方法,但到目前为止,代码正在运行,已经使用了多年,并且大部分问题都得到了解决。好吧,除了表演之外。
  • 更改 JVM(当前为 1.5)也不是一个选择。不幸的是,这是安装在客户端计算机上的默认 JVM,并且更新到 1.6(在这种情况下可能会更快,也可能不会更快)是不可能的。任何在大型组织工作过的人都可能明白为什么......
  • 除此之外,我们已经遇到了内存限制,因此尝试缓存至少较大的字符串及其字节数组表示,虽然是一个潜在的优雅解决方案,但可能会导致比它解决的问题更多的问题
有帮助吗?

解决方案

我猜测部分问题可能是 Java 字符串采用 UTF-16 格式 - 即每个字符两个字节;所以 getBytes() 正在做大量工作将每个 UTF-16 元素转换为一个或两个字节,具体取决于您当前的字符集。

你有没有尝试过使用 字符集编码器 - 这应该可以让您更好地控制字符串编码,并允许您跳过默认情况下的一些开销 getBytes 执行。

或者,您是否尝试显式指定字符集 getBytes, ,并使用 US-ASCII 作为字符集?

其他提示

我看到几个选项:

  • 如果你有 Latin-1 字符串,你可以只分割字符串中字符的高字节(我认为 Charset 也这样做)
  • 如果您有更多核心,您还可以将工作分配给多个核心(fork-join 框架曾经向后移植到 1.5)
  • 您还可以将数据构建到字符串构建器中,并仅在最后将其转换为字节数组一次。
  • 查看您的 GC/内存使用情况。内存利用率过高可能会因频繁的 GC 中断而减慢算法速度
  • 如果每次转换的字符串都是相同的,则可以将结果缓存在 WeakHashMap 中。

    另外,请查看 getBytes() 方法(如果安装 SDK,则可以使用源代码)以了解它的具体用途。

    问题在于,即使在今天,Java 中的所有方法都使用 UTF-8 生成方式分配内存。为了获得编码性能,您需要编写自定义代码并重用 byte[] 缓冲区。Colfer 可以生成代码或只是简单地复制其实现。

    https://github.com/pascaldekloe/colfer/blob/4c6f022c5183c0aebb8bc73e8137f976d31b1083/java/gen/O.java#L414

    许可以下: CC-BY-SA归因
    不隶属于 StackOverflow
    scroll top