拉姆达表演的区别? [英] Difference in lambda performances?

查看:130
本文介绍了拉姆达表演的区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个不是重复的我的问题。我检查了它,这是更多关于内部匿名类。



我对Lambda表达式感兴趣,并测试了以下内容:


  • 给定一个一万个条目的数组,那么删除特定索引的速度会更快:Lambda表达式还是带有if测试的For循环?

  • < >

    第一个结果并不令人感到意外,因为我不知道我将要做什么:

      final int NUMBER_OF_LIST_INDEXES = 10_000; 
    列表< String> myList = new ArrayList<>();
    String [] myWords =使用此字符串数组测试Lamba表达式.split();
    $ b $ for(int i = 0; i< NUMBER_OF_LIST_INDEXES; i ++){
    myList.add(myWords [i%6]);
    }

    long time = System.currentTimeMillis();

    //两个测试都是单独运行的
    //将其中的未使用的一个放在备注中,当其他工作正常时

    // 250毫秒用于Lambda表达式
    myList.removeIf(x - > x.contains(s));

    // 16毫秒为传统循环
    for(int i = NUM​​BER_OF_LIST_INDEXES - 1; i> = 0; i - ){
    if(myList.get i).contains(s))myList.remove(i);

    System.out.println(System.currentTimeMillis() - time +milliseconds);

    但是,我决定改变常量 NUMBER_OF_LIST_INDEXES 100万,结果如下:

      final int NUMBER_OF_LIST_INDEXES = 1_000_000; 
    列表< String> myList = new ArrayList<>();
    String [] myWords =使用此字符串数组测试Lamba表达式.split();
    $ b $ for(int i = 0; i< NUMBER_OF_LIST_INDEXES; i ++){
    myList.add(myWords [i%6]);
    }

    long time = System.currentTimeMillis();

    //两个测试都是单独运行的
    //将其中的未使用的一个放在备注中,而其他的则运行

    // 390毫秒用于Lambda表达式
    myList.removeIf(x - > x.contains(s));

    //传统循环的32854毫秒
    for(int i = NUM​​BER_OF_LIST_INDEXES - 1; i> = 0; i - ){
    if(myList.get i).contains(s))myList.remove(i);

    System.out.println(System.currentTimeMillis() - time +milliseconds);

    为了让事情更简单,下面是结果:

      | | 10.000 | 1.000.000 | 
    | LAMBDA | 250ms | 390ms | 156%进化
    | FORLOOP | 16ms | 32854ms | 205000 +%进化

    我有以下问题:


    • 这背后有什么魔力?我们如何才能对数组产生如此巨大的差异,而对索引进行处理的时候,却不是lambda表达式。* / $>

    • 在性能方面,我们如何知道何时使用Lambdas以及何时坚持传统的方式来处理数据?

    • 这是 List 方法?其他Lambda表达式也产生这样的随机性能吗?

    • 写了一个JMH基准来衡量这一点。有4种方法:
      $ b $ ul
      $ li $ removeIf 关于 ArrayList
    • removeIf LinkedList ArrayList 上使用 iterator.remove() LinkedList 中的 iterator.remove() li>


    基准点是显示 removeIf ,迭代器应该提供性能相同,但对于 ArrayList 不是这种情况。



    默认情况下, removeIf 在内部使用一个迭代器去除元素,所以我们应该期望与 removeIf 和一个迭代器






    现在考虑一个 ArrayList 它在内部使用数组来保存元素。每次我们调用 remove 时,索引之后的其余元素必须移位一个;所以每次都要复制很多元素。当使用迭代器来遍历 ArrayList 时,我们需要删除一个元素,这个复制需要一次又一次地发生,这使得这非常慢。对于一个 LinkedList ,情况并非如此:当一个元素被删除时,唯一的变化就是指向下一个元素的指针。



    那么为什么 removeIf ArrayList 上的速度如同在 LinkedList上一样快?因为它实际上是被覆盖的,并且不使用迭代器:代码实际上标记了要在第一遍中被删除的元素,然后在第二遍中删除它们(移动剩余的元素)。在这种情况下,优化是可能的:每次需要移除剩余的元素时,我们只会在知道所有需要移除的元素时才执行一次。






    结论:


    • removeIf 来删除每个匹配谓词的元素。
    • remove 应该被用来删除一个已知的元素。






    基准测试结果:
    $ b基准模式Cnt得分错误单位
    RemoveTest.removeIfArrayList avgt 30 4,478±0,194 ms / op
    RemoveTest.removeIfLinkedList avgt 30 3,634±0,184 ms / op
    RemoveTest.removeIteratorArrayList avgt 30 27197,046±536,584 ms / op
    RemoveTest.removeIteratorLinkedList avgt 30 3,601±0,195 ms / op


    基准:

      @Warmup(iterations = 5,time = 1000, timeUnit = TimeUnit.MILLISECONDS)
    @Measurement(iterations = 10,time = 1000,timeUnit = TimeUnit.MILLISECONDS)
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @Fork(3)
    @State(Scope.Benchmark)
    public class RemoveTest {

    private static final int NUMBER_OF_LIST_INDEXES = 1_000_000;
    private static final String [] words =用这个String数组测试Lamba表达式.split();

    private ArrayList< String>数组列表;
    私人LinkedList< String> LinkedList的;

    @Setup(Level.Iteration)
    public void setUp(){
    arrayList = new ArrayList<>();
    linkedList = new LinkedList<>();
    for(int i = 0; i< NUMBER_OF_LIST_INDEXES; i ++){
    arrayList.add(words [i%6]);
    linkedList.add(words [i%6]);



    @Benchmark
    public void removeIfArrayList(){
    arrayList.removeIf(x - > x.contains(s)) );


    @Benchmark
    public void removeIfLinkedList(){
    linkedList.removeIf(x - > x.contains(s));

    $ b $ @ benchmark
    public void removeIteratorArrayList(){
    for(ListIterator< String> it = arrayList.listIterator(arrayList.size()); it。 hasPrevious();){
    if(it.previous()。contains(s))it.remove();



    $B

    public void removeIteratorLinkedList(){
    for(ListIterator< String> it = linkedList.listIterator(linkedList.size )); it.hasPrevious();){
    if(it.previous()。contains(s))it.remove();



    public static void main(String [] args)throws Exception {
    Main.main(args);
    }

    }


    This is not a duplicate of my question. I checked it and it is more about inner anonymous classes.

    I was curious about Lambda expressions and tested the following :

    • Given an array of ten thousand entries, what would be the faster to delete certain indexes : Lamba expression or For-Loop with an if test inside?

    First results were not surprising in the fact that I did not know what I was going to come up with :

    final int NUMBER_OF_LIST_INDEXES = 10_000;
    List<String> myList = new ArrayList<>();
    String[] myWords = "Testing Lamba expressions with this String array".split(" ");
    
    for (int i = 0 ; i < NUMBER_OF_LIST_INDEXES ; i++){
        myList.add(myWords[i%6]);
    }
    
    long time = System.currentTimeMillis();
    
    // BOTH TESTS WERE RUN SEPARATELY OF COURSE
    // PUT THE UNUSED ONE IN COMMENTS WHEN THE OTHER WAS WORKING
    
    // 250 milliseconds for the Lambda Expression
    myList.removeIf(x -> x.contains("s"));
    
    // 16 milliseconds for the traditional Loop
    for (int i = NUMBER_OF_LIST_INDEXES - 1 ; i >= 0 ; i--){
        if (myList.get(i).contains("s")) myList.remove(i);
    }
    System.out.println(System.currentTimeMillis() - time + " milliseconds");
    

    But then, I decided to change the constant NUMBER_OF_LIST_INDEXES to one million and here is the result :

    final int NUMBER_OF_LIST_INDEXES = 1_000_000;
    List<String> myList = new ArrayList<>();
    String[] myWords = "Testing Lamba expressions with this String array".split(" ");
    
    for (int i = 0 ; i < NUMBER_OF_LIST_INDEXES ; i++){
        myList.add(myWords[i%6]);
    }
    
    long time = System.currentTimeMillis();
    
    // BOTH TESTS WERE RUN SEPARATELY OF COURSE
    // PUT THE UNUSED ONE IN COMMENTS WHEN THE OTHER WAS WORKING
    
    // 390 milliseconds for the Lambda Expression
    myList.removeIf(x -> x.contains("s"));
    
    // 32854 milliseconds for the traditional Loop
    for (int i = NUMBER_OF_LIST_INDEXES - 1 ; i >= 0 ; i--){ 
        if (myList.get(i).contains("s")) myList.remove(i);
    }
    System.out.println(System.currentTimeMillis() - time + " milliseconds");
    

    To make things simpler to read, here are the results :

    |        |  10.000 | 1.000.000 |
    | LAMBDA |  250ms  |   390ms   | 156% evolution
    |FORLOOP |   16ms  |  32854ms  | 205000+% evolution
    

    I have the following questions :

    • What is magic behind this? How do we come to such a big difference for the array and not for the lambda when the indexes to work with is *100.

    • In terms of performance, how do we know when to use Lambdas and when to stick to traditional ways to work with data?

    • Is this a specific behavior of the List method? Are other Lambda expression also produce random performances like this one?

    解决方案

    I wrote a JMH benchmark to measure this. There are 4 methods in it:

    • removeIf on an ArrayList.
    • removeIf on a LinkedList.
    • iterator with iterator.remove() on an ArrayList.
    • iterator with iterator.remove() on a LinkedList.

    The point of the benchmark is to show that removeIf and an iterator should provide the same performance, but that it is not the case for an ArrayList.

    By default, removeIf uses an iterator internally to remove the elements so we should expect the same performance with removeIf and with an iterator.


    Now consider an ArrayList which uses an array internally to hold the elements. Everytime we call remove, the remaining elements after the index have to be shifted by one; so each time a lot of elements have to be copied. When an iterator is used to traverse the ArrayList and we need to remove an element, this copying needs to happen again and again, making this very slow. For a LinkedList, this is not the case: when an element is deleted, the only change is the pointer to the next element.

    So why is removeIf as fast on an ArrayList as on a LinkedList? Because it is actually overriden and it does not use an iterator: the code actually marks the elements to be deleted in a first pass and then deletes them (shifting the remaining elements) in a second pass. An optimization is possible in this case: instead of shifting the remaining elements each time one needs to be removed, we only do it once when we know all the elements that need to be removed.


    Conclusion:

    • removeIf should be used when one needs to remove every elements matching a predicate.
    • remove should be used to remove a single known element.

    Result of benchmark:

    Benchmark                            Mode  Cnt      Score      Error  Units
    RemoveTest.removeIfArrayList         avgt   30      4,478 ±   0,194  ms/op
    RemoveTest.removeIfLinkedList        avgt   30      3,634 ±   0,184  ms/op
    RemoveTest.removeIteratorArrayList   avgt   30  27197,046 ± 536,584  ms/op
    RemoveTest.removeIteratorLinkedList  avgt   30      3,601 ±   0,195  ms/op
    

    Benchmark:

    @Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
    @Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @Fork(3)
    @State(Scope.Benchmark)
    public class RemoveTest {
    
        private static final int NUMBER_OF_LIST_INDEXES = 1_000_000;
        private static final String[] words = "Testing Lamba expressions with this String array".split(" ");
    
        private ArrayList<String> arrayList;
        private LinkedList<String> linkedList;
    
        @Setup(Level.Iteration)
        public void setUp() {
            arrayList = new ArrayList<>();
            linkedList = new LinkedList<>();
            for (int i = 0 ; i < NUMBER_OF_LIST_INDEXES ; i++){
                arrayList.add(words[i%6]);
                linkedList.add(words[i%6]);
            }
        }
    
        @Benchmark
        public void removeIfArrayList() {
            arrayList.removeIf(x -> x.contains("s"));
        }
    
        @Benchmark
        public void removeIfLinkedList() {
            linkedList.removeIf(x -> x.contains("s"));
        }
    
        @Benchmark
        public void removeIteratorArrayList() {
            for (ListIterator<String> it = arrayList.listIterator(arrayList.size()); it.hasPrevious();){
                if (it.previous().contains("s")) it.remove();
            }
        }
    
        @Benchmark
        public void removeIteratorLinkedList() {
            for (ListIterator<String> it = linkedList.listIterator(linkedList.size()); it.hasPrevious();){
                if (it.previous().contains("s")) it.remove();
            }
        }
    
        public static void main(String[] args) throws Exception {
             Main.main(args);
        }
    
    }
    

    这篇关于拉姆达表演的区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