【发布时间】:2022-01-13 06:22:08
【问题描述】:
查看 UTF8 解码性能,我注意到 protobuf 的 UnsafeProcessor::decodeUtf8 的性能优于 String(byte[] bytes, int offset, int length, Charset charset) 对于以下非 ascii 字符串:"Quizdeltagerne spiste jordbær med flØde, mens cirkusklovnen"。
我试图找出原因,所以我复制了String中的相关代码,并将数组访问替换为不安全的数组访问,与UnsafeProcessor::decodeUtf8相同。
以下是 JMH 基准测试结果:
Benchmark Mode Cnt Score Error Units
StringBenchmark.safeDecoding avgt 10 127.107 ± 3.642 ns/op
StringBenchmark.unsafeDecoding avgt 10 100.915 ± 4.090 ns/op
我认为差异是由于缺少我希望启动的边界检查消除,特别是因为在 String(byte[] bytes, int offset, int length, Charset charset) 的开头以调用 checkBoundsOffCount(offset, length, bytes.length) 的形式进行了明确的边界检查。
问题真的是缺少边界检查消除吗?
这是我使用 OpenJDK 17 和 JMH 进行基准测试的代码。请注意,这只是String(byte[] bytes, int offset, int length, Charset charset) 构造函数代码的一部分,并且仅适用于这个特定的德语字符串。
静态方法是从String 复制而来的。
查找// the unsafe version: cmets,它指示我将安全访问替换为不安全的位置。
private static byte[] safeDecode(byte[] bytes, int offset, int length) {
checkBoundsOffCount(offset, length, bytes.length);
int sl = offset + length;
int dp = 0;
byte[] dst = new byte[length];
while (offset < sl) {
int b1 = bytes[offset];
// the unsafe version:
// int b1 = UnsafeUtil.getByte(bytes, offset);
if (b1 >= 0) {
dst[dp++] = (byte)b1;
offset++;
continue;
}
if ((b1 == (byte)0xc2 || b1 == (byte)0xc3) &&
offset + 1 < sl) {
// the unsafe version:
// int b2 = UnsafeUtil.getByte(bytes, offset + 1);
int b2 = bytes[offset + 1];
if (!isNotContinuation(b2)) {
dst[dp++] = (byte)decode2(b1, b2);
offset += 2;
continue;
}
}
// anything not a latin1, including the repl
// we have to go with the utf16
break;
}
if (offset == sl) {
if (dp != dst.length) {
dst = Arrays.copyOf(dst, dp);
}
return dst;
}
return dst;
}
跟进
显然,如果我将 while 循环条件从 offset < sl 更改为 0 <= offset && offset < sl
我在两个版本中都获得了相似的性能:
Benchmark Mode Cnt Score Error Units
StringBenchmark.safeDecoding avgt 10 100.802 ± 13.147 ns/op
StringBenchmark.unsafeDecoding avgt 10 102.774 ± 3.893 ns/op
【问题讨论】:
-
有趣,我一直相信 JIT 会优化边界检查,特别是在方法中不修改
bytes和offset的情况下。我认为你应该使用 LinuxPerfAsmProfiler 来查看执行的程序集 -
我打印了生成的程序集,但我无法充分利用它。我的主要观点是,我希望 HotSpot 优化的 UTF8 解码至少与 protobuf(或其他第 3 方)一样快替代品)。目前只对包含纯 ascii 的字符串进行高度优化,但非 ascii latin1 字符串的情况可以大大改善。
-
我尝试使用提供的字符串在 JDK 代码库上测试
0 <= offset && offset < sl并让您知道结果 -
我已经回答了下面的问题,请查看带有链接的答案。你是对的
标签: java arrays performance protobuf-java bounds-check-elimination