我很好奇在使用运算符 +
和 *
进行数学运算时,大型结构与小型结构的开销。所以我做了两个结构,一个 Small
有 1 个双字段(8 字节)和一个 Big
有 10 个 double 字段(80 字节)。在我的所有操作中,我只操作一个名为 x
的字段。
首先我在两个结构中都定义了数学运算符,比如
public static Small operator +(Small a, Small b)
{
return new Small(a.x + b.x);
}
public static Small operator *(double x, Small a)
{
return new Small(x * a.x);
}
正如预期的那样,它会在堆栈中占用大量内存来复制字段。我运行了 5,000,000 次数学运算迭代并得到了我怀疑的结果(减速 3 倍)。
public double TestSmall()
{
pt.Start(); // pt = performance timing object
Small r = new Small(rnd.NextDouble()); //rnd = Random number generator
for (int i = 0; i < N; i++)
{
a = 0.6 * a + 0.4 * r; // a is a local field of type Small
}
pt.Stop();
return pt.ElapsedSeconds;
}
Release 代码的结果(以秒为单位)
Small=0.33940 Big=0.98909 Big is Slower by x2.91
现在是有趣的部分。我用带有 ref
参数的静态方法定义相同的操作
public static void Add(ref Small a, ref Small b, ref Small res)
{
res.x = a.x + b.x;
}
public static void Scale(double x, ref Small a, ref Small res)
{
res.x = x * a.x;
}
并在此测试代码上运行相同次数的迭代:
public double TestSmall2()
{
pt.Start(); // pt = performance timing object
Small a1 = new Small(); // local
Small a2 = new Small(); // local
Small r = new Small(rnd.NextDouble()); //rdn = Random number generator
for (int i = 0; i < N; i++)
{
Small.Scale(0.6, ref a, ref a1);
Small.Scale(0.4, ref r, ref a2);
Small.Add(ref a1, ref a2, ref a);
}
pt.Stop();
return pt.ElapsedSeconds;
}
结果显示(以秒为单位)
Small=0.11765 Big=0.07130 Big is Slower by x0.61
因此,与内存复制密集型运算符相比,我得到了 x3 和 x14 的加速,这很好,但是将 Small 结构时间与 Big 进行比较,您会发现 Small 比 Big 慢 60% 慢比大。
谁能解释一下?它是否与 CPU 流水线有关,并且在(空间)内存中分离操作可以更有效地预取数据?
如果您想自己尝试,请从我的保管箱中获取代码 http://dl.dropbox.com/u/11487099/SmallBigCompare.zip
最佳答案
您的基准测试似乎存在一些缺陷。
- 使用
Stopwatch
而不是PerformanceTimer
类型。我不熟悉后者,它似乎是第 3 方组件。尤其麻烦的是,它以EllapsedSeconds
而不是EllapsedMilliseconds
为单位测量时间。 - 应将每个测试运行两次并只计算第二次以消除潜在的 JIT 成本
Marshal.SizeOf
不会产生结构的实际大小,只是它的编码大小。
切换到 Stopwatch
后,我看到基准测试按预期执行,在静态引用情况下两种类型的时间几乎相等。
关于c# - 与结构大小和性能相关的特殊结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3806839/