Java for循环优化

2023-12-27

我用 java for 循环做了一些运行时测试,并发现了一个奇怪的行为。 对于我的代码,我需要原始类型(如 int、double 等)的包装对象来模拟 io 和输出参数,但这不是重点。 只要看我的代码即可。具有字段访问的对象如何比原始类型更快?

for原始类型循环:

public static void main(String[] args) {
    double max = 1000;
    for (int j = 1; j < 8; j++) {
        double i;
        max = max * 10;
        long start = System.nanoTime();
        for (i = 0; i < max; i++) {
        }
        long end = System.nanoTime();
        long microseconds = (end - start) / 1000;
        System.out.println("MicroTime primitive(max: ="+max + "): " + microseconds);
    }
}

Result:

MicroTime 原语(最大值:=10000.0):110
MicroTime 原语(最大值:=100000.0):1081
MicroTime 原语(最大值:=1000000.0):2450
MicroTime 原语(最大值:=1.0E7):28248
MicroTime 原语(最大值:=1.0E8):276205
MicroTime 原语(最大值:=1.0E9):2729824
MicroTime原语(最大值:=1.0E10):27547009

for简单类型循环(包装对象):

public static void main(String[] args) {
    HDouble max = new HDouble();
    max.value = 1000;
    for (int j = 1; j < 8; j++) {
        HDouble i = new HDouble();
        max.value = max.value*10;
        long start = System.nanoTime();
        for (i.value = 0; i.value <max.value; i.value++) {
        }
        long end = System.nanoTime();
        long microseconds = (end - start) / 1000;
        System.out.println("MicroTime wrapper(max: ="+max.value + "): " + microseconds);
    }
}

Result:

MicroTime 包装器(最大值:=10000.0):157
MicroTime 包装器(最大值:=100000.0):1561
MicroTime 包装器(最大值:=1000000.0):3174
MicroTime 包装器(最大值:=1.0E7):15630
MicroTime 包装器(最大值:=1.0E8):155471
MicroTime 包装器(最大值:=1.0E9):1520967
MicroTime 包装器(最大值:=1.0E10):15373311

迭代次数越多,第二个代码就越快。但为什么?我知道 java 编译器和 jvm 正在优化我的代码,但我从未想过原始类型会比具有字段访问的对象慢。
有人对此有合理的解释吗?

编辑: HD双级:

public class HDouble {
    public double value;

    public HDouble() {
    }

    public HDouble(double value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }
}

我还用其中的代码测试了我的循环。例如,我计算总和 -> 相同的行为(差异不是很大,但我认为原始算法必须更快?)。首先我认为,计算需要这么长时间,现场访问几乎没有区别。

包装 for 循环:

for (i.value = 0; i.value <max.value; i.value++) {
    sum.value = sum.value + i.value;
}

Result:

MicroTime 包装器(最大值:=10000.0):243
MicroTime 包装器(最大值:=100000.0):2805
MicroTime 包装器(最大值:=1000000.0):3409
MicroTime 包装器(最大值:=1.0E7):28104
MicroTime 包装器(最大值:=1.0E8):278432
MicroTime 包装器(最大值:=1.0E9):2678322
MicroTime 包装器(最大值:=1.0E10):26665540

原始 for 循环:

for (i = 0; i < max; i++) {
    sum = sum + i;
}

Result:

MicroTime 原语(最大值:=10000.0):149
MicroTime 原语(最大值:=100000.0):1996
MicroTime 原语(最大值:=1000000.0):2289
MicroTime 原语(最大值:=1.0E7):27085
MicroTime原语(最大值:=1.0E8):279939
MicroTime 原语(最大值:=1.0E9):2759133
MicroTime原语(最大值:=1.0E10):27369724


很容易被手工制作的微基准愚弄 - 你永远不知道它们是什么actually措施。这就是为什么有像这样的特殊工具JMH http://openjdk.java.net/projects/code-tools/jmh/。但让我们来分析一下原始手工制作的基准会发生什么:

static class HDouble {
    double value;
}

public static void main(String[] args) {
    primitive();
    wrapper();
}

public static void primitive() {
    long start = System.nanoTime();
    for (double d = 0; d < 1000000000; d++) {
    }
    long end = System.nanoTime();
    System.out.printf("Primitive: %.3f s\n", (end - start) / 1e9);
}

public static void wrapper() {
    HDouble d = new HDouble();
    long start = System.nanoTime();
    for (d.value = 0; d.value < 1000000000; d.value++) {
    }
    long end = System.nanoTime();
    System.out.printf("Wrapper:   %.3f s\n", (end - start) / 1e9);
}

结果与您的有些相似:

Primitive: 3.618 s
Wrapper:   1.380 s

现在重复测试几次:

