【问题标题】:Why is struct slower than float?为什么结构比浮动慢?
【发布时间】:2011-04-21 12:09:56
【问题描述】:

如果我有结构数组MyStruct[]:

struct MyStruct 
{
    float x;
    float y;
}

而且它比我做 float[] -> x = > i; 慢y => i + 1(所以这个数组比结构体大 2 倍)。

10,000 个项目的时间差相互比较(内部有两个 fors):结构 500 毫秒,只有浮点数的数组 - 78 毫秒

我想,该结构看起来像例如。 float、int 等(在堆上)。

【问题讨论】:

  • 您为什么不继续向我们展示您的基准测试代码?
  • 请注意,当我测试它时,使用 foreach 循环迭代 MyStruct[] 所需的时间大约是使用 for 循环的两倍。
  • 基准代码绝对是必须的,但我也想指出,在.net中,数组是一种引用类型,所以原语数组仍然在堆上(无论是int/float/whatever 或结构)。你仍然可以获得一些好处,因为如果你有一个对象数组,当你调用 objarr[0].ToString() 时,它实际上是两个引用查找,一个查找对对象的引用数组,另一个引用查找来获取到对象。
  • @Brian - foreach 在数组上得到特殊处理,并且不比 for 慢。不知道如果你使用多态会发生什么,例如IEnumerable<float> floats = new [] { 1f };,所以如果编译器/运行时仍然能够优化它。
  • @JulianR:我听说过,但由于某种原因我尝试它时仍然较慢。如果您想尝试,我发布了我的代码。 stackoverflow.com/questions/3867331/…

标签: c# performance struct


【解决方案1】:

首先,结构不一定出现在堆上——它们可以而且经常出现在堆栈上。

关于您的性能测量,我认为您一定是错误地测试了它。使用这个基准测试代码,我得到了两种类型的几乎相同的性能结果:

TwoFloats[] a = new TwoFloats[10000];
float[] b = new float[20000];

void test1()
{
    int count = 0;
    for (int i = 0; i < 10000; i += 1)
    {
        if (a[i].x < 10) count++;
    }
}

void test2()
{
    int count = 0;
    for (int i = 0; i < 20000; i += 2)
    {
        if (b[i] < 10) count++;
    }
}

结果:

每秒方法迭代次数 测试1 55200000 测试2 54800000

【讨论】:

  • 根据 OP,在他使用 float[] 而不是 MyStruct 的情况下,他使用的数组使用的元素数量是其两倍。因此,结构数组不会有两倍大,除非由于偏移问题或其他原因(例如,可能在 64 位机器上),结构每个 float 使用超过 4 个字节。
  • 顺便说一句,我尝试了我自己的基准测试版本,将a[i].xa[i].yb[i]b[i+1] 进行比较。不出所料,结果与您的相似。
  • 您的代码按预期运行...我想,如果我使用 Lit 与简单数组相比,List 在您的代码(结构体为 110 毫秒,2 个浮点数为 46 毫秒)上显着变慢(使用20000000 项)。而 List 不做拳击
【解决方案2】:

如果你遇到这样的情况,你就做错了。浮点比较非常快,我用循环开销将它们计时为 2 纳秒。制作这样的测试很棘手,因为如果您不使用比较结果,JIT 编译器会优化掉一些东西。

结构稍快,1.96 纳秒,而我笔记本电脑上的 float[] 为 2.20 纳秒。应该是这样,访问结构的 Y 成员不需要额外的数组索引。

测试代码:

using System;
using System.Diagnostics;

class Program {
    static void Main(string[] args) {
        var test1 = new float[100000000];  // 100 million
        for (int ix = 0; ix < test1.Length; ++ix) test1[ix] = ix;
        var test2 = new Test[test1.Length / 2];
        for (int ix = 0; ix < test2.Length; ++ix) test2[ix].x = test2[ix].y = ix;
        for (int cnt = 0; cnt < 20; ++cnt) {
            var sw1 = Stopwatch.StartNew();
            bool dummy = false;
            for (int ix = 0; ix < test1.Length; ix += 2) {
                dummy ^= test1[ix] >= test1[ix + 1];
            }
            sw1.Stop();
            var sw2 = Stopwatch.StartNew();
            for (int ix = 0; ix < test2.Length; ++ix) {
                dummy ^= test2[ix].x >= test2[ix].y;
            }
            sw2.Stop();
            Console.Write("", dummy);
            Console.WriteLine("{0} {1}", sw1.ElapsedMilliseconds, sw2.ElapsedMilliseconds);
        }
        Console.ReadLine();
    }
    struct Test {
        public float x;
        public float y;
    }
}

