Java 9 个集合的便利工厂方法作为集合文字的替代方法
Java 9 collections' convenience factory methods as an alternative to collection literals
考虑这种方法(仅供说明):
boolean isSmallNumber(String s) {
return (n in ["one", "two", "three", "four"]);
}
当然,这不是 Java,但它可能是您最喜欢的支持集合文字的替代语言,例如 Groovy 或 Kotlin。表达式简洁,并且,就像字符串文字一样,允许编译器将集合文字放在某个静态存储区域(甚至 "intern()"
它)。
现在输入Java 9:
boolean isSmallNumber(String s) {
return Set.of("one", "two", "three", "four").contains(s);
}
这也很简洁,但不幸的是,它会在您每次调用它时在堆上分配一个新的 Set,然后立即使其可用于垃圾回收。
你当然可以定义一个集合常量:
private static final Set<String> SMALL_NUMBERS = Set.of(...);
但是这个定义可能与方法定义相差一千行 class,您可能无法为它想出一个好的描述性名称,而文字可能更清晰 (在这种假设情况下)。
那么,如果我在方法内部使用 Set.of(...)
,JIT 编译器是否会在每次调用该方法时优化新对象的创建?
我制作了一个简单的 JMH 基准测试:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class Temp {
private Object value;
@Setup
public void setUp() {
value = 50;
}
@Benchmark
public boolean list1() {
return List.of("one").contains(value);
}
@Benchmark
public boolean list2() {
return List.of("one", "two").contains(value);
}
@Benchmark
public boolean list3() {
return List.of("one", "two", "three").contains(value);
}
@Benchmark
public boolean list4() {
return List.of("one", "two", "three", "four").contains(value);
}
@Benchmark
public boolean set1() {
return Set.of("one").contains(value);
}
@Benchmark
public boolean set2() {
return Set.of("one", "two").contains(value);
}
@Benchmark
public boolean set3() {
return Set.of("one", "two", "three").contains(value);
}
@Benchmark
public boolean set4() {
return Set.of("one", "two", "three", "four").contains(value);
}
}
经过运行与-prof gc
的benchmark后,我可以得出以下结论:JIT优化了list1
、list2
、set1
、set2
,但不是 list3
、list4
、set3
、set4
[1]
这似乎完全合理,因为 N >= 3
listN
/setN
比 N <= 2
创建更复杂的 List
/Set
实现。
List
2 个元素的实现:
static final class List2<E> extends AbstractImmutableList<E> {
private final E e0;
private final E e1;
...
}
List
3 个或更多元素的实现:
static final class ListN<E> extends AbstractImmutableList<E> {
private final E[] elements;
...
}
ListN
包含另一个间接级别(一个数组),这显然使逃逸分析变得更加困难。
JMH 输出(略微更改以适应页面):
Benchmark Mode Cnt Score Error Units
list1 avgt 5 3,075 ? 1,165 ns/op
list1:·gc.alloc.rate avgt 5 0,131 ? 1,117 MB/sec
list1:·gc.alloc.rate.norm avgt 5 ? 10?? B/op
list1:·gc.count avgt 5 ? 0 counts
list2 avgt 5 3,161 ? 0,543 ns/op
list2:·gc.alloc.rate avgt 5 0,494 ? 3,065 MB/sec
list2:·gc.alloc.rate.norm avgt 5 0,001 ? 0,003 B/op
list2:·gc.count avgt 5 ? 0 counts
list3 avgt 5 33,094 ? 4,402 ns/op
list3:·gc.alloc.rate avgt 5 6316,970 ? 750,240 MB/sec
list3:·gc.alloc.rate.norm avgt 5 64,016 ? 0,089 B/op
list3:·gc.count avgt 5 169,000 counts
list3:·gc.time avgt 5 154,000 ms
list4 avgt 5 32,718 ? 3,657 ns/op
list4:·gc.alloc.rate avgt 5 6403,487 ? 729,235 MB/sec
list4:·gc.alloc.rate.norm avgt 5 64,004 ? 0,017 B/op
list4:·gc.count avgt 5 165,000 counts
list4:·gc.time avgt 5 146,000 ms
set1 avgt 5 3,218 ? 0,822 ns/op
set1:·gc.alloc.rate avgt 5 0,237 ? 1,973 MB/sec
set1:·gc.alloc.rate.norm avgt 5 ? 10?? B/op
set1:·gc.count avgt 5 ? 0 counts
set2 avgt 5 7,087 ? 2,029 ns/op
set2:·gc.alloc.rate avgt 5 0,647 ? 4,755 MB/sec
set2:·gc.alloc.rate.norm avgt 5 0,001 ? 0,010 B/op
set2:·gc.count avgt 5 ? 0 counts
set3 avgt 5 88,460 ? 16,834 ns/op
set3:·gc.alloc.rate avgt 5 3565,506 ? 687,900 MB/sec
set3:·gc.alloc.rate.norm avgt 5 96,000 ? 0,001 B/op
set3:·gc.count avgt 5 143,000 counts
set3:·gc.time avgt 5 108,000 ms
set4 avgt 5 118,652 ? 41,035 ns/op
set4:·gc.alloc.rate avgt 5 2887,359 ? 920,180 MB/sec
set4:·gc.alloc.rate.norm avgt 5 104,000 ? 0,001 B/op
set4:·gc.count avgt 5 136,000 counts
set4:·gc.time avgt 5 94,000 ms
[1] Java HotSpot(TM) 64 位服务器 VM(构建 9+181,混合模式)
考虑这种方法(仅供说明):
boolean isSmallNumber(String s) {
return (n in ["one", "two", "three", "four"]);
}
当然,这不是 Java,但它可能是您最喜欢的支持集合文字的替代语言,例如 Groovy 或 Kotlin。表达式简洁,并且,就像字符串文字一样,允许编译器将集合文字放在某个静态存储区域(甚至 "intern()"
它)。
现在输入Java 9:
boolean isSmallNumber(String s) {
return Set.of("one", "two", "three", "four").contains(s);
}
这也很简洁,但不幸的是,它会在您每次调用它时在堆上分配一个新的 Set,然后立即使其可用于垃圾回收。
你当然可以定义一个集合常量:
private static final Set<String> SMALL_NUMBERS = Set.of(...);
但是这个定义可能与方法定义相差一千行 class,您可能无法为它想出一个好的描述性名称,而文字可能更清晰 (在这种假设情况下)。
那么,如果我在方法内部使用 Set.of(...)
,JIT 编译器是否会在每次调用该方法时优化新对象的创建?
我制作了一个简单的 JMH 基准测试:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class Temp {
private Object value;
@Setup
public void setUp() {
value = 50;
}
@Benchmark
public boolean list1() {
return List.of("one").contains(value);
}
@Benchmark
public boolean list2() {
return List.of("one", "two").contains(value);
}
@Benchmark
public boolean list3() {
return List.of("one", "two", "three").contains(value);
}
@Benchmark
public boolean list4() {
return List.of("one", "two", "three", "four").contains(value);
}
@Benchmark
public boolean set1() {
return Set.of("one").contains(value);
}
@Benchmark
public boolean set2() {
return Set.of("one", "two").contains(value);
}
@Benchmark
public boolean set3() {
return Set.of("one", "two", "three").contains(value);
}
@Benchmark
public boolean set4() {
return Set.of("one", "two", "three", "four").contains(value);
}
}
经过运行与-prof gc
的benchmark后,我可以得出以下结论:JIT优化了list1
、list2
、set1
、set2
,但不是 list3
、list4
、set3
、set4
[1]
这似乎完全合理,因为 N >= 3
listN
/setN
比 N <= 2
创建更复杂的 List
/Set
实现。
List
2 个元素的实现:
static final class List2<E> extends AbstractImmutableList<E> {
private final E e0;
private final E e1;
...
}
List
3 个或更多元素的实现:
static final class ListN<E> extends AbstractImmutableList<E> {
private final E[] elements;
...
}
ListN
包含另一个间接级别(一个数组),这显然使逃逸分析变得更加困难。
JMH 输出(略微更改以适应页面):
Benchmark Mode Cnt Score Error Units
list1 avgt 5 3,075 ? 1,165 ns/op
list1:·gc.alloc.rate avgt 5 0,131 ? 1,117 MB/sec
list1:·gc.alloc.rate.norm avgt 5 ? 10?? B/op
list1:·gc.count avgt 5 ? 0 counts
list2 avgt 5 3,161 ? 0,543 ns/op
list2:·gc.alloc.rate avgt 5 0,494 ? 3,065 MB/sec
list2:·gc.alloc.rate.norm avgt 5 0,001 ? 0,003 B/op
list2:·gc.count avgt 5 ? 0 counts
list3 avgt 5 33,094 ? 4,402 ns/op
list3:·gc.alloc.rate avgt 5 6316,970 ? 750,240 MB/sec
list3:·gc.alloc.rate.norm avgt 5 64,016 ? 0,089 B/op
list3:·gc.count avgt 5 169,000 counts
list3:·gc.time avgt 5 154,000 ms
list4 avgt 5 32,718 ? 3,657 ns/op
list4:·gc.alloc.rate avgt 5 6403,487 ? 729,235 MB/sec
list4:·gc.alloc.rate.norm avgt 5 64,004 ? 0,017 B/op
list4:·gc.count avgt 5 165,000 counts
list4:·gc.time avgt 5 146,000 ms
set1 avgt 5 3,218 ? 0,822 ns/op
set1:·gc.alloc.rate avgt 5 0,237 ? 1,973 MB/sec
set1:·gc.alloc.rate.norm avgt 5 ? 10?? B/op
set1:·gc.count avgt 5 ? 0 counts
set2 avgt 5 7,087 ? 2,029 ns/op
set2:·gc.alloc.rate avgt 5 0,647 ? 4,755 MB/sec
set2:·gc.alloc.rate.norm avgt 5 0,001 ? 0,010 B/op
set2:·gc.count avgt 5 ? 0 counts
set3 avgt 5 88,460 ? 16,834 ns/op
set3:·gc.alloc.rate avgt 5 3565,506 ? 687,900 MB/sec
set3:·gc.alloc.rate.norm avgt 5 96,000 ? 0,001 B/op
set3:·gc.count avgt 5 143,000 counts
set3:·gc.time avgt 5 108,000 ms
set4 avgt 5 118,652 ? 41,035 ns/op
set4:·gc.alloc.rate avgt 5 2887,359 ? 920,180 MB/sec
set4:·gc.alloc.rate.norm avgt 5 104,000 ? 0,001 B/op
set4:·gc.count avgt 5 136,000 counts
set4:·gc.time avgt 5 94,000 ms
[1] Java HotSpot(TM) 64 位服务器 VM(构建 9+181,混合模式)