1

我正在开发一个用 .Net 开发的遗传机器学习项目(与 Matlab – My Norm 相对)。我不是专业的 .net 编码器,所以请原谅任何愚蠢的实现。

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

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

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

基本上,多个并行线程需要读取一个非常大的数据集的连续部分(数据一旦加载就不会改变)。该数据集由大约 300 到 1000 个连续的 Doubles 和超过 500k 行的任何内容组成。由于数据集可以超过 2GB 的 .Net 对象限制,它不能存储在普通的二维数组中——最简单的方法是使用单个数组的通用列表。

并行可扩展性似乎是一个很大的限制因素,因为在具有 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; }
        }

    }
}
}
4

1 回答 1

4

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

从最初阅读代码(即静态分析)开始,唯一让我感到震惊的是循环内 Temp 的不断重新分配;这效率不高,如果可能的话,需要移出循环。

使用分析器,您可以看到正在发生的事情:

资料概要

我首先使用您发布的代码进行了分析,(如果您没有发布问题的完整可编译示例,您将获得最高分,如果您没有,我现在不会回答这个问题)。

这表明大容量在循环内部,我将分配移动到 Parallel.ForEach 循环。

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

原始代码

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

这将我带到了我的第一条优化规则,即首先检查您的算法而不是优化代码。一个好的算法的一个糟糕的实现通常比一个高度优化的糟糕的算法更快。

去掉 Temp 的重复分配,画面略有变化;

将分配移至外循环后

还值得通过指定并行度进行一些调整;我发现 Parallel.ForEach 对于我使用它的用途来说已经足够好了,但是再次手动将工作划分为队列可能会获得更好的结果。

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

在运行时,我在 CPU 使用率方面得到了我所期望的结果:尽管我的机器也在运行另一个冗长的进程,该进程处于基本级别(下图中的峰值是分析该程序时)。

在此处输入图像描述

所以总结一下

  1. 查看最常执行的部分,并在可能的情况下提出新算法。
  2. 目标机器上的配置文件
  3. 只有当您确定上述 (1) 时,才值得考虑优化算法;考虑以下因素 a) 代码优化 b) 内存调整/数据分区以尽可能多地保留在缓存中 c) 改进线程使用
于 2013-07-25T00:06:04.050 回答