【问题标题】:5x Performance with Parallel.For... on a Dual Core?Parallel.For... 的 5 倍性能在双核上?
【发布时间】:2012-07-19 08:00:46
【问题描述】:

我正在做一些有趣的实验计算,这时我遇到了一个有趣的结果:

Completed 1024x1024 pixels with 700 points in...
For Loop (Inline): 19636ms
For Loop: 12612ms
Parallel.For Loop: 3835ms

这不是我所期望的。

系统:Windows 7 64、i3 2120 [双核、4 线程]、Visual Studio 2010。

构建:优化开启,发布模式 [无调试器],32 位。

其次是令人失望的 64 位性能。虽然它在比率方面更符合我的预期,但它通过全面减慢来实现这一点。

Completed 1024x1024 pixels with 700 points in...
For Loop (Inline): 23409ms
For Loop: 24373ms
Parallel.For Loop: 6839ms

计算很简单:对于索引 x 和 y,找到最接近的 Vector3 并将其存储在二维数组中。

如果你敢的话,这个问题是试图解释为什么内联 for 循环如此缓慢。解释 64 位版本性能不足的加分项。

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace TextureFromPoints
{
    class Program
    {
        const int numPoints = 700;
        const int textureSize = 1024;

        static Random rnd = new Random();

        static void Main(string[] args)
        {
            while (true)
            {
                Console.WriteLine("Starting");
                Console.WriteLine();

                var pointCloud = new Vector3[numPoints];

                for (int i = 0; i < numPoints; i++)
                    pointCloud[i] = new Vector3(textureSize);

                var result1 = new Vector3[textureSize, textureSize];
                var result2 = new Vector3[textureSize, textureSize];
                var result3 = new Vector3[textureSize, textureSize];

                var sw1 = Stopwatch.StartNew();
                for (int x = 0; x < textureSize; x++)
                    for (int y = 0; y < textureSize; y++)
                    {
                        var targetPos = new Vector3(x, y, 0);
                        var nearestV3 = pointCloud[0];
                        var nearestV3Distance = nearestV3.DistanceToPoint(targetPos);

                        for (int i = 1; i < numPoints; i++)
                        {
                            var currentV3 = pointCloud[i];
                            var currentV3Distance = currentV3.DistanceToPoint(targetPos);
                            if (currentV3Distance < nearestV3Distance)
                            {
                                nearestV3 = currentV3;
                                nearestV3Distance = currentV3Distance;
                            }
                        }
                        result1[x, y] = nearestV3;
                    }
                sw1.Stop();

                var sw2 = Stopwatch.StartNew();
                for (int x = 0; x < textureSize; x++)
                    for (int y = 0; y < textureSize; y++)
                        Computation(pointCloud, result2, x, y);
                sw2.Stop();


                var sw3 = Stopwatch.StartNew();

                Parallel.For(0, textureSize, x =>
                {
                    for (int y = 0; y < textureSize; y++)
                        Computation(pointCloud, result3, x, y);
                });
                sw3.Stop();

                Console.WriteLine("Completed {0}x{0} pixels with {1} points in...", textureSize, numPoints);
                Console.WriteLine("{0}: {1}ms", "For Loop (Inline)", sw1.ElapsedMilliseconds);
                Console.WriteLine("{0}: {1}ms", "For Loop", sw2.ElapsedMilliseconds);
                Console.WriteLine("{0}: {1}ms", "Parallel.For Loop", sw3.ElapsedMilliseconds);
                Console.WriteLine();
                Console.Write("Verifying Data: ");
                Console.WriteLine(CheckResults(result1, result2) && CheckResults(result1, result3) ? "Valid" : "Error");
                Console.WriteLine(); Console.WriteLine();
                Console.ReadLine();
            }
        }

        private static bool CheckResults(Vector3[,] lhs, Vector3[,] rhs)
        {
            for (int x = 0; x < textureSize; x++)
                for (int y = 0; y < textureSize; y++)
                    if (!lhs[x, y].Equals(rhs[x, y]))
                        return false;
            return true;
        }

        private static void Computation(Vector3[] pointCloud, Vector3[,] result, int x, int y)
        {
            var targetPos = new Vector3(x, y, 0);
            var nearestV3 = pointCloud[0];
            var nearestV3Distance = nearestV3.DistanceToPoint(targetPos);

            for (int i = 1; i < numPoints; i++)
            {
                var currentV3 = pointCloud[i];
                var currentV3Distance = currentV3.DistanceToPoint(targetPos);
                if (currentV3Distance < nearestV3Distance)
                {
                    nearestV3 = currentV3;
                    nearestV3Distance = currentV3Distance;
                }
            }
            result[x, y] = nearestV3;
        }

        struct Vector3
        {
            public float x;
            public float y;
            public float z;

            public Vector3(float x, float y, float z)
            {
                this.x = x;
                this.y = y;
                this.z = z;
            }
            public Vector3(float randomDistance)
            {
                this.x = (float)rnd.NextDouble() * randomDistance;
                this.y = (float)rnd.NextDouble() * randomDistance;
                this.z = (float)rnd.NextDouble() * randomDistance;
            }

            public static Vector3 operator -(Vector3 a, Vector3 b)
            {
                return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
            }

            public float sqrMagnitude()
            {
                return x * x + y * y + z * z;
            }

            public float DistanceToPoint(Vector3 point)
            {
                return (this - point).sqrMagnitude();
            }
        }
    }
}

