Java Math.abs(A-b)-Math.abs(c-d)的更快实现?

Java Math.abs(A-b)-Math.abs(c-d)的更快实现?,java,algorithm,optimization,Java,Algorithm,Optimization,我有一个Java方法,它在一个非常紧密的循环中反复计算以下表达式,并进行大量重复: Math.abs(a - b) - Math.abs(c - d) a、b、c和d是可以跨越其类型整个范围的长值。它们在每个循环迭代中都是不同的,并且它们不满足我所知道的任何不变量 探查器表明处理器的大部分时间都用在这个方法上。虽然我将首先寻求其他优化途径,但我想知道是否有更聪明的方法来计算上述表达式 除了手动内联Math.abs()调用以获得非常微小的(如果有的话)性能增益之外,还有什么数学技巧可以用来加速这

我有一个Java方法,它在一个非常紧密的循环中反复计算以下表达式,并进行大量重复:

Math.abs(a - b) - Math.abs(c - d)
a
b
c
d
是可以跨越其类型整个范围的
长值。它们在每个循环迭代中都是不同的,并且它们不满足我所知道的任何不变量

探查器表明处理器的大部分时间都用在这个方法上。虽然我将首先寻求其他优化途径,但我想知道是否有更聪明的方法来计算上述表达式


除了手动内联
Math.abs()
调用以获得非常微小的(如果有的话)性能增益之外,还有什么数学技巧可以用来加速这个表达式的计算吗?

我怀疑探查器没有给你一个真实的结果,因为它试图剖析(并因此直接添加)这样一个微不足道的“方法”。如果没有配置文件Math.abs,它可以转换为少量的机器代码指令,并且您将无法使其更快

我建议你做一个微观基准来确认这一点。我希望加载数据的成本要高出一个数量级

long a = 10, b = 6, c = -2, d = 3;

int runs = 1000 * 1000 * 1000;
long start = System.nanoTime();
for (int i = 0; i < runs; i += 2) {
    long r = Math.abs(i - a) - Math.abs(c - i);
    long r2 = Math.abs(i - b) - Math.abs(d - i);
    if (r + r2 < Integer.MIN_VALUE) throw new AssertionError();
}
long time = System.nanoTime() - start;
System.out.printf("Took an average of %.1f ns per abs-abs. %n", (double) time / runs);

你确定是方法本身导致了问题吗?也许这个方法的调用量很大,而您只需在您的探查器中看到聚合结果(如方法执行时间X调用次数)?

您始终可以尝试展开函数并手动优化,如果没有更多缓存未命中,可能会更快

如果我的展开正确,可能是这样的:

    if(a<b)
{
    if(c<d)
    {
        r=b-a-d+c;
    }
    else
    {
        r=b-a+d-c;
    }
}
else
{
    if(c<d)
    {
        r=a-b-d+c;
    }
    else
    {
        r=a-b+d-c;
    }
}

如果(a我最终使用了这个小方法:

public static long diff(final long a, final long b, final long c, final long d) {
    final long a0 = (a < b)?(b - a):(a - b);
    final long a1 = (c < d)?(d - c):(c - d);

    return a0 - a1;
}
公共静态长差(最终长a、最终长b、最终长c、最终长d){
最终长a0=(a
我经历了可衡量的性能提升——整个应用程序的性能提升了约10-15%。我认为这主要是由于:

  • 消除方法调用:我没有调用两次
    Math.abs()
    ,而是只调用一次这个方法。当然,静态方法调用不会花费太多,但它们仍然会产生影响

  • 消除两个否定操作:这可能会被稍微增加的代码大小所抵消,但我很乐意欺骗自己,让自己相信它确实起到了作用

编辑:


实际上,情况似乎正好相反。显式内联代码似乎不会影响我的微基准测试中的性能。更改绝对值的计算方式会…

如果值跨越其类型的整个范围,则此代码将有大量溢出,例如,如果a是非常大的负值e和b是一个非常大的正数(反之亦然)。检查您的算法。@JBNizet:我还没有遇到任何溢出,因此我认为这是某种不变量。
跨越整个范围的条件主要是为了避免假设所有变量都适合32位的答案。请注意,此类溢出不会导致任何异常:只有错误的计算值。因此,它们可能会导致错误“@JBNizet:我知道整数溢出是无声的-我主要是一个C程序员:-)我有一个测试工具,使用已知良好代码的结果作为参考。我仍然在寻找角落案例,但我还没有遇到任何问题。@thkala简单的角落案例:
a=-2^63;b=任何正值
。如果您的测试线束没有测试这样的数字,最好绝对确保这样的情况是不可能的。特别有趣的例子:
a=-2^63;b=0
-我希望你的算法能够处理Math.abs结果为负值的情况!这绝对是可能的,尽管我认为这个循环足够紧密,可以作为参考。顺便说一句,你确定在x86_64上有这样一条64位整数的指令吗?嗯,它有
movabs
,但这并不像我想的那样。这实际上是在测试解释器的性能(JIT本身认识到循环可以被优化,似乎-多次运行此测试会为我返回
178176,0,0,0,…
。将变量提升到方法之外似乎效果良好:
mean=185ms,var=13ms
,100次测试运行,每次循环迭代1亿次。你的第一点不重要。JIT在不管怎么说,这些调用都很简单——你可以通过将逻辑放入函数并调用它来轻松测试,在时间上没有什么不同(也没有很好的实践)。第二点似乎是有效的——我在一个微基准测试中获得了大约10%的加速(可以发布代码,但是的,我正在观察OSR、预热等等!)@Voo:没错,我的基准测试中有一个计数器无法计数的问题,因此会扭曲结果。
public static long diff(final long a, final long b, final long c, final long d) {
    final long a0 = (a < b)?(b - a):(a - b);
    final long a1 = (c < d)?(d - c):(c - d);

    return a0 - a1;
}