使用Comparator.comparingLong相比于long.compare有什么优势吗?

5

出于好奇,如果在Java8中使用比较器(Comparator)的Lambda表达式风格是否比常规排序有任何优势?

按id排序的一种方法是:

List sortedAcs = ac
.stream()
.sorted((a, b) -> Long.compare(a.getId(), b.getId())) //sorted by id here
.collect(Collectors.toList());

其他方法可以使用Java 8的方式:

List sortedAcs = ac
.stream()
.sorted(Comparator.comparingLong(AC::getId)) //sorted by id here
.collect(Collectors.toList());

后一种方法(Java-8 方法引用)相对于前一种方法是否有性能优势?
请帮忙!!!

4
工程师喜欢测量事物。请尝试通过进行100K次操作来衡量性能?在答案部分分享您的发现将会非常棒。 - Yogi Devendra
{btsdaf} - Vinay Prajapati
2
{btsdaf} - Kayaman
{btsdaf} - Vinay Prajapati
6
第二种变体的好处并不在于性能,而在于简化。它更短并且避免了代码重复。这不仅仅是一个理论上的观点;我曾经在如此短小的代码片段中看到过复制粘贴错误,例如 (a, b) -> Long.compare(a.getId(), a.getId()),你必须看两次才能发现错误。如果您想让该特定代码片段运行得更快,可以使用 List<AC> sortedAcs=new ArrayList<>(ac); sortedAcs.sort(Comparator.comparingLong(AC::getId)); 进行原地排序。 - Holger
显示剩余5条评论
4个回答

9
唯一不同的是实现所需方法的数量。 Comparator.comparingLong 会为每个参数应用一个 ToLongFunction,然后委托给 Long.compare。但这只是一个简单的优化,JIT 应该会处理它。因此,我认为由于这个差异,可能会有一些小差异(在 JIT 启动之前),但非常微小,绝对可以忽略不计,并且不应以任何方式影响您的决定。
另一方面,如果您确实看到了任何差异,那么很可能是您的测试代码存在问题,而不是正在被测量的代码。

{btsdaf} - Vinay Prajapati
1
{btsdaf} - Kayaman
{btsdaf} - Vinay Prajapati
1
{btsdaf} - Kayaman

2
两个代码片段的性能差异将是微不足道的。如果您真的需要优化该代码,则不使用流可能比替换比较器带来更大的性能提升。
选择两种变体之间唯一的标准应该是“清晰度”:哪一个您认为更清楚地传达了代码的意图?最终,这是个人偏好,取决于您对Java 8功能的熟练程度等因素。
就我个人而言,第二个代码片段比第一个更清晰。comparingLong方法(以及其他comparingX方法)立即告诉我:我们正在根据(long类型的)属性值比较对象。在第一个片段中,我首先需要解析代码才能确定确实发生了这种情况。

1
所以,下面是一个关于性能的观点的答案。
这是我用来测试的代码:
AC类:
package com.test;

public class AC {

    private Long id;

    public AC(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "AC{" +
                "id=" + id +
                '}';
    }
}

主类:-

package test.java;

import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Main {

    @org.openjdk.jmh.annotations.Benchmark
    public void measureName() {
        List<AC> acs = new ArrayList<>();

        acs.add(new AC(20l));
        acs.add(new AC(30l));
        acs.add(new AC(10l));
        acs.add(new AC(30l));
        acs.add(new AC(80l));
        acs.add(new AC(50l));
        acs.add(new AC(30l));
        acs.add(new AC(90l));
        acs.add(new AC(80l));
        acs.add(new AC(110l));

   /*     acs
                .stream()
                .sorted(Comparator.comparingLong(AC::getId)) //sorted by id here
                .collect(Collectors.toList());*/

        acs.stream()
                .sorted((a, b) -> Long.compare(a.getId(), b.getId())) //sorted by id here
                .collect(Collectors.toList());

    }

    public static void main(String[] args) {

        Options opt = new OptionsBuilder()
                .include(".*" + Main.class.getSimpleName() + ".*")
                .forks(1)
                .build();

        try {
            new Runner(opt).run();
        } catch (RunnerException e) {
            e.printStackTrace();
        }
    }
}

使用JMH输出以下结果,使用Comparator.comparingLong

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

Benchmark          Mode  Cnt        Score       Error  Units
Main.measureName  thrpt   20  4130836.283 ± 86675.431  ops/s

而对于下面的 Long.compare

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

Benchmark          Mode  Cnt        Score        Error  Units
Main.measureName  thrpt   20  4106542.318 ± 146956.814  ops/s

如果按照这些统计数据来看,Long.compare 似乎更快,尽管差异非常小。请随时在评论中分享您的发现,我也会尝试使用它们。

如果我计算一下,一般来说大约有24毫秒的差异。但是似乎使用Long.compare会更加不确定。 - Adesh Kumar
2
这些结果表明,*comparingLong()* 稍微更快——它具有更高的吞吐量,但只有约0.6%。然而,comparingLong() 更易读,并且由于其性能至少与其他方法一样好,因此使用它是一个明确的选择。 - erickson

0

我在尝试对一个Map进行排序之前收到了有关使用的警告。

(entry1,entry2)->Long.compare(entry1.getKey(),entry2.getKey())

应该使用Comparator.comparingLong进行替换,这样实现的结果确实更好:

Comparator.comparingLong(Map.Entry::getKey)

检查比较长整型的实现,我发现它基本上是相同的实现,只是更加清晰和节省时间。

return (Comparator<T> & Serializable)
(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接