Arrays.stream(array_name).sum() 比迭代方法慢吗?

2024-03-20

我正在编写一个 leetcode 问题:https://oj.leetcode.com/problems/gas-station/ https://oj.leetcode.com/problems/gas-station/使用Java 8。

我的解决方案在使用时得到了 TLEArrays.stream(integer_array).sum()计算总和,同时使用迭代计算数组中元素的总和接受相同的解决方案。这个问题的最佳时间复杂度是 O(n),我很惊讶在使用 Java 8 中的流 API 时得到了 TLE。我只在 O(n) 中实现了该解决方案。

import java.util.Arrays;

public class GasStation {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int start = 0, i = 0, runningCost = 0, totalGas = 0, totalCost = 0; 
        totalGas = Arrays.stream(gas).sum();
        totalCost = Arrays.stream(cost).sum();

        // for (int item : gas) totalGas += item;
        // for (int item : cost) totalCost += item;

        if (totalGas < totalCost)
            return -1;

        while (start > i || (start == 0 && i < gas.length)) {
            runningCost += gas[i];
            if (runningCost >= cost[i]) {
                runningCost -= cost[i++];
            } else {
                runningCost -= gas[i];
                if (--start < 0)
                    start = gas.length - 1;
                runningCost += (gas[start] - cost[start]);
            }
        }
        return start;
    }

    public static void main(String[] args) {
        GasStation sol = new GasStation();
        int[] gas = new int[] { 10, 5, 7, 14, 9 };
        int[] cost = new int[] { 8, 5, 14, 3, 1 };
        System.out.println(sol.canCompleteCircuit(gas, cost));

        gas = new int[] { 10 };
        cost = new int[] { 8 };
        System.out.println(sol.canCompleteCircuit(gas, cost));
    }
}

解可得accepted什么时候, 我评论以下两行:(使用流计算总和)

totalGas = Arrays.stream(gas).sum();
totalCost = Arrays.stream(cost).sum();

并取消注释以下两行(使用迭代计算总和):

//for (int item : gas) totalGas += item;
//for (int item : cost) totalCost += item;

现在该解决方案已被接受。为什么Java 8 中的流 API速度较慢输入比基元的迭代大?


处理此类问题的第一步是将代码带入受控环境。这意味着在您控制(并且可以调用)的 JVM 中运行它,并在良好的基准测试工具中运行测试,例如JMH http://openjdk.java.net/projects/code-tools/jmh/。分析一下,不要猜测。

这是我使用 JMH 制定的一个基准测试,对此进行了一些分析:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
public class ArraySum {
    static final long SEED = -897234L;

    @Param({"1000000"})
    int sz;

    int[] array;

    @Setup
    public void setup() {
        Random random = new Random(SEED);
        array = new int[sz];
        Arrays.setAll(array, i -> random.nextInt());
    }

    @Benchmark
    public int sumForLoop() {
        int sum = 0;
        for (int a : array)
            sum += a;
        return sum;
    }

    @Benchmark
    public int sumStream() {
        return Arrays.stream(array).sum();
    }
}

基本上,这会创建一个包含一百万个整数的数组,并对它们求和两次:一次使用 for 循环,一次使用流。运行基准测试会产生一堆输出(为了简洁和显着效果而省略),但摘要结果如下:

Benchmark                 (sz)  Mode  Samples     Score  Score error  Units
ArraySum.sumForLoop    1000000  avgt        3   514.473      398.512  us/op
ArraySum.sumStream     1000000  avgt        3  7355.971     3170.697  us/op

哇! Java 8 流的东西就是 SUXX0R!它比 for 循环慢 14 倍,不要使用它!!!1!

嗯,不。首先让我们看一下这些结果,然后更仔细地看看我们是否能弄清楚发生了什么。

