【问题标题】:Performance issue with very high number of lists created in loop循环中创建的列表数量非常多的性能问题
【发布时间】:2016-04-07 16:37:56
【问题描述】:

这是我在这里的第一篇文章...
请考虑到我是一名业余程序员。
症状:
我的目标是提高我的代码的性能。 该程序仅使用 65% 的 CPU 和 500Mb 内存。还有另外 800Mb 的可用物理内存可供程序使用,并且大约 30% 的 CPU 处于空闲状态。我不知道进一步利用资源和提高代码性能的瓶颈在哪里。
背景:
我写了一个程序来大规模测试金融算法。该算法有许多参数,我正在尝试找到最佳的参数组合。为此,我针对所有可能的参数组合运行它。
该算法具有数据系列的输入。它遍历数据系列并产生结果。
所以为了做到这一点,我将算法的代码插入到 Parallel.Foreach 循环中,该循环为每个参数组合运行一次。优化的关键代码是循环。
由于代码很长,我发布了主干:

...
class candle : IComparable<candle>  //Data element to represent a candle and all chart data
    {
        public double open;
        public double high;
        ...40 more
    }
class param : IComparable<param>
{
    public int par1;
    public int par2;
    public int par3;
    ... a few more    
}
//Running program
{
List<candle> sourceCandleList = new List<candle>();
List<param> paramList = new List<param>(1000000);

// Code to populate sourceCandleList and paramList in here
}
// EDIT: Start parallel processing
Parallel.ForEach(paramList,
    p =>
    {
        List<candle> CandleList = new List<candle>(sourceCandleList.Count);
        foreach (var cndl in sourceCandleList)
           {
            candle c = new candle();
            c.open = cndl.open;
            c.high = cndl.high;
            ...
            //Run calculations and populate fields in CandleList
            }
         //Evaluate results
     }

paramlist 有大约 140.000 个元素。 sourceCandleList 有大约 2.000 个元素。这意味着我不断创建每个包含 2.000 个项目的列表,然后在处理后删除列表。我可以在运行代码时看到 GC 每秒清理大约 200Mb 内存。目前 Parallel.ForEach 的 1 个循环需要 80 毫秒。该程序不会将任何数据写入磁盘,只会向控制台写入非常少的数据。
也许阻止 GC 工作的一种方法是将 CandleList 保留在内存中,然后在下一个循环运行时覆盖它。为此,我需要为每个线程定义一个静态列表,但我不确定这怎么可能。也许只是创建一堆列表,然后在新线程启动时尝试使用 TryEnter 捕获一个空闲的列表?


编辑
我没有明确说明程序首先生成 2 个列表:sourceCandleList 和 paramList。然后,一旦它们完成,它们就再也不会改变了。在这两个列表完全填充后,程序开始测试算法:sourceCandleList 是输入,paramList 中的一条记录是要应用的参数集。问题是程序每次都定义一个新的 CandleList。所以我需要类似的东西:
ConcurrentQueue<List<candle>> ListOfCanldes = new ConcurrentQueue<List<candle>>();

但是我想不出正确的语法。


问题:
为什么CPU都没有充分利用内存?我是否达到了内存的最大读/写速度!?
如何避免 GC 减慢程序的速度?
我怎么知道因为 GC 而我实际上损失了多少时间?
我怎样才能提高性能?

【问题讨论】:

  • 检查 ObjectPool 类的实现
  • 如果您想提高性能,您可以拥有的最重要的工具是分析器。使用一个。
  • 首先 - 很好的第一个问题!您如何看待“GC 每秒清理大约 200 Mb 内存”?您是在使用分析器,还是自己打电话询问,还是什么?
  • @arootbeer 我只是在 Windows 任务管理器中观察到“工作集增量(内存)”列。
  • 所以你每次都克隆 sourceCandleList 并且测试的算法会修改它?您正在寻找最佳参数,而参数的质量是一个数字?

标签: c# performance list garbage-collection


【解决方案1】:

为什么CPU都没有充分利用内存?我是否达到了内存的最大读/写速度!?

  • 没有足够的信息来说明。但是使用Parallel.ForEach 会限制您使用的线程数。可能没有使用列表,而是使用ConcurrentQueueConcurrentStack 之类的东西。

我会做这样的事情来更快地处理它们:

class Program
    {
        static ConcurrentQueue<candle> sourceCandleList = new ConcurrentQueue<candle>();
        static ConcurrentBag<param> paramList = new ConcurrentBag<param>();
        static void Main(string[] args)
        {
            var threads = new List<Thread>();
            var numberOfThreads = 10;
            for (int i = 0; i < numberOfThreads; i++)
            {
                threads.Add(new Thread(Run));
            }
            threads.ForEach(i => i.Start());
        }
        static void Run()
        {
            candle item;
            while (sourceCandleList.TryDequeue(out item))
            {
                //do you processing here
            }
        }
    }

如何避免 GC 减慢程序速度? : 别再担心 GC。

我怎么知道我实际上因为 GC 而浪费了多少时间? 您可以使用 VS 2015 的分析 - 或下载 Ant Profiler 之类的工具。但请不要再担心 GC。

如何提高性能?请参阅我上面的代码片段。我不知道你的程序还在做什么。

【讨论】:

  • 我更新了原来的问题。我也不再担心GC。我现在可能会担心编写 CPU 密集型代码会变成内存密集型代码。
【解决方案2】:

最后我用下面的代码解决了。原因是我不能让多个线程在同一个队列上工作,因为我需要保持顺序或元素。在这种情况下,不需要额外的 order by。
结果是内存使用量减少了 50%,并且在程序运行时变化很小。现在程序以 100% 的速度运行所有内核。

static void Main(string[] args)
    {
        // Parameters to test
        List<candle> paramList = new List<candle>();

        // Original list
        List<candle> sourceList = new List<candle>();

        // Create a copy for each thread
        int maxThreads = 16;
        ConcurrentBag<int> pool = new ConcurrentBag<int>();
        List<candle>[] processList = new List<candle>[maxThreads];
        for (int i = 0; i <= maxThreads - 1; i++)
        {
            pool.Add(i);
            processList[i] = sourceList.ConvertAll(p => p);
        }
        Parallel.For(0, paramList.Count,
        new ParallelOptions { MaxDegreeOfParallelism = maxThreads },
        p =>
        {
            int slot = 0;
            int item;
            for (int i = 0; i <= 2; i++)
            {
                if (pool.TryTake(out item))
                {
                    slot = item;
                    break;
                }
                else
                {
                    i = 0;
                }
            }
            lock (processList[slot])
            {
                // Do processing here
            }
            pool.Add(slot);
        });
    }

【讨论】:

  • 看看albahari.com/threading/part5.aspx,每个线程都可以有自己的List(使用ThreadLocal是一种选择),这种情况下不需要洗牌。
  • 使用Parallel.For 使用多个线程。在这种情况下,线程池由您控制。在我的示例中,您可以创建任意数量的线程。您正在使用最大值。您的代码片段中有 16 个线程。但不能保证 16 个线程。但是,Parallel.For 在该块执行结束时会重新同步。哪个方便。
猜你喜欢
  • 2011-07-27
  • 1970-01-01
  • 1970-01-01
  • 2014-06-14
  • 1970-01-01
  • 1970-01-01
  • 2020-03-25
  • 1970-01-01
  • 2014-09-20
相关资源
最近更新 更多