我不知道为啥多线程编码不能减少我的代码中的计算时间

Posted

技术标签:

【中文标题】我不知道为啥多线程编码不能减少我的代码中的计算时间【英文标题】:I have no idea why the multi-thread coding cannot reduce the computing time in my code我不知道为什么多线程编码不能减少我的代码中的计算时间 【发布时间】:2020-12-19 16:12:52 【问题描述】:

我正在尝试使用多线程技术来减少计算时间。我的问题是计算独立项目的分数。例如,我有 10 个项目,需要获取每个项目的分数。我可以单线程连续使用十次来获取分数,也可以多线程并行使用。

在我的代码中,当我使用多线程代码时,计算时间平均减少了大约 7%。我预计多线程代码会将其减少约 50%。

我还将电源计划(控制面板>硬件和声音>电源选项)从平衡更改为高性能。

我用四个选项解决了同样的问题。

    单线程和平衡选项:11 分钟 多线程和平衡选项:10 分 40 秒(减少 20 秒) 单线程和高性能:10 分钟 多线程和高性能:10 分 10 秒。(增加 10 秒)

我的问题是

    下面的代码有什么问题? 为什么power选项设置高性能时,多线程代码的计算时间比单线程代码的计算时间增加(如上3和4情况) 我需要多少个内核来缩短计算时间?越多越好??

我是 C# 多线程世界的初学者。请指导我向右走。 提前感谢您的友好回答。

我做了一个简单的例子来解释我们的代码结构。该代码创建 10000 个项目并获取它们的分数。我们也使用“锁”来并行计算List结构。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleCode

    class Program
           
        static List<Item> Storage = new List<Item>();
        static object key = new object();

        static void Main(string[] args)
        
            List<Item> items = CreateItems(10000);
            int maxDegreeOfParallelism = 4;

            Parallel.ForEach(items, new ParallelOptions  MaxDegreeOfParallelism = maxDegreeOfParallelism ,
                (item) => Evaluate(item));                
        

        public static void Evaluate(Item item)
        
            item.Score = 0;

            item.Score += GetScore1(item);
            item.Score += GetScore2(item);            
        

        public static double GetScore1(Item item)
        
            return item.UnitQty * 100;
        
        public static double GetScore2(Item item)
        
            lock (key)
            
                if (item.UnitQty % 2 == 0)
                    Storage.Add(item);
                else if (Storage.Count > 0)
                    Storage.RemoveAt(Storage.Count - 1);

                return item.UnitQty * Storage.Count;
            
        
        
        public static List<Item> CreateItems(int count)
        
            List<Item> items = new List<Item>();
            for(int i=0; i<count; i++)
            
                items.Add(CreateItem(i));
            

            return items;
        
        public static Item CreateItem(int index)
        
            Item item = new Item();

            item.UnitQty = index % 25 + 1;
            return item;
        

        internal class Item
        
            public double Score;
            public int UnitQty;
        
    


【问题讨论】:

计算量大的部分可能在 lock 语句中,所有线程都必须相互等待,因此您不会从额外的线程中看到太多好处。 欢迎您!想象一下让一个人将用户添加到一个列表中。这需要一定的时间。现在想象一下让 100 个人再次将相同的用户添加到同一个列表中。您必须确保同步该操作,因为一次只有一个人可以使用该列表。这就是您的 lock 语句发生的情况,没有速度提高。仅当计算/创建要添加的用户的实际任务需要很长时间时,并行性才有用。 我很难看到GetScore2 中的代码即使使用lock 也可以是确定性的,因为它会根据数据从Storage 中删除项目。这意味着处理项目的顺序很重要,并且多个线程争夺锁,这是不可预测的。这段代码的期望结果是什么? 感谢您的关注。这是解释我的代码的简化代码。原始代码只是根据项目的优先级、位置、延迟程度等获得项目的确定性分数。 如果您可以删除对 Storage.RemoveAt 的调用,使用 PLinq 和 select 重写它应该很简单,这将消除围绕该锁的争用。但就目前而言,提供建议有点困难,因为预期的结果尚不清楚。 【参考方案1】:

您有 4 个项目在进行微小的计算,然后排队等待能够从容器中添加或删除结果。

并行性更好地用于计算繁重的任务,现在我估计启动/停止开销几乎等于它所做的计算工作。将锁定添加到等式中,可以节省更多时间。

【讨论】:

【参考方案2】:

根据您的评论:

我们假设 item_1 的 priroity_score =2,location_score = 3,delay_score =2; item_2 的 priroity_score =4,location_score = 3,delay_score =1; item_3 的 priroity_score = 5, location_score = 2, delay_score =3 和 priority_weight = 30, location_weight = 40, delay_weight = 50。item_1 = 230 + 340 + 2 50 = 280,item_2 = 4 30 + 340+ 150 = 290,item_3 = 5 * 30 + 2 * 40 + 3 * 50 = 380

using System.Collections.Generic;
using System.Linq;

