函数调用与i+1、++i的实现有区别吗?
Function call with i + 1, ++i implementation difference?
关于 Java 的快速说明,让我先说一下:由于性能优化,我并不担心这个,只是对一般幕后的东西感到好奇。由于我对此一无所获,因此我假设它们在各个方面都是等效的,但只是为了确定:
在递归函数中 foo(int i)
,或者我猜一般在函数调用中,foo(++i)
和 foo(i + 1)
之间有区别吗?我知道结果是一样的,但我认为带有 ++i
的那个可能会在后台做一些额外的工作,因为它是一项任务?所以也许它会在后台创建一个 (anonymous field) = i
,然后递增然后引用?
还是编译器只是将 ++i
优化为 i + 1
?
如您所见,我的理解有点模糊,因此不胜感激。
编辑澄清:
起初这是由于递归函数,例如
public static int foo(int i) {
return (i >= 4) ? 0
: i + foo(++i);
}
“一般功能”部分是在编写问题的过程中出现的,并且如前所述,这使它变得模棱两可等等。希望这能澄清一切。
如果在foo(++i)
之后使用i
,则值会被改变
如果您在 foo(i + 1)
之后处理 i
,则该值不会更改
foo(int i)
{
//process stuff A
foo(i + 1);
//process stuff B
}
bar(int i)
{
//process stuff A
bar(i + 1);
//process stuff B
}
根据 //process stuff B
中的内容,您可能想要使用 i + 1
并使 i
的值相同,或者 ++i
并使 i
递增
foo(++i)
和foo(i + 1)
的执行没有区别:传给foo
的参数值是一样的
但是,在前一种情况下,局部变量i
增加了1;在后一种情况下,它保留其原始值。
所以如果你对同一个方法调用两次,就会有区别:
foo(++i);
foo(++i); // Invoke with i 1 greater than before.
不同于
foo(i + 1);
foo(i + 1); // Invoke with the same argument
因此,一般来说,编译器无法优化任何东西,因为它们具有不同的语义。
除了关于使用前缀增量后 i
的值的其他评论。生成的字节码略有不同。请参阅下面的简化结果。
foo(++i) 编译为
iinc
iload_1
invokestatic
foo(i + 1) 编译为
iload_1
iconst_1
iadd
invokestatic
是的,++i由加法和assignment.Since两个操作组成,以后就不需要i了,i+1比++i好用
如果答案不是关于语义而是关于 IR 优化后机器级别的性能,翻译成机器指令,我会说,"no unless measured and proven otherwise."
在 f(++i)
和 f(i+1)
之间进行所有优化后,性能差异不太可能存在,假设您的代码实际上可以将它们视为替代方案(假设i
的状态在函数调用后不再相关。
这只是基本的硬件和编译器设计,已经存储在寄存器中的内存指令的成本,甚至在半能编译器中将此代码优化为相同机器代码的简单性(我认为Java 的 JIT 至少是这样)。识别不必要的副作用并彻底消除它们是编译器的一个非常基本的特性(这实际上是人工微观基准可能产生误导的原因之一,除非它们以防止优化器跳过某些代码的方式非常仔细地编写彻底)。最容易消除的副作用之一是这样的情况,我们正在递增一个变量,i
但不依赖于之后的状态变化。
似乎不太可能对性能产生任何实际影响。当然,最终的方法是查看最终生成的机器代码(不是字节码 IR,而是实际的最终机器代码)或对其进行测量和分析。但至少可以说,如果一个比另一个快,我会感到非常震惊,这会让我认为编译器在指令选择或寄存器分配方面做得很差。
就是说,如果一个人实际上(偶然机会)比另一个人快,我认为你把它弄反了。 ++i
可能需要更少的工作,因为它可以增加寄存器中的值。 ++i
是对一个操作数的一元运算,适用于可变寄存器。 i + 1
是一个表达式,它要求 i
被视为不可变的,并且会调用第二个寄存器(但仅在一种没有优化任何东西的非常可怕的玩具编译器场景中,尽管在那种情况下我们也在函数调用的上下文中编译这个表达式,并且必须考虑堆栈溢出之类的事情,所以即使是玩具编译器也可能使这些有些等价)。
关于 Java 的快速说明,让我先说一下:由于性能优化,我并不担心这个,只是对一般幕后的东西感到好奇。由于我对此一无所获,因此我假设它们在各个方面都是等效的,但只是为了确定:
在递归函数中 foo(int i)
,或者我猜一般在函数调用中,foo(++i)
和 foo(i + 1)
之间有区别吗?我知道结果是一样的,但我认为带有 ++i
的那个可能会在后台做一些额外的工作,因为它是一项任务?所以也许它会在后台创建一个 (anonymous field) = i
,然后递增然后引用?
还是编译器只是将 ++i
优化为 i + 1
?
如您所见,我的理解有点模糊,因此不胜感激。
编辑澄清:
起初这是由于递归函数,例如
public static int foo(int i) {
return (i >= 4) ? 0
: i + foo(++i);
}
“一般功能”部分是在编写问题的过程中出现的,并且如前所述,这使它变得模棱两可等等。希望这能澄清一切。
如果在foo(++i)
之后使用i
,则值会被改变
如果您在 foo(i + 1)
之后处理 i
,则该值不会更改
foo(int i)
{
//process stuff A
foo(i + 1);
//process stuff B
}
bar(int i)
{
//process stuff A
bar(i + 1);
//process stuff B
}
根据 //process stuff B
中的内容,您可能想要使用 i + 1
并使 i
的值相同,或者 ++i
并使 i
递增
foo(++i)
和foo(i + 1)
的执行没有区别:传给foo
的参数值是一样的
但是,在前一种情况下,局部变量i
增加了1;在后一种情况下,它保留其原始值。
所以如果你对同一个方法调用两次,就会有区别:
foo(++i);
foo(++i); // Invoke with i 1 greater than before.
不同于
foo(i + 1);
foo(i + 1); // Invoke with the same argument
因此,一般来说,编译器无法优化任何东西,因为它们具有不同的语义。
除了关于使用前缀增量后 i
的值的其他评论。生成的字节码略有不同。请参阅下面的简化结果。
foo(++i) 编译为
iinc
iload_1
invokestatic
foo(i + 1) 编译为
iload_1
iconst_1
iadd
invokestatic
是的,++i由加法和assignment.Since两个操作组成,以后就不需要i了,i+1比++i好用
如果答案不是关于语义而是关于 IR 优化后机器级别的性能,翻译成机器指令,我会说,"no unless measured and proven otherwise."
在 f(++i)
和 f(i+1)
之间进行所有优化后,性能差异不太可能存在,假设您的代码实际上可以将它们视为替代方案(假设i
的状态在函数调用后不再相关。
这只是基本的硬件和编译器设计,已经存储在寄存器中的内存指令的成本,甚至在半能编译器中将此代码优化为相同机器代码的简单性(我认为Java 的 JIT 至少是这样)。识别不必要的副作用并彻底消除它们是编译器的一个非常基本的特性(这实际上是人工微观基准可能产生误导的原因之一,除非它们以防止优化器跳过某些代码的方式非常仔细地编写彻底)。最容易消除的副作用之一是这样的情况,我们正在递增一个变量,i
但不依赖于之后的状态变化。
似乎不太可能对性能产生任何实际影响。当然,最终的方法是查看最终生成的机器代码(不是字节码 IR,而是实际的最终机器代码)或对其进行测量和分析。但至少可以说,如果一个比另一个快,我会感到非常震惊,这会让我认为编译器在指令选择或寄存器分配方面做得很差。
就是说,如果一个人实际上(偶然机会)比另一个人快,我认为你把它弄反了。 ++i
可能需要更少的工作,因为它可以增加寄存器中的值。 ++i
是对一个操作数的一元运算,适用于可变寄存器。 i + 1
是一个表达式,它要求 i
被视为不可变的,并且会调用第二个寄存器(但仅在一种没有优化任何东西的非常可怕的玩具编译器场景中,尽管在那种情况下我们也在函数调用的上下文中编译这个表达式,并且必须考虑堆栈溢出之类的事情,所以即使是玩具编译器也可能使这些有些等价)。