java - 解释性能差异

标签 java performance java-8 java-stream

这是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 最坏的情况吗?

更新

虽然我喜欢随机作为来源的理论,也许这是真的。将我的代码更改为此导致 13s 运行,这比运行时间更长,是 RandomUtils::nextInt 时间的两倍多。

public static DataFactory defaultInstance() {
    return new DataFactoryImpl( (a, b) -> a == b ? a :    ThreadLocalRandom.current().nextInt(a, b), dict() ); 
}

最佳答案

差异实际上在于 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() 实例(例如在字段或静态变量中):此调用必须在实际请求随机数的同一线程中执行。

关于java - 解释性能差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34012000/

相关文章:

javascript - 将 jQuery 数据附加为字符串时在 div 上设置它

java - 如果使用Java 8 Streams列表为空,则返回默认列表?

java - 如何在Android Studio中将JRE更改为JDK8?

java - 解释Java中的匿名类

java - testNG 出现 noClassDefFound 错误

c# - 性能缓慢 - 单击重定向链接后运行 ASP .NET ASPNET_WP.EXE 和 CSC.EXE

java - API请求(GET调用)是否可以向客户端返回响应并启动后台任务来完成请求

java - 目标无法到达,标识符 'demoBean' 解析为空

java: FileInputStream.read()读取一个字节但可以读取一个字符,这是怎么回事?

java - 泛型是否始终提供类型安全以及在哪些最佳用例下