Java中的向量距离计算-优化
Posted
技术标签:
【中文标题】Java中的向量距离计算-优化【英文标题】:Vector Distance Calculation in Java - Optimization 【发布时间】:2012-09-28 19:28:42 【问题描述】:作为图像处理功能的一部分,我需要计算两者之间的平方和 图像中有两行。
这部分代码占用了 96% 的运行时间:
for(int dx=0;dx<size;dx++)
int left = a[pa+dx];
int right = b[pb+dx];
int diff = (left & 0xFF) - (right & 0xFF);
sum += diff*diff;
地点:
a
、b
属于 byte[]
类型
sum
是 long
size
是 int
并且通常具有较大的值(大约 400)
运行 Java 7 64 位。我试图用 a[pa++]
之类的东西替换 a[pa+dx]
性能也好不到哪里去。
执行保存运行的代码与用 C++ 编写的代码完全相同 整体快两倍(!)和 据我所知,应该没有重要的理由为什么不使用这个 Java 代码要一样快,尤其是当边界检查可以通过以下方式移出循环时 编译器。
如何优化这些东西以使其与 C++ 代码一样好 - 最终它是 整数运算在 Java 中应该不会慢很多
编辑:C++ 示例如下:
unsigned char const *srcptr=&a[pa];
unsigned char const *tgtptr=&b[pb];
for(int dx=0;dx < size;dx++)
int p1=*srcptr++;
int p2=*tgtptr++;
int diff = p1 - p2;
sum += diff * diff;
我想了解如何使用 HotSpot 优化器来创建代码 和上面显示的 C++ 代码一样快,最后还是相当简单易行 优化线条。
【问题讨论】:
由于 HotSpot,Java 中的性能测量并不容易。你是怎么测量的? 占用大部分时间的函数,如果删除这些行,代码几乎不会运行。 您可以尝试在热身中反复运行代码以允许优化启动。我无法告诉您多久,但可能大约 1.000 到 10.000 次。之后,您测量并查看是否有差异。 代码是温热 CPU 密集型,运行大约一分钟... 很难帮助任何人,因为我们无法对您提供的隔离 sn-p 进行基准测试,因此无法测试我们建议的任何方法是否更快。 【参考方案1】:它只是很小,但您不需要& 0xFF
来计算差异:差异将是相同的有符号或无符号。
100 - -1 = 101 // signed
228 - 127 = 101 // unsigned
那就是更紧的循环体:
for (int dx = 0; dx < size; dx++)
int diff = a[pa+dx] - b[pb+dx];
sum += diff*diff;
编辑:
关于有符号和无符号字节算术似乎有些混淆。如果您怀疑它们是否相同,请执行以下操作:
byte a = -128;
byte b = 127;
int diff = a - b;
System.out.println(diff); // -255
a = 127;
b = -128;
diff = a - b;
System.out.println(diff); // 255
原因diff值的范围大于byte
(-128..127),是java自动将byte
加宽到int
之前 计算因为目标变量是int
。
【讨论】:
不,不一样我需要减去无符号的数字:128-127 = 1;但是 (signed)(128) = -128 因此 (singed -128 - 127) 更多 @Artyom 是的,它是相同的,因为128
不是一个有效的byte
值! byte
范围是 -128
到 127
看,我处理的是一个图像数据,其中的值是 0..255 范围内的无符号字节,但是它们存储在 Java 字节中,这些字节仅被 singed。
但是无论数据类型是否有符号,值之间的差异都是相同的。
@Artyom Dude,你没有“明白”。区分有符号字节也会生成[-255,255]
范围内的值!因为java在计算之前会自动将它们扩大到int
。执行我添加到答案中的代码,自己看看【参考方案2】:
将& 0xFF
移到循环之外。
通过计算a
和b
的int[]
-version 并使用它们重写您的循环来做到这一点。
【讨论】:
你说得对,将运行时间从1:20
提高到0:53
。我还使用了 short 而不是 int,因为它减少了内存使用并提高了性能。我认为编译器能够优化到像movzbl
这样的单个指令,因为它只是获取无符号字节!
也许另一个 JVM 或 -client/-server 选择可以。基本上,尽可能简单地表达你的需求,给 JVM 最好的工作条件。我还认为您应该使用int
s 计时并进行比较。
我检查了int
和short
和short
更快。此外,Java 7 上的差异更明显(没有 0xFF),在 Java 6 上,运行时间类似于 1:10【参考方案3】:
在我使用不同的 C++ 编译器和不同的 Java 版本测试了相同的算法后,我得出的结论是 GCC 是非常优秀的编译器,它比 intel 和 clang 更好地优化了代码!
这些是用 C++ 和 Java 实现的相同算法的运行时间(当上面的行是运行时间的 96% 时:
Intel 12.1 1:58
GCC 4.6 0:43
GCC 4.4 0:43
Clang 1:20
Java 7 1:20
Java 6 1:23
这表明 Java 的运行速度与 clang 一样快,而英特尔编译器由于某种原因做得非常糟糕,但是 gcc 给出了最好的结果,所以我真的不能指望 Java 的运行速度比 大多数 C++ 编译器都可以。
注意这是 gcc 生成的程序集:
.L225:
movzbl (%rcx), %r8d
movzbl (%rsi), %r10d
addl $1, %edx
addq $1, %rcx
addq $1, %rsi
subl %r10d, %r8d
imull %r8d, %r8d
movslq %r8d, %r8
addq %r8, %rax
cmpl %edx, %ebp
ja .L225
还有这个是由clang生成的:
.LBB0_26:
movzbl (%r11), %r13d
movzbl (%r14), %esi
subl %r13d, %esi
imull %esi, %esi
movslq %esi, %rsi
addq %rsi, %rcx
incq %r11
incq %r14
decq %r12
jne .LBB0_26
有什么区别? GCC 重新排列指令,以便它们可以 在管道中并行运行,例如:
movzbl (%rcx), %r8d
movzbl (%rsi), %r10d
addl $1, %edx
addq $1, %rcx
addq $1, %rsi
底线,Java 运行时间很好。
编辑:在向 Intel 编译器提供 -xHost
选项(针对当前 CPU 进行优化)后,运行时间提高到 56 秒(使用 mmx 指令),但仍然不如 gcc,但好不了多少比Java
【讨论】:
编译器通常有很多标志打开和关闭优化。考虑在所有平台上使用 -O3 或更高版本进行编译。【参考方案4】:如果数组a或b的大小为“size”,则可以避免for条件:
try
for (int dx = 0; ; dx++)
...
...
catch(ArrayIndexOutOfBoundException e)
两条线是直线还是曲线?您可以发布问题的图形表示,还是数组的数字示例?也许有更好的几何解决方案?
【讨论】:
您知道,即使您不检查 dy 的范围,数组本身仍会检查边界。此外,总和范围通常在数组的实际结束之前结束。 现代 JVM 可以优化循环外的范围检查。以上是关于Java中的向量距离计算-优化的主要内容,如果未能解决你的问题,请参考以下文章
R语言计算曼哈顿距离(Manhattan Distance)实战:计算两个向量的曼哈顿距离dist函数计算矩阵中两两元素的曼哈顿距离