Java 就性能 Lambda 或简单循环而言,哪个更好?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31675319/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 11:24:25  来源:igfitidea点击:

Which would be better in terms of performance Lambda or simple loop?

javalambdajava-8

提问by Ankur Mahajan

I have quickly read over the Oracle Lambda Expressiondocumentation.

我已经快速阅读了Oracle Lambda 表达式文档。

This kind of example has helped me to understand better, though:

不过,这种例子帮助我更好地理解:

//Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
System.out.println(n);
}

//New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));


//or we can use :: double colon operator in Java 8
list.forEach(System.out::println);

Still, I don't understand why it's such an innovation. It's just a method that dies when the "method variable" ends, right? Why should I use this instead of a real method? Which would be better option in terms of performance. Lambda or simple loop.

不过,我不明白为什么这是一项创新。它只是一个在“方法变量”结束时死亡的方法,对吗?为什么我应该使用它而不是真正的方法?就性能而言,这将是更好的选择。Lambda 或简单循环。

采纳答案by Stephen C

My advice would be:

我的建议是:

  1. Use the style that you and your coworkers agreeis most maintainable.

  2. If you and your colleagues are not yet comfortablewith lambdas, keep learning.

  3. Don't obsess over performance. It is often not the most important thing.

  1. 使用您和您的同事认为最易于维护的样式。

  2. 如果您和您的同事还没有舒适与lambda表达式,不断地学习。

  3. 不要执着于性能。这往往不是最重要的事情。

Generally speaking, lambdas and streams provide a more concise and (once everyone is up to speed) more readable way of expressing this kind of algorithm. Performance is not the primary goal.

一般来说,lambdas 和流提供了一种更简洁且(一旦每个人都掌握了速度)更易读的方式来表达这种算法。性能不是主要目标。

If performance does become an issue, then the standard advice is to code, test, profile, optimize. You can easily waste time by optimizing at the coding stage. Let the profiler point out what parts of your code are worthy of the effort of optimization.

如果性能确实成为问题,那么标准的建议是编码、测试、分析和优化。您可以通过在编码阶段进行优化来轻松浪费时间。让分析器指出代码的哪些部分值得优化。



In this specific example, the performance difference is going to be too small to measure. And if you scaled up to a list of millions of elements, the performance will be dominated by the time taken to build the list and write the numbers. The different ways of iteration will only contribute a small part to the overall performance.

在此特定示例中,性能差异将太小而无法测量。如果您扩展到包含数百万个元素的列表,性能将取决于构建列表和编写数字所花费的时间。不同的迭代方式只会对整体性能贡献很小的一部分。



And for folks, who (despite all of the above) stillwant to know whether it is faster to use a lambda or a conventional loop, the best general answer is:

对于那些(尽管有上述所有情况)仍然想知道使用 lambda 或传统循环是否更快的人来说,最好的一般答案是:

"It depends on all sorts of factors that 1) are not well understood, and 2) liable to change as Java compiler technology evolves.

“这取决于各种因素,1) 还没有被很好地理解,2) 随着 Java 编译器技术的发展可能会发生变化。

We could give you an answer for a specific example with a specific Java major/minor/patch release, but it would be unwise to generalize.

我们可以为您提供具有特定 Java 主要/次要/补丁版本的特定示例的答案,但一概而论是不明智的。

回答by Ashay Jain

In terms of performance a normal function will be better as compare to lambda because in groovy there are closures present which is more or like same as lambda.

在性能方面,与 lambda 相比,普通函数会更好,因为在 groovy 中存在与 lambda 差不多的闭包。

These things are working in a way like if you write a closure for any collection it will internally create a another class which actually does action for mentioned closure or lambda expression.

这些事情的工作方式是,如果您为任何集合编写闭包,它将在内部创建另一个类,该类实际上为提到的闭包或 lambda 表达式执行操作。

But, by using lambda and closure i can iterate things in better way as well as i can debug easily. You can write less line of codes.

但是,通过使用 lambda 和闭包,我可以以更好的方式迭代事物,并且可以轻松调试。您可以编写更少的代码行。

回答by barunsthakur

When using lambda in your code, you are telling what to do not how to do it. Above code passing a method reference instead of looping over the list, I think the lambda one is more ergonomic.

在您的代码中使用 lambda 时,您是在告诉该做什么而不是如何去做。上面的代码传递一个方法引用而不是遍历列表,我认为 lambda 更符合人体工程学。

As far as performance is concerned, printing of a list of 1 million items took almost same time but I have not bench-marked other kind of operation.

就性能而言,打印 100 万个项目的列表几乎花费了相同的时间,但我没有对其他类型的操作进行基准测试。