namespace SampleCode


    /*
     * We assume 
     * item_1 has priroity_score =2, location_score = 3, delay_score =2; 
     * item_2 has priroity_score =4, location_score = 3, delay_score =1; 
     * item_3 has priroity_score = 5, location_score = 2, delay_score =3 
     * 
     * and priority_weight = 30, location_weight = 40, delay_weight = 50. 
     * The scores of items are item_1 = 2*30 + 3*40 + 2*50 = 280, item_2 = 4 *30 + 3*40+ 1*50 = 290, item_3 = 5 * 30 + 2 * 40 + 3 * 50 = 38
     * */
    class Program
    
        static List<Item> Storage = new List<Item>();
        static object key = new object();

        static void Main(string[] args)
        
            var range = Enumerable.Range(0, 10000);

            var maxDegreeOfParallelism = 4;

            var priorityWeight = 30;
            var locationWeight = 40;
            var delayWeight = 50;


            var items = range
                .AsParallel()
                .WithDegreeOfParallelism(maxDegreeOfParallelism)
                .Select(i =>
                
                    var item = new Item  PriorityScore = i + 2, LocationScore = i + 3, DelayScore = i + 2 ;
                    item.Score = Evaluate(item, priorityWeight, locationWeight, delayWeight);
                    return item;
                );

            Storage.AddRange(items);
        

        private static double Evaluate(Item item, int priorityWeight, int locationWeight, int delayWeight)
        
            return item.PriorityScore * priorityWeight + item.LocationScore * locationWeight + item.DelayScore * delayWeight;
        

        internal class Item
        
            public double Score;
            public double PriorityScore;
            public double LocationScore;
            public double DelayScore;
        
    

但即使在这里,在调试中使用 PLinq 运行大约需要 30 毫秒,而删除多线程将需要大约 2 毫秒,因为线程开销与您正在执行的轻量级操作相比。另请注意,内存是共享资源,不受 CPU 限制,因此堆分配将出现争用区域。 (Thread contention when allocating memory) 在您的示例代码中,争用是由GetScore2 中的锁创建的。

如果您的最终代码具有更复杂的操作,或者您正在处理大量输入。只要您避免锁定,使用 plinq 可能会给您带来比此处显示的更好的改进。

如果您有预先分配的值,您也可以使用 Parallel.ForEach:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace SampleCode


    /*
     * We assume 
     * item_1 has priroity_score =2, location_score = 3, delay_score =2; 
     * item_2 has priroity_score =4, location_score = 3, delay_score =1; 
     * item_3 has priroity_score = 5, location_score = 2, delay_score =3 
     * 
     * and priority_weight = 30, location_weight = 40, delay_weight = 50. 
     * The scores of items are item_1 = 2*30 + 3*40 + 2*50 = 280, item_2 = 4 *30 + 3*40+ 1*50 = 290, item_3 = 5 * 30 + 2 * 40 + 3 * 50 = 38
     * */
    class Program
    
        static List<Item> Storage = new List<Item>();
        static void Main(string[] args)
        
            //     Bumped n to see results  vvvvvvvv
            var range = Enumerable.Range(0, 10000000);

            var maxDegreeOfParallelism = 4;

            var priorityWeight = 30;
            var locationWeight = 40;
            var delayWeight = 50;

            var items = range
                .Select(i => new Item  PriorityScore = i + 2, LocationScore = i + 3, DelayScore = i + 2 )
                .ToList();

            Parallel.ForEach(items, 
                new ParallelOptions  MaxDegreeOfParallelism = maxDegreeOfParallelism , 
                i => i.Score = Evaluate(i, priorityWeight, locationWeight, delayWeight));
            //~43ms

            foreach(var i in items)
            
                i.Score = Evaluate(i, priorityWeight, locationWeight, delayWeight);
            
            //~99ms

            Storage.AddRange(items);
        

        private static double Evaluate(Item item, int priorityWeight, int locationWeight, int delayWeight)
        
            return item.PriorityScore * priorityWeight + item.LocationScore * locationWeight + item.DelayScore * delayWeight;
        

        internal class Item
        
            public double Score;
            public double PriorityScore;
            public double LocationScore;
            public double DelayScore;
        
    

与往常一样,需要权衡取舍,您需要针对自己的特定需求进行基准测试。

【讨论】:

【参考方案3】:

多线程在线程上下文之间切换可能需要相当长的时间,因此没有必要提供一些加速。有时一切都会反过来。

【讨论】:

以上是关于我不知道为啥多线程编码不能减少我的代码中的计算时间的主要内容,如果未能解决你的问题,请参考以下文章

我有一个错误,我不确定它为啥不工作(线程)

我不知道为啥我不能在 C++ 中初始化一个数组的数组

java - 为啥main方法没有转换为java中的守护线程[重复]

为啥看起来两个线程正在访问我的代码中的一个锁?

知道为啥我的 DbInitializer 中的数据没有填充到我的数据库中

我在 csound 中收到一条错误消息,但我不知道为啥