【发布时间】:2022-01-12 11:34:21
【问题描述】:
如果这是在错误的论坛中,我深表歉意。尽管在此站点上发现了很多数组操作,但其中大多数都是平均/求和...使用 LINQ 将数字数组作为一个集合,它可以很好地处理数组中的所有值。但我需要处理多个数组(大小相同)上的每个索引。
我的例程从设备接收数组数据,通常是double[512] 或ushort[512];单个设备本身将始终具有相同大小的数组数据,但数组大小的范围可以从 256 到 2048,具体取决于设备。我需要保持CountToAverage 数组的数量来平均。每次收到一个数组,都要从队列中push和pop,以保证平均进程中数组的个数是一致的(这部分进程固定在Setup()中,用于本次基准测试。为了比较的目的,基准测试结果显示在代码之后。
-
我正在寻找的是平均所有数组的每个索引的值的最快最有效的方法,并返回一个新数组(大小相同),其中每个索引是从数组集中平均的。要平均的数组数量可以在 3 到 25 之间(下面的代码将基准参数设置为 10)。我在测试中有 2 种不同的平均方法,第 2 种明显快,比第一种快 6-7 倍。我的第一个问题是;有什么方法可以更快地实现这一点,可以在 O(1) 或 O(log n) 时间复杂度下完成?
-
其次,我使用一个队列(可以更改为
ConcurrentQueue来实现)作为要处理的数组的持有者。我使用队列的主要原因是因为我可以保证对数组的馈送进行 FIFO 处理,这很关键。此外,我可以通过foreach循环(就像List)处理队列中的值,而无需在我准备好之前出列。如果有人知道这是否会阻碍性能,我会很感兴趣,因为我没有对其进行基准测试。请记住,它必须是线程安全的。如果您有另一种方法来以线程安全的方式处理多组数组数据,我会“全神贯注”。
性能要求的原因是这不是唯一发生的过程,我有多个设备以大约每 1-5 毫秒 1 的速率“流式传输”发送阵列结果,每个设备来自不同的线程/进程/连接,还有其他几个更密集的算法需要处理,所以这不是瓶颈。
感谢您对优化和性能的任何见解。
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Microsoft.Diagnostics.Tracing.Parsers.MicrosoftAntimalwareEngine;
namespace ArrayAverage
{
public class ArrayAverage
{
[Params(10)]
public int CountToAverage;
[Params(512, 2048)]
public int PixelSize;
static Queue<double[]> calcRepo = new Queue<double[]>();
static List<double[]> spectra = new();
[Benchmark]
public double[] CalculateIndexAverages()
{
// This is too slow
var avg = new double[PixelSize];
for (int i = 0; i < PixelSize; i++)
{
foreach (var arrayData in calcRepo)
{
avg[i] += arrayData[i];
}
avg[i] /= calcRepo.Count;
}
return avg;
}
[Benchmark]
public double[] CalculateIndexAverages2()
{
// this is faster, but is it the fastest?
var sum = new double[PixelSize];
int cnt = calcRepo.Count;
foreach (var arrayData in calcRepo)
{
for (int i = 0; i < PixelSize; i++)
{
sum[i] += arrayData[i];
}
}
var avg = new double[PixelSize];
for (int i = 0; i < PixelSize; i++)
{
avg[i] = sum[i] / cnt;
}
return avg;
}
[GlobalSetup]
public void Setup()
{
// Just generating some data as simple Triangular curve simulating a range of spectra
for (double offset = 0; offset < CountToAverage; offset++)
{
var values = new double[PixelSize];
var decrement = 0;
for (int i = 0; i < PixelSize; i++)
{
if (i > (PixelSize / 2))
decrement--;
values[i] = (offset / 7) + i + (decrement * 2);
}
calcRepo.Enqueue(values);
}
}
}
public class App
{
public static void Main()
{
BenchmarkRunner.Run<ArrayAverage>();
}
}
}
基准测试结果:
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1348 (21H1/May2021Update)
Intel Core i7-6700HQ CPU 2.60GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100-preview.7.21379.14
[Host] : .NET 5.0.12 (5.0.1221.52207), X64 RyuJIT [AttachedDebugger]
DefaultJob : .NET 5.0.12 (5.0.1221.52207), X64 RyuJIT
| Method | Arrays To Average | Array Size | Mean | Error | StdDev |
|---|---|---|---|---|---|
| CalculateIndexAverages | 10 | 512 | 32.164 μs | 0.5485 μs | 0.5130 μs |
| CalculateIndexAverages2 | 10 | 512 | 5.792 μs | 0.1135 μs | 0.2241 μs |
| CalculateIndexAverages | 10 | 2048 | 123.628 μs | 2.3394 μs | 1.9535 μs |
| CalculateIndexAverages2 | 10 | 2048 | 22.311 μs | 0.4366 μs | 0.8093 μs |
【问题讨论】:
-
你没有任何分支,你只处理顺序数据。这是恕我直言,你会得到最快的。您可以展开循环,一次处理 4 个整数,但我认为编译器已经这样做了。为什么需要 double 而不是 int?也许使用 LONG,而不是双精度数。
-
如果您希望平均
n数组与m元素,那么它的复杂性是O(n * m)。不能是O(1),甚至不能是O(n)或O(m)。 -
我认为您应该阅读How to Ask,然后向我们询问您的整体问题,而不是关于此优化。
-
你的第一个版本很慢,因为你在内存位置之间跳转,第二个版本更好,因为你顺序处理一个完整的数组。 CPU 缓存会更好地处理这个问题。
-
@Charles - 这是有道理的,谢谢你的洞察力。
标签: c# arrays multithreading performance optimization