CLR 如何优化 属性 引用?
How does CLR optimize property references?
我当了很长时间的程序员,最近找到了一份编写 C# 的工作。我很好奇是否 Visual Studio 优化了对简单内存移动的 属性 调用而不是执行函数调用和 return。所以我写了一个程序,它有两个版本的 3D 点 class 和一种计算大小的方法:一个版本直接访问字段,一个使用属性。我 运行 都获得了 100,000,000 分,他们花费的时间相同。但是当我使用 ildasm 查看生成的代码时,使用属性的版本似乎使用函数调用来访问 属性 值。 (这是发布版本,因此代码优化已打开。)
我的问题:
对 get_X 的函数调用是否针对运行时的内存移动进行了优化? (看起来是这样,因为它的执行时间与直接字段引用相同。)
有没有办法使用 ildasm 或其他工具来查看在运行时发生了哪些优化?
我已经尝试 运行 没有调试器的进程然后附加到进程但是 VS2017 说 "No disassembly available"。
直接调用私有字段的版本:
.method public hidebysig instance float64
Abs() cil managed
{
// Code size 47 (0x2f)
.maxstack 8
//000052: return Math.Sqrt(_x * _x + _y * _y + _z * _z);
IL_0000: ldarg.0
IL_0001: ldfld float64 CPUTests.Point3d::_x
IL_0006: ldarg.0
IL_0007: ldfld float64 CPUTests.Point3d::_x
IL_000c: mul
IL_000d: ldarg.0
IL_000e: ldfld float64 CPUTests.Point3d::_y
IL_0013: ldarg.0
IL_0014: ldfld float64 CPUTests.Point3d::_y
IL_0019: mul
IL_001a: add
IL_001b: ldarg.0
IL_001c: ldfld float64 CPUTests.Point3d::_z
IL_0021: ldarg.0
IL_0022: ldfld float64 CPUTests.Point3d::_z
IL_0027: mul
IL_0028: add
IL_0029: call float64 [mscorlib]System.Math::Sqrt(float64)
IL_002e: ret
} // end of method Point3d::Abs
调用属性的版本,后面是get_X
方法:
.method public hidebysig instance float64
Abs() cil managed
{
// Code size 47 (0x2f)
.maxstack 8
//000052: return Math.Sqrt(X * X + Y * Y + Z * Z);
IL_0000: ldarg.0
IL_0001: call instance float64 CPUTests.Point3dProperties::get_X()
IL_0006: ldarg.0
IL_0007: call instance float64 CPUTests.Point3dProperties::get_X()
IL_000c: mul
IL_000d: ldarg.0
IL_000e: call instance float64 CPUTests.Point3dProperties::get_Y()
IL_0013: ldarg.0
IL_0014: call instance float64 CPUTests.Point3dProperties::get_Y()
IL_0019: mul
IL_001a: add
IL_001b: ldarg.0
IL_001c: call instance float64 CPUTests.Point3dProperties::get_Z()
IL_0021: ldarg.0
IL_0022: call instance float64 CPUTests.Point3dProperties::get_Z()
IL_0027: mul
IL_0028: add
IL_0029: call float64 [mscorlib]System.Math::Sqrt(float64)
IL_002e: ret
} // end of method Point3dProperties::Abs
.method public hidebysig specialname instance float64
get_X() cil managed
{
// Code size 7 (0x7)
.maxstack 8
//000016: get { return _x; }
IL_0000: ldarg.0
IL_0001: ldfld float64 CPUTests.Point3dProperties::_x
IL_0006: ret
} // end of method Point3dProperties::get_X
Jeffrey Richter 在他的《通过 C# 实现 CLR》一书中写道:
For simple get and set accessor methods, the just-in-time (JIT)
compiler inlines the code so that there’s no run-time performance hit
as a result of using properties rather than fields.
通过 C# 的 CLR 是我的圣经,所以对我来说,这足以证明它们是内联的。
ildasm 只能向您显示编译时发生的优化的最终结果。如果要检查 运行 时间优化,则必须实际查看 运行 时间代码,即从程序集生成并正在 运行 的代码。 S.O.S WinDbg 扩展可能是一个可以帮助您的工具。
调试时使用反汇编视图可能会显示 属性 getter 和 setter 确实是内联的。
您可以在 visual studio 中使用 CTRL-ALT-D 访问它。
如果您想发现抖动优化,可以使用 MethodImplOptions 禁用它们。
public struct foo
{
private int _bar;
public int bar
{
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
get { return _bar; }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
set { _bar = value; }
}
}
将在调试时向您展示:
而如果你不放属性,你连断点都打不到,因为所有的getter都被内联了。
我当了很长时间的程序员,最近找到了一份编写 C# 的工作。我很好奇是否 Visual Studio 优化了对简单内存移动的 属性 调用而不是执行函数调用和 return。所以我写了一个程序,它有两个版本的 3D 点 class 和一种计算大小的方法:一个版本直接访问字段,一个使用属性。我 运行 都获得了 100,000,000 分,他们花费的时间相同。但是当我使用 ildasm 查看生成的代码时,使用属性的版本似乎使用函数调用来访问 属性 值。 (这是发布版本,因此代码优化已打开。)
我的问题:
对 get_X 的函数调用是否针对运行时的内存移动进行了优化? (看起来是这样,因为它的执行时间与直接字段引用相同。)
有没有办法使用 ildasm 或其他工具来查看在运行时发生了哪些优化?
我已经尝试 运行 没有调试器的进程然后附加到进程但是 VS2017 说 "No disassembly available"。
直接调用私有字段的版本:
.method public hidebysig instance float64
Abs() cil managed
{
// Code size 47 (0x2f)
.maxstack 8
//000052: return Math.Sqrt(_x * _x + _y * _y + _z * _z);
IL_0000: ldarg.0
IL_0001: ldfld float64 CPUTests.Point3d::_x
IL_0006: ldarg.0
IL_0007: ldfld float64 CPUTests.Point3d::_x
IL_000c: mul
IL_000d: ldarg.0
IL_000e: ldfld float64 CPUTests.Point3d::_y
IL_0013: ldarg.0
IL_0014: ldfld float64 CPUTests.Point3d::_y
IL_0019: mul
IL_001a: add
IL_001b: ldarg.0
IL_001c: ldfld float64 CPUTests.Point3d::_z
IL_0021: ldarg.0
IL_0022: ldfld float64 CPUTests.Point3d::_z
IL_0027: mul
IL_0028: add
IL_0029: call float64 [mscorlib]System.Math::Sqrt(float64)
IL_002e: ret
} // end of method Point3d::Abs
调用属性的版本,后面是get_X
方法:
.method public hidebysig instance float64
Abs() cil managed
{
// Code size 47 (0x2f)
.maxstack 8
//000052: return Math.Sqrt(X * X + Y * Y + Z * Z);
IL_0000: ldarg.0
IL_0001: call instance float64 CPUTests.Point3dProperties::get_X()
IL_0006: ldarg.0
IL_0007: call instance float64 CPUTests.Point3dProperties::get_X()
IL_000c: mul
IL_000d: ldarg.0
IL_000e: call instance float64 CPUTests.Point3dProperties::get_Y()
IL_0013: ldarg.0
IL_0014: call instance float64 CPUTests.Point3dProperties::get_Y()
IL_0019: mul
IL_001a: add
IL_001b: ldarg.0
IL_001c: call instance float64 CPUTests.Point3dProperties::get_Z()
IL_0021: ldarg.0
IL_0022: call instance float64 CPUTests.Point3dProperties::get_Z()
IL_0027: mul
IL_0028: add
IL_0029: call float64 [mscorlib]System.Math::Sqrt(float64)
IL_002e: ret
} // end of method Point3dProperties::Abs
.method public hidebysig specialname instance float64
get_X() cil managed
{
// Code size 7 (0x7)
.maxstack 8
//000016: get { return _x; }
IL_0000: ldarg.0
IL_0001: ldfld float64 CPUTests.Point3dProperties::_x
IL_0006: ret
} // end of method Point3dProperties::get_X
Jeffrey Richter 在他的《通过 C# 实现 CLR》一书中写道:
For simple get and set accessor methods, the just-in-time (JIT) compiler inlines the code so that there’s no run-time performance hit as a result of using properties rather than fields.
通过 C# 的 CLR 是我的圣经,所以对我来说,这足以证明它们是内联的。
ildasm 只能向您显示编译时发生的优化的最终结果。如果要检查 运行 时间优化,则必须实际查看 运行 时间代码,即从程序集生成并正在 运行 的代码。 S.O.S WinDbg 扩展可能是一个可以帮助您的工具。
调试时使用反汇编视图可能会显示 属性 getter 和 setter 确实是内联的。 您可以在 visual studio 中使用 CTRL-ALT-D 访问它。
如果您想发现抖动优化,可以使用 MethodImplOptions 禁用它们。
public struct foo
{
private int _bar;
public int bar
{
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
get { return _bar; }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
set { _bar = value; }
}
}
将在调试时向您展示:
而如果你不放属性,你连断点都打不到,因为所有的getter都被内联了。