摘要显示了两种基准测试方法,其中“sz”参数为一百万。可以改变这个参数,但在这种情况下并没有什么区别。我也只运行了 3 次基准测试方法,正如您从“样本”列中看到的那样。 (也只有 3 次预热迭代,此处不可见。)得分以每次操作的微秒为单位,显然流代码比 for 循环代码慢得多。但还要注意分数误差:这是不同运行中的变异量。 JMH 有助于打印结果的标准差(此处未显示),但您可以轻松地看到分数误差占报告分数的很大一部分。这降低了我们对分数的信心。

运行更多迭代应该会有所帮助。更多的预热迭代将使 JIT 在运行基准测试之前完成更多的工作并稳定下来,并且运行更多的基准迭代将消除系统上其他地方的瞬态活动所产生的任何错误。因此,让我们尝试 10 次预热迭代和 10 次基准迭代:

Benchmark                 (sz)  Mode  Samples     Score  Score error  Units
ArraySum.sumForLoop    1000000  avgt       10   504.803       34.010  us/op
ArraySum.sumStream     1000000  avgt       10  7128.942      178.688  us/op

性能总体上更快了一些,并且测量误差也小了很多,因此运行更多的迭代已经达到了预期的效果。但流代码仍然比 for 循环代码慢得多。这是怎么回事?

通过查看 Streams 方法的各个计时可以获得重要线索:

# Warmup Iteration   1: 570.490 us/op
# Warmup Iteration   2: 491.765 us/op
# Warmup Iteration   3: 756.951 us/op
# Warmup Iteration   4: 7033.500 us/op
# Warmup Iteration   5: 7350.080 us/op
# Warmup Iteration   6: 7425.829 us/op
# Warmup Iteration   7: 7029.441 us/op
# Warmup Iteration   8: 7208.584 us/op
# Warmup Iteration   9: 7104.160 us/op
# Warmup Iteration  10: 7372.298 us/op

发生了什么?前几次迭代相当快,但第四次和后续迭代(以及随后的所有基准迭代)突然慢得多。

我以前见过这个。它是在这个问题 https://stackoverflow.com/questions/25847397/erratic-performance-of-arrays-stream-map-sum and 这个答案 https://stackoverflow.com/a/25851390/1441122SO 上的其他地方。我建议阅读该答案;它解释了在这种情况下 JVM 的内联决策如何导致性能较差。

这里有一些背景知识:for 循环编译为非常简单的增量和测试循环,并且可以通过循环剥离和展开等常用优化技术轻松处理。流代码虽然在本例中不是很复杂,但与 for 循环代码相比实际上相当复杂;有相当多的设置,每个循环至少需要一个方法调用。因此,JIT 优化,特别是其内联决策,对于使流代码快速运行至关重要。而且有可能出错。

另一个背景点是整数求和是您能想到的在循环或流中执行的最简单的操作。这往往会使流设置的固定开销看起来相对更昂贵。它也非常简单,以至于可能会引发内联策略中的异常情况。

其他答案的建议是添加 JVM 选项-XX:MaxInlineLevel=12增加可内联的代码量。使用该选项重新运行基准测试会给出:

Benchmark                 (sz)  Mode  Samples    Score  Score error  Units
ArraySum.sumForLoop    1000000  avgt       10  502.379       27.859  us/op
ArraySum.sumStream     1000000  avgt       10  498.572       24.195  us/op

啊,好多了。使用禁用分层编译-XX:-TieredCompilation也起到了避免病态行为的作用。我还发现使循环计算更加昂贵,例如对整数的平方求和(即添加单个乘法)也可以避免病态行为。

现在,你的问题是关于在leetcode环境,它似乎在您无法控制的 JVM 中运行代码,因此您无法更改内联或编译选项。而且您可能也不想让计算变得更加复杂以避免出现这种病态。因此,对于这种情况,您不妨坚持使用旧的 for 循环。但不要害怕使用流,即使是处理原始数组。除了一些狭窄的边缘情况外,它的性能相当好。

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

