【问题标题】: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%。

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

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

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

我的问题是

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

我是 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 重写它应该很简单,这将消除围绕该锁的争用。但就目前而言,提供建议有点困难,因为预期的结果尚不清楚。

标签: c# multithreading parallel-processing locking performance-testing


【解决方案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】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-09-19
        • 2014-01-17
        • 2023-03-29
        • 2015-05-05
        • 1970-01-01
        • 1970-01-01
        • 2015-06-12
        • 1970-01-01
        相关资源
        最近更新 更多