【问题标题】:Code optimization to covert list of integers to list of objects in JavaJava中将整数列表转换为对象列表的代码优化
【发布时间】:2020-11-08 01:56:35
【问题描述】:

我有大约 50000 到 500000 个员工 ID,我希望将这些员工 ID 转换为详细对象。

我做了这样的事情来实现这一点:

private Set<Detail> setDetail(List<Integer> employees, Group group) {
  Set<Detail> details = employees.stream().parallel().map(id -> new Detail(id, group)).collect(Collectors.toSet());
  return details ;
}

但这非常慢,并且随着员工 ID 数量的增加而变得越来越慢。如何优化此代码?我可以使用哪些优化技术/算法来更好地优化它。

【问题讨论】:

  • parallel 在数据量大的情况下是个不错的选择。
  • 我在有/没有并行的情况下尝试了这个,但没有注意到这样的性能。我不能更好地改善这一点吗?
  • 你能定义慢吗? 500k 个对象在合理的硬件上应该不是问题,你的硬件合理吗? ,Detail 构造函数中发生了什么“有趣”的事情吗?
  • 使用经典线程池(ExecutorService),感受ConcurrentSkipListSet而不是列表。
  • 根据您的返回类型的动态,您可能想尝试使用ArrayList 而不是一组。对我来说,它快了大约 5 倍。特别是如果您预先分配列表。

标签: java optimization


【解决方案1】:

您应该尽量避免创建那么多对象。无论您选择哪种算法,如果您的数据库在某个时候会继续增长,您将无法将其全部放入内存中。此外,瓶颈很可能是从 DB 获取数据(而不是创建对象)。

因此请尝试重新构建您的应用,以便在执行相关操作时预先计算数据并将其存储在 DB 中。

如果经过仔细考虑,您决定确实需要使用这么多对象,那么更好的选择是继续使用原语:

class EmployeesInGroup {
   private final int[] ids;
   private final Group group;
   ...

   Detail get(int idx) {
      return new Details(ids[idx], group);
   }

   int size() {
     return ids.length;
   }
}

然后您可以遍历此列表并一次使用 1 个对象,而无需在内存中保留大量对象:

EmployeesInGroup list = new EmployeesInGroup(ids, group);
for(int i = 0; i < list.size(); i++) {
  Detail d = list.get(i);
  ...
}

你可以让它实现Iterable并使用for-each循环。

基准测试

我上面列出的方法比创建 Detail 对象的 array 至少快 20 倍。使用流和列表会更加缓慢。我没有与Integer 核对过,但我预测它会使一切速度减慢 2 倍左右。

Benchmark                                        Mode  Cnt     Score    Error  Units
EmployeeConversionBenchmark.objectArray         thrpt   20   368.702 ±  3.483  ops/s
EmployeeConversionBenchmark.primitiveArray      thrpt   20  7595.080 ± 68.841  ops/s
EmployeeConversionBenchmark.streamsWithObjects  thrpt   20   197.923 ±  1.616  ops/s

这是我使用的代码:

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;

import java.util.Arrays;
import java.util.List;
import java.util.Random;

import static java.util.stream.Collectors.toList;

public class EmployeeConversionBenchmark {
    public static void main(String[] args) throws Exception {
        org.openjdk.jmh.Main.main(new String[]{EmployeeConversionBenchmark.class.getSimpleName()});
    }

    @Benchmark @Fork(value = 1, warmups = 0)
    public int primitiveArray(Data data) {
        EmployeesInGroup e = new EmployeesInGroup(data.ids, data.group);
        int sum = 0;
        for (int i = 0; i < e.size(); i++)
            sum += e.get(i).getId();
        return sum;
    }
    @Benchmark @Fork(value = 1, warmups = 0)
    public int objectArray(Data data) {
        EmployeesInGroup.Detail[] e = new EmployeesInGroup.Detail[data.ids.length];
        for (int i = 0; i < data.ids.length; i++)
            e[i] = new EmployeesInGroup.Detail(data.ids[i], data.group);

        int sum = 0;
        for (EmployeesInGroup.Detail detail : e)
            sum += detail.getId();
        return sum;
    }
    @Benchmark @Fork(value = 1, warmups = 0)
    public int streamsWithObjects(Data data) {
        List<EmployeesInGroup.Detail> e = Arrays.stream(data.ids).mapToObj(id -> new EmployeesInGroup.Detail(id, data.group)).collect(toList());
        int sum = 0;
        for (EmployeesInGroup.Detail detail : e)
            sum += detail.getId();
        return sum;
    }

