MKi*_*ing 22 .net c# optimization performance field
在你对肠道作出反应之前,正如我最初所做的那样,请阅读整个问题.我知道他们让你感觉很脏,我知道我们以前都被烧过,我知道这不是"好风格",但公共场地还好吗?
我正在研究一个相当大规模的工程应用程序,它创建并使用结构的内存模型(从高层建筑到桥梁到棚子,无关紧要).该项目涉及TON的几何分析和计算.为了支持这一点,该模型由许多微小的不可变只读结构组成,用于表示点,线段等事物.这些结构的某些值(如点的坐标)可被访问数十亿或数亿典型程序执行期间的时间.由于模型的复杂性和计算量,性能绝对至关重要.
我觉得我们正在尽我们所能优化我们的算法,性能测试以确定瓶颈,使用正确的数据结构等.我不认为这是过早优化的情况.性能测试显示直接访问字段而不是通过对象上的属性时性能提升的数量级(至少).鉴于此信息,以及我们还可以公开与属性相同的信息以支持数据绑定和其他情况......这样可以吗? 请记住,只读取不可变结构上的字段. 任何人都可以想到我会后悔的原因吗?
这是一个示例测试应用程序:
struct Point {
public Point(double x, double y, double z) {
_x = x;
_y = y;
_z = z;
}
public readonly double _x;
public readonly double _y;
public readonly double _z;
public double X { get { return _x; } }
public double Y { get { return _y; } }
public double Z { get { return _z; } }
}
class Program {
static void Main(string[] args) {
const int loopCount = 10000000;
var point = new Point(12.0, 123.5, 0.123);
var sw = new Stopwatch();
double x, y, z;
double calculatedValue;
sw.Start();
for (int i = 0; i < loopCount; i++) {
x = point._x;
y = point._y;
z = point._z;
calculatedValue = point._x * point._y / point._z;
}
sw.Stop();
double fieldTime = sw.ElapsedMilliseconds;
Console.WriteLine("Direct field access: " + fieldTime);
sw.Reset();
sw.Start();
for (int i = 0; i < loopCount; i++) {
x = point.X;
y = point.Y;
z = point.Z;
calculatedValue = point.X * point.Y / point.Z;
}
sw.Stop();
double propertyTime = sw.ElapsedMilliseconds;
Console.WriteLine("Property access: " + propertyTime);
double totalDiff = propertyTime - fieldTime;
Console.WriteLine("Total difference: " + totalDiff);
double averageDiff = totalDiff / loopCount;
Console.WriteLine("Average difference: " + averageDiff);
Console.ReadLine();
}
}
Run Code Online (Sandbox Code Playgroud)
结果:
直接现场访问:3262
物业访问:24248
总差异:20986
平均差异:0.00020986
这只有 21秒,但为什么不呢?
Sta*_*ker 31
您的测试对于基于属性的版本并不公平.JIT足够聪明,可以内联简单属性,使它们具有与直接字段访问相当的运行时性能,但是(当今)检测属性何时访问常量值似乎不够智能.
在您的示例中,字段访问版本的整个循环体被优化掉,变为:
for (int i = 0; i < loopCount; i++)
00000025 xor eax,eax
00000027 inc eax
00000028 cmp eax,989680h
0000002d jl 00000027
}
Run Code Online (Sandbox Code Playgroud)
而第二个版本实际上是在每次迭代时执行浮点除法:
for (int i = 0; i < loopCount; i++)
00000094 xor eax,eax
00000096 fld dword ptr ds:[01300210h]
0000009c fdiv qword ptr ds:[01300218h]
000000a2 fstp st(0)
000000a4 inc eax
000000a5 cmp eax,989680h
000000aa jl 00000096
}
Run Code Online (Sandbox Code Playgroud)
对应用程序进行两次小的更改以使其更加真实,这使得两个操作在性能上几乎完全相同.
首先,随机化输入值,使它们不是常量,JIT不够智能,不能完全删除除法.
改变自:
Point point = new Point(12.0, 123.5, 0.123);
Run Code Online (Sandbox Code Playgroud)
至:
Random r = new Random();
Point point = new Point(r.NextDouble(), r.NextDouble(), r.NextDouble());
Run Code Online (Sandbox Code Playgroud)
其次,确保在某处使用每个循环迭代的结果:
在每个循环之前,设置calculatedValue = 0,使它们都在同一点开始.在每个循环之后调用Console.WriteLine(calculatedValue.ToString())以确保结果"已使用",因此编译器不会对其进行优化.最后,将循环体从"calculatedValue = ..."更改为"calculatedValue + = ...",以便使用每次迭代.
在我的机器上,这些更改(使用发布版本)会产生以下结果:
Direct field access: 133
Property access: 133
Total difference: 0
Average difference: 0
Run Code Online (Sandbox Code Playgroud)
正如我们所期望的那样,每个修改过的循环的x86都是相同的(循环地址除外)
000000dd xor eax,eax
000000df fld qword ptr [esp+20h]
000000e3 fmul qword ptr [esp+28h]
000000e7 fdiv qword ptr [esp+30h]
000000eb fstp st(0)
000000ed inc eax
000000ee cmp eax,989680h
000000f3 jl 000000DF (This loop address is the only difference)
Run Code Online (Sandbox Code Playgroud)
eri*_*len 10
IMO,"无公共字段"规则是技术上正确的规则之一,但除非您正在设计一个供公众使用的库,否则如果您破坏它,则不太可能导致任何问题.
在我被大量投票之前,我应该补充一点,封装是一件好事.给定不变"如果HasValue为false,则Value属性必须为null",这种设计存在缺陷:
class A {
public bool HasValue;
public object Value;
}
Run Code Online (Sandbox Code Playgroud)
但是,鉴于这种不变性,这种设计同样存在缺陷:
class A {
public bool HasValue { get; set; }
public object Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
正确的设计是
class A {
public bool HasValue { get; private set; }
public object Value { get; private set; }
public void SetValue(bool hasValue, object value) {
if (!hasValue && value != null)
throw new ArgumentException();
this.HasValue = hasValue;
this.Value = value;
}
}
Run Code Online (Sandbox Code Playgroud)
(甚至更好的是提供初始化构造函数并使类不可变).