首页 文章

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

提问于
浏览
1

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

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

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

在这个项目中,收敛到接近理想的解决方案通常需要大约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 回答

  • 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)时才值得考虑优化算法;考虑以下因素a)代码优化b)内存调整/分区数据以保持缓存c)线程使用的改进

相关问题