    @State(Scope.Benchmark)
    public static class Data {
        private final int[] ids = new int[500_000];
        private final EmployeesInGroup.Group group = new EmployeesInGroup.Group();
        public Data() {
            for (int i = 0; i < ids.length; i++)
                ids[i] = new Random().nextInt();
        }
    }

    public static class EmployeesInGroup {
        private final int[] ids;
        private final Group group;

        public EmployeesInGroup(int[] ids, Group group) {
            this.ids = ids;
            this.group = group;
        }
        public Detail get(int idx) {
            return new Detail(ids[idx], group);
        }

        public int size() {
            return ids.length;
        }

        public static class Group {
        }

        public static class Detail {
            private final int id;
            private final Group group;

            public Detail(int id, Group group) {
                this.id = id;
                this.group = group;
            }
            public int getId() {
                return id;
            }
        }
    }
}

【讨论】:

  • "w/o 在内存中保留很多" - 调用后: newEmployeesInGroup(ids, group) ,如果不在内存中,它们保存在哪里?
  • @DavidSoroko,EmployeesInGroup 包含一个原始的 int[] 数组 - 不是所有 Detail 对象的列表。使用它这么多更快。迭代时,它将一次创建 1 个短暂的 Detail 对象。然后您将迭代到下一个并忘记对前一个对象的引用。这将允许 GC 清理它。所以只有 int[] 数组会在内存中保存很长时间。
  • 一次创建和“忘记”(如果忘记对应用程序有意义)一个对象比一次性分配所有实例然后一次性丢弃它们需要更少的内存。但是你认为你的方法会更快吗?如果您认为是,您是否愿意进行基准测试和比较?
  • @DavidSoroko,当然。我的测量结果显示,只需保留int[] 而不是Details[],速度至少提高了 20 倍。 Streams 和 Integer 会使它变得更慢。我很快就会发布基准测试。
  • 感谢您抽出宝贵时间来做这件事。我玩过你的 JMH 基准测试,不管它有什么价值,都不要认为primitiveArray 的更好性能与 GC 有关,因为它在小尺寸方面也领先。更有可能是因为 int 被布置在连续的内存块中,而对象列表则遍布堆。
【解决方案2】:

一般来说,关于性能和优化的对话应该包括数字,例如:您尝试实现的延迟、您实际观察到的延迟、有关您的硬件的一些细节等等...

您的问题(如果有)不在您发布的方法中。在我的笔记本电脑上,下面的代码在大约 250 毫秒内执行。如果您删除 parallel(),甚至更快(~ 200 毫秒):

class Detail {
    private Integer id;
    private String group;

    public Detail(Integer id, String group) {
        this.id = id;
        this.group = group;
    }
}

public class Main {
    public Set<Detail> setDetail(List<Integer> employees, String group) {
        return employees.stream().parallel().map(id -> new Detail(id, group)).collect(toSet());
    }

    public static void main(String[] args) {
        List<Integer> idsList = new Random().ints().boxed().limit(500_000).collect(toList());

        long start = System.currentTimeMillis();

        Set<Detail> details = new Main().setDetail(idsList, "group");

        long duration = (System.currentTimeMillis() - start);
        System.out.println("Done in " + duration + " ms. Size was " + details.size());
    }
}

【讨论】:

  • 删除并行在我的测试中也跑得更快。
  • 使用无聊的 for 循环可能会更快。
  • 您可能是对的,但这不是我要更改的代码。此外,应该有理由让代码运行得更快——为了我的钱,OP 的代码足够快
猜你喜欢
  • 2021-08-08
  • 2019-08-04
  • 1970-01-01
  • 1970-01-01
  • 2019-06-09
  • 2019-01-17
  • 2013-12-02
  • 2014-12-20
  • 1970-01-01
相关资源
最近更新 更多