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;

地点:

ab 属于 byte[] 类型 sumlong sizeint 并且通常具有较大的值(大约 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】:

它只是很小,但您不需要&amp; 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 范围是 -128127 看,我处理的是一个图像数据,其中的值是 0..255 范围内的无符号字节,但是它们存储在 Java 字节中,这些字节仅被 singed。 但是无论数据类型是否有符号,值之间的差异都是相同的。 @Artyom Dude,你没有“明白”。区分有符号字节也会生成[-255,255] 范围内的值!因为java在计算之前会自动将它们扩大到int。执行我添加到答案中的代码,自己看看【参考方案2】:

&amp; 0xFF 移到循环之外。

通过计算abint[]-version 并使用它们重写您的循环来做到这一点。

【讨论】:

你说得对,将运行时间从1:20 提高到0:53。我还使用了 short 而不是 int,因为它减少了内存使用并提高了性能。我认为编译器能够优化到像movzbl 这样的单个指令,因为它只是获取无符号字节! 也许另一个 JVM 或 -client/-server 选择可以。基本上,尽可能简单地表达你的需求,给 JVM 最好的工作条件。我还认为您应该使用ints 计时并进行比较。 我检查了intshortshort 更快。此外,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中的向量距离计算-优化的主要内容,如果未能解决你的问题,请参考以下文章

Matlab中的马氏距离

计算数组中向量之间的最大距离

R中的距离计算优化

R语言计算曼哈顿距离(Manhattan Distance)实战:计算两个向量的曼哈顿距离dist函数计算矩阵中两两元素的曼哈顿距离

Matlab求两个向量之间的各种距离

点到平面的距离怎么计算?