性能从多个并行线程读取大型数据集

时间:2013-07-24 15:27:43

标签: c# machine-learning bigdata

我正在研究.Net开发的遗传机器学习项目(而不是Matlab - My Norm)。我不是专业的.net编码器,所以请原谅任何noobish实现。

项目本身是巨大的所以我不会厌倦你的全部细节,但基本上人口神经网络(如决策树)的每一个都在问题域上进行评估,在这种情况下使用感知输入流。允许人口中表现最佳的人群繁殖和生产后代(继承父母双方的倾向),表现不佳的人群被杀死或繁殖出来。继续进化直到找到可接受的解决方案。一旦找到,最终演变的“网络”将从实验室中提取出来并置于轻量级的实际应用程序中。该技术可用于开发非常复杂的控制解决方案,正常编程几乎不可能或太耗时,如自动驾驶汽车,机械稳定性控制,数据中心负载平衡等等。

无论如何,该项目到目前为止取得了巨大的成功,并且产生了惊人的结果,但唯一的问题是,一旦我转向更大的数据集,性能就会非常缓慢。我希望只是我的代码,所以非常感谢一些专家的帮助。

在这个项目中,收敛到接近理想的解决方案通常需要大约7天的处理时间!只需对参数进行一些调整并等待结果就太痛苦了。

基本上,多个并行线程需要读取非常大的数据集的连续部分(加载后数据不会更改)。该数据集包含连续约300至1000个双打和超过500k行的任何内容。由于数据集可能超过.Net对象限制2GB,因此无法存储在普通的2d数组中 - 最简单的方法是使用单个数组的通用列表。

并行可扩展性似乎是一个很大的限制因素,因为在具有32个Xeon核心的服务器上运行代码,通常在早餐时吃大数据集并不会比Corei3桌面产生更多的性能提升!

随着内核数量的增加,性能提升迅速减少。

通过分析代码(我的知识有限),我得到的印象是,从多个线程中读取数据集存在大量争用。

我尝试使用Jagged数组和各种并发集合尝试不同的数据集实现,但无济于事。

我为基准测试编写了一个快速而肮脏的代码,类似于原始的核心实现,但仍然表现出类似的读取性能问题和并行可伸缩性问题。

任何想法或建议都会非常感激或确认这是我最好的。

非常感谢

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

//Benchmark script to time how long it takes to read dataset per iteration

namespace Benchmark_Simple
{
class Program

{
    public static TrainingDataSet _DataSet;
    public static int Features = 100; //Real test will require 300+
    public static int Rows = 200000; //Real test will require 500K+
    public static int _PopulationSize = 500; //Real test will require 1000+
    public static int _Iterations = 10;
    public static List<NeuralNetwork> _NeuralNetworkPopulation = new List<NeuralNetwork>();

    static void Main()
    {
        Stopwatch _Stopwatch = new Stopwatch();

        //Create Dataset
        Console.WriteLine("Creating Training DataSet");
        _DataSet = new TrainingDataSet(Features, Rows);
        Console.WriteLine("Finished Creating Training DataSet");

        //Create Neural Network Population
        for (int i = 0; i <= _PopulationSize - 1; i++)
        {
            _NeuralNetworkPopulation.Add(new NeuralNetwork());
        }

        //Main Loop
        for (int i = 0; i <= _Iterations - 1; i++)
        {
            _Stopwatch.Restart();

            Parallel.ForEach(_NeuralNetworkPopulation, _Network => { EvaluateNetwork(_Network); });

            //######## Removed for simplicity ##########

            //Run Evolutionary Genetic Algorithm on population - I.E. Breed the strong, kill of the weak

            //##########################################
            //Repeat until acceptable solution is found

            Console.WriteLine("Iteration time: {0}", _Stopwatch.ElapsedMilliseconds / 1000);

            _Stopwatch.Stop();

        }

        Console.ReadLine();

    }

