【问题标题】:C# Increase Array Find Loop PerformanceC# 提高数组查找循环性能
【发布时间】: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 复制到列表中。由于你做了很多突变,它们在列表上会比每次迭代都创建多个新数组要快。
  • 不能用列表吗?

标签: c# arrays winforms performance loops


【解决方案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定义一个唯一的键。

      【讨论】:

        猜你喜欢
        • 2017-09-26
        • 2020-08-25
        • 1970-01-01
        • 1970-01-01
        • 2013-01-29
        • 2018-11-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多