草庐IT

Java Math.min/max 性能

coder 2023-05-17 原文

编辑:maaartinus 给出了我一直在寻找的答案,并且 tmyklebu 关于该问题的数据有很大帮助,所以感谢两位! :)

我读过一些关于 HotSpot 如何在代码中注入(inject)一些“内在函数”的文章,特别是对于 Java 标准数学库 (from here)

所以我决定试一试,看看 HotSpot 与直接进行比较相比有何不同(特别是因为我听说 min/max 可以编译为无分支 asm)。

public class OpsMath {
    public static final int max(final int a, final int b) {
        if (a > b) {
            return a;
        }
        return b;
    }
}

这是我的实现。从另一个 SO question 我读到使用三元运算符使用额外的寄存器,我没有发现执行 if block 和使用三元运算符(即 return ( a > b ) ? a : b )之间的显着差异。

分配一个 8Mb 的 int 数组(即 200 万个值),并将其随机化,我做了以下测试:

try ( final Benchmark bench = new Benchmark( "millis to max" ) )
    {
        int max = Integer.MIN_VALUE;

        for ( int i = 0; i < array.length; ++i )
        {
            max = OpsMath.max( max, array[i] );
            // max = Math.max( max, array[i] );
        }
    }

我在 try-with-resources block 中使用 Benchmark 对象。当它完成时,它在对象上调用 close() 并打印 block 完成的时间。测试是通过在上面的代码中注释入/出最大调用来单独完成的。

'max' 被添加到基准 block 之外的列表中并稍后打印,以避免 JVM 优化整个 block 。

每次测试运行时数组都是随机的。

运行 6 次测试,结果如下:

Java 标准数学:

millis to max 9.242167 
millis to max 2.1566199999999998
millis to max 2.046396 
millis to max 2.048616  
millis to max 2.035761
millis to max 2.001044 

第一次运行后相当稳定,再次运行测试会得到类似的结果。

运维数学:

millis to max 8.65418 
millis to max 1.161559  
millis to max 0.955851 
millis to max 0.946642 
millis to max 0.994543 
millis to max 0.9469069999999999 

再次,第一次运行后的结果非常稳定。

问题是:为什么?这有很大的不同。我不知道为什么。即使我像 Math.max() 一样完全实现我的 max() 方法(即 return (a >= b) ? a : b )我仍然会得到更好的结果!没有意义。

规范:

CPU:英特尔 i5 2500, 3,3Ghz。 Java 版本:JDK 8(3 月 18 日公开发布),x64。 Debian Jessie(测试版)x64。

我还没有尝试使用 32 位 JVM。

编辑:按要求进行独立测试。添加了一行以强制 JVM 预加载 Math 和 OpsMath 类。这消除了 OpsMath 测试第一次迭代的 18 毫秒成本。

// Constant nano to millis.
final double TO_MILLIS = 1.0d / 1000000.0d;
// 8Mb alloc.
final int[] array = new int[(8*1024*1024)/4];
// Result and time array.
final ArrayList<Integer> results = new ArrayList<>();
final ArrayList<Double> times = new ArrayList<>();
// Number of tests.
final int itcount = 6;
// Call both Math and OpsMath method so JVM initializes the classes.
System.out.println("initialize classes " + 
OpsMath.max( Math.max( 20.0f, array.length ), array.length / 2.0f ));
    
final Random r = new Random();
for ( int it = 0; it < itcount; ++it )
{
    int max = Integer.MIN_VALUE;
    
    // Randomize the array.
    for ( int i = 0; i < array.length; ++i )
    {
        array[i] = r.nextInt();
    }
    
    final long start = System.nanoTime();
    for ( int i = 0; i < array.length; ++i )
    {
        max = Math.max( array[i], max );
            // OpsMath.max() method implemented as described.
        // max = OpsMath.max( array[i], max );
    }
    // Calc time.
    final double end = (System.nanoTime() - start);
    // Store results.
    times.add( Double.valueOf( end ) );
    results.add( Integer.valueOf(  max ) );
}
// Print everything.
for ( int i = 0; i < itcount; ++i )
{
    System.out.println( "IT" + i + " result: " + results.get( i ) );
    System.out.println( "IT" + i + " millis: " + times.get( i ) * TO_MILLIS );
}

Java Math.max 结果:

IT0 result: 2147477409
IT0 millis: 9.636998
IT1 result: 2147483098
IT1 millis: 1.901314
IT2 result: 2147482877
IT2 millis: 2.095551
IT3 result: 2147483286
IT3 millis: 1.9232859999999998
IT4 result: 2147482828
IT4 millis: 1.9455179999999999
IT5 result: 2147482475
IT5 millis: 1.882047

OpsMath.max 结果:

IT0 result: 2147482689
IT0 millis: 9.003616
IT1 result: 2147483480
IT1 millis: 0.882421
IT2 result: 2147483186
IT2 millis: 1.079143
IT3 result: 2147478560
IT3 millis: 0.8861169999999999
IT4 result: 2147477851
IT4 millis: 0.916383
IT5 result: 2147481983
IT5 millis: 0.873984

总体结果仍然相同。我尝试只对数组进行一次随机化,并在同一个数组上重复测试,总体上我得到了更快的结果,但 Java Math.max 和 OpsMath.max 之间的差异相同 2 倍。