public static void main(String[] args) {
    for (int i = 0; i < 5; i++) {
        primitive();
        wrapper();
    }
}

它变得更有趣:

Primitive: 3.661 s
Wrapper:   1.382 s
Primitive: 3.461 s
Wrapper:   1.380 s
Primitive: 1.376 s <-- starting from 3rd iteration
Wrapper:   1.381 s <-- the timings become equal
Primitive: 1.371 s
Wrapper:   1.372 s
Primitive: 1.379 s
Wrapper:   1.378 s

看来这两种方法最终都得到了优化。再次运行它,现在记录 JIT 编译器活动:-XX:-TieredCompilation -XX:CompileOnly=Test -XX:+PrintCompilation

    136    1 %           Test::primitive @ 6 (53 bytes)
   3725    1 %           Test::primitive @ -2 (53 bytes)   made not entrant
Primitive: 3.589 s
   3748    2 %           Test::wrapper @ 17 (73 bytes)
   5122    2 %           Test::wrapper @ -2 (73 bytes)   made not entrant
Wrapper:   1.374 s
   5122    3             Test::primitive (53 bytes)
   5124    4 %           Test::primitive @ 6 (53 bytes)
Primitive: 3.421 s
   8544    5             Test::wrapper (73 bytes)
   8547    6 %           Test::wrapper @ 17 (73 bytes)
Wrapper:   1.378 s
Primitive: 1.372 s
Wrapper:   1.375 s
Primitive: 1.378 s
Wrapper:   1.373 s
Primitive: 1.375 s
Wrapper:   1.378 s

Note %在第一次迭代时登录编译日志。这意味着这些方法是在 OSR 中编译的(堆栈上替换) https://stackoverflow.com/questions/9105505/differences-between-just-in-time-compilation-and-on-stack-replacement模式。在第二次迭代期间,方法在正常模式下重新编译。此后,从第三次迭代开始,primitive 和wrapper 在执行速度上就没有区别了。

您实际测量的是 OSR 存根的性能。它通常与应用程序的实际性能无关,您不应该太关心它。

但问题仍然存在,为什么包装器的 OSR 存根比原始变量的 OSR 存根编译得更好?为了找到这一点,我们需要深入研究生成的汇编代码:
-XX:CompileOnly=Test -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

我将省略所有不相关的代码,只留下已编译的循环。

原始:

0x00000000023e90d0: vmovsd 0x28(%rsp),%xmm1      <-- load double from the stack
0x00000000023e90d6: vaddsd -0x7e(%rip),%xmm1,%xmm1
0x00000000023e90de: test   %eax,-0x21f90e4(%rip)
0x00000000023e90e4: vmovsd %xmm1,0x28(%rsp)      <-- store to the stack
0x00000000023e90ea: vucomisd 0x28(%rsp),%xmm0    <-- compare with the stack value
0x00000000023e90f0: ja     0x00000000023e90d0

Wrapper:

0x00000000023ebe90: vaddsd -0x78(%rip),%xmm0,%xmm0
0x00000000023ebe98: vmovsd %xmm0,0x10(%rbx)      <-- store to the object field
0x00000000023ebe9d: test   %eax,-0x21fbea3(%rip)
0x00000000023ebea3: vucomisd %xmm0,%xmm1         <-- compare registers
0x00000000023ebea7: ja     0x00000000023ebe90

正如您所看到的,“原始”情况会对堆栈位置进行多次加载和存储,而“包装器”则主要执行寄存器内操作。 OSR 存根引用堆栈的原因是很容易理解的:在解释模式下,局部变量存储在堆栈上,并且 OSR 存根与此解释框架兼容。在“包装器”情况下,值存储在堆上,并且对对象的引用已缓存在寄存器中。

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

Java for循环优化 的相关文章