更新:感谢 Drew Marsh 的努力,我们现在有了这个超级优化的版本,它内联了所有 V3 操作。

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace TextureFromPoints
{
    class RevisedProgram
    {
        const int numPoints = 700;
        const int textureSize = 1024;

        static Random rnd = new Random();

        static void Main(string[] args)
        {
            while (true)
            {
                Console.WriteLine("Starting REVISED");
                Console.WriteLine();

                var pointCloud = new Vector3[numPoints];

                for (int i = 0; i < numPoints; i++)
                    pointCloud[i] = new Vector3(textureSize);

                var result1 = new Vector3[textureSize, textureSize];
                var result2 = new Vector3[textureSize, textureSize];
                var result3 = new Vector3[textureSize, textureSize];

                var sw1 = Inline(pointCloud, result1);

                var sw2 = NotInline(pointCloud, result2);

                var sw3 = Parallelized(pointCloud, result3);

                Console.WriteLine("Completed {0}x{0} pixels with {1} points in...", textureSize, numPoints);
                Console.WriteLine("{0}: {1}ms", "For Loop (Inline)", sw1.ElapsedMilliseconds);
                Console.WriteLine("{0}: {1}ms", "For Loop", sw2.ElapsedMilliseconds);
                Console.WriteLine("{0}: {1}ms", "Parallel.For Loop", sw3.ElapsedMilliseconds);
                Console.WriteLine();
                Console.Write("Verifying Data: ");
                Console.WriteLine(CheckResults(result1, result2) && CheckResults(result1, result3) ? "Valid" : "Error");
                Console.WriteLine();
                Console.WriteLine();
                Console.ReadLine();
            }
        }

        private static Stopwatch Parallelized(Vector3[] pointCloud, Vector3[,] result3)
        {
            var sw3 = Stopwatch.StartNew();

            Parallel.For(0, textureSize, x =>
            {
                for (int y = 0; y < textureSize; y++)
                    Computation(pointCloud, result3, x, y);
            });
            sw3.Stop();
            return sw3;
        }

        private static Stopwatch NotInline(Vector3[] pointCloud, Vector3[,] result2)
        {
            var sw2 = Stopwatch.StartNew();
            for (int x = 0; x < textureSize; x++)
                for (int y = 0; y < textureSize; y++)
                    Computation(pointCloud, result2, x, y);
            sw2.Stop();
            return sw2;
        }

        private static Stopwatch Inline(Vector3[] pointCloud, Vector3[,] result1)
        {
            var sw1 = Stopwatch.StartNew();
            for (int x = 0; x < textureSize; x++)
                for (int y = 0; y < textureSize; y++)
                {
                    var targetPos = new Vector3(x, y, 0);
                    var nearestV3 = pointCloud[0];
                    Vector3 temp1 = new Vector3(nearestV3.x - targetPos.x, nearestV3.y - targetPos.y, nearestV3.z - targetPos.z);
                    var nearestV3Distance = temp1.x * temp1.x + temp1.y * temp1.y + temp1.z * temp1.z;

                    for (int i = 1; i < numPoints; i++)
                    {
                        var currentV3 = pointCloud[i];
                        Vector3 temp2 = new Vector3(currentV3.x - targetPos.x, currentV3.y - targetPos.y, currentV3.z - targetPos.z);
                        var currentV3Distance = temp2.x * temp2.x + temp2.y * temp2.y + temp2.z * temp2.z;
                        if (currentV3Distance < nearestV3Distance)
                        {
                            nearestV3 = currentV3;
                            nearestV3Distance = currentV3Distance;
                        }
                    }
                    result1[x, y] = nearestV3;
                }
            sw1.Stop();
            return sw1;
        }

        private static bool CheckResults(Vector3[,] lhs, Vector3[,] rhs)
        {
            for (int x = 0; x < textureSize; x++)
                for (int y = 0; y < textureSize; y++)
                    if (!lhs[x, y].Equals(rhs[x, y]))
                        return false;
            return true;
        }

        private static void Computation(Vector3[] pointCloud, Vector3[,] result, int x, int y)
        {
            var targetPos = new Vector3(x, y, 0);
            var nearestV3 = pointCloud[0];
            Vector3 temp1 = new Vector3(nearestV3.x - targetPos.x, nearestV3.y - targetPos.y, nearestV3.z - targetPos.z);

            var nearestV3Distance = temp1.x * temp1.x + temp1.y * temp1.y + temp1.z * temp1.z;

            for (int i = 1; i < numPoints; i++)
            {
                var currentV3 = pointCloud[i];
                Vector3 temp2 = new Vector3(currentV3.x - targetPos.x, currentV3.y - targetPos.y, currentV3.z - targetPos.z);
                var currentV3Distance = temp2.x * temp2.x + temp2.y * temp2.y + temp2.z * temp2.z;
                if (currentV3Distance < nearestV3Distance)
                {
                    nearestV3 = currentV3;
                    nearestV3Distance = currentV3Distance;
                }
            }
            result[x, y] = nearestV3;
        }


        struct Vector3
        {
            public float x;
            public float y;
            public float z;

            public Vector3(float x, float y, float z)
            {
                this.x = x;
                this.y = y;
                this.z = z;
            }
            public Vector3(float randomDistance)
            {
                this.x = (float)rnd.NextDouble() * randomDistance;
                this.y = (float)rnd.NextDouble() * randomDistance;
                this.z = (float)rnd.NextDouble() * randomDistance;
            }
        }
    }
}

