为什么 Files.list() 并行流的执行速度比使用 Collection.parallelStream() 慢得多?

2024-02-08

以下代码片段是获取目录列表、对每个文件调用提取方法并将生成的药物对象序列化为 xml 的方法的一部分。

try(Stream<Path> paths = Files.list(infoDir)) {
    paths
        .parallel()
        .map(this::extract)
        .forEachOrdered(drug -> {
            try {
                marshaller.write(drug);
            } catch (JAXBException ex) {
                ex.printStackTrace();
            }
        });
}

这是完全相同的代码,执行完全相同的操作,但使用普通的.list()调用以获取目录列表并调用.parallelStream()在结果列表中。

Arrays.asList(infoDir.toFile().list())
    .parallelStream()
    .map(f -> infoDir.resolve(f))
    .map(this::extract)
    .forEachOrdered(drug -> {
        try {
            marshaller.write(drug);
        } catch (JAXBException ex) {
            ex.printStackTrace();
    }
});

我的机器是四核 MacBook Pro,Java v 1.8.0_60(内部版本 1.8.0_60-b27)。

我正在处理约 7000 个文件。 3次运行的平均值:

第一个版本: 和.parallel():20秒。没有.parallel():41秒

第二个版本: 和.parallelStream():12秒。和.stream():41秒。

并行模式下的那 8 秒似乎是一个巨大的差异,因为extract从流中读取并完成所有繁重工作的方法write进行最终写入的调用没有改变。


问题是 Stream API 的当前实现以及IteratorSpliterator对于未知大小的源,严重地将此类源拆分为并行任务。您很幸运拥有超过 1024 个文件,否则您将根本没有并行化的好处。当前的 Stream API 实现考虑了estimateSize()返回值来自Spliterator. The IteratorSpliterator未知大小的回报Long.MAX_VALUE在 split 之前,其后缀总是返回Long.MAX_VALUE以及。其分裂策略如下:

  1. 定义当前批量大小。当前公式是从 1024 个元素开始,然后按算术增加(2048、3072、4096、5120 等),直到MAX_BATCH已达到大小(即 33554432 个元素)。
  2. 将输入元素(在您的情况下为路径)消耗到数组中,直到达到批量大小或输入耗尽。
  3. 返回一个ArraySpliterator迭代创建的数组作为前缀,将其自身作为后缀。

假设您有 7000 个文件。 Stream API 要求估计大小,IteratorSpliterator回报Long.MAX_VALUE。好的,Stream API 询问IteratorSpliterator为了分割,它从底层收集 1024 个元素DirectoryStream到数组并拆分为ArraySpliterator(估计大小 1024)及其本身(估计大小仍然是Long.MAX_VALUE). As Long.MAX_VALUE远大于 1024,Stream API 决定继续分割较大的部分,甚至不尝试分割较小的部分。所以整体的分裂树是这样的:

                     IteratorSpliterator (est. MAX_VALUE elements)
                           |                    |
ArraySpliterator (est. 1024 elements)   IteratorSpliterator (est. MAX_VALUE elements)
                                           |        |
                           /---------------/        |
                           |                        |
ArraySpliterator (est. 2048 elements)   IteratorSpliterator (est. MAX_VALUE elements)
                                           |        |
                           /---------------/        |
                           |                        |
ArraySpliterator (est. 3072 elements)   IteratorSpliterator (est. MAX_VALUE elements)
                                           |        |
                           /---------------/        |
                           |                        |
ArraySpliterator (est. 856 elements)    IteratorSpliterator (est. MAX_VALUE elements)
                                                    |
                                        (split returns null: refuses to split anymore)

因此,之后有 5 个并行任务需要执行:实际上包含 1024、2048、3072、856 和 0 个元素。请注意,即使最后一个块有 0 个元素,它仍然报告它估计有Long.MAX_VALUE元素,因此 Stream API 会将其发送到ForkJoinPool以及。不好的是 Stream API 认为进一步分割前四个任务是没有用的,因为它们的估计大小要小得多。因此,您得到的输入分割非常不均匀,最多使用四个 CPU 核心(即使您有更多核心)。如果每个元素的处理对于任何元素都花费大致相同的时间,那么整个过程将等待最大的部分(3072 个元素)完成。因此,您可能获得的最大加速是 7000/3072=2.28 倍。因此,如果顺序处理需要 41 秒,那么并行流将花费大约 41/2.28 = 18 秒(接近实际数字)。

您的解决方案完全没问题。请注意,使用Files.list().parallel()你也有所有的输入Path存储在内存中的元素(在ArraySpliterator对象)。因此,如果您手动将它们转储到List。数组支持的列表实现,例如ArrayList(目前由Collectors.toList())可以毫无问题地均匀分割,从而带来额外的加速。

为什么这样的情况没有优化呢?当然,这不是不可能的问题(尽管实现可能相当棘手)。对于 JDK 开发人员来说,这似乎并不是一个优先考虑的问题。邮件列表中对此主题进行了多次讨论。您可以阅读 Paul Sandoz 的留言here http://mail.openjdk.java.net/pipermail/core-libs-dev/2015-July/034539.html他对我的优化工作发表了评论。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 Files.list() 并行流的执行速度比使用 Collection.parallelStream() 慢得多? 的相关文章

