Java 8 Streams:多个过滤器与复杂条件

dea*_*mon 206 java lambda filter java-8 java-stream

有时您想要Stream使用多个条件过滤a :

myList.stream().filter(x -> x.size() > 10).filter(x -> x.isCool()) ...
Run Code Online (Sandbox Code Playgroud)

或者你可以用复杂的条件和单一的方式 做同样的事情filter:

myList.stream().filter(x -> x.size() > 10 && x -> x.isCool()) ...
Run Code Online (Sandbox Code Playgroud)

我的猜测是第二种方法具有更好的性能特征,但我不知道.

第一种方法在可读性方面取胜,但性能更好?

Hol*_*ger 139

必须为两个备选方案执行的代码非常相似,以至于无法可靠地预测结果.底层对象结构可能不同,但对热点优化器没有挑战.因此,如果存在任何差异,它将取决于其他周围条件,这将导致更快的执行.

组合两个过滤器实例会创建更多对象,从而创建更多的委托代码,但如果使用方法引用而不是lambda表达式,则可能会更改,例如替换filter(x -> x.isCool())filter(ItemType::isCool).这样就消除了为lambda表达式创建的合成委托方法.因此,使用两个方法引用组合两个过滤器可能会创建与filter使用lambda表达式的单个调用相同或更少的委托代码&&.

但是,如上所述,HotSpot优化器将消除这种开销,并且可以忽略不计.

从理论上讲,两个滤波器可以比单个滤波器更容易并行化,但这只与计算密集型任务相关¹.

所以没有简单的答案.

最重要的是,不要考虑低于气味检测阈值的这种性能差异.使用更具可读性的东西.


¹...并且需要实现并行处理后续阶段,这是目前标准Stream实现未采用的道路

  • @Juan Carlos Diaz:不,溪流不会这样.阅读"懒惰评估"; 中间操作不做任何事情,它们只会改变终端操作的结果. (9认同)
  • 代码是否必须在每个过滤器之后迭代生成的流? (2认同)

Han*_*k D 21

此测试表明您的第二个选项可以表现得更好.先查找,然后是代码:

one filter with predicate of form u -> exp1 && exp2, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=4142, min=29, average=41.420000, max=82}
two filters with predicates of form u -> exp1, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=13315, min=117, average=133.150000, max=153}
one filter with predicate of form predOne.and(pred2), list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=10320, min=82, average=103.200000, max=127}
Run Code Online (Sandbox Code Playgroud)

现在的代码:

enum Gender {
    FEMALE,
    MALE
}

static class User {
    Gender gender;
    int age;

    public User(Gender gender, int age){
        this.gender = gender;
        this.age = age;
    }

    public Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

static long test1(List<User> users){
    long time1 = System.currentTimeMillis();
    users.stream()
            .filter((u) -> u.getGender() == Gender.FEMALE && u.getAge() % 2 == 0)
            .allMatch(u -> true);                   // least overhead terminal function I can think of
    long time2 = System.currentTimeMillis();
    return time2 - time1;
}

static long test2(List<User> users){
    long time1 = System.currentTimeMillis();
    users.stream()
            .filter(u -> u.getGender() == Gender.FEMALE)
            .filter(u -> u.getAge() % 2 == 0)
            .allMatch(u -> true);                   // least overhead terminal function I can think of
    long time2 = System.currentTimeMillis();
    return time2 - time1;
}

static long test3(List<User> users){
    long time1 = System.currentTimeMillis();
    users.stream()
            .filter(((Predicate<User>) u -> u.getGender() == Gender.FEMALE).and(u -> u.getAge() % 2 == 0))
            .allMatch(u -> true);                   // least overhead terminal function I can think of
    long time2 = System.currentTimeMillis();
    return time2 - time1;
}

public static void main(String... args) {
    int size = 10000000;
    List<User> users =
    IntStream.range(0,size)
            .mapToObj(i -> i % 2 == 0 ? new User(Gender.MALE, i % 100) : new User(Gender.FEMALE, i % 100))
            .collect(Collectors.toCollection(()->new ArrayList<>(size)));
    repeat("one filter with predicate of form u -> exp1 && exp2", users, Temp::test1, 100);
    repeat("two filters with predicates of form u -> exp1", users, Temp::test2, 100);
    repeat("one filter with predicate of form predOne.and(pred2)", users, Temp::test3, 100);
}

private static void repeat(String name, List<User> users, ToLongFunction<List<User>> test, int iterations) {
    System.out.println(name + ", list size " + users.size() + ", averaged over " + iterations + " runs: " + IntStream.range(0, iterations)
            .mapToLong(i -> test.applyAsLong(users))
            .summaryStatistics());
}
Run Code Online (Sandbox Code Playgroud)

  • 有趣 - 当我在test1之前更改运行test2的顺序时,test1运行稍慢.只有当test1首先运行时它似乎更快.任何人都可以重现这个或有任何见解? (3认同)
  • 这可能是因为HotSpot编译的成本是由首先运行的任何测试引起的. (3认同)

Ser*_*rge 12

从性能的角度来看,复杂的过滤条件会更好,但是最好的性能将显示使用标准循环的老式方法if clause是最好的选择。小阵列上的差异为10个元素,差异可能约为2倍,大阵列上的差异不是那么大。
您可以看一下我的GitHub项目,其中我对多个数组迭代选项进行了性能测试。

对于小型阵列10元素吞吐量ops / s: 10元素数组 对于中等10,000个元素,吞吐量ops / s: 在此处输入图片说明 对于大型阵列1,000,000个元素,吞吐量ops / s: 1M元素

注意:测试在

  • 8个CPU
  • 1 GB内存
  • 操作系统版本:16.04.1 LTS(Xenial Xerus)
  • Java版本:1.8.0_121
  • 的JVM:-XX:+ UseG1GC-服务器-Xmx1024m -Xms1024m

更新: Java 11在性能上取得了一些进步,但动态特性保持不变

基准模式:吞吐量,运维/时间 Java 8版本11