它给出了以下结果:

x86

Completed 1024x1024 pixels with 700 points in...
For Loop (Inline): 3820ms
For Loop: 3962ms
Parallel.For Loop: 1681ms

x64

Completed 1024x1024 pixels with 700 points in...
For Loop (Inline): 10978ms
For Loop: 10924ms
Parallel.For Loop: 3073ms

所以好消息是,我们可以大幅提高此代码的性能 - 并使单线程版本的运行速度与并行表亲保持一致。

坏消息是,这意味着完全放弃 x64 并手动内联所有数学。

在这个阶段,我对编译器的性能非常失望 - 我希望它们会更好。

结论

这很令人沮丧和悲伤......虽然我们真的不知道为什么我们可以对它是由愚蠢的编译器引起的做出有根据的猜测。只需将编译器从 x64 更改为 x86 并进行一些手动内联,24 秒到 3.8 秒并不是我所期望的。然而,我已经完成了我正在编写的概念证明,并且由于一个简单的空间散列,我可以在 0.7 秒内计算出一个 1024 x 1024 的图像,其中包含 70,000 个“点”——比我原来的 x64 场景快约 340000%,并且没有螺纹或内衬。因此,我已经接受了一个答案——当前的需求已经消失,尽管我仍在调查这个问题。

代码可用herehere - 它生成一个很好的Voronoi 图作为副作用:P

【问题讨论】:

  • 你的双核更像是 4 核,或者至少是 2+2。
  • @Henk Holterman,不,它有点像最新测试中的双 :P 一旦你把愚蠢的编译器排除在外,性能只有单核的 226% - 更多的是多亏了超线程,我对双核的预期 + 约 20-40% 的提升。
  • 如果不说明测试等,单个数字有点毫无意义。我见过 2+2 达到 350% 以上的基准
  • @Henk Holterman - 我不仅说明了测试,还提供了代码。我感兴趣的是为什么我通常认为的默认快速高效代码非常缓慢且效率低下。这并不是要证明单线程很慢,而是要问为什么在为现实世界应用程序开发的代码上它IS很慢!

