文字的算术运算是在编译时还是运行时计算的?

Posted

技术标签:

【中文标题】文字的算术运算是在编译时还是运行时计算的?【英文标题】:Are arithmetic operations on literals calculated at compile time or run time? 【发布时间】:2015-10-09 14:20:56 【问题描述】:

我有以下几点:

double timeInMinutes = (double) timeInMilliseconds / (1000 * 60);

操作(1000 * 60) 是在编译时还是在运行时完成的?换句话说,上面的代码sn-p和以下代码在运行时是否存在性能差异:

double timeInMinutes = (double) timeInMilliseconds / 60000;

编辑:我的问题与Will the Java compiler precalculate sums of literals? 不同,因为我在算术运算中混合使用变量和文字。这是一个很小的差异,但正如 @TagirValeev 在 cmets (Are arithmetic operations on literals calculated at compile time or run time?) 中指出的那样,在某些情况下,即使可以预编译某些文字。

【问题讨论】:

查找常量表达式。 根据答案,相关:***.com/questions/2012528/… 这是编译器可以实现的最简单的优化之一。即使是旧的“未优化”VB3-VB6 也曾经这样做过(不确定早期版本,他们可能也这样做过)。 Will the Java compiler precalculate sums of literals?的可能重复 您误解了 TagirVallev 的观点:(double) time / 1000 / 60 不是对文字的操作,并且出于浮点原因,甚至不能期望给出与 (double) time / (1000 * 60) 相同的结果。 【参考方案1】:

根据JLS §15.2 - Forms of Expressions

有些表达式的值可以在编译时确定。 这些是常量表达式(第 15.28 节)。

*, /, and % 这样的乘法运算符属于常量表达式,因此将在编译时确定。

@SergeyMorozov 比我更快地编写和获得字节码证明 (#2 = Integer 60000) 但这是实际证明,以上是理论/官方声明:

尝试使用1000 * 6060000 在您的最后生成字节码,您将看到相同的字节码指令,因此会有相同的运行时性能。

Java 类:

public class Test 
    public static void main(String[] args) 
        int compileTest = 1000 * 60;
    

字节码:

Classfile /E:/Test.class
  Last modified Oct 9, 2015; size 265 bytes
  MD5 checksum fd115be769ec6ef7995e4c84f7597d67
  Compiled from "Test.java"
public class Test
  SourceFile: "Test.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#13         //  java/lang/Object."<init>":()V
   #2 = Integer            60000
   #3 = Class              #14            //  Test
   #4 = Class              #15            //  java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               SourceFile
  #12 = Utf8               Test.java
  #13 = NameAndType        #5:#6          //  "<init>":()V
  #14 = Utf8               Test
  #15 = Utf8               java/lang/Object

  public Test();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // int 60000
         2: istore_1
         3: return
      LineNumberTable:
        line 3: 0
        line 4: 3

【讨论】:

您的示例int compileTest = 1000 * 60; 与问题有点不同;我将算术与变量和文字混合在一起。阅读所有答案,它可能不会改变结果,但只是想注意它略有不同。 @neverendingqs 我举了一个例子来回答你的问题“操作(1000 * 60)是在编译时还是在运行时完成的?”......无论如何,关键是除了理论之外,您还可以生成字节码并最终验证.. @hagrawal:我认为 OP 的问题可以更好地表述为“当常量表达式作为更大的非常量表达式的一部分出现时,它们是否仍会被优化?” @Hurkyl 当我们回答他的问题时,他的问题很明确 - “操作 (1000 * 60) 是在编译时还是在运行时完成的?”,以及这就是我们的回答。但是,是的,它可以像你说的那样扩大,我认为 OP 已经编辑了他的问题。 很高兴对问题标题或正文进行任何编辑。考虑到不同的因素,我不确定如何接受答案。【参考方案2】:

在编译时。这是最基本的编译器优化之一,称为Constant Folding。

【讨论】:

可能值得注意的是,timeInMilliseconds / 1000 / 60 将在运行时计算。 @TagirValeev 可能值得添加您自己的答案或编辑现有答案之一以指出这一点。 @TagirValeev 决定试试你所说的,看起来它是在运行时计算的:pastebin.com/D2RF0ygF。 timeInMilliseconds / (1000 / 60) 不过还好。【参考方案3】:

只需创建类 Test

public class Test 
    public static void main(String [] args) 
        long timeInMilliseconds = System.currentTimeMillis();
        double timeInMinutes = (double) timeInMilliseconds / (1000 * 60);
        System.out.println(timeInMinutes);
    

并使用命令反编译:javap -v Test

可以看到反编译类的输出:

  public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
  stack=4, locals=5, args_size=1
     0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
     3: lstore_1
     4: lload_1
     5: l2d
     6: ldc2_w        #3                  // double 60000.0d
     9: ddiv
    10: dstore_3
    11: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
    14: dload_3
    15: invokevirtual #6                  // Method java/io/PrintStream.println:(D)V
    18: return
  LineNumberTable:
    line 3: 0
    line 4: 4
    line 5: 11
    line 6: 18

看看第 6 行:ldc2_w #3 // double 60000.0d

【讨论】:

以上是关于文字的算术运算是在编译时还是运行时计算的?的主要内容,如果未能解决你的问题,请参考以下文章

python基础学习日记注释算术运算符变量的基本使用

从C ++代码中提取算术运算信息[关闭]

8 位和 16 位整数的算术运算

jsonnet 和 Kubernetes

vb 的取余运算符是怎么算的

76.算术运算符