C# 提高数组查找循环性能
Posted
技术标签:
【中文标题】C# 提高数组查找循环性能【英文标题】:C# Increase Array Find Loop Performance 【发布时间】:2018-12-12 17:24:38 【问题描述】:我有一个 Datapoint[] file = new Datapoint[2592000]
数组。这个数组充满了时间戳和随机值。创建它们要花费我 2 秒。但是在另一个函数prepareData();
中,我正在为另一个数组TempBuffer
准备240 个值。
在prepareData()
函数中,我在file
数组中搜索匹配值。如果我找不到任何时间戳并将值设置为 0,否则我将使用找到的值 + 相同的时间戳。
函数如下所示:
public void prepareData()
stopWatch.Reset();
stopWatch.Start();
Int32 unixTimestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
for (double i = unixTimestamp; unixTimestamp - 240 < i; i--)
bool exists = true;
if (exists != (Array.Exists(file, element => element.XValue == i)))
TempBuffer = TempBuffer.Skip(1).Concat(new DataPoint[] new DataPoint(UnixTODateTime(i).ToOADate(), 0) ).ToArray();
else
DataPoint point = Array.Find(file, element => element.XValue == i);
TempBuffer = TempBuffer.Skip(1).Concat(new DataPoint[] new DataPoint(UnixTODateTime(i).ToOADate(), point.YValues) ).ToArray();
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
现在的问题是file
(2'592'000) 中的数据量需要 40 秒!对于像 10'000 这样的较小数量,这不是问题,并且工作正常且快速。但是,一旦我将 file
大小设置为我喜欢的 2'592'000 点,CPU 就会被推到 99% 的性能,而函数需要的时间太长了。
TempBuffer 样本值: X = 将 UnixTimeStamp 转换为 DateTime 并将 DateTime 转换为 AODate X=43285.611087963, Y=23
文件样本值: X = Unix时间戳 X=1530698090, Y=24
将 tempbuffer 值转换为 AODate 很重要,因为 tempbuffer 数组中的数据显示在 mschart 中。
有没有办法改进我的代码,从而获得更好的性能?
【问题讨论】:
您可以在循环之前将TempBuffer
复制到列表中。由于你做了很多突变,它们在列表上会比每次迭代都创建多个新数组要快。
不能用列表吗?
【参考方案1】:
这是您的任务最高效的方式(这只是一个模板,不是最终代码):
public void prepareData()
// it will be initialized with null values
var tempbuffer = new DataPoint[240];
var timestamp = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
var oldest = timestamp - 240 + 1;
// fill tempbuffer with existing DataPoints
for (int i = 0; i < file.Length; i++)
if (file[i].XValue <= timestamp && file[i].XValue > timestamp - 240)
tempbuffer[file[i].XValue - oldest] = new DataPoint(file[i].XValue, file[i].YValues);
// fill null values in tempbuffer with 'empty' DataPoints
for (int i = 0; i < tempbuffer.Length; i++)
tempbuffer[i] = tempbuffer[i] ?? new DataPoint(oldest + i, 0);
我有大约 10 毫秒
来自 cmets 的更新:
如果您想获取多个DataPoint's
并使用某个函数(例如平均值)获取结果,那么:
public void prepareData()
// use array of lists of YValues
var tempbuffer = new List<double>[240];
// initialize it
for (int i = 0; i < tempbuffer.Length; i++)
tempbuffer[i] = new List<double>(); //set capacity for better performance
var timestamp = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
var oldest = timestamp - 240 + 1;
// fill tempbuffer with existing DataPoint's YValues
for (int i = 0; i < file.Length; i++)
if (file[i].XValue <= timestamp && file[i].XValue > timestamp - 240)
tempbuffer[file[i].XValue - oldest].Add(file[i].YValues);
// get result
var result = new DataPoint[tempbuffer.Length];
for (int i = 0; i < result.Length; i++)
result[i] = new DataPoint(oldest + i, tempbuffer[i].Count == 0 ? 0 : tempbuffer[i].Average());
【讨论】:
我也正在尝试一起获取数据并获得假设 2 个数据的平均值。因此,对于文件中的 2 个样本,我得到平均值并将它们放入临时缓冲区。你能帮我解决这个问题吗?由于我尝试修改代码但没有成功。【参考方案2】:您尚未向我们提供您的代码的完整图片。理想情况下,我想要样本数据和完整的类定义。但鉴于可用的限制信息,我认为您会发现类似这样的方法:
public void prepareData()
Int32 unixTimestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
var map = file.ToLookup(x => x.XValue);
TempBuffer =
Enumerable
.Range(0, 240)
.Select(x => unixTimestamp - x)
.SelectMany(x =>
map[x]
.Concat(new DataPoint(UnixTODateTime(x).ToOADate(), 0)).Take(1))
.ToArray();
【讨论】:
【参考方案3】:Array.Exists() 和 Array.Find() 是 O(N) 次操作,您执行它们 x M (240) 次。
改用 LINQ Join:
DataPoint[] dataPoints; // your "file" variable
var seekedTimestamps = Enumerable.Range(0, 240).Select(i => unixTimestamp - i);
var matchingDataPoints = dataPoints.Join(seekedTimestamps, dp => dp.XValue, sts => sts, (dp, sts) => dp);
var missingTimestamps = seekedTimestamps.Except(matchingDataPoints.Select(mdp => mdp.XValue));
// do your logic with found and missing here
// ...
LINQ Join 使用散列(在选定的“键”上)并且接近 O(n)
或者,假设输入中的时间戳是唯一的,并且您计划对输入执行多个操作,请构造一个 Dictionary<int (Timestamp), DataPoint>
(昂贵),这将使您获得所需数据点的 O(1) 检索:var dataPoint = dict[wantedTimestamp];
【讨论】:
【参考方案4】:如果 DataPoint 是唯一的(没有 2 个具有相同值的实例),您应该将列表 file
切换为字典。字典查找比潜在地迭代数组的所有成员要快得多。
当然你需要实现GetHashCode
和Equals
或者为每个Datapoint
定义一个唯一的键。
【讨论】:
以上是关于C# 提高数组查找循环性能的主要内容,如果未能解决你的问题,请参考以下文章