    private static void EvaluateNetwork(NeuralNetwork Network)
    {
        //Evaluate network on 10% of the Training Data at a random starting point

        double Score = 0;

        Random Rand = new Random();

        int Count = (Rows / 100) * 10;

        int RandonStart = Rand.Next(0, Rows - Count);

        //The data must be read sequentially
        for (int i = RandonStart; i <= RandonStart + Count; i++)
        {
            double[] NetworkInputArray = _DataSet.GetDataRow(i);

            //####### Dummy Evaluation - just give it somthing to do for the sake of it
            double[] Temp = new double[NetworkInputArray.Length + 1];
            for (int j = 0; j <= NetworkInputArray.Length - 1; j++)
            {
                Temp[j] = Math.Log(NetworkInputArray[j] * Rand.NextDouble());
            }
            Score += Rand.NextDouble();
            //##################
        }
        Network.Score = Score;
    }

    public class TrainingDataSet
    {
        //Simple demo class of fake data for benchmarking

        private List<double[]> DataList = new List<double[]>();

        public TrainingDataSet(int Features, int Rows)
        {
            Random Rand = new Random();

            for (int i = 1; i <= Rows; i++)
            {
                double[] NewRow = new double[Features];
                for (int j = 0; j <= Features - 1; j++)
                {
                    NewRow[j] = Rand.NextDouble();
                }
                DataList.Add(NewRow);
            }

        }

        public double[] GetDataRow(int Index)
        {
            return DataList[Index];
        }

    }

    public class NeuralNetwork
    {
        //Simple Class to represent a dummy Neural Network - 
        private double _Score;
        public NeuralNetwork()
        {
        }

        public double Score
        {
            get { return _Score; }
            set { _Score = value; }
        }

    }
}
}

1 个答案:

答案 0 :(得分:4)

首先,回答任何性能问题的唯一方法是分析应用程序。我使用的是VS 2012内置分析器 - 还有其他https://stackoverflow.com/a/100490/19624

从初始阅读代码开始,即静态分析,唯一突然出现在我身上的是Temp在循环中的不断重新分配;这不是有效的,如果可能的话,需要移出循环。

使用分析器,您可以看到发生了什么:

Profile Summary

我首先使用您发布的代码进行了分析,(如果您还没有发布完整的可编辑问题示例,请给您留下最高分,如果您现在还没有回答这个问题)。

这表明批量位于循环内部,我将分配移动到Parallel.ForEach循环。

Parallel.ForEach(_NeuralNetworkPopulation, _Network => 
{
    double[] Temp = new double[Features + 1];
    EvaluateNetwork(_Network, Temp); 
});

Original Code

所以我从上面可以看出,重新分配有4.4%的浪费;但可能不足为奇的是,内循环占87.6%。

这使我开始了我的第一个优化规则,即首先检查算法而不是优化代码。良好算法的不良实现通常比高度优化的差算法快。

删除Temp的重复分配会稍微改变图片;

After moving allocation to outer loop

通过指定并行性也值得调整一下;我发现Parallel.ForEach足以满足我的需求,但是再次通过手动将工作分区为队列可以获得更好的结果。

Parallel.ForEach(_NeuralNetworkPopulation, 
                 new ParallelOptions { MaxDegreeOfParallelism = 32 },  
                 _Network => 
            {
                double[] Temp = new double[Features + 1];
                EvaluateNetwork(_Network, Temp); 
            });

虽然在运行中我得到了我在CPU使用方面所期望的东西:虽然我的机器也在运行另一个冗长的过程,它正在采用基准水平(下图中的峰值是在分析这个时程序)。

enter image description here

总结

  1. 查看最常执行的部分,并在可能的情况下提出新算法。
  2. 目标计算机上的配置文件
  3. 只有在您确定上述(1)之后才值得考虑优化算法;考虑以下因素 a)代码优化 b)内存调整/分区数据以保持缓存中的数量 c)线程使用的改进