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&lt;int (Timestamp), DataPoint&gt;(昂贵),这将使您获得所需数据点的 O(1) 检索:var dataPoint = dict[wantedTimestamp];

【讨论】:

【参考方案4】:

如果 DataPoint 是唯一的(没有 2 个具有相同值的实例),您应该将列表 file 切换为字典。字典查找比潜在地迭代数组的所有成员要快得多。

当然你需要实现GetHashCodeEquals或者为每个Datapoint定义一个唯一的键。

【讨论】:

以上是关于C# 提高数组查找循环性能的主要内容,如果未能解决你的问题,请参考以下文章

for循环遍历查找数据与sqlite数据库查找数据性能问题

C#中怎么判断一个数是不是在一个集合里?

C# 减少嵌套循环

C#中的大数组算术

c#如何查找string数组的某一个值的索引

在实体中查找对象和加载实体、Foreach 或在哪里使用 LinQ ON C# 的最佳性能?