如何解决JVM是否跳过临时的通用转换
我正在寻找一种通过通用接口使用原始集合的方法。
对于 IntArray 类和 scenario 函数,JVM将创建临时的 Integer 对象,还是直接传递 int ?
元素存储在基本元素 int [] 中,并且仅直接分配给基本元素 int ,因此,如果未对其进行优化,则意味着不必要的对象创建,只是一小部分地将其破坏秒。
public class Test {
private interface Array<E> {
E get(int index);
void set(int index,E element);
}
private static class GenericArray<E> implements Array<E> {
private final E[] elements;
@SuppressWarnings("unchecked")
public GenericArray(int capacity) {
this.elements = (E[]) new Object[capacity];
}
@Override
public E get(int index) {
return elements[index];
}
@Override
public void set(int index,E element) {
elements[index] = element;
}
}
private static class IntArray<E> implements Array<Integer> {
private final int[] elements; // primitive int array
public IntArray(int capacity) {
this.elements = new int[capacity];
}
@Override
public Integer get(int index) {
return elements[index];
}
@Override
public void set(int index,Integer element) {
elements[index] = element;
}
}
private static void scenario(Array<Integer> array) {
int element = 256;
array.set(16,element); // primitive int given
element = array.get(16); // converted directly to primitive int
System.out.println(element);
}
public static void main(String[] args) {
Array<Integer> genericArray = new GenericArray<>(64);
Array<Integer> primitiveArray = new IntArray<>(64);
scenario(genericArray);
scenario(primitiveArray);
}
}
解决方法
Java没有原始类型(yet)的泛型。
您的at java.net.DualStackPlainSocketImpl.connect0(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(Unknown Source)
at java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
at java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
at java.net.AbstractPlainSocketImpl.connect(Unknown Source)
at java.net.PlainSocketImpl.connect(Unknown Source)
at java.net.SocksSocketImpl.connect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at com.mysql.cj.protocol.StandardSocketFactory.connect(StandardSocketFactory.java:155)
at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:65)
... 16 more
处理IntArray
对象,至少在字节码级别。如果我们反编译该类,则会清楚地看到对装箱Integer
和拆箱Integer.valueOf
方法的调用:
Integer.intValue
javap -c -private Test$IntArray
但是,JIT编译器进行了一项优化,以消除冗余的装箱对装箱对: public java.lang.Integer get(int);
Code:
0: aload_0
1: getfield #2 // Field elements:[I
4: iload_1
5: iaload
6: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: areturn
public void set(int,java.lang.Integer);
Code:
0: aload_0
1: getfield #2 // Field elements:[I
4: iload_1
5: aload_2
6: invokevirtual #4 // Method java/lang/Integer.intValue:()I
9: iastore
10: return
。该优化默认情况下为“开”,但是不幸的是并不总是有效。在JMH基准测试的帮助下,看看它是否对您有用。
-XX:+EliminateAutoBox
在JDK 14.0.2上运行基准测试时,我得到以下分数(越低越好)。
package bench;
import org.openjdk.jmh.annotations.*;
@State(Scope.Benchmark)
public class GenericArrays {
Array<Integer> genericArray = new GenericArray<>(64);
Array<Integer> primitiveArray = new IntArray(64);
int n;
@Setup
public void setup() {
for (int i = 0; i < 64; i++) {
genericArray.set(i,i + 256);
primitiveArray.set(i,i + 256);
}
}
@Benchmark
public int getGeneric() {
return genericArray.get(n++ & 63);
}
@Benchmark
public int getPrimitive() {
return primitiveArray.get(n++ & 63);
}
@Benchmark
@Fork(jvmArgsAppend = "-XX:-EliminateAutoBox")
public int getPrimitiveNoOpt() {
return primitiveArray.get(n++ & 63);
}
@Benchmark
public void setGeneric() {
genericArray.set(n++ & 63,n);
}
@Benchmark
public void setPrimitive() {
primitiveArray.set(n++ & 63,n);
}
@Benchmark
@Fork(jvmArgsAppend = "-XX:-EliminateAutoBox")
public void setPrimitiveNoOpt() {
primitiveArray.set(n++ & 63,n);
}
private interface Array<E> {
E get(int index);
void set(int index,E element);
}
static class GenericArray<E> implements Array<E> {
private final E[] elements;
@SuppressWarnings("unchecked")
public GenericArray(int capacity) {
this.elements = (E[]) new Object[capacity];
}
@Override
public E get(int index) {
return elements[index];
}
@Override
public void set(int index,E element) {
elements[index] = element;
}
}
static class IntArray implements Array<Integer> {
private final int[] elements;
public IntArray(int capacity) {
this.elements = new int[capacity];
}
@Override
public Integer get(int index) {
return elements[index];
}
@Override
public void set(int index,Integer element) {
elements[index] = element;
}
}
}
这导致我们有两个发现:
- 原始数组似乎表现更好;
-
Benchmark Mode Cnt Score Error Units GenericArrays.getGeneric avgt 20 3,769 ± 0,039 ns/op GenericArrays.getPrimitive avgt 20 3,445 ± 0,037 ns/op GenericArrays.getPrimitiveNoOpt avgt 20 5,147 ± 0,073 ns/op GenericArrays.setGeneric avgt 20 10,491 ± 0,055 ns/op GenericArrays.setPrimitive avgt 20 3,896 ± 0,023 ns/op GenericArrays.setPrimitiveNoOpt avgt 20 4,078 ± 0,077 ns/op
优化显然可行,因为关闭优化后,计时会更高。
现在让我们验证优化是否有助于避免不必要的分配。
内置到JMH(EliminateAutoBox
)中的GC事件探查器将完成这项工作。
-prof gc
在这里,我们看到Benchmark Mode Cnt Score Error Units
GenericArrays.getGeneric:·gc.alloc.rate.norm avgt 20 ≈ 10⁻⁵ B/op
GenericArrays.getPrimitive:·gc.alloc.rate.norm avgt 20 ≈ 10⁻⁵ B/op
GenericArrays.getPrimitiveNoOpt:·gc.alloc.rate.norm avgt 20 16,000 ± 0,001 B/op
GenericArrays.setGeneric:·gc.alloc.rate.norm avgt 20 16,001 B/op
GenericArrays.setPrimitive:·gc.alloc.rate.norm avgt 20 16,001 B/op
GenericArrays.setPrimitiveNoOpt:·gc.alloc.rate.norm avgt 20 16,001 B/op
基准的分配率为零。这意味着,JVM能够消除对临时getPrimitive
对象的分配。取消优化后,每次操作的分配速率预计为16个字节-恰好是一个Integer
对象的大小。
由于某些原因,JVM无法消除Integer
中的装箱。如前所述,该优化非常脆弱,并且并非在所有情况下都有效。
但是,setPrimitive
仍然比setPrimitive
快。这样做的好处是,存储原语比存储引用更有效,因为存储引用通常需要GC屏障。
„ …JVM是否跳过了临时的通用转换…”
不。没有。它会执行Boxing Conversion…
...
5.1.7装箱转换
装箱转换将原始类型的表达式视为相应引用类型的表达式。具体来说,以下九种转换称为装箱转换:
...
- 从int类型到Integer类型
...
...还有一个Unboxing Conversion ...
...
5.1.8。取消装箱转换
拆箱转换将引用类型的表达式视为相应原始类型的表达式。具体来说,以下八种转换称为拆箱转换:
...
- 从Integer类型到int类型
...
请参见Java Tutorial's Autoboxing and Unboxing trail。
„ ………JVM将创建临时的 Integer 对象,还是直接传递 int ?…”
两者都做。首先是前者,然后是后者。
„ …不必要的对象创建,只需要在不到一秒钟的时间内破坏它……”
Oracle设计Java语言的架构师可能会同意您的观点……
„ ………拳击的问题是[即席]和昂贵;为解决这两个问题,我们进行了广泛的工作……” — — Brian Goetz,State of Valhalla,March 2020
他们会为您的请求提供解决方案……
„ …有关通过通用接口使用原始集合的方法…”
...如果你有耐心...
„ …我们正在努力为特殊泛型保留空间,作为将来的功能……我们希望将来为
List<int>
…这样的特殊类型保留自然的符号。 Migration: specialized generics,Brian Goetz,State of Valhalla
专用泛型还不是什么。但是您可以take other early-access features of Valhalla out for a spin today。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。