The above code is a trivial operation but lambda has quite a few advantages as you can have functions which you can pass around, using multiple core(parallel streams) is easy etc.

上面的代码是一个微不足道的操作,但是 lambda 有很多优点,因为您可以拥有可以传递的函数,使用多个核心(并行流)很容易等。

回答by Tagir Valeev

Why should I use this instead of a real method?

为什么我应该使用它而不是真正的方法?

You should not. Use the approach which you like more.

你不应该。使用您更喜欢的方法。

As for performance, I guess, all these versions are roughly equally fast. Here I/O operation (println) is much slower than all possible overhead of calling lambda or creating an iterator. In general forEachmight be slightly faster as it does everything inside the single method without creating the Iteratorand calling hasNextand next(which is implicitly done by for-each loop). Anyway, this depends on many factors, such as how often you call this code, how long your list is, whether JIT compiler managed to devirtualize the iterator and/or lambda, and so on.

至于性能,我想,所有这些版本的速度大致相同。这里的 I/O 操作 ( println) 比调用 lambda 或创建迭代器的所有可能的开销要慢得多。通常forEach可能会稍微快一点,因为它在单个方法中执行所有操作,而无需创建Iterator和调用hasNextand next(这是由 for-each 循环隐式完成的)。无论如何,这取决于许多因素,例如您调用此代码的频率、您的列表有多长、JIT 编译器是否设法对迭代器和/或 lambda 进行了去虚拟化,等等。

回答by libik

It allows you write in one line (while having enough readability) something, what was not possible before. Performance is not issue here O(n)stays O(n).

它允许您在一行中(同时具有足够的可读性)写一些以前不可能的东西。性能不是问题在这里O(n)停留O(n)

For example in my Windows Phone App, I have sessions and in that sessions are performers and I want to select all sessions which have one concrete performer (like you want to see all the movies some actor plays in). In Java 1.7 or less I had to create loop with inner loop, checking for it, returning null if there is no performer etc. And with lambda expressions, I can do this :

例如,在我的 Windows Phone 应用程序中,我有会话,在这些会话中是表演者,我想选择所有有一个具体表演者的会话(就像你想看某个演员出演的所有电影一样)。在 Java 1.7 或更低版本中,我必须创建带有内循环的循环,检查它,如果没有执行者则返回 null 等。使用 lambda 表达式,我可以这样做:

//performerId is parameter passed by method before
Sessions.Where(x => x.Performers.Where(y => y.PerformerId == performerId).FirstOrDefault() != null)

It is same in Java now (however I am not working on 1.8 project right now, I do not have Java example, but I am looking forward to it).

现在在 Java 中也是一样(但是我现在不在 1.8 项目上工作,我没有 Java 示例,但我很期待)。

回答by Bhuwan Prasad Upadhyay

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Timeout;
import org.openjdk.jmh.annotations.Warmup;

/**
 *
 * @author devb
 */
