从Java函数高效返回两个值

从Java函数高效返回两个值,java,performance,jvm-hotspot,Java,Performance,Jvm Hotspot,有人知道有没有一种方法可以从Java返回两个值,开销(接近)为零?我只寻找两个值-我有几个用例,从处理字节数组(需要返回值和下一个起始位置)到尝试返回带有错误代码的值,再到进行一些不太好的定点计算,需要全部和部分 我不是在一些真正丑陋的黑客之下。该函数很小,可以将其内联。所以现在,我只需要让Hotspot基本上消除任何对象创建或位移动 如果我将返回的值限制为int,那么我已经尝试将它们打包成一个长文件,但即使在内联之后,Hotspot似乎也无法发现所有的位移位和掩码实际上没有任何作用,它很高兴地

有人知道有没有一种方法可以从Java返回两个值,开销(接近)为零?我只寻找两个值-我有几个用例,从处理字节数组(需要返回值和下一个起始位置)到尝试返回带有错误代码的值,再到进行一些不太好的定点计算,需要全部和部分

我不是在一些真正丑陋的黑客之下。该函数很小,可以将其内联。所以现在,我只需要让Hotspot基本上消除任何对象创建或位移动

如果我将返回的值限制为int,那么我已经尝试将它们打包成一个长文件,但即使在内联之后,Hotspot似乎也无法发现所有的位移位和掩码实际上没有任何作用,它很高兴地将int打包并解包成相同的值(显然,Hotspot的窥视孔优化器需要帮助)。但至少我没有创建一个对象

我更困难的情况是,我需要返回的一个项是引用,另一个是长引用或其他引用(对于int情况,我认为我可以压缩OOP并使用上面描述的位打包)


有没有人试图让Hotspot为它生成无垃圾代码?现在最糟糕的情况是,我必须随身携带一件物品并将其传递进去,但我希望它能保持独立。线程局部变量非常昂贵(散列查找),并且需要可重入。

您描述的解决方案与您在Hotspot中所能得到的非常好——传入一个对象以保存返回值并对其进行变异。(Java 10值类型可能在这里做得更好,但我认为这还不到原型阶段。)


也就是说:小的短期对象实际上离零开销不远。短命对象的垃圾收集非常便宜。

我不得不处理这个问题,并发现最好的方法是用
公共
字段实例化一个简单的
最终类
,然后将其传入参数到方法中。将结果推送到该实例

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


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

-XX:+EliminateAllocations
优化(在Java8中默认为ON)可以很好地实现这一点

每当您在被调用方方法的末尾返回新对(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;
        }
    }
}
import org.openjdk.jmh.annotations.*;
导入org.openjdk.jmh.infra.Blackhole;
导入java.util.concurrent.ThreadLocalRandom;
@国家(范围、基准)
公共类返回对{
整数计数器;
@基准
公共网络(黑洞bh){
消耗(计数器++);
使用(ThreadLocalRandom.current().nextInt());
}
@基准
公共无效打包(黑洞bh){
长包装=得到包装();
bh.消费((int)(打包>>>32));
bh.消耗((整数)包装);
}
@基准
公共空间pairObject(黑洞bh){
Pair Pair=getPair();
bh.消耗(对a);
bh.消耗(第b对);
}
@基准
@Fork(jvmArgs=“-XX:-删除位置”)
已分配的公共空间(黑洞bh){
Pair Pair=getPair();
bh.消耗(对a);
bh.消耗(第b对);
}
公共长getPacked(){
int a=计数器++;
int b=ThreadLocalRandom.current().nextInt();

return(long)a使用固定的分隔符将它们连接到字符串中,创建一个组合对象,返回一组对象,…@Stultuske所有这些方法都会产生垃圾,尤其是将结果连接到字符串中。long shot(有点),但您是否考虑过根本不返回任何内容?您可以在调用后提取代码(假定这是某种映射或累积逻辑)。尽管大家可能都同意HotSpot应该能够节省时间,但简单地以不同的方式表示逻辑可能会推动优化器朝着正确的方向前进。(显然,你不会在CPS中写入循环本身,否则你会毁掉堆栈;只有单个项目的处理代码和结果处理。)@t在某些情况下,它会被映射到另一些情况下(例如,在进行固定计算时,需要传回十进制位置的值和数量)不是。但我对你的想法很感兴趣。你能告诉我更具体一点吗?我会把什么转换成CPS?你可能需要发布一个带有特定代码的单独问题来获得有用的答案,但非常一般:在调用之后和循环结束之前将代码提取到一个单独的类中。你需要将映射的数据集或字段成员“稍后检索”中的累加器。然后将该类的实例注入目标方法的对象中。该方法将不返回结果,而是反过来调用注入的代码,结果作为参数,并且不返回任何内容(void)。一旦离开循环,就可以从调用方检索注入对象中的结果。实际上,我感到震惊的是,在函数内联后,将int打包成一个长的,然后解包的函数并没有得到优化。当我使用JMH查看程序集转储时,它实际上会将值移动和OR到一起,即立即取消移动和屏蔽。有时热点是神奇的,有时它是蹩脚的。而且,还不够便宜。虽然它可能只是一个TLB凹凸,但它可以更多,而且它还有B零开销。@JasonN如果你能做到这一点,移位和取消移位