标签: c# multithreading optimization task-parallel-library


【解决方案1】:

我知道该怎么做!用 F# 写!

Completed 1024x1024 pixels with 700 points in...
Sync: 4393ms
Parallel: 2409ms

它更快,更小...对于我在几个小时内清除的东西来说还不错,几乎没有语言知识。

module Program

open System
open System.IO
open System.Linq
open System.Threading.Tasks


let main() = 
    let numPoints = 700
    let textureSize = 1024
    let rnd = new Random()

    let randomPos() = (single (rnd.NextDouble()*(double textureSize)))
    let pointCloud = Array.init numPoints (fun _ -> (randomPos(), randomPos()))

    let distanceToPoint(sourceX :int ,sourceY : int, point ) = 
        let x = (single sourceX) - fst point
        let y = (single sourceY) - snd point
        x*x + y*y

    let syncCalc() =
        let resultData = Array2D.zeroCreate<single*single>  textureSize textureSize
        for x in 0..(textureSize-1) do
          for y in 0..(textureSize-1) do
             let mutable closestPoint = pointCloud.[0]
             let mutable closestDistance = distanceToPoint(x,y, closestPoint)
             for p in 1..(numPoints-1) do
                 let point = pointCloud.[p]
                 let distance = distanceToPoint(x,y, closestPoint)
                 if (distance < closestDistance) then
                    closestPoint <- point
                    closestDistance <- distance
             resultData.[x,y] <- closestPoint

    (*let asyncCalc() =
        let resultData = Array2D.zeroCreate<single*single>  textureSize textureSize
        let z = 
            Async.Parallel [ 
                for x in 0..(textureSize-1) -> async { 
                     for y in 0..(textureSize-1) do
                         let closestPoint = ref pointCloud.[0]
                         let closestDistance = ref (distanceToPoint(x,y, !closestPoint))
                         for p in 1..(numPoints-1) do
                             let point = pointCloud.[p]
                             let distance = distanceToPoint(x,y, !closestPoint)
                             if (distance < !closestDistance) then
                                closestPoint := point
                                closestDistance := distance
                         resultData.[x,y] <- !closestPoint
            } ]   |>Async.RunSynchronously 
        resultData*)

    let parallelCalc() =
        let resultData = Array2D.zeroCreate<single*single>  textureSize textureSize
        let z = 
            Parallel.For (0, textureSize,  fun x ->
                     for y in 0..(textureSize-1) do
                         let closestPoint = ref pointCloud.[0]
                         let closestDistance = ref (distanceToPoint(x,y, !closestPoint))
                         for p in 1..(numPoints-1) do
                             let point = pointCloud.[p]
                             let distance = distanceToPoint(x,y, !closestPoint)
                             if (distance < !closestDistance) then
                                closestPoint := point
                                closestDistance := distance
                         resultData.[x,y] <- !closestPoint)
        resultData

    //4.2s
    let sw1 = System.Diagnostics.Stopwatch.StartNew();
    let r1 = syncCalc() 
    sw1.Stop()

    //Slow!
    //let sw2 = System.Diagnostics.Stopwatch.StartNew();
    //let r2 = asyncCalc()      
    //sw2.Stop()

    //2.25s
    let sw3 = System.Diagnostics.Stopwatch.StartNew();
    let r3 = parallelCalc() 
    sw3.Stop()

    Console.WriteLine("Completed {0}x{0} pixels with {1} points in...", textureSize, numPoints)
    Console.WriteLine("Sync: {0}ms", sw1.ElapsedMilliseconds)
    //Console.WriteLine("ASync: {0}ms", sw2.ElapsedMilliseconds)
    Console.WriteLine("Parallel: {0}ms", sw3.ElapsedMilliseconds)
    Console.ReadLine() |> ignore

while true do main()

