不必要的大括号会降低性能吗?
Do unnecessary curly braces reduce performance?
最近在编程的时候遇到这个问题,一直在想这个问题。以下是 2 个合法且可编译的片段。具体来说,我的问题是......在第二种情况下,括号是否会使程序变慢?还有为什么允许这样做?
第一种情况:
if (statement)
{
// do something
}
第二种情况:
{
if (statement)
{
// do something
}
}
此外,如果我有类似下面的代码会怎样。运行时是否与不带任何大括号调用函数 X 相同。
{
{
{
// call function X
}
}
}
没有花括号不会降低性能。
有助于理解代码并提供良好的代码格式。但是一些大括号是强制性的,比如 function start /end,loop start/end,condition start/end 并且这个 barces 也有助于理解 scrop of variables.
简答为 "no, they do not reduce performance"。
编译器需要花括号来确定变量的范围,并知道当前语句组的结束位置。一旦编译器完成处理,带和不带不必要的大括号的代码将产生相同的输出。
请注意,这与编译代码的性能有关,与编译器本身的性能无关。编译器将花费额外的时间来编译您的代码,这仅仅是因为输入的原始大小更大。然而,为了让这个额外的时间变得可测量,不必要的牙套数量需要相当多。
它不会导致任何性能下降,但使用大括号肯定会增加代码的可读性。
在真实的场景中,当你有同行代码审查或结对编程时,你写的东西会更加清晰易读。
完成以下link
对于此类问题,答案在于它生成的 IL。对于以下代码示例:
public int X()
{
{
{
{
return 0;
}
}
}
}
public int Y()
{
return 0;
}
我们最终得到以下编译的 IL:
.method public hidebysig instance int32 X() cil managed
{
// Code size 2 (0x2)
.maxstack 8
IL_0000: ldc.i4.0
IL_0001: ret
} // end of method SomeType::X
.method public hidebysig instance int32 Y() cil managed
{
// Code size 2 (0x2)
.maxstack 8
IL_0000: ldc.i4.0
IL_0001: ret
} // end of method SomeType::Y
它们是相同的。所以不,它对性能没有影响。 X
读起来很糟糕,但这是另一个问题。
更新
{}
影响变量的范围,所以这可能会产生影响。再一次,让我们检查一下:
public int X()
{
var i = 1;
{
{
i++;
{
return i;
}
}
}
}
public int Y()
{
var i = 1;
i++;
return i;
}
再一次,生成的 IL 是相同的:
// Code size 8 (0x8)
.maxstack 2
.locals init ([0] int32 i)
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: ldc.i4.1
IL_0004: add
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ret
但是,如果变量在闭包中被捕获,它确实会影响事情。 X
在以下情况下确实会创建更多 IL,这会对性能产生影响:
public Func<int> X()
{
{
var i = 1;
{
i++;
{
return () => i;
}
}
}
}
public Func<int> Y()
{
var i = 1;
i++;
return () => i;
}
与 C++ 不同,在 C++ 中,编译器可能需要在变量进入或超出范围时生成代码,C# 中的大多数变量有效地提升到封闭的函数级范围。代码:
void foo()
{
{
int i;
... stuff using i as int
}
{
char i;
... stuff using i as char
}
}
实际上会变成:
void foo()
{
int i__1;
char i__2;
... stuff using i__1 as int
... stuff using i__2 as char
}
第一个大括号部分的代码使用第一个变量 i__1
,而在本应使用 i
的地方,第二个代码使用 i__2
。在某些情况下,在多个范围块中声明具有相同名称和用途的变量可能会生成比在外部范围块中声明一个具有相同用途的变量效率更低的代码,但它很少会产生有意义的效果。在大多数情况下,即时编译器将能够确定代码中的多个变量可以安全地映射到相同的存储位置,即使在那些它不能的情况下,一些额外变量所需的存储也不太可能非常影响性能。
大多数 时间它没有任何区别 - 你绝对应该为可读性编码而不是其他任何东西。
但是,大括号 可以 以令人惊讶的方式影响性能,尽管这很不寻常。考虑这段代码:
using System;
using System.Collections.Generic;
class Test
{
static void FewerCurlies()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 100; i++)
{
int x;
if (i % 3 == 0)
{
actions.Add(() => x = 10);
}
int y;
if (i % 3 == 1)
{
actions.Add(() => y = 10);
}
}
}
static void MoreCurlies()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 100; i++)
{
{
int x;
if (i % 3 == 0)
{
actions.Add(() => x = 10);
}
}
{
int y;
if (i % 3 == 1)
{
actions.Add(() => y = 10);
}
}
}
}
}
MoreCurlies
中的额外大括号看起来多余,对吧?不太...生成的代码看起来更像这样:
using System;
using System.Collections.Generic;
class Test
{
static void FewerCurlies()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 100; i++)
{
FewerCurliesCapture capture = new FewerCurliesCapture();
if (i % 3 == 0)
{
actions.Add(capture.Method1);
}
if (i % 3 == 1)
{
actions.Add(capture.Method2);
}
}
}
static void MoreCurlies()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 100; i++)
{
{
MoreCurliesCapture1 capture = new MoreCurliesCapture1();
if (i % 3 == 0)
{
actions.Add(capture.Method);
}
}
{
MoreCurliesCapture1 capture = new MoreCurliesCapture2();
if (i % 3 == 1)
{
actions.Add(capture.Method);
}
}
}
}
private class FewerCurliesCapture
{
public int x;
public int y;
public void Method1()
{
x = 10;
}
public void Method2()
{
y = 10;
}
}
private class MoreCurliesCapture1
{
public int x;
public void Method()
{
x = 10;
}
}
private class MoreCurliesCapture2
{
public int y;
public void Method()
{
y = 10;
}
}
}
这里的区别是:
- 捕获实例 class 在
FewerCurlies
循环的每次迭代中创建,即使它没有被使用
FewerCurlies
中使用的捕获 class 的每个实例都包含两个变量,即使每个代表实际上只会使用其中一个,而在 MoreCurlies
中每个捕获 class 只捕获单个变量
这在某种程度上是特定于实现的,但它表明看起来多余的卷曲 可以 产生影响。
使用不必要的大括号,假设您没有嵌套变量,在将您的代码转换为字节码或机器代码时,只会向标签 table 添加一个标签。所以更糟糕的是构建时间会变慢。如果你在嵌套中有变量,如果它们是没有破坏代码的基元,你仍然不应该有问题,但是如果你在嵌套大括号中创建了对象,那么就需要对 GC 有更深入的了解,但我强烈怀疑任何明显的差异都会存在。在所有情况下,因为编译器在构建您的项目时通过在查找中存储引用来做额外的工作 table,因此在构建您的项目时会有轻微的延迟,尽管可能不明显。
最近在编程的时候遇到这个问题,一直在想这个问题。以下是 2 个合法且可编译的片段。具体来说,我的问题是......在第二种情况下,括号是否会使程序变慢?还有为什么允许这样做?
第一种情况:
if (statement)
{
// do something
}
第二种情况:
{
if (statement)
{
// do something
}
}
此外,如果我有类似下面的代码会怎样。运行时是否与不带任何大括号调用函数 X 相同。
{
{
{
// call function X
}
}
}
没有花括号不会降低性能。
有助于理解代码并提供良好的代码格式。但是一些大括号是强制性的,比如 function start /end,loop start/end,condition start/end 并且这个 barces 也有助于理解 scrop of variables.
简答为 "no, they do not reduce performance"。
编译器需要花括号来确定变量的范围,并知道当前语句组的结束位置。一旦编译器完成处理,带和不带不必要的大括号的代码将产生相同的输出。
请注意,这与编译代码的性能有关,与编译器本身的性能无关。编译器将花费额外的时间来编译您的代码,这仅仅是因为输入的原始大小更大。然而,为了让这个额外的时间变得可测量,不必要的牙套数量需要相当多。
它不会导致任何性能下降,但使用大括号肯定会增加代码的可读性。 在真实的场景中,当你有同行代码审查或结对编程时,你写的东西会更加清晰易读。
完成以下link
对于此类问题,答案在于它生成的 IL。对于以下代码示例:
public int X()
{
{
{
{
return 0;
}
}
}
}
public int Y()
{
return 0;
}
我们最终得到以下编译的 IL:
.method public hidebysig instance int32 X() cil managed
{
// Code size 2 (0x2)
.maxstack 8
IL_0000: ldc.i4.0
IL_0001: ret
} // end of method SomeType::X
.method public hidebysig instance int32 Y() cil managed
{
// Code size 2 (0x2)
.maxstack 8
IL_0000: ldc.i4.0
IL_0001: ret
} // end of method SomeType::Y
它们是相同的。所以不,它对性能没有影响。 X
读起来很糟糕,但这是另一个问题。
更新
{}
影响变量的范围,所以这可能会产生影响。再一次,让我们检查一下:
public int X()
{
var i = 1;
{
{
i++;
{
return i;
}
}
}
}
public int Y()
{
var i = 1;
i++;
return i;
}
再一次,生成的 IL 是相同的:
// Code size 8 (0x8)
.maxstack 2
.locals init ([0] int32 i)
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: ldc.i4.1
IL_0004: add
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ret
但是,如果变量在闭包中被捕获,它确实会影响事情。 X
在以下情况下确实会创建更多 IL,这会对性能产生影响:
public Func<int> X()
{
{
var i = 1;
{
i++;
{
return () => i;
}
}
}
}
public Func<int> Y()
{
var i = 1;
i++;
return () => i;
}
与 C++ 不同,在 C++ 中,编译器可能需要在变量进入或超出范围时生成代码,C# 中的大多数变量有效地提升到封闭的函数级范围。代码:
void foo()
{
{
int i;
... stuff using i as int
}
{
char i;
... stuff using i as char
}
}
实际上会变成:
void foo()
{
int i__1;
char i__2;
... stuff using i__1 as int
... stuff using i__2 as char
}
第一个大括号部分的代码使用第一个变量 i__1
,而在本应使用 i
的地方,第二个代码使用 i__2
。在某些情况下,在多个范围块中声明具有相同名称和用途的变量可能会生成比在外部范围块中声明一个具有相同用途的变量效率更低的代码,但它很少会产生有意义的效果。在大多数情况下,即时编译器将能够确定代码中的多个变量可以安全地映射到相同的存储位置,即使在那些它不能的情况下,一些额外变量所需的存储也不太可能非常影响性能。
大多数 时间它没有任何区别 - 你绝对应该为可读性编码而不是其他任何东西。
但是,大括号 可以 以令人惊讶的方式影响性能,尽管这很不寻常。考虑这段代码:
using System;
using System.Collections.Generic;
class Test
{
static void FewerCurlies()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 100; i++)
{
int x;
if (i % 3 == 0)
{
actions.Add(() => x = 10);
}
int y;
if (i % 3 == 1)
{
actions.Add(() => y = 10);
}
}
}
static void MoreCurlies()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 100; i++)
{
{
int x;
if (i % 3 == 0)
{
actions.Add(() => x = 10);
}
}
{
int y;
if (i % 3 == 1)
{
actions.Add(() => y = 10);
}
}
}
}
}
MoreCurlies
中的额外大括号看起来多余,对吧?不太...生成的代码看起来更像这样:
using System;
using System.Collections.Generic;
class Test
{
static void FewerCurlies()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 100; i++)
{
FewerCurliesCapture capture = new FewerCurliesCapture();
if (i % 3 == 0)
{
actions.Add(capture.Method1);
}
if (i % 3 == 1)
{
actions.Add(capture.Method2);
}
}
}
static void MoreCurlies()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 100; i++)
{
{
MoreCurliesCapture1 capture = new MoreCurliesCapture1();
if (i % 3 == 0)
{
actions.Add(capture.Method);
}
}
{
MoreCurliesCapture1 capture = new MoreCurliesCapture2();
if (i % 3 == 1)
{
actions.Add(capture.Method);
}
}
}
}
private class FewerCurliesCapture
{
public int x;
public int y;
public void Method1()
{
x = 10;
}
public void Method2()
{
y = 10;
}
}
private class MoreCurliesCapture1
{
public int x;
public void Method()
{
x = 10;
}
}
private class MoreCurliesCapture2
{
public int y;
public void Method()
{
y = 10;
}
}
}
这里的区别是:
- 捕获实例 class 在
FewerCurlies
循环的每次迭代中创建,即使它没有被使用 FewerCurlies
中使用的捕获 class 的每个实例都包含两个变量,即使每个代表实际上只会使用其中一个,而在MoreCurlies
中每个捕获 class 只捕获单个变量
这在某种程度上是特定于实现的,但它表明看起来多余的卷曲 可以 产生影响。
使用不必要的大括号,假设您没有嵌套变量,在将您的代码转换为字节码或机器代码时,只会向标签 table 添加一个标签。所以更糟糕的是构建时间会变慢。如果你在嵌套中有变量,如果它们是没有破坏代码的基元,你仍然不应该有问题,但是如果你在嵌套大括号中创建了对象,那么就需要对 GC 有更深入的了解,但我强烈怀疑任何明显的差异都会存在。在所有情况下,因为编译器在构建您的项目时通过在查找中存储引用来做额外的工作 table,因此在构建您的项目时会有轻微的延迟,尽管可能不明显。