从 Java 函数有效地返回两个值
Returning two values from Java function efficiently
有人知道是否有办法 return 来自 Java 的两个值且开销(接近)零吗?我只是在寻找两个值——我有几个用例,从处理字节数组(需要 return 值和下一个起始位置)到尝试 return 一个有错误的值用定点计算做一些丑陋的代码,需要整数和小数部分。
我不在乎一些非常丑陋的技巧。该函数很小,Hotspot 很乐意将其内联。所以现在,我只需要让 Hotspot 基本上消除任何对象创建或位移。
如果我将我的 returned 值限制为整数,我会尝试将它们打包成一个 long,但即使在内联之后,Hotspot 似乎也无法弄清楚所有的位移位和掩码都没有真的做任何事情,它愉快地将整数打包和解包成相同的值(显然,这是 Hotspot 的窥孔优化器需要帮助的地方)。但至少我没有创建对象。
我更困难的情况是当我需要 return 的项目之一是引用而另一个是长引用或另一个引用时(对于 int 情况,我想我可以压缩 OOP 并使用上面描述的位封装)。
有没有人试图让 Hotspot 为此生成无垃圾代码?现在最糟糕的情况是我必须随身携带一个物体并将其传递进去,但我想保持它独立。 Thread Locals 开销很大(哈希查找),需要可重入。
您描述的解决方案与您在 Hotspot 中获得的解决方案几乎一样好——传递一个对象来保存 return 值并改变它。 (Java 10 种值类型在这里可能做得更好,但我认为这甚至还未处于原型阶段。)
就是说:短寿命的小对象实际上距离零开销并不远。寿命短的对象的垃圾收集故意非常便宜。
我不得不处理这个问题,发现最好的方法是用 public
字段实例化一个简单的 final class
,然后将它作为参数传递给您的方法。将结果推送到该实例。
如果您有循环,请尝试尽可能长时间地重复使用该实例。
在 Java 7 中(当我这样做时)具有 setter 和 getter 的开销非常小。每个循环实例化新对象也是如此。
-XX:+EliminateAllocations
优化(在 Java 8 中默认开启)对此效果很好。
每当您 return new Pair(a, b)
就在被调用方方法的末尾并立即在调用方中使用结果时,如果被调用方是内联的,JVM 很可能会进行标量替换。
一个简单的实验表明 return 对象几乎没有开销。这不仅是一种高效的方式,也是最易读的方式。
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;
}
}
}
有人知道是否有办法 return 来自 Java 的两个值且开销(接近)零吗?我只是在寻找两个值——我有几个用例,从处理字节数组(需要 return 值和下一个起始位置)到尝试 return 一个有错误的值用定点计算做一些丑陋的代码,需要整数和小数部分。
我不在乎一些非常丑陋的技巧。该函数很小,Hotspot 很乐意将其内联。所以现在,我只需要让 Hotspot 基本上消除任何对象创建或位移。
如果我将我的 returned 值限制为整数,我会尝试将它们打包成一个 long,但即使在内联之后,Hotspot 似乎也无法弄清楚所有的位移位和掩码都没有真的做任何事情,它愉快地将整数打包和解包成相同的值(显然,这是 Hotspot 的窥孔优化器需要帮助的地方)。但至少我没有创建对象。
我更困难的情况是当我需要 return 的项目之一是引用而另一个是长引用或另一个引用时(对于 int 情况,我想我可以压缩 OOP 并使用上面描述的位封装)。
有没有人试图让 Hotspot 为此生成无垃圾代码?现在最糟糕的情况是我必须随身携带一个物体并将其传递进去,但我想保持它独立。 Thread Locals 开销很大(哈希查找),需要可重入。
您描述的解决方案与您在 Hotspot 中获得的解决方案几乎一样好——传递一个对象来保存 return 值并改变它。 (Java 10 种值类型在这里可能做得更好,但我认为这甚至还未处于原型阶段。)
就是说:短寿命的小对象实际上距离零开销并不远。寿命短的对象的垃圾收集故意非常便宜。
我不得不处理这个问题,发现最好的方法是用 public
字段实例化一个简单的 final class
,然后将它作为参数传递给您的方法。将结果推送到该实例。
如果您有循环,请尝试尽可能长时间地重复使用该实例。
在 Java 7 中(当我这样做时)具有 setter 和 getter 的开销非常小。每个循环实例化新对象也是如此。
-XX:+EliminateAllocations
优化(在 Java 8 中默认开启)对此效果很好。
每当您 return new Pair(a, b)
就在被调用方方法的末尾并立即在调用方中使用结果时,如果被调用方是内联的,JVM 很可能会进行标量替换。
一个简单的实验表明 return 对象几乎没有开销。这不仅是一种高效的方式,也是最易读的方式。
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;
}
}
}