【讨论】:

    【解决方案2】:

    我怀疑 64 位性能与对齐有关。你的 Vector3 是一个 12 字节的结构;这些将在 32 位环境中占用 12 个字节,但在 64 位环境中它们将被填充到 16 个字节。如果这意味着您的数组大了 33%,那么您可以预期多出 33% 的缓存未命中。

    我的怀疑完全不正确。睡在上面之后,我尝试了以下方法:

    class Program
    {
        private struct V3
        {
            public float x;
            public float y;
            public float z;
        }
    
        private static unsafe long GetDistance()
        {
            var array = new V3[2];
            fixed (V3* pointerOne = &array[0])
            fixed (V3* pointerTwo = &array[1])
                return ((byte*)pointerTwo - (byte*)pointerOne);
        }
    
        unsafe static void Main()
        {
            Console.WriteLine(GetDistance());
            Console.WriteLine(sizeof(IntPtr));
        }
    }
    

    输出,32 位系统:

    12
    4
    

    输出,64 位系统:

    12
    8
    

    【讨论】:

    • 我喜欢你的想法,我将如何验证这个理论?
    • 啊,是的,好点。分析是您能够看到这一点的方式。
    • 对齐不是这样工作的。 struct 包含 4 个字节的 floats,这意味着每个 float 必须与 4 个字节对齐。但在这种情况下,这根本不会改变 struct 的大小。而且 64 位完全不影响对齐,至少在 struct 不包含任何引用的情况下。
    • @svick 感谢您的更正。写的时候很累,睡着了,越想越觉得不对。
    • @NPSF3000 你不会,因为这是错误的。我在答案中添加了一些验证码。
    【解决方案3】:

    来自 8 核 i7、Win7、x64 的所有数据

    令人惊讶的是,您肯定会获得 5 倍。编写此测试的一个问题是,您已将所有三种方法都放在 Main 方法中,这迫使编译器必须创建并保持同步以满足 Parallel.For 中使用的闭包的需求妨碍了内联方法。如果您按以下方式分解工作,您将看到所有三种实现的性能都显着提高……至少对于 x86:

    x86 之前:

    For Loop (Inline): 24313ms 
    For Loop: 25236ms 
    Parallel.For Loop: 3840ms
    

    x86 之后:

    For Loop (Inline): 13007ms
    For Loop: 13013ms
    Parallel.For Loop: 2208ms
    

    因此,查看我的 x86 Parallel。对于结果,您会发现它的扩展速度约为 5.9 倍,并且每个版本在隔离时都快得多。

    接下来,有趣的是,在进行同样的更改之后,x64 绝对没有任何收益。事实上,在 3 次测试中的 2 次中,每次运行的结果都略高一点。

    x64 之前

    For Loop (Inline): 24222ms
    For Loop: 25197ms
    Parallel.For Loop: 3810ms
    

    x64 之后

    For Loop (Inline): 25302ms
    For Loop: 25209ms
    Parallel.For Loop: 3821ms
    

    我没有直接的答案,为什么 x64 会如此糟糕,除了人们不断提出这样的代码,这使得 x64 JIT 看起来很糟糕,所以也许其他人可以插话。

    也就是说,在这样的实现中,您可能还需要考虑另一件事:缓存行失效。 @StephenToub 编写的There is an awesome MSDN article here 解释了这一切。 TL;DR;就是这样,因为您的所有数据都存储在一个数组中并且存在差异。具有不同本地 (L2) 缓存的内核将修改该阵列的部分内容,以便将数据与与其重叠的其他内核同步。如果部分差异。正在处理的内核太靠近了,您最终会得到很多这样的同步,这会影响您的并行收益。这篇文章展示了一种技术,您实际上在工作数组中分配了足够的额外空间,以分隔包含您要处理的数据的实际部分,这样当这些核心处理数据时,它们不必使另一个无效核心。 for 循环,而不是更接近 8 倍。我敢打赌,如果您努力解决任何缓存行失效问题,您可以再从中挤出 10% 以上。请记住,在设置和协调并行工作时总会有一些开销,因此您永远不会得到 100% 的完美。

    这是您的程序的修订版本,每种方法都被分解为单独的方法:

    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace TextureFromPoints
    {
        class RevisedProgram
        {
            const int numPoints = 700;
            const int textureSize = 1024;
    
            static Random rnd = new Random();
    
            static void Main(string[] args)
            {
                while(true)
                {
                    Console.WriteLine("Starting REVISED");
                    Console.WriteLine();
    
                    var pointCloud = new Vector3[numPoints];
    
                    for(int i = 0; i < numPoints; i++)
                        pointCloud[i] = new Vector3(textureSize);
    
                    var result1 = new Vector3[textureSize, textureSize];
                    var result2 = new Vector3[textureSize, textureSize];
                    var result3 = new Vector3[textureSize, textureSize];
    
                    var sw1 = Inline(pointCloud, result1);
    
                    var sw2 = NotInline(pointCloud, result2);
    
    
                    var sw3 = Parallelized(pointCloud, result3);
    
                    Console.WriteLine("Completed {0}x{0} pixels with {1} points in...", textureSize, numPoints);
                    Console.WriteLine("{0}: {1}ms", "For Loop (Inline)", sw1.ElapsedMilliseconds);
                    Console.WriteLine("{0}: {1}ms", "For Loop", sw2.ElapsedMilliseconds);
                    Console.WriteLine("{0}: {1}ms", "Parallel.For Loop", sw3.ElapsedMilliseconds);
                    Console.WriteLine();
                    Console.Write("Verifying Data: ");
                    Console.WriteLine(CheckResults(result1, result2) && CheckResults(result1, result3) ? "Valid" : "Error");
                    Console.WriteLine();
                    Console.WriteLine();
                    Console.ReadLine();
                }
            }
    
            private static Stopwatch Parallelized(Vector3[] pointCloud, Vector3[,] result3)
            {
                var sw3 = Stopwatch.StartNew();
    
                Parallel.For(0, textureSize, x =>
                {
                    for(int y = 0; y < textureSize; y++)
                        Computation(pointCloud, result3, x, y);
                });
                sw3.Stop();
                return sw3;
            }
    
            private static Stopwatch NotInline(Vector3[] pointCloud, Vector3[,] result2)
            {
                var sw2 = Stopwatch.StartNew();
                for(int x = 0; x < textureSize; x++)
                    for(int y = 0; y < textureSize; y++)
                        Computation(pointCloud, result2, x, y);
                sw2.Stop();
                return sw2;
            }
    
            private static Stopwatch Inline(Vector3[] pointCloud, Vector3[,] result1)
            {
                var sw1 = Stopwatch.StartNew();
                for(int x = 0; x < textureSize; x++)
                    for(int y = 0; y < textureSize; y++)
                    {
                        var targetPos = new Vector3(x, y, 0);
                        var nearestV3 = pointCloud[0];
                        var nearestV3Distance = nearestV3.DistanceToPoint(targetPos);
    
                        for(int i = 1; i < numPoints; i++)
                        {
                            var currentV3 = pointCloud[i];
                            var currentV3Distance = currentV3.DistanceToPoint(targetPos);
                            if(currentV3Distance < nearestV3Distance)
                            {
                                nearestV3 = currentV3;
                                nearestV3Distance = currentV3Distance;
                            }
                        }
                        result1[x, y] = nearestV3;
                    }
                sw1.Stop();
                return sw1;
            }
    
            private static bool CheckResults(Vector3[,] lhs, Vector3[,] rhs)
            {
                for(int x = 0; x < textureSize; x++)
                    for(int y = 0; y < textureSize; y++)
                        if(!lhs[x, y].Equals(rhs[x, y]))
                            return false;
                return true;
            }
    
            private static void Computation(Vector3[] pointCloud, Vector3[,] result, int x, int y)
            {
                var targetPos = new Vector3(x, y, 0);
                var nearestV3 = pointCloud[0];
                var nearestV3Distance = nearestV3.DistanceToPoint(targetPos);
    
                for(int i = 1; i < numPoints; i++)
                {
                    var currentV3 = pointCloud[i];
                    var currentV3Distance = currentV3.DistanceToPoint(targetPos);
                    if(currentV3Distance < nearestV3Distance)
                    {
                        nearestV3 = currentV3;
                        nearestV3Distance = currentV3Distance;
                    }
                }
                result[x, y] = nearestV3;
            }
    
            struct Vector3
            {
                public float x;
                public float y;
                public float z;
    
                public Vector3(float x, float y, float z)
                {
                    this.x = x;
                    this.y = y;
                    this.z = z;
                }
                public Vector3(float randomDistance)
                {
                    this.x = (float)rnd.NextDouble() * randomDistance;
                    this.y = (float)rnd.NextDouble() * randomDistance;
                    this.z = (float)rnd.NextDouble() * randomDistance;
                }
    
                public static Vector3 operator -(Vector3 a, Vector3 b)
                {
                    return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
                }
    
                public float sqrMagnitude()
                {
                    return x * x + y * y + z * z;
                }
    
                public float DistanceToPoint(Vector3 point)
                {
                    return (this - point).sqrMagnitude();
                }
            }
        }
    }
    

    更新:

    根据冯元指出的x64 JIT没有内联的方法,您可以更改程序以内联计算,并从x64版本中获得比x86版本更好的性能。这显然很糟糕,但这是我以前见过 x64 JIT 破坏的那种东西。这是新的数字:

    内联 x64 后:

    For Loop (Inline): 19032ms
    For Loop: 19209ms
    Parallel.For Loop: 3015ms
    

    代码的内联版本:

    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace TextureFromPoints
    {
        class RevisedProgram
        {
            const int numPoints = 700;
            const int textureSize = 1024;
    
            static Random rnd = new Random();
    
            static void Main(string[] args)
            {
                while(true)
                {
                    Console.WriteLine("Starting REVISED");
                    Console.WriteLine();
    
                    var pointCloud = new Vector3[numPoints];
    
                    for(int i = 0; i < numPoints; i++)
                        pointCloud[i] = new Vector3(textureSize);
    
                    var result1 = new Vector3[textureSize, textureSize];
                    var result2 = new Vector3[textureSize, textureSize];
                    var result3 = new Vector3[textureSize, textureSize];
    
                    var sw1 = Inline(pointCloud, result1);
    
                    var sw2 = NotInline(pointCloud, result2);
    
    
                    var sw3 = Parallelized(pointCloud, result3);
    
                    Console.WriteLine("Completed {0}x{0} pixels with {1} points in...", textureSize, numPoints);
                    Console.WriteLine("{0}: {1}ms", "For Loop (Inline)", sw1.ElapsedMilliseconds);
                    Console.WriteLine("{0}: {1}ms", "For Loop", sw2.ElapsedMilliseconds);
                    Console.WriteLine("{0}: {1}ms", "Parallel.For Loop", sw3.ElapsedMilliseconds);
                    Console.WriteLine();
                    Console.Write("Verifying Data: ");
                    Console.WriteLine(CheckResults(result1, result2) && CheckResults(result1, result3) ? "Valid" : "Error");
                    Console.WriteLine();
                    Console.WriteLine();
                    Console.ReadLine();
                }
            }
    
            private static Stopwatch Parallelized(Vector3[] pointCloud, Vector3[,] result3)
            {
                var sw3 = Stopwatch.StartNew();
    
                Parallel.For(0, textureSize, x =>
                {
                    for(int y = 0; y < textureSize; y++)
                        Computation(pointCloud, result3, x, y);
                });
                sw3.Stop();
                return sw3;
            }
    
            private static Stopwatch NotInline(Vector3[] pointCloud, Vector3[,] result2)
            {
                var sw2 = Stopwatch.StartNew();
                for(int x = 0; x < textureSize; x++)
                    for(int y = 0; y < textureSize; y++)
                        Computation(pointCloud, result2, x, y);
                sw2.Stop();
                return sw2;
            }
    
            private static Stopwatch Inline(Vector3[] pointCloud, Vector3[,] result1)
            {
                var sw1 = Stopwatch.StartNew();
                for(int x = 0; x < textureSize; x++)
                    for(int y = 0; y < textureSize; y++)
                    {
                        var targetPos = new Vector3(x, y, 0);
                        var nearestV3 = pointCloud[0];
                        Vector3 temp1 = nearestV3 - targetPos;
                        var nearestV3Distance = temp1.x * temp1.x + temp1.y * temp1.y + temp1.z * temp1.z;
    
                        for(int i = 1; i < numPoints; i++)
                        {
                            var currentV3 = pointCloud[i];
                            Vector3 temp2 = currentV3 - targetPos;
                            var currentV3Distance = temp2.x * temp2.x + temp2.y * temp2.y + temp2.z * temp2.z;
                            if(currentV3Distance < nearestV3Distance)
                            {
                                nearestV3 = currentV3;
                                nearestV3Distance = currentV3Distance;
                            }
                        }
                        result1[x, y] = nearestV3;
                    }
                sw1.Stop();
                return sw1;
            }
    
            private static bool CheckResults(Vector3[,] lhs, Vector3[,] rhs)
            {
                for(int x = 0; x < textureSize; x++)
                    for(int y = 0; y < textureSize; y++)
                        if(!lhs[x, y].Equals(rhs[x, y]))
                            return false;
                return true;
            }
    
            private static void Computation(Vector3[] pointCloud, Vector3[,] result, int x, int y)
            {
                var targetPos = new Vector3(x, y, 0);
                var nearestV3 = pointCloud[0];
                Vector3 temp1 = nearestV3 - targetPos;
                var nearestV3Distance = temp1.x * temp1.x + temp1.y * temp1.y + temp1.z * temp1.z;
    
                for(int i = 1; i < numPoints; i++)
                {
                    var currentV3 = pointCloud[i];
                    Vector3 temp2 = currentV3 - targetPos;
                    var currentV3Distance = temp2.x * temp2.x + temp2.y * temp2.y + temp2.z * temp2.z;
                    if(currentV3Distance < nearestV3Distance)
                    {
                        nearestV3 = currentV3;
                        nearestV3Distance = currentV3Distance;
                    }
                }
                result[x, y] = nearestV3;
            }
    
            private static float DistanceToPoint(Vector3 vector, Vector3 point)
            {
                Vector3 final = vector - point;
    
                return final.x * final.x + final.y * final.y + final.z * final.z;
            }
    
            struct Vector3
            {
                public float x;
                public float y;
                public float z;
    
                public Vector3(float x, float y, float z)
                {
                    this.x = x;
                    this.y = y;
                    this.z = z;
                }
                public Vector3(float randomDistance)
                {
                    this.x = (float)rnd.NextDouble() * randomDistance;
                    this.y = (float)rnd.NextDouble() * randomDistance;
                    this.z = (float)rnd.NextDouble() * randomDistance;
                }
    
                public static Vector3 operator -(Vector3 a, Vector3 b)
                {
                    return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
                }
            }
        }
    }
    

    【讨论】:

    • 做了一些测试:第一个 改进的 版本将内联版本与非内联版本相提并论,但除此之外比我的原始代码慢(无论出于何种原因)。第二个版本(内联操作)要快得多 - x86 的 4s、4s 和 1.8s...但是 x64 的 19s、19s 和 5.5。
    • 时间不早了,我居然意识到我忘记在线Vector3减法运算符逻辑了。很有可能也会提高性能。您是否碰巧抓住了它并尝试内联它?我现在要睡觉了。
    • 谢谢,和你一起上床睡觉:P 我在问题中添加了最新的代码构建[我为你内联了减号]。可悲的是......它在 x64 上仍然是垃圾,如果我们必须竭尽全力才能获得不错的性能,编译器的表现真的很差!
    【解决方案4】:

    结构体在 64 位系统上仍然是 12 字节。

    由于没有内联 DistanceToPoint,64 位速度较慢

     2     0 [  0] TextureFromPoints.Program+Vector3.DistanceToPoint(Vector3)
    23     0 [  0] Texture!TextureFromPoints.Program+Vector3.DistanceToPoint(Vector3)
    22     0 [  1]   Texture!TextureFromPoints.Program+Vector3.op_Subtraction(Vector3, Vector3)
    30    22 [  0] Texture!TextureFromPoints.Program+Vector3.DistanceToPoint(Vector3)
    10     0 [  1]   Texture!TextureFromPoints.Program+Vector3.sqrMagnitude()
    33    32 [  0] Texture!TextureFromPoints.Program+Vector3.DistanceToPoint(Vector3)
    

    在 32 位系统上,只有 sqrtMagnitude 是一个函数调用,DistanceToPoint 和 op_Subtraction 是内联的。

    【讨论】:

    • 他是对的。如果您稍微修改代码以不使用该辅助方法,您会从 x64 中获得相同的性能。我会更详细地修改我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-10
    • 2013-01-22
    • 2015-04-22
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 2011-09-05
    相关资源
    最近更新 更多