最佳答案

很难说为什么 Math.maxOps.max 慢,但很容易解释为什么这个基准测试强烈支持分支到条件移动:在n-第一次迭代,概率

Math.max( array[i], max );

不等于maxarray[n-1] 大于所有先前元素的概率。显然,这个概率随着 n 和给定

的增长而变得越来越低
final int[] array = new int[(8*1024*1024)/4];

大部分时间都可以忽略不计。条件移动指令对分支概率不​​敏感,它总是花费相同的时间来执行。条件移动指令比分支预测更快如果分支很难预测。另一方面,如果可以以高概率很好地预测分支,则分支预测会更快。目前,与分支的最佳和最坏情况相比,我不确定条件移动的速度。1

在您的情况下,除了前几个分支之外的所有分支都是相当可预测的。从大约 n == 10 开始,使用条件移动毫无意义,因为可以保证正确预测分支并且可以与其他指令并行执行(我猜你每次迭代都需要一个周期)。

这似乎发生在计算最小值/最大值或进行一些低效排序的算法(良好的分支可预测性意味着每步的熵低)。


1 条件移动和预测分支都需要一个周期。前者的问题在于它需要两个操作数,这需要额外的指令。最后,当分支单元空闲时,关键路径可能会变得更长和/或 ALU 饱和。通常,但并非总是如此,在实际应用中可以很好地预测分支;这就是最初发明分支预测的原因。

关于时间条件移动与分支预测最佳和最坏情况的详细信息,请参阅下面的评论中的讨论。我的 my own benchmark表明当分支预测遇到最坏情况时,条件移动明显快于分支预测,但我不能忽略 contradictory results .我们需要一些解释究竟是什么造成了差异。更多的基准和/或分析可能会有所帮助。

关于Java Math.min/max 性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22752198/

有关Java Math.min/max 性能的更多相关文章

  1. ruby-on-rails - 事件记录 : Select max of limit - 2

    我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).

  2. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  3. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  4. ruby-on-rails - 如果条件与 &&,是否有任何性能提升 - 2

    如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:

  5. ruby - 如何找到我的 Ruby 应用程序中的性能瓶颈? - 2

    我编写了一个Ruby应用程序,它可以解析来自不同格式html、xml和csv文件的源中的大量数据。我如何找出代码的哪些区域花费的时间最长?有没有关于如何提高Ruby应用程序性能的好资源?或者您是否有任何始终遵循的性能编码标准?例如,你总是用加入你的字符串吗?output=String.newoutput或者你会使用output="#{part_one}#{part_two}\n" 最佳答案 好吧,有一些众所周知的做法,例如字符串连接比“#{value}”慢得多,但是为了找出您的脚本在哪里消耗了大部分时间或比所需时间更多,您需要进行分

  6. STM32的HAL和LL库区别和性能对比 - 2

    LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L

  7. javascript - jQuery 的 jquery-1.10.2.min.map 正在触发 404(未找到) - 2

    我看到有关未找到文件min.map的错误消息:GETjQuery'sjquery-1.10.2.min.mapistriggeringa404(NotFound)截图这是从哪里来的? 最佳答案 如果ChromeDevTools报告.map文件的404(可能是jquery-1.10.2.min.map、jquery.min.map或jquery-2.0.3.min.map,但任何事情都可能发生)首先要知道的是,这仅在使用DevTools时才会请求。您的用户不会遇到此404。现在您可以修复此问题或禁用sourcemap功能。修复:获取文

  8. ruby - GC.disable 的任何性能缺点? - 2

    是否存在GC.disable会降低性能的情况?只要我使用的是真正的RAM而不是交换内存,就可以这样做吗?我正在使用MRIRuby2.0,据我所知,它是64位的,并且使用的是64位的Ubuntu:ruby2.0.0p0(2013-02-24revision39474)[x86_64-linux]Linux[redacted]3.2.0-43-generic#68-UbuntuSMPWedMay1503:33:33UTC2013x86_64x86_64x86_64GNU/Linux 最佳答案 GC.disable将禁用垃圾回收。像rub

  9. ruby-on-rails - Rails with angular 与 Rails pure(查看性能) - 2

    我尝试在Internet上搜索有关使用angularJS进入RubyonRails项目与RubyonRailspure的View性能的信息。我的问题是因为2个月前我开始使用纯AngularJS,现在我需要将AngularJS集成到一个新项目中,但需要展示使用带有RubyonRails的AngularJS呈现View的性能如何,并消除对RubyonRails的负担.例如:带Rails的Angular:使用RubyonRails获取数据(从数据库或GET请求),将信息发送到file.js.erb并使用AngularJS操作数据并显示带有解析数据的View。纯粹的Rails:(自然流程)使用

  10. ruby-on-rails - 在 Rails 3 应用程序中使用 require_dependency 对性能有何影响? - 2

    我觉得我理解require和require_dependency之间的区别(来自Howarerequire,require_dependencyandconstantsreloadingrelatedinRails?)。但是,我想知道如果我使用一些不同的方法(参见http://hemju.com/2010/09/22/rails-3-quicktip-autoload-lib-directory-including-all-subdirectories/和Bestwaytoloadmodule/classfromlibfolderinRails3?)来加载所有文件会发生什么,所以我们:

随机推荐