Arrays.stream(array_name).sum() 比迭代方法慢吗? 的相关文章

  • 为什么即使我的哈希码值相同,“==”也会返回 false

    我写了一个像这样的课程 public class HashCodeImpl public int hashCode return 1 public static void main String args TODO Auto generat
  • 如何在 JPQL 或 HQL 中进行限制查询?

    在 Hibernate 3 中 有没有办法在 HQL 中执行相当于以下 MySQL 限制的操作 select from a table order by a table column desc limit 0 20 如果可能的话 我不想使用
  • 优化数据可视化 Web 应用程序的性能

    我正在重写 3 年前编写的数据可视化网络工具 从那时起 浏览器的 JavaScript 引擎变得更快 所以我正在考虑将部分工作从服务器转移到客户端 在页面上 数据在表格和地图 或图表 中可视化 它使用相同的数据 但以不同的方式 因此准备显示
  • 断言 Kafka 发送有效

    我正在使用 Spring Boot 编写一个应用程序 因此要写信给 Kafka 我这样做 Autowired private KafkaTemplate
  • 如何检查某个元素是否存在于一组项目中?

    In an ifJava中的语句如何检查一个对象是否存在于一组项目中 例如 在这种情况下 我需要验证水果是苹果 橙子还是香蕉 if fruitname in APPLE ORANGES GRAPES Do something 这是一件非常微
  • 将非 Android 项目添加到 Android 项目

    我在 Eclipse 中有三个项目 Base Server 和 AndroidClient Base和Server是Java 1 7项目 而AndroidClient显然是一个android项目 基础项目具有在服务器和 Android 客户
  • Android 无法解析日期异常

    当尝试解析发送到我的 Android 客户端的日期字符串时 我得到一个无法解析的日期 这是例外 java text ParseException 无法解析的日期 2018 09 18T00 00 00Z 位于 偏移量 19 在 java t
  • 蓝牙发送和接收文本数据

    我是 Android 开发新手 我想制作一个使用蓝牙发送和接收文本的应用程序 我得到了有关发送文本的所有内容逻辑工作 但是当我尝试在手机中测试它时 我看不到界面 这是Main Activity Code import android sup
  • 在常数空间中创建 1..N 的随机排列

    我正在寻找枚举固定空间中数字 1 N 的随机排列 这意味着我无法将所有数字存储在列表中 原因是 N 可能非常大 超过可用内存 我仍然希望能够一次遍历这样一个数字的排列 只访问每个数字一次 我知道对于某些 N 可以这样做 许多随机数生成器随机
  • Java Swing - 如何禁用 JPanel?

    我有一些JComponents on a JPanel我想在按下 开始 按钮时禁用所有这些组件 目前 我通过以下方式显式禁用所有组件 component1 setEnabled false 但是有什么办法可以一次性禁用所有组件吗 我尝试禁用
  • 将 JavaFX FXML 对象分组在一起

    非常具有描述性和信息性的答案将从我这里获得价值 50 声望的赏金 我正在 JavaFX 中开发一个应用程序 对于视图 我使用 FXML
  • 部署 .war 时出现 Glassfish 服务器错误:部署期间发生错误:准备应用程序时出现异常:资源无效

    我正在使用以下内容 NetBeans IDE 7 3 内部版本 201306052037 爪哇 1 7 0 17 Java HotSpot TM 64 位服务器虚拟机 23 7 b01 NetBeans 集成 GlassFish Serve
  • 将 JScrollPane 添加到 JFrame

    我有一个关于向 Java 框架添加组件的问题 我有一个带有两个按钮的 JPanel 和一个添加了 JTable 的 JScrollPane 我想将这两个添加到 JFrame 中 我可以将 JPanel 添加到 JFrame 或将 JScro
  • 手动设置Android Studio的JDK路径

    如何为 Android Studio 使用自定义 JDK 路径 我不想弄乱 PATH 因为我没有管理员权限 是否有某个配置设置文件允许我进行设置 如果您查看项目设置 您可以从那里访问 jdk 在标准 Windows 键盘映射上 您可以在项目
  • Android S8+ 警告消息“不支持当前的显示尺寸设置,可能会出现意外行为”

    我在 Samsung S8 Android 7 中收到此警告消息 APP NAME 不支持当前的显示尺寸设置 可能会 行为出乎意料 它意味着什么以及如何删除它 谢谢 通过添加解决supports screens 机器人 xlargeScre
  • C# 中的 strstr() 等效项

    我有两个byte 我想找到第二个的第一次出现byte 在第一个byte 或其中的一个范围 我不想使用字符串来提高效率 翻译第一个byte to a string会效率低下 基本上我相信就是这样strstr 在 C 中做 最好的方法是什么 这
  • 如何有效地找到距给定点最远的点(从一组点中)?

    我正在寻找一种算法或数据结构来解决以下问题 给你一组点 S 然后你会得到另一个点形式的 Q 查询 对于每个查询 找到集合中距离给定点最远的点 集合中最多有 10 5 个点和 10 5 个查询 所有点的坐标都在 0 到 10 5 范围内 我想
  • Java 正则表达式中的逻辑 AND

    是否可以在 Java Regex 中实现逻辑 AND 如果答案是肯定的 那么如何实现呢 正则表达式中的逻辑 AND 由一系列堆叠的先行断言组成 例如 foo bar glarch 将匹配包含所有三个 foo bar 和 glarch 的任何
  • MiniDFSCluster UnsatisfiedLinkError org.apache.hadoop.io.nativeio.NativeIO$Windows.access0

    做时 new MiniDFSCluster Builder config build 我得到这个异常 java lang UnsatisfiedLinkError org apache hadoop io nativeio NativeIO
  • iPhone 3GS 上的 ARM 与 Thumb 性能比较,非浮点代码

    我想知道是否有人有关于 iPhone 3GS 上 ARM 与 Thumb 代码性能的硬性数据 特别是对于非浮点 VFP 或 NEON 代码 我知道 Thumb 模式下的浮点性能问题 更大的 ARM 指令的额外代码大小是否会在某个时刻成为性能

