【问题标题】:Performance paranoia: how much expensive are Float.parseFloat(String), Integer.parseInt(String)?性能偏执狂:Float.parseFloat(String)、Integer.parseInt(String) 到底有多贵?
【发布时间】:2011-07-29 18:33:35
【问题描述】:

我最近使用 JSON 来存储同一类的一定数量的子类的配置参数。为了保持统一的接口,我为父类提供了公共void setParameter(String, String)String getParameter(String) 方法。然后,每个子类将提供的参数转换为其本机类型,并使用它们进行某种计算。

现在,我想知道:既然我已经将每个参数存储在 HashMap 中,那么为每个参数保留一个具有正确类型的单独字段真的有意义吗?考虑到我需要经常使用它们,每次我需要它们时将 String 参数转换为其本机类型的计算开销是多少?

谢谢
图努兹

【问题讨论】:

  • 你也可以使用 Float.valueOf() 来代替 Float.parseFloat() 它会提高一点性能,因为它使用了一个简单的缓存。
  • @bvk256: Float.valueOf 不做任何缓存。 javadoc 建议这样做,但如果您查看源代码(至少在 Sun JDK 中),它不会。它只是创建一个新实例。整数类型的 valueOf 方法可以,但浮点类型没有。

标签: java performance interface types casting


【解决方案1】:

我建议你测试一下。如果您需要多次执行此操作,这是一个相当昂贵的操作,但如果您首先使用它们来创建数据,它可能比 Double.toString() 或 Integer.toString() 便宜。

我还建议您只使用double,除非您知道使用float 永远不会导致舍入问题。 ;)

它与创建对象(如 String)或向 HashMap 添加条目的成本差不多。除非您也打算避免这样做,否则我不会担心。

编辑:类似于@Stackers 的基准测试,我会运行更长时间的测试并使用 nanoTime()

int runs = 10000000;
String val = "" + Math.PI;
long start = System.nanoTime();
for (int i = 0; i < runs; i++)
    Float.parseFloat(val);
long time = (System.nanoTime() - start) / runs;
System.out.println("Average Float.parseFloat() time was " + time + " ns.");

long start2 = System.nanoTime();
for (int i = 0; i < runs; i++)
    Double.parseDouble(val);
long time2 = (System.nanoTime() - start2) / runs;
System.out.println("Average Double.parseDouble() time was " + time2 + " ns.");

打印

Average Float.parseFloat() time was 474 ns.
Average Double.parseDouble() time was 431 ns.

顺便说一句:我有一个从直接 ByteBuffer 读取双精度数据的函数,需要 80 ns。它更快,因为它不需要 String 并且不创建任何对象。但是,这样做绝非易事,您必须设计核心系统以避免任何对象创建。 ;)

【讨论】:

    【解决方案2】:

    测量很简单:

    public class PerfTest {
        public static void main(String[] args) {
            String val = "" + (float) Math.PI;
            long start = System.currentTimeMillis();
            for ( int i = 0 ; i < 100000 ; i++ ) {
                Float.parseFloat( val );
            }
            System.out.println( System.currentTimeMillis() - start + " ms." );
        }
    }
    

    100.000 次迭代为 62 毫秒。

    【讨论】:

    • +1:我会运行更长时间的测试,例如至少多秒,使用 nanoTime() 并打印平均时间。
    • 并在开始实际测量之前进行试运行,这样您就不会测量任何 JIT 开销。编辑:
    • ...将您的基准测试放在一个单独的函数中(使用@PeterLawrey 的 nanoTime() 建议和 1000 万次迭代)并调用它两次,第一次运行始终比第二次慢(~20%)在我的慢速 x86 上网本上运行。
    • @pauluss86 原因可能是虚拟机(JIT)执行的优化
    • @stacker 感谢您的回复,没想到这种可能性。但最终结果仍然相同:多次执行测试以更好地了解实际性能。
    【解决方案3】:

    fwiw 上面的微基准测试似乎有点不安全,你会期望热点发现 val 永远不会改变并且永远不会被使用。要记住的另一件事是,有时(2 个实现的)平均值可能在绝对值上很接近,但与另一个相比,1 的尾部成本非常糟糕,例如您的第 90 个百分位值可能非常相似,但最后 10% 的值要差得多。

    例如,每次将其更改为使用不同的值并将该值转储到 stderr 会在我的盒子上产生更高的平均成本(~3300ns vs~2500ns,对于重复使用该值的情况)。这比其他帖子要高得多,大概是因为实际获得时间需要一些时间,因此测量值被人为夸大了。不过,这只是说明了做一个好的微基准测试的困难之一。

    可能还值得注意的是,我无法衡量我建议的效果可能存在,例如如果它存在,那么您可能会期望它会完全优化。如果你真的很感兴趣,我想你可以通过LogCompilation 看到发生了什么。

        int runs = 10000000;
        long totalTime = 0;
        for (int i = 0; i < runs; i++) {
            String val = "" + Math.random();
            long start = System.nanoTime();
            float f = Float.parseFloat(val);
            long end = System.nanoTime();
            System.err.println(f);
            totalTime += (end-start);
        }
        long time = totalTime / runs;
        totalTime = 0;
        for (int i = 0; i < runs; i++) {
            String val = "" + Math.random();
            long start = System.nanoTime();
            double d = Double.parseDouble(val);
            long end = System.nanoTime();
            System.err.println(d);
            totalTime += (end-start);
        }
        long time2 = totalTime / runs;
        System.out.println("Average Float.parseFloat() time was " + time + " ns.");
        System.out.println("Average Double.parseDouble() time was " + time2 + " ns.");
    

    【讨论】:

      猜你喜欢
      • 2012-12-31
      • 2021-12-26
      • 2012-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多