可能重复:
为什么.net/C#不会消除尾递归?
请使用以下C#代码:
using System;
namespace TailTest
{
class MainClass
{
public static void Main (string[] args)
{
Counter(0);
}
static void Counter(int i)
{
Console.WriteLine(i);
if (i < int.MaxValue) Counter(++i);
}
}
}
Run Code Online (Sandbox Code Playgroud)
C#编译器(无论如何)我会将Counter方法编译成以下CIL:
.method private static hidebysig default void Counter (int32 i) cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call void class [mscorlib]System.Console::WriteLine(int32)
IL_0006: ldarg.0
IL_0007: ldc.i4 2147483647
IL_000c: bge IL_0019
IL_0011: ldarg.0
IL_0012: ldc.i4.1
IL_0013: add
IL_0014: call void class TailTest.MainClass::Counter(int32)
IL_0019: ret
}
Run Code Online (Sandbox Code Playgroud)
上面代码的问题是它会导致堆栈溢出(在我的硬件上约为i = …
返回值优化(RVO)是一种涉及复制省略的优化技术,它消除了在某些情况下为保存函数返回值而创建的临时对象.我一般都了解RVO的好处,但我有几个问题.
该标准在本工作草案的第12段第32段(强调我的)中说明了以下内容.
当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用.在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间.没有优化就被破坏了.
然后,当实现可以执行此优化时,它会列出许多条件.
关于这种潜在的优化,我有几个问题:
我习惯于约束优化,以至于它们无法改变可观察的行为.此限制似乎不适用于RVO.我是否需要担心标准中提到的副作用?是否存在可能导致问题的角落案例?
作为程序员,我需要做什么(或不做)才能执行此优化?例如,以下是否禁止使用复制省略(由于move):
std::vector<double> foo(int bar){
std::vector<double> quux(bar,0);
return std::move(quux);
}
Run Code Online (Sandbox Code Playgroud)
我将此作为一个新问题发布,因为我提到的具体问题在其他相关问题中没有直接回答.
有时将复杂或长表达式分成多个步骤是明智的(例如,第二个版本不是更清楚,但它只是一个例子):
return object1(object2(object3(x)));
Run Code Online (Sandbox Code Playgroud)
可以写成:
object3 a(x);
object2 b(a);
object1 c(b);
return c;
Run Code Online (Sandbox Code Playgroud)
假设所有3个类都实现了以rvalue作为参数的构造函数,第一个版本可能更快,因为临时对象被传递并可以移动.我假设在第二个版本中,局部变量被认为是左值.但是如果以后没有使用变量,那么C++ 11编译器是否会优化代码,因此变量被认为是rvalues,两个版本的工作方式完全相同?我最感兴趣的是Visual Studio 2013的C++编译器,但我也很高兴知道GCC编译器在这个问题上的行为.
谢谢,米哈尔
我认为我正在观察.NET JIT编译器没有内联或优化对没有副作用的空静态方法的调用,考虑到一些直言不讳的在线资源,这有点令人惊讶.
我的环境是x64,Windows 8.1,.NET Framework 4.5上的Visual Studio 2013.
鉴于这个简单的测试程序(https://ideone.com/2BRCpC)
class Program
{
static void EmptyBody()
{
}
static void Main()
{
EmptyBody();
}
}
Run Code Online (Sandbox Code Playgroud)
发布版本与上述程序的最优化产生以下MSIL为Main和EmptyBody:
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 6 (0x6)
.maxstack 8
IL_0000: call void Program::EmptyBody()
IL_0005: ret
} // end of method Program::Main
.method private hidebysig static void EmptyBody() cil managed
{
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // …Run Code Online (Sandbox Code Playgroud) 我正在阅读Simon Peyton Jones等人撰写的论文.命名为"遵循规则:重写为GHC中的实用优化技术".在第二部分,即他们写的"基本思想":
考虑熟悉的
map函数,它将函数应用于列表的每个元素.写在Haskell中,map看起来像这样:
map f [] = []
map f (x:xs) = f x : map f xs
Run Code Online (Sandbox Code Playgroud)
现在假设编译器遇到以下调用
map:
map f (map g xs)
Run Code Online (Sandbox Code Playgroud)
我们知道这个表达式相当于
map (f . g) xs
Run Code Online (Sandbox Code Playgroud)
(其中"."是函数组合),我们知道后一个表达式比前者更有效,因为没有中间列表.但是编译器没有这样的知识.
一个可能的反驳是编译器应该更聪明---但程序员总是会知道编译器无法弄清楚的事情.另一个建议是:允许程序员将这些知识直接传递给编译器.这是我们在这里探索的方向.
我的问题是,为什么我们不能让编译器变得更聪明?作者说"但程序员总是会知道编译器无法弄清楚的东西".但是,这不是一个有效的答案,因为编译器确实可以找出map f (map g xs)相当于的map (f . g) xs,这里是如何:
map f (map g xs)
Run Code Online (Sandbox Code Playgroud)
map g xs与...结合map f [] = [].
因此map g [] = [].
map f (map g …
考虑以下代码片段:
int* find_ptr(int* mem, int sz, int val) {
for (int i = 0; i < sz; i++) {
if (mem[i] == val) {
return &mem[i];
}
}
return nullptr;
}
Run Code Online (Sandbox Code Playgroud)
-O3上的GCC将其编译为:
find_ptr(int*, int, int):
mov rax, rdi
test esi, esi
jle .L4 # why not .L8?
lea ecx, [rsi-1]
lea rcx, [rdi+4+rcx*4]
jmp .L3
.L9:
add rax, 4
cmp rax, rcx
je .L8
.L3:
cmp DWORD PTR [rax], edx
jne .L9
ret
.L8:
xor eax, eax
ret
.L4: …Run Code Online (Sandbox Code Playgroud) 我写的这段代码乍一看很简单。它修改被引用变量引用的变量,然后返回引用的值。重现奇怪行为的简化版本如下所示:
#include <iostream>
using std::cout;
struct A {
int a;
int& b;
A(int x) : a(x), b(a) {}
A(const A& other) : a(other.a), b(a) {}
A() : a(0), b(a) {}
};
int foo(A a) {
a.a *= a.b;
return a.b;
}
int main() {
A a(3);
cout << foo(a) << '\n';
return 0;
}
Run Code Online (Sandbox Code Playgroud)
但是,当它在启用优化(g++ 7.5)的情况下编译时,它会产生与未优化代码不同的输出(即 9 个没有优化 -正如预期的那样,3 个启用了优化)。
我知道volatile关键字,它可以防止编译器在存在某些副作用(例如异步执行和特定于硬件的东西)的情况下重新排序和其他优化,并且在这种情况下也有帮助。
但是,我不明白为什么在这种特殊情况下需要将引用 b 声明为 volatile ?这段代码的错误来源在哪里?
使用条件移动(汇编cmov)来优化?:C中的条件表达式是一种常见的优化.但是,C标准说:
第一个操作数被评估; 在其评估与第二或第三操作数的评估之间存在一个序列点(以评估者为准).仅当第一个操作数不等于0时才评估第二个操作数; 仅当第一个操作数比较等于0时才评估第三个操作数; 结果是第二个或第三个操作数的值(无论哪个被评估),转换为下面描述的类型.110)
例如,以下C代码
#include <stdio.h>
int main() {
int a, b;
scanf("%d %d", &a, &b);
int c= a > b ? a + 1 : 2 + b;
printf("%d", c);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
将生成优化的相关asm代码,如下所示:
call __isoc99_scanf
movl (%rsp), %esi
movl 4(%rsp), %ecx
movl $1, %edi
leal 2(%rcx), %eax
leal 1(%rsi), %edx
cmpl %ecx, %esi
movl $.LC1, %esi
cmovle %eax, %edx
xorl %eax, %eax
call __printf_chk
Run Code Online (Sandbox Code Playgroud)
根据标准,条件表达式将仅评估一个分支.但是这里对两个分支进行了评估,这违反了标准的语义.这是针对C标准的优化吗?或者许多编译器优化是否与语言标准不一致?
c assembly ternary-operator compiler-optimization language-lawyer
我一直在审查代码,其中一些编码器一直在使用冗余三元运算符"以提高可读性."例如:
boolean val = (foo == bar && foo1 != bar) ? true : false;
Run Code Online (Sandbox Code Playgroud)
显然,将语句的结果分配给boolean变量会更好,但编译器是否关心?
format_disk如果以下程序从未在代码中调用过程,如何调用它?
#include <cstdio>
static void format_disk()
{
std::puts("formatting hard disk drive!");
}
static void (*foo)() = nullptr;
void never_called()
{
foo = format_disk;
}
int main()
{
foo();
}
Run Code Online (Sandbox Code Playgroud)
这与编译器不同.通过优化启用Clang进行编译,该函数never_called在运行时执行.
$ clang++ -std=c++17 -O3 a.cpp && ./a.out
formatting hard disk drive!
Run Code Online (Sandbox Code Playgroud)
但是,使用GCC进行编译时,此代码只会崩溃:
$ g++ -std=c++17 -O3 a.cpp && ./a.out
Segmentation fault (core dumped)
Run Code Online (Sandbox Code Playgroud)
编译器版本:
$ clang --version
clang version 5.0.0 (tags/RELEASE_500/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$ gcc --version
gcc (GCC) 7.2.1 20171128
Copyright (C) …Run Code Online (Sandbox Code Playgroud)