我正在使用一个类,比如Widget,它具有大量的数字真实世界属性(例如,高度,长度,重量,成本等)。有不同类型的小部件(链轮,齿轮等),但每个小部件共享完全相同的属性(当然,小部件的值会有所不同,但它们都有重量,重量等)。我有1000种每种类型的小部件(1,000个齿轮,1,000个链轮等)
我需要对这些属性执行大量计算(比如计算1000个不同小部件的属性的加权平均值)。对于加权平均值,我对每种小部件类型都有不同的权重(即,我可能更关心链轮的长度而不是齿轮的长度)。
现在,我将所有属性存储在Dictionary< string,double>在每个小部件中(小部件具有指定其类型的枚举:cog,sprocket等)。然后,我有一些计算器类,将每个属性的权重存储为字典< WidgetType,Dictionary< string,double>>。要计算每个小部件的加权平均值,我只需遍历其属性字典键,如:
double weightedAvg = 0.0;
foreach (string attibuteName in widget.Attributes.Keys)
{
double attributeValue = widget.Attributes[attributeName];
double attributeWeight = calculator.Weights[widget.Type][attributeName];
weightedAvg += (attributeValue * attributeWeight);
}
所以这个工作正常并且非常易读且易于维护,但基于某些分析,1000个小部件的速度非常慢。我的属性名称世界是已知的,并且在应用程序的生命周期中不会改变,所以我想知道一些更好的选项是什么。我能想到的几个:
1)以double [] s存储属性值和权重。我认为这可能是最有效的选择,但我需要确保数组始终以小部件和计算器之间的正确顺序存储。这也将数据与元数据分离,因此我需要将属性名称和索引之间的数组(?)存储在属性值和权重的double []中。
2)将属性值和权重存储在不可变结构中。我喜欢这个选项,因为我不必担心订购,而且数据是"自我记录"。但是有一种简单的方法可以在代码中循环这些属性吗?我有近100个属性,因此我不想对代码中的所有内容进行硬编码。我可以使用反射,但我担心这会导致更大的惩罚,因为我正在循环这么多的小部件,并且必须在每个小部件上使用反射。
还有其他选择吗?
答案 0 :(得分:4)
立即想到三种可能性。第一个,我认为你太容易拒绝了,就是在你班上有个别的领域。也就是说,名为double
,height
,length
,weight
等的个人cost
值。您说这是更多的代码计算,但你不会有字典查找的间接。
其次是放弃字典而不是数组。因此,您只需拥有Dictionary<string, double>
而不是double[]
。我再次认为你太过拒绝了这一点。您可以使用枚举轻松替换字符串字典键。所以你有:
enum WidgetProperty
{
First = 0,
Height = 0,
Length = 1,
Weight = 2,
Cost = 3,
...
Last = 100
}
鉴于此项和double
数组,您可以轻松浏览每个实例的所有值:
for (int i = (int)WidgetProperty.First; i < (int)WidgetProperty.Last; ++i)
{
double attributeValue = widget.Attributes[i];
double attributeWeight = calculator.Weights[widget.Type][i];
weightedAvg += (attributeValue * attributeWeight);
}
直接数组访问速度远远快于按字符串访问字典。
最后,您可以稍微优化字典访问。而不是在键上执行foreach
然后进行字典查找,而是在字典本身上执行foreach
:
foreach (KeyValuePair<string, double> kvp in widget.Attributes)
{
double attributeValue = kvp.Value;
double attributeWeight = calculator.Weights[widget.Type][kvp.Key];
weightedAvg += (attributeValue * attributeWeight);
}
答案 1 :(得分:1)
要计算没有循环或反射的加权平均值,一种方法是计算各个属性的加权平均值并将它们存储在某个位置。这应该在您创建窗口小部件的实例时发生。以下是需要根据您的需求进行修改的示例代码。 此外,为了进一步处理小部件本身,您可以使用数据并行性。在这个主题中看到我的另一个回应。
public enum WidgetType { }
public class Claculator { }
public class WeightStore
{
static Dictionary<int, double> widgetWeightedAvg = new Dictionary<int, double>();
public static void AttWeightedAvgAvailable(double attwightedAvg, int widgetid)
{
if (widgetWeightedAvg.Keys.Contains(widgetid))
widgetWeightedAvg[widgetid] += attwightedAvg;
else
widgetWeightedAvg[widgetid] = attwightedAvg;
}
}
public class WidgetAttribute
{
public string Name { get; }
public double Value { get; }
public WidgetAttribute(string name, double value, WidgetType type, int widgetId)
{
Name = name;
Value = value;
double attWeight = Calculator.Weights[type][name];
WeightStore.AttWeightedAvgAvailable(Value*attWeight, widgetId);
}
}
public class CogWdiget
{
public int Id { get; }
public WidgetAttribute height { get; set; }
public WidgetAttribute wight { get; set; }
}
public class Client
{
public void BuildCogWidgets()
{
CogWdiget widget = new CogWdiget();
widget.Id = 1;
widget.height = new WidgetAttribute("height", 12.22, 1);
}
}
答案 2 :(得分:0)
由于数据规范化始终如此,选择规范化级别决定了性能的很大一部分。看起来您必须从当前模型转到另一个模型或混合模式。
如果不使用C#端处理此方案,而是使用数据库,则可以提高场景的性能。然后,您可以获得索引的好处,除了想要的结果之外没有数据传输,还有已经花费在性能优化上的100000个工时。
答案 3 :(得分:0)
使用.net 4及更高版本支持的数据并行。
https://msdn.microsoft.com/en-us/library/dd537608(v=vs.110).aspx
以上链接的摘录
当并行循环运行时,TPL对数据源进行分区,以便循环可以同时在多个部分上运行。在幕后,任务计划程序根据系统资源和工作负载对任务进行分区。如果可能,调度程序会在工作负载变得不平衡时在多个线程和处理器之间重新分配工作