【讨论】:

    【解决方案3】:

    我得到的结果似乎同意你(不同意马克)。我很好奇我在构建这个(尽管很粗糙)基准时是否犯了错误,或者是否还有其他因素在起作用。

    在面向 .NET 3.5 框架和 VS2008 的 MS C# 上编译。发布模式,未附加调试器。

    这是我的测试代码:

    class Program {
        static void Main(string[] args) {
            for (int i = 0; i < 10; i++) {
                RunBench();
            }
    
            Console.ReadKey();
        }
    
        static void RunBench() {
            Stopwatch sw = new Stopwatch();
    
            const int numPoints = 10000;
            const int numFloats = numPoints * 2;
            int numEqs = 0;
            float[] rawFloats = new float[numFloats];
            Vec2[] vecs = new Vec2[numPoints];
    
            Random rnd = new Random();
            for (int i = 0; i < numPoints; i++) {
                rawFloats[i * 2] = (float) rnd.NextDouble();
                rawFloats[i * 2 + 1] = (float)rnd.NextDouble();
                vecs[i] = new Vec2() { X = rawFloats[i * 2], Y = rawFloats[i * 2 + 1] };
            }
    
            sw.Start();
            for (int i = 0; i < numFloats; i += 2) {
                for (int j = 0; j < numFloats; j += 2) {
                    if (i != j &&
                        Math.Abs(rawFloats[i] - rawFloats[j]) < 0.0001 &&
                        Math.Abs(rawFloats[i + 1] - rawFloats[j + 1]) < 0.0001) {
                        numEqs++;
                    }
                }
            }
            sw.Stop();
    
            Console.WriteLine(sw.ElapsedMilliseconds.ToString() + " : numEqs = " + numEqs);
    
            numEqs = 0;
            sw.Reset();
            sw.Start();
            for (int i = 0; i < numPoints; i++) {
                for (int j = 0; j < numPoints; j++) {
                    if (i != j &&
                        Math.Abs(vecs[i].X - vecs[j].X) < 0.0001 &&
                        Math.Abs(vecs[i].Y - vecs[j].Y) < 0.0001) {
                        numEqs++;
                    }
                }
            }
            sw.Stop();
    
            Console.WriteLine(sw.ElapsedMilliseconds.ToString() + " : numEqs = " + numEqs);
        }
    }
    
    struct Vec2 {
        public float X;
        public float Y;
    }
    

    编辑:啊!我没有迭代适当的数量。使用更新的代码,我的时间看起来像我预期的那样:

    269 : numEqs = 8
    269 : numEqs = 8
    270 : numEqs = 2
    269 : numEqs = 2
    268 : numEqs = 4
    270 : numEqs = 4
    269 : numEqs = 2
    268 : numEqs = 2
    270 : numEqs = 6
    270 : numEqs = 6
    269 : numEqs = 8
    268 : numEqs = 8
    268 : numEqs = 4
    270 : numEqs = 4
    269 : numEqs = 6
    269 : numEqs = 6
    268 : numEqs = 2
    270 : numEqs = 2
    268 : numEqs = 4
    270 : numEqs = 4
    

    【讨论】:

    • 啊!我想我忽略了一些问题。我实际上是在看到您的评论之前就抓住了它;)
    【解决方案4】:

    最可能的原因是,当您使用具有完整结构的浮点数时,C# 运行时优化器会执行更好的工作,这可能是因为优化器将 x 和 y 映射到寄存器或类似的更改未使用完整结构完成。

    在您的特定示例中,似乎没有任何根本原因可以解释为什么当您使用结构时它不能表现得那么好(如果没有看到实际的基准测试代码就很难确定),但事实并非如此。但是,将生成的代码与其他 C# 实现进行编译时的性能进行比较会很有趣(我正在考虑 Linux 上的单声道)。

    我用单声道测试了 Ron Warholic 基准测试,结果与 Mark 的一致,两种访问类型之间的差异似乎很小(带有浮点数的版本快 1%)。但是我仍然应该做更多的测试,因为像 Math.Abs​​ 这样的库调用会花费大量时间并且可能隐藏真正的差异并不意外。

    在删除对 Math.Abs​​ 的调用并仅进行 rawFloats[i] &lt; rawFloats[j] 之类的测试后,结构版本的速度比两个浮点数组稍快(约 5%)。

    【讨论】:

    • 当我运行 Ron Warholic 的基准测试时,这会加剧结果,但并没有完全考虑到它们。
    • @Brian:Ron Warholic 基准测试非常有趣,看他的代码我敢打赌缓存效果。这很容易测试:只需以随机顺序访问相同数量的条目,而不是按 1 步进并检查它是否改变了某些内容。
    • /Brian: 实际上在基准测试中出错。我以前用结构做过类似的事情,从来没有注意到速度差异,这就是为什么我高度怀疑我的基准测试中有错误。显示的更新代码显示了几乎相同的行为(正确)。
    • @Ron Warholic:现在这是更正常的行为。我觉得我没有在基准测试中发现问题...
    【解决方案5】:

    下面的代码基于不同的迭代方式。在我的机器上,Test1b 几乎是 Test1a 的两倍。我想知道这是否与您的问题有关。

    class Program
    {
        struct TwoFloats
        {
            public float x;
            public float y;
        }
    
        static TwoFloats[] a = new TwoFloats[10000];
    
        static int Test1a()
        {
            int count = 0;
            for (int i = 0; i < 10000; i += 1)
            {
                if (a[i].x < a[i].y) count++;
            }
            return count;
        }
    
        static int Test1b()
        {
            int count = 0;
            foreach (TwoFloats t in a)
            {
                if (t.x < t.y) count++;
            }
            return count;
        }
    
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int j = 0; j < 5000; ++j)
            {
                Test1a();
            }
            sw.Stop();
            Trace.WriteLine(sw.ElapsedMilliseconds);
            sw.Reset();
            sw.Start();
            for (int j = 0; j < 5000; ++j)
            {
                Test1b();
            }
            sw.Stop();
            Trace.WriteLine(sw.ElapsedMilliseconds);
        }
    
    }
    

    【讨论】:

    • foreach 和 for-loop 给我几乎相同的结果
    猜你喜欢
    • 1970-01-01
    • 2011-03-23
    • 2017-01-06
    • 1970-01-01
    • 2010-10-05
    • 2021-09-18
    • 2015-07-18
    • 1970-01-01
    相关资源
    最近更新 更多