随机推荐

  • 跨浏览器事件处理

    我需要一个跨浏览器功能来注册事件处理程序和 大部分 一致的处理程序体验 我不需要 jQuery 等库的全部功能或功能 因此我编写了自己的库 我相信我已经用下面的代码实现了我的目标 到目前为止我的测试已经成功 但我已经盯着它太久了 我的逻辑是
  • Android ListView 与简单适配器

    我创建ListView列出从服务器检索的一些数据的活动 这是列出Atm活动 public class ListAtmActivity extends ListActivity private static String url http 1
  • 在 C# 中创建 MS Teams 团队 - AddAsync 返回 null

    Context 我正在使用 C 中的 MS Graph API 创建新的 MS Teams 团队 My code var newTeam new Team DisplayName model DisplayName Description
  • 如何将两个 MySQL 列合并为一列?

    我想转换这个 MySQL 表 title1 title2 type qwe1 qwe2 3 asd1 asd2 7 PHP 中的此表 title type asd1 7 asd2 7 qwe1 3 qwe1 3 但我不知道如何按第一列正确排
  • History.js - 正确的实现

    我使用 JQuery Ajax 在名为 container 的 div 中加载网站上的内容 我必须有不同类型的链接 正常锚链接 JQuery 触发器 当单击特定 div 时触发事件 现在我想添加功能来支持后退和前进浏览器按钮以及书签功能 我
  • 如何加快 MongoDB 视图上的计数

    我一直在排查为什么我创建的 MongoDB 视图如此慢 该视图的目标是transactions集合 并返回具有openBalance大于0 我还运行一些额外的聚合阶段来按照我想要的方式塑造数据 为了加快视图的执行速度 它通过匹配视图聚合管道
  • 服务的任何公共方法的 AOP 切入点表达式

    什么是最简单的切入点表达式 它将拦截所有带有注释的bean的所有公共方法 Service 例如 我希望它会影响该 bean 的两个公共方法 Service public MyServiceImpl implements MyService
  • SVN对单个文件的权限

    SVN 是否可以为单个文件设置写权限 而存储库的其余部分都是只读的 另一种可能性是预提交挂钩脚本 http svnbook red bean com en 1 5 svn ref reposhooks pre commit html
  • 计算特定范围sql server的记录数

    我正在尝试编写一个查询来根据多个不同范围来计算记录数 我成功使用了union 但我觉得有更好的方法来做到这一点 这是我所做的 select count col1 as range1 from tbl1 where col1 lt 15000
  • 将 IOS 应用程序从一个帐户的 Testflight 转移到另一个帐户

    App is 未发表在 App Store 上 但通过以下方式分发给测试人员试飞在以前的帐户上 现在我必须将应用程序转移到新帐户 具有相同的捆绑包标识符 我可以将其放置在具有相同捆绑包标识符的新帐户中吗 转移应用程序的程序是什么 因为它在
  • 如何格式化 MongoEngine PointField 的数据

    所以我想用mongodb中的位置数据做一些实验 所以我写了一些python代码来生成一些测试数据 不幸的是 文档位于http docs mongoengine org apireference html mongoengine fields
  • 获取应用程序共享意图的包名称

    我想获取创建共享意图的应用程序名称或其包名称 我正在制作通过意图接收共享数据的应用程序 如何获取创建者应用程序的包名称 类似于 intent getSourceBackageName EDIT 我不确定我的问题是否清楚 但我需要找出调用我的
  • Git clean 排除嵌套子目录

    我在使用 git clean 和排除嵌套目录选项时遇到问题 我想从存储库中清除所有未提交的文件 不包括vendor bundle为了 我的测试报告如下 debugg dir git file txt not commited file no
  • Javascript整数从零开始[重复]

    这个问题在这里已经有答案了 我有一个关于 javascript 如何解析以零开头的整数 例如银行帐号 的问题 下面是来自 Chrome 开发工具的简单示例 var zeroTest 022 undefined zeroTest 18 为什么
  • R.java 无法为 Android 库应用程序正确生成

    我创建了一个简单的 Android 库应用程序 其中只有 1 个包含 TextView 的活动 它工作正常 但是当我将其标记为库并在另一个应用程序中引用时 当我尝试使用 findViewById R id welcome textview
  • 使用 env('APP_ENV')、config('app.env') 或 App::environment() 获取应用程序环境有什么区别?

    使用上有什么区别env APP ENV config app env or App environment 获取应用程序环境 我知道env APP ENV 愿意 ENV config app env 读取配置并App environment
  • 在 Spring Boot 中为 Undertow 启用 HTTP 2.0

    我想知道如何使用 Spring Boot 为 Undertow 启用 HTTP 2 0 我监控了该协议 目前 HTTPS 使用的是 1 1 有什么财产可以用吗 或者我应该使用此选项创建一个 EmbeddedServletContainerF
  • Python isnumeric 函数仅适用于 unicode

    我正在尝试使用以下命令检查字符串是否为数字isnumeric功能正常 但结果并不如预期 该函数仅在它是 unicode 字符串时才起作用 gt gt gt a u 1 gt gt gt a isnumeric True gt gt gt a
  • ajax 自动完成扩展器不起作用

    我在文本框中有一个自动完成扩展器 它将记录显示为数据库中的列表 但是当我单击文本框并开始输入任何内容时 却没有发生任何事情 我的html代码是
  • Java for循环优化

    我用 java for 循环做了一些运行时测试 并发现了一个奇怪的行为 对于我的代码 我需要原始类型 如 int double 等 的包装对象来模拟 io 和输出参数 但这不是重点 只要看我的代码即可 具有字段访问的对象如何比原始类型更快