【问题标题】:Why does the execution timing of certain functions vary greatly?为什么某些函数的执行时间差别很大?
【发布时间】:2013-11-02 23:09:31
【问题描述】:

我的代码:

import java.math.BigDecimal;
public class ScienceFair {

private static long NewtonMethod()
{
    BigDecimal TWO = new BigDecimal(2);
    BigDecimal SQRT_TWO = new BigDecimal("1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727");
    BigDecimal TOLERANCE = BigDecimal.ONE.scaleByPowerOfTen(-100);

    long start = System.nanoTime();

    BigDecimal a = new BigDecimal(1);

    while(a.subtract(SQRT_TWO).abs().compareTo(TOLERANCE) >= 0) {
        a = a.add(TWO.divide(a, 100, BigDecimal.ROUND_HALF_UP)).divide(TWO);
    }

    return System.nanoTime() - start;
}

private static long MidpointMethod()
{
    BigDecimal TWO = new BigDecimal(2);
    BigDecimal SQRT_TWO = new BigDecimal("1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727");
    BigDecimal TOLERANCE = BigDecimal.ONE.scaleByPowerOfTen(-100);

    long start = System.nanoTime();

    BigDecimal a = new BigDecimal(1);
    BigDecimal b = new BigDecimal(2);
    while(a.add(b).divide(TWO).subtract(SQRT_TWO).abs().compareTo(TOLERANCE) >= 0) {
        if(a.multiply(a).subtract(TWO).abs().compareTo(b.multiply(b).subtract(TWO).abs()) == 1)
        {
            a = a.add(b).divide(TWO);
        }
        else
        {
            b = a.add(b).divide(TWO);
        }
    }
    return System.nanoTime() - start;
}
private static long SecantMethod()
{
    BigDecimal TWO = new BigDecimal(2);
    BigDecimal SQRT_TWO = new BigDecimal("1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727");
    BigDecimal TOLERANCE = BigDecimal.ONE.scaleByPowerOfTen(-100);

    long start = System.nanoTime();

    BigDecimal a = new BigDecimal(1);
    BigDecimal b = new BigDecimal(2);
    BigDecimal b_old = new BigDecimal(2);
    while(a.add(b).divide(TWO).subtract(SQRT_TWO).abs().compareTo(TOLERANCE) >= 0) {
        b_old = b;
        b = a.multiply(b).add(TWO).divide(a.add(b), 100, BigDecimal.ROUND_HALF_UP);
        a = b_old;
    }

    return System.nanoTime() - start;
}

public static void main(String[] args) {
    double a = 0;
    int trials = 100;
    for(int i=1; i<= trials; i++)
    {
        a += (NewtonMethod() / 10e6);
    }
    System.out.printf("Newton's Method: %f\n", a/trials);
    a = 0;
    for(int i=1; i<= trials; i++)
    {
        a += (MidpointMethod() / 10e6);
    }
    System.out.printf("Midpoint Method: %f\n", a/trials);
    a = 0;
    for(int i=1; i<= trials; i++)
    {
        a += (SecantMethod() / 10e6);
    }
    System.out.printf("Secant Method: %f\n", a/trials);
}
}

旨在运行牛顿法、中点法和正割法,以找出逼近小数点后 2 到 100 位的平方根所需的时间。

它对每个测试运行 100 次试验,然后对它们进行平均以输出所用的毫秒数。

中点法总是在 1.5 秒左右。但是,牛顿法和正割法相差很大,从 0.08 到 0.14 秒(大约一倍)不等。为什么会这样?

编辑:这是我现在尝试的(仅适用于 NewtonMethod)

public class ScienceFairTwo {
public static void main(String[] args) throws Exception {
Runnable NewtonMethod = new Runnable()          
 {
        public void run()
        {
                BigDecimal TWO = new BigDecimal(2);
                BigDecimal SQRT_TWO = new BigDecimal("1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727");
                BigDecimal TOLERANCE = BigDecimal.ONE.scaleByPowerOfTen(-100);

                long start = System.nanoTime();
                BigDecimal a = new BigDecimal(1);

                while(a.subtract(SQRT_TWO).abs().compareTo(TOLERANCE) >= 0) {
                    a = a.add(TWO.divide(a, 100, BigDecimal.ROUND_HALF_UP)).divide(TWO);
                }
        }
    };
        System.out.println("Newton's Method: " + new Benchmark(NewtonMethod));
}
}

【问题讨论】:

  • 尝试在不测量的情况下运行 1000 次试验,然后在测量的情况下运行 1000 次试验。 JIT 起着重要的作用,它需要多次执行来检测热点并编译它们。此外,您的测试分配了许多对象,因此 GC 可能会启动缓慢的测试之一。我建议使用像 Caliper 这样的真正的微基准测试框架。

标签: java performance-testing


【解决方案1】:

一般来说,对 Java 代码进行基准测试是很困难的。您的简单基准测试不够可靠。

我可以看到您的方法存在三个问题(可能还有更多):

  1. 您没有让 JVM “热身”。当相同的代码运行多次时,JVM 开始优化事物。例如内联和JIT 开始启动。
  2. 在您的时间测量期间,GC 可以打开。它会严重影响结果。
  3. JVM 仅在第一次使用类时才加载它们。所以,使用java.math.BigDecimal 的第一个方法是不走运的,因为它浪费时间将该类加载到内存中。其他方法没有此惩罚 - BigDecimal 已加载。

我推荐你两件事:

  1. 阅读这篇精彩的文章:Robust Java benchmarking
  2. 使用可靠的 Java 基准测试框架 - Caliper。它会自动生成在线可见的漂亮报告 - an example 指的是 question

(当您正确执行基准测试时 - 在评论中发布链接。我想看看您的方法如何执行)

【讨论】:

  • 嗨。我只尝试了牛顿迭代,并得到一个错误,说基准无法解析为一个类型。在问题中查看我的编辑
  • 1.确保正确导入 Benchmark 类并引用 Caliper jar。 2.看这个example,应该有方法timeNewtontimeMidpointtimeSecant
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-29
  • 2019-04-05
  • 2020-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多