随机推荐

  • unicode中字符串的长度不同

    尽管字符串中的字符数相同 但为什么以下字符串的长度不同 echo strlen 馐 馑 馒 馓 馔 馕 首 馗 馘 br echo strlen br Outputs 35 26 第一批字符每个占用 3 个字节 因为它们在 39000 个字
  • 如何删除信号处理程序

    我已经制作了以下信号处理程序 struct sigaction pipeIn pipeIn sa handler updateServer sigemptyset pipeIn sa mask sa sa flags SA RESTART
  • 如何在iPhone上进行Base64编码

    如何在iPhone上进行Base64编码 我发现了一些看起来很有希望的例子 但永远无法让它们在手机上工作 你可以看一个例子here http iosdevelopertips com core services encode decode
  • 图像捕获延迟 - React Native Camera / Expo Camera

    我正在尝试实现与 Facebook 或 Instagram 相同的功能 即时预览相机拍摄的图像 此时 当调用此函数时 我的 take 被正确获取 takePicture async function if this camera this
  • 将 8 字节的小端二进制转换为双精度浮点数

    我有一个二进制文件 我逐字节读取 我遇到一个 8 字节长的部分 包含一个双精度浮点 小端 我不知道如何读取它并通过掩蔽和 或转换正确计算它 具体来说 文件类型是 LAS 但这并不重要 Java有什么技巧吗 您可以使用ByteBuffer h
  • Python Fabric:如何响应键盘输入?

    我想自动响应某些程序提示的某些问题 例如 mysql 提示输入密码 或者 apt 询问 是 或 当我想使用 manage pyrebuild index 重建我的干草堆索引时 对于 MySQL 我可以使用 password 开关 并且我确信
  • 在 MSVC 中处理 __attribute__

    我想知道处理包含 GCC 的代码的最佳方法是什么 attribute 使用 MSVC 时的扩展 以下是处理此问题的安全方法 define attribute x blank should simply ignore thanks to C
  • 如何跨包引用 Android“资产”?

    我有一个以 免费 和 专业 版本发布的 Android 应用程序 我已经使用从两个版本引用的基础 库 项目设置了我的项目 这样我的包集如下所示 com example myapp com example myapp free com exa
  • 如何翻译活动记录模型验证

    当我提交有错误的表单时 它会返回一条错误消息 如何使用 i18n 翻译这些错误消息 我已经翻译了我视图中的所有其他文本 所以我知道 l18n 在 Rails 中是如何工作的 我现在明白了 2 errors prohibited this u
  • 寻找最高的键

    我只是对为什么我的代码不起作用感到困惑 这是我到目前为止的问题和代码 测试运行表明我的答案是错误的 给定字典d 找到字典中最大的key并将对应的值与变量关联起来val of max 例如 给定字典 5 3 4 1 12 2 2 将与val
  • 循环数组和另一个对象中的对象

    我有以下结构 我需要在 React 中获取内部值并通过 我想我需要获取一个值数组 例如 Bitcoin Etherium 并通过它进行映射 我怎样才能实现它 let arr CoinInfo Id 1182 Name BTC FullNam
  • 汇编中的结构或类

    我需要 C 中的结构或类之类的东西 例如 我需要一个带有数组和两个属性 大小和长度 的类以及一些函数 例如append和remove 如何使用宏和过程在汇编中实现这一点 Tasm 支持例如 struc String note without
  • Java流按2个字段排序

    我有需要按 2 个参数排序的项目列表 第一个参数是 orderIndex 并且我使该部分正常工作 参见下面的代码 orderIndex 之后的第二个参数是 amount 所以基本上第一个项目应该是顺序索引最低的项目 并且需要按数量排序 re
  • 具有路径变量的多个请求映射值

    RequestMapping value abcd employees value id public String getEmployees PathVariable value value String val PathVariable
  • PHPUnit @dataProvider 根本不起作用

    我已阅读有关该主题的文档 并且我的代码遵循数据提供程序实现的所有要求 首先 这是测试的完整代码 http pastebin com tuT9pV9h以防万一它是相关的 这是实现数据提供者的函数 Test the createGroup fu
  • Webpack 在 SSR 构建期间挂起 - Angular 12

    我正在运行 webpack v5 50 用于 Angular 项目的服务器端渲染构建 配置如下 module exports mode none entry server server ts externals dist server ma
  • 无法读取响应输出中的 application/json 消息

    我正在测试 REST API 当我进行 GET 调用来检索资源时 它会导致 500 内部服务器错误 并且在输出中返回具有媒体类型的消息application json messageType Some error type messageT
  • 调试使用 Vista API 中的 FileDialog 的 Visual Studio 2010 解决方案时出现问题

    我有一个 WinForms C Visual Studio 2008 NET 3 5 解决方案 需要升级到 Visual Studio 2010 NET 保持版本 3 5 该解决方案利用 Vista API 中的 FileDialog 有两
  • Recyclerview DiffUtil 项目更新

    我的回收视图中有无限滚动 因此 当有新数据时它会更新 我正在使用 DiffUtil 更新回收器视图中的数据 DiffUtil 确实会更新数据 但每当有更新数据时 recyclerview 就会滚动到顶部 看起来就像 使用 notificat
  • 为什么 Files.list() 并行流的执行速度比使用 Collection.parallelStream() 慢得多?

    以下代码片段是获取目录列表 对每个文件调用提取方法并将生成的药物对象序列化为 xml 的方法的一部分 try Stream