【问题标题】:Explain a performance difference解释性能差异
【发布时间】:2015-12-01 03:19:08
【问题描述】:

这是Full Sourcea 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() ); 
}

【问题讨论】:

  • 您是在问为什么 stream 与 parallelStream 实现之间的时间不同,或者为什么两个测试之间的时间不同?如果您制作其中之一stackoverflow.com/help/mcve,则遵循此操作也更简单
  • @xenoterracide 考虑到唯一的区别似乎是调用 getLength 和 getMaxLength,这似乎是显而易见的地方。很遗憾,您没有提供它们。
  • 是的,我可以看到,但是要遵循所有级别的间接性非常困难。本质上,您似乎在问为什么 getRange(1,something) 比 getRange(something,something) 慢。可能是因为过滤器在后一种情况下丢弃了很多东西吗?无法判断,我们没有您的数据,而且您的代码非常庞大且高度间接。
  • @xenoterracide,看看stackoverflow.com/help/mcve 事实上,我敢打赌,如果你写了一个,你会很快看到性能差异的来源。
  • 似乎我明白你的问题是什么,但总的来说@pvg 是正确的:在发布到 SO 之前,你应该尽可能地简化你的代码。通常在简化过程中,您可能会自己理解问题所在。

标签: java performance java-8 java-stream


【解决方案1】:

区别实际上在于您用来生成随机数的RandomUtils.nextInt() 实现。如果startInclusiveendInclusive 参数匹配(如getLength()),它只返回非常快的参数。否则它会请求java.util.Random 对象的静态实例来获取随机数。 java.util.Random 是线程安全的,但存在非常严重的争用问题:您不能仅仅独立地从不同线程请求随机数:它们将在 CAS 循环中饿死。当您在 valueFromMap 中使用 .parallelStream() 时,您遇到了这些问题。

在这里应用的最简单的解决方法是改用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() 实例(如在字段或静态变量中):此调用必须在实际请求随机数的同一线程中执行。

【讨论】:

  • 所以我将defaultInstance 的实现替换为您的负1 差nextInt( a, b ) 而不是b+1,因为这会在从列表中请求值时导致随机越界。这个实现需要13s...public static DataFactory defaultInstance() { return new DataFactoryImpl( (a, b) -&gt; a == b ? a : ThreadLocalRandom.current().nextInt(a, b), dict() ); }
【解决方案2】:

为什么这两种方法共享时的测量结果如此不同 一个实现?

好吧,假设您有一个“共享实现”来计算一组书中的页数。

在第一种情况下,该集合由一本书组成。你打开最后一页,看看它的编号——就是这样!小菜一碟。

在第二种情况下,给定的书集是国家图书馆......这个类比有帮助吗?


你的测试也是如此。 testGetLength 选择一个具有给定长度的随机单词,其中所有单词都已按其长度分组。

filter( e -&gt; e.getKey() &gt;= minInclusive &amp;&amp; e.getKey() &lt;= maxInclusive ) 最多保留一组单词,但更多情况下,甚至为零(没有长度大于 30 的单词)。

testGetMaxLength 选择一个比给定长度短的随机词。这些词的列表永远不会是空的。更糟糕的是,您的flatMap + collect 将所有按长度的列表合并到一个非常大的组合列表中,而且这个操作非常慢。您是否尝试过使用分析器?

有什么方法可以改善 getMaxLength 的最坏情况?

当然。但这需要完全重新设计所使用的算法和数据结构。例如,您可以按单词长度对所有字典进行排序,然后构建一个数组支持的索引,将长度映射到该长度单词的结果列表中的最后一个位置。在这种情况下,您将能够在恒定时间内获得一个范围 (1, maxLength)。

【讨论】:

  • 实际上一直在寻找一个很好的分析器来解决这种与 Intellij Idea 很好地集成的问题,从那时起。这是我遇到的少数几个性能问题之一,我认为更需要分析器(问题是相对的,因为实际使用不打算循环使用 1000 次)
猜你喜欢
  • 2013-07-12
  • 1970-01-01
  • 2022-01-22
  • 2021-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-17
  • 2013-04-29
相关资源
最近更新 更多