随机推荐

  • Eclipse RCP:ClassNotFoundException 或如何使其他包加载我的类

    详细信息 我正在尝试使用 Jalapeno 框架将我的 RCP 应用程序与 Cache 数据库连接起来 建立连接后 我尝试从表中获取所有数据 就像墨西哥胡椒手册中一样 if objManager null return DBClass co
  • 二叉搜索树的定义中是否允许重复键?

    我正在尝试找到二叉搜索树的定义 并且我一直在到处寻找不同的定义 有人说对于任何给定的子树 左子键小于或等于根 有人说对于任何给定的子树 右子键大于或等于根 我以前的大学数据结构书上说 每个元素都有一个键 并且没有两个元素具有相同的键 bst
  • Terraform - 我应该使用 user_data 还是 Provisioner 来引导资源?

    看来我可以使用user data使用模板文件或 远程执行 provisioner使用内联命令进行引导 那么哪一个被认为更惯用呢 你应该使用user data The 用户数据 http docs aws amazon com AWSEC2
  • 如何使用 RSAEncryption 创建带有 SHA1 摘要的 PKCS7/CMS?

    我创建了一个pkcs7块 可以自己验证 但是结果和我使用OpenSSL的伙伴不一样 我创建的p7块无法被我的伙伴验证 我们仔细检查代码 只找到c 中找不到对应项的代码 OPENSSL signInfo gt digest enc alg g
  • ASP.Net 使用什么 URL 重写器? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 为什么 android:fullBackupOnly 默认值是 false?

    In https developer android com guide topics manifest application element https developer android com guide topics manife
  • 如何避免链接多个 AsyncTask 调用?

    我必须对 Web 服务进行多次调用 但每个步骤都使用上一步中的值 因此现在我有一个巨大的 AsyncTasks 链 每个 AsyncTask 都在上一步的 AsyncTask 的 onPostExecute 中执行 这非常非常难看 而且很难
  • Perl DBIx::Class 可以覆盖从数据库检索列的方式吗?

    直到今天我才使用过 DBIx Class 所以我对它完全陌生 我不确定这是否可能 但基本上我的 SQLite 数据库中有一个表 其中有一个时间戳列 时间戳列的默认值为 CURRENT TIMESTAMP SQLite 将其存储在 GMT 时
  • 总是收到“消息”:“未经身份验证。” - Laravel 护照

    我一整天都找到了很多教程 我的设置与所有基本教程完全相同 目前 我可以访问http localhost oauth token成功地将令牌返回给我 之后 我使用 ARC Advanced Rest Client 来进行调用我自己的 api
  • 如何在SQL中获取2个表中不匹配的行?

    我有两个 SQL Server 表 CHANNELS SUBSCRIBERS 我想从中获取行CHANNELS不存在于SUBSCRIBERS在某种条件下 我尝试过INNER和OUTER LEFT JOIN但这对我不起作用 他们都给了我相同的答
  • 将一组字符串转换为 byte[] 数组

    我正在尝试将一组字符串转换为 byte 数组 首先 我执行以下操作将字节数组转换为字符串 public String convertByte byte msg String str for int i 0 i lt msg length i
  • 如何在iPhone中获取DNS服务器IP

    我尝试通过以下方式获取 etc resolv conf 打开 etc resolv conf 0644 但它返回 1并且errno是2这意味着 没有这样的文件 我能做些什么 您无法访问应用程序沙箱之外的文件
  • 反应本机错误 RCTJSONStringify() 遇到以下错误:JSON 写入中的类型无效 (NSURL)

    我正在尝试使用反应本机fbsdk在我的反应本机应用程序中 直到昨天为止都运行良好 但是 今天它给出了一个奇怪的错误RCTJSONStringify 遇到以下错误 JSON 写入 NSURL 中的类型无效 RN v0 42 0 这是我的代码
  • 从 dll 内的函数返回时堆损坏

    我有一个具有如下原型的函数 void function std string str 这个函数在另一个加载和使用该 dll 的程序的主函数中被调用 function some string value here 从该函数返回时 我收到堆损坏
  • 使用 Nokogiri 解析大型 HTML 文件

    我正在尝试解析与 Nokogiri 但不幸的是我无法从页面获取所有项目 我的简单测试代码是 require open uri require nokogiri html Nokogiri HTML open http www pro med
  • bash 中的视频方向检测

    我需要检测视频是以纵向还是横向模式录制的 然后以脚本方式将其转换为正确的方向 if v orient landscape then ffmpeg i file mp4 vf transpose 1 file ogv else ffmpeg
  • ABAP中调用方法的不同方式

    抱歉这个基本的 ABAP 问题 ABAP中调用方法有哪些不同的方式 他们的 官方 名字是什么 我听说过执行 方法调用和内部 内联方法调用 执行使用PERFORM关键字和方法调用CALL METHOD语法 我猜 但什么是 内部 或 内联方法调
  • 如何使用 std::cin 读取 bool

    我是 C 新手 我想知道函数 cin 在布尔数据的情况下如何工作 比方说 bool a cin gt gt a 我知道如果我给出 0 或 1 我的数据 a 将是 true 或 false 但是如果我给出另一个整数甚至一个字符串会发生什么 我
  • 允许所有用户进行临时分发查询

    我正在使用 AD Hoc 分布式查询将数据从 MS SQL Server 2008 传输到 MS Access 该过程使用单个 SQL 语句启动 INSERT INTO OpenDataSource Microsoft Jet OLEDB
  • Arrays.stream(array_name).sum() 比迭代方法慢吗?

    我正在编写一个 leetcode 问题 https oj leetcode com problems gas station https oj leetcode com problems gas station 使用Java 8 我的解决方