这里是Full Source和a direct link to the data
这些测试的时序差异很大,但是经历了相同的实现。我想了解为什么时间不同。
private static final int ITERATIONS = 100;
private static final DataFactory RANDOM_DF = DataFactoryImpl.defaultInstance();
@Test // 6s
public void testGetMaxLength() throws Exception {
for ( int i = 1; i < ITERATIONS; i++ ) {
testGetMaxLength( i );
}
}
private void testGetMaxLength( final int length ) {
for ( int i = 0; i < ITERATIONS; i++ ) {
String word = RANDOM_DF.word().getMaxLength( length );
assertThat( word, not( isEmptyOrNullString() ) );
assertThat( word.length(), allOf( greaterThanOrEqualTo( 1 ), lessThanOrEqualTo( length ) ) );
}
}
@Test // 301ms
public void testGetLength() throws Exception {
for ( int i = 1; i < ITERATIONS; i++ ) {
testGetLength( i );
}
}
private void testGetLength( final int length ) {
for ( int i = 0; i < ITERATIONS; i++ ) {
String word = RANDOM_DF.word().getLength( length );
assertThat( word, not( isEmptyOrNullString() ) );
assertThat( word.length(), equalTo( length ) );
这是类DataFactoryUtil
,它很可能包含导致巨大差异的代码。
final class DataFactoryUtil {
private DataFactoryUtil() {
}
static <T> Optional<T> valueFromMap(
final Map<Integer, List<T>> map,
final IntUnaryOperator randomSupplier,
final int minInclusive,
final int maxInclusive
) {
List<T> list = map.entrySet()
.parallelStream() // line 26
.filter( e -> e.getKey() >= minInclusive && e.getKey() <= maxInclusive )
.map( Map.Entry::getValue )
.flatMap( Collection::stream )
.collect( Collectors.toList() );
return valueFromList( list, randomSupplier );
}
static <T> Optional<T> valueFromList( final List<T> list, final IntUnaryOperator randomSupplier ) {
int random = randomSupplier.applyAsInt( list.size() );
return list.isEmpty() ? Optional.empty() : Optional.of( list.get( random ) );
}
static List<String> dict() {
try {
URL url = DataFactoryUtil.class.getClassLoader().getResource( "dictionary" );
assert url != null;
return Files.lines( Paths.get( url.toURI() ) ).collect( Collectors.toList() );
}
catch ( URISyntaxException | IOException e ) {
throw new IllegalStateException( e );
}
}
}
这里是不同的实现
@FunctionalInterface
public interface RandomStringFactory {
default String getMaxLength( final int maxInclusive ) {
return this.getRange( 1, maxInclusive );
}
String getRange( final int minInclusive, final int maxInclusive );
default String getLength( int length ) {
return this.getRange( length, length );
}
}
以及word
DataFactoryImpl( final IntBinaryOperator randomSource, final List<String> wordSource ) {
this.random = randomSource;
this.wordSource = wordSource.stream().collect( Collectors.groupingBy( String::length ) );
}
public static DataFactory defaultInstance() {
return new DataFactoryImpl( RandomUtils::nextInt, dict() );
}
default RandomStringFactory word() {
return ( min, max ) -> valueFromMap( getWordSource(), ( size ) -> getRandom().applyAsInt( 0, size ), min, max )
.orElse( alphabetic().getRange( min, max ) );
}
为什么这两种方法的测量在共享实现时会如此不同?有什么方法可以改善getMaxLength
的最坏情况吗?
更新
虽然我喜欢Random作为源的理论,但也许它是真的。将我的代码更改为此会导致13s
运行,这比运行时长,这比RandomUtils::nextInt
的时间长两倍。
public static DataFactory defaultInstance() {
return new DataFactoryImpl( (a, b) -> a == b ? a : ThreadLocalRandom.current().nextInt(a, b), dict() );
}
答案 0 :(得分:6)
差异实际上在RandomUtils.nextInt()
实现中,您使用它来生成随机数。如果startInclusive
和endInclusive
参数匹配(如getLength()
中),它只返回非常快的参数。否则,它会请求java.util.Random
对象的静态实例来获取随机数。 java.util.Random
是线程安全的,但存在非常严重的争用问题:您不能独立地请求来自不同线程的随机数:它们将在CAS循环中挨饿。当您在.parallelStream()
中使用valueFromMap
时,您遇到了这些问题。
此处适用的最简单方法是使用ThreadLocalRandom
代替:
new DataFactoryImpl( (a, b) -> ThreadLocalRandom.current().nextInt(a, b+1), dict() );
请注意,ThreadLocalRandom.nextInt()
没有RandomUtils.nextInt()
之类的快速路径,因此如果您想保留它,请使用:
new DataFactoryImpl(
(a, b) -> a == b ? a : ThreadLocalRandom.current().nextInt(a, b+1), dict() );
注意不要在外部某处缓存ThreadLocalRandom.current()
实例(如字段或静态变量):此调用必须在实际请求随机数的同一线程中执行。
答案 1 :(得分:3)
为什么这两种方法的测量结果如此不同 实施?
好吧,考虑一下你的共享实现&#34;用于计算一套书中的页面。
在第一种情况下,该集合由一本书组成。你打开最后一页,查看它的号码 - 那就是它!一块蛋糕。
在第二种情况下,给定的一套书是国家图书馆......这个比喻有帮助吗?
同样在你的测试中。 testGetLength
选择具有给定长度的随机单词,其中所有单词已按其长度分组。
filter( e -> e.getKey() >= minInclusive && e.getKey() <= maxInclusive )
最多只保留一组单词,但更常见,甚至为零(没有长度> 30的单词)。
testGetMaxLength
选择一个比给定长度短的随机字。这些单词的列表永远不会是空的。更糟糕的是,您的flatMap + collect
将所有按长度列表合并到一个大型组合列表中,并且此操作在速度上非常慢。你有没有试过使用分析器?
有什么办法可以改善getMaxLength的最坏情况吗?
当然。但这需要完全重新设计算法和使用的数据结构。例如,您可以按字长对所有字典进行排序,然后构建一个数组支持的索引,该索引将长度映射到此长度的单词的结果列表中的最后一个位置。在这种情况下,您将能够在恒定时间内获得范围(1,maxLength)。