@BenchmarkMode(Mode.Throughput)
@Fork(value = 1)
@Warmup(iterations = 1, time = 32, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 16, time = 1, timeUnit = TimeUnit.SECONDS)
@Timeout(time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class CheckLamdaPerformance {

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

    @Benchmark
    public void testPerOld() {
        //Old way:
        for (Integer n : list) {
            System.out.println(n);
        }
    }

    @Benchmark
    public void testPerNew() {
        //New way:
        list.forEach(n -> System.out.println(n));
    }

    @Benchmark
    public void testPerDoubleColon() {
        //or we can use :: double colon operator in Java 8
        list.forEach(System.out::println);
    }

}

To Test benchmark

测试基准

import java.io.IOException;
import org.openjdk.jmh.Main;
import org.openjdk.jmh.runner.RunnerException;

/**
 *
 * @author devb
 */
public class MyBenchmark {

    private static final String TEST = ".*CheckLamdaPerformance.*"; 

    public static void main(String[] args) throws IOException, RunnerException {
        Main.main(getArguments(TEST, 1, 5000, 1));
    }

    private static String[] getArguments(String className, int nRuns, int runForMilliseconds, int nThreads) {
        return new String[]{className,
            "-i", "" + nRuns,
            "-r", runForMilliseconds + "ms",
            "-t", "" + nThreads,
            "-w", "5000ms",
            "-wi", "1"
        };
    }

}

After Running Output is:

运行后输出为:

# Run complete. Total time: 00:00:34

Benchmark                                  Mode  Cnt     Score   Error  Units
CheckLamdaPerformance.testPerDoubleColon  thrpt        776.008          ops/s
CheckLamdaPerformance.testPerNew          thrpt       1096.423          ops/s
CheckLamdaPerformance.testPerOld          thrpt        873.542          ops/s

回答by Holger

If you want to understand the value of lambda expressions you shouldn't look at the only new method which has a language counterpart which existed before. How about these examples:

如果您想了解 lambda 表达式的价值,您不应该查看具有以前存在的语言对应物的唯一新方法。这些例子怎么样:

button.addActionListener( ev -> BACKGROUND_EXECUTOR_SERVICE.execute( () -> {
   String result = longComputation();
   SwingUtilities.invokeLater( () -> label.setText(result) );
});

Just think about how the equivalent pre-Java?8 code looks like and you see that the main advantage is not performance here.

想想等效的前 Java?8 代码的样子,您会发现主要优势不是这里的性能。

If you want to look at Collection operations, how about this one?

如果你想看看 Collection 操作,这个怎么样?

map.merge(key, 1, Integer::sum);

This will put 1into the map, if the key doesn't exist in the map yet or add 1to the value of the existing mapping otherwise. Again, think about how the old counterpart looks like. The fact that it might be even more efficient as it avoids multiple hash operations, if the map has an appropriate implementation of the method, is only an additional benefit.

1如果键不存在于映射中,或者添加1到现有映射的值中,这将放入映射中。再一次,想想旧的对应物的样子。如果映射具有该方法的适当实现,它可能会更高效,因为它避免了多次散列操作,这只是一个额外的好处。

Surely, the forEachmethod can not provide such a big gain in expressiveness as there is the for-each language counterpart. Still, not needing to declare a loop variable can improve the source code, if the declaration requires to repeat a long type name and generic parameters. That's especially true in the context of Maps:

当然,该forEach方法无法提供如此大的表达能力,因为存在 for-each 语言对应物。尽管如此,如果声明需要重复长类型名称和泛型参数,则不需要声明循环变量可以改进源代码。在Maps的上下文中尤其如此:

Map<ContainerOrderFocusTraversalPolicy, List<AttributedCharacterIterator>> map;
//…

?

?

map.forEach((p,l)->l.forEach(i->{ /* do your work using p and i */}));

here, this new iteration clearly wins over

在这里,这个新的迭代显然胜出

for(Map.Entry<ContainerOrderFocusTraversalPolicy, List<AttributedCharacterIterator>> e:
                                                                      map.entrySet()) {
    ContainerOrderFocusTraversalPolicy p=e.getKey();
    for(AttributedCharacterIterator i: e.getValue()) {
        /* do your work using p and i */
    }
}

Of course, it only matters if the actual work statements are small enough but that's how lambda expressions should be used: to encapsulate small pieces of code. And there are still tasks which can't be done this way and require an ordinary for-each loop, just as there are also tasks which can't be done with a for-each loop and need the even-older forloop dealing with an Iteratormanually…

当然,只有实际的工作语句是否足够小才重要,但这就是 lambda 表达式应该如何使用:封装小段代码。还有一些任务不能用这种方式完成,需要一个普通的 for-each 循环,就像也有一些任务不能用 for-each 循环完成,需要更老的for循环来处理Iterator手动…

回答by user158037

Look at first part in Guava documentation. It is about older Java version but makes important point - using lambdas everywhere might actually make code less readable. Better example might be the one from Stream API:

查看Guava 文档中的第一部分。它是关于较旧的 Java 版本,但有一点很重要 - 在任何地方使用 lambdas 实际上可能会使代码的可读性降低。更好的例子可能是来自 Stream API 的例子:

 // Group employees by department
 Map<Department, List<Employee>> byDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));

回答by Cain

The run-time and readability certainly seems to vary case-by-case, but in my mind there is a pretty good answer to

运行时和可读性似乎因情况而异,但在我看来,有一个很好的答案

Why should I use this instead of a real method?

为什么我应该使用它而不是真正的方法?

You can pass a lambda function as a variable
Correct me if I'm off base on this, as I've been mostly a Fantom user for a while now, but you can pass a Lambda function as an argument to a method call. For example, if you have a sort method built, it could take a lambda argument as the comparator to use, allowing you to use the single sort method to compare different fields in an object, or even completely different object, easily.

您可以将 lambda 函数作为变量传递
如果我不了解这一点,请纠正我,因为我现在主要是 Fantom 用户有一段时间了,但是您可以将 Lambda 函数作为参数传递给方法调用。例如,如果您构建了一个排序方法,它可以将 lambda 参数作为要使用的比较器,从而允许您使用单个排序方法轻松比较对象中的不同字段,甚至完全不同的对象。