有效地从Java函数返回两个值

时间:2015-11-17 19:45:57

标签: java performance jvm-hotspot

有没有人知道是否有办法从Java返回两个值(接近)零开销?我只是在寻找两个值 - 我有几个用例来处理一个字节数组(需要返回值和下一个起始位置)来尝试返回一个带有错误代码的值来做一些丑陋的事情定点计算,需要整个和小数部分。

我不会低于一些非常难看的黑客。功能很小,Hotspot很高兴地介绍它。所以现在,我只需要让Hotspot基本上忽略任何对象创建或位移。

如果我将我的返回值限制为整数,我已经尝试将它们打包成一个长的,但即使在内联之后,Hotspot似乎也无法弄清楚所有的位移和面具都不是真的没有任何东西,它都很乐意打包并打开相同的值(显然,Hotspot的窥视孔优化器需要帮助的地方)。但至少我没有创造一个物体。

我更难的情况是当我需要返回的项目之一是引用而另一项是长引用或另一个引用时(对于int情况,我认为我可以压缩OOP并使用上面描述的位填充)

有没有人试图让Hotspot为此生成无垃圾代码?现在最糟糕的情况是,我必须携带一个物体并传递它,但我想保持它自包含。线程本地是昂贵的(哈希查找),它需要是可重入的。

3 个答案:

答案 0 :(得分:6)

-XX:+EliminateAllocations优化(Java 8中默认为ON)可以正常工作。

每当你在callee方法结束时返回new Pair(a, b)并在调用者中立即使用结果时,如果被调用者被内联,JVM很可能会进行标量替换。

一个简单的实验表明,返回一个对象几乎没有开销。这不仅是一种有效的方式,也是最具可读性的方式。

Benchmark                        Mode  Cnt    Score   Error   Units
ReturnPair.manualInline         thrpt   30  127,713 ± 3,408  ops/us
ReturnPair.packToLong           thrpt   30  113,606 ± 1,807  ops/us
ReturnPair.pairObject           thrpt   30  126,881 ± 0,478  ops/us
ReturnPair.pairObjectAllocated  thrpt   30   92,477 ± 0,621  ops/us

基准:

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.util.concurrent.ThreadLocalRandom;

@State(Scope.Benchmark)
public class ReturnPair {
    int counter;

    @Benchmark
    public void manualInline(Blackhole bh) {
        bh.consume(counter++);
        bh.consume(ThreadLocalRandom.current().nextInt());
    }

    @Benchmark
    public void packToLong(Blackhole bh) {
        long packed = getPacked();
        bh.consume((int) (packed >>> 32));
        bh.consume((int) packed);
    }

    @Benchmark
    public void pairObject(Blackhole bh) {
        Pair pair = getPair();
        bh.consume(pair.a);
        bh.consume(pair.b);
    }

    @Benchmark
    @Fork(jvmArgs = "-XX:-EliminateAllocations")
    public void pairObjectAllocated(Blackhole bh) {
        Pair pair = getPair();
        bh.consume(pair.a);
        bh.consume(pair.b);
    }

    public long getPacked() {
        int a = counter++;
        int b = ThreadLocalRandom.current().nextInt();
        return (long) a << 32 | (b & 0xffffffffL);
    }

    public Pair getPair() {
        int a = counter++;
        int b = ThreadLocalRandom.current().nextInt();
        return new Pair(a, b);
    }

    static class Pair {
        final int a;
        final int b;

        Pair(int a, int b) {
            this.a = a;
            this.b = b;
        }
    }
}

答案 1 :(得分:0)

您描述的解决方案与Hotspot中的解决方案非常相似 - 传入一个对象来保存返回值并对其进行变更。 (Java 10值类型可能会在这里做得更好,但我认为即使在原型阶段也是如此。)

那就是说:小的短期对象实际上并不是零开销。短期物品的垃圾收集故意非常便宜。

答案 2 :(得分:0)

我必须处理这个问题,并发现最好的方法是使用final class字段实例化一个简单的public,然后将参数传递给您的方法。将结果推送到该实例。

如果您有循环,请尝试尽可能长时间地重用该实例。

拥有setter和getter,在Java 7中(当我这样做时),开销非常小。每个循环都实例化新对象也是如此。