我正在研究.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; }
}
}
}
}
答案 0 :(得分: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使用方面所期望的东西:虽然我的机器也在运行另一个冗长的过程,它正在采用基准水平(下图中的峰值是在分析这个时程序)。
总结