我有一个双精度数组(通过计算),具有327,680个值。我需要将这些值转换为每个彩色图像的8位彩色。我需要一个查找表,该表将double值保存为带有byte [3]数组的索引,以将RGB值用作该温度值的可视表示形式。我最多需要15毫秒来执行此操作,而已。
我想出的最好的主意是为颜色使用字典。这是我用于测试的最少,完整和可验证的代码:
//Create lookup table
Dictionary<int, byte[]> Lookup = new Dictionary<int, byte[]>();
for (int i = 0; i < 1200; i++)
{
byte bValue = (byte)i;
byte[] b = new byte[3] { bValue, bValue, bValue };
Lookup.Add(i, b);
}
//Make proto temp readings
int[] temps = new int[640 * 512];
Random r = new Random();
for (int i = 0; i < 640 * 512; i++)
{
temps[i] = r.Next(0, 255);
}
int size = 640 * 512 * 3;
byte[] imageValues = new byte[size];
for (int i = 0; i < 50; i++)
{
Stopwatch sw = new Stopwatch();
sw.Start();
int index = 0;
foreach (int item in temps)
{
byte[] pixel = new byte[3];
if (Lookup.TryGetValue(item, out pixel))
{
imageValues[index] = pixel[0];
imageValues[index + 1] = pixel[1];
imageValues[index + 2] = pixel[2];
index += 3;
}
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
第一个问题:运行此命令时,我得到的时间在10毫秒至14毫秒范围内,具体取决于查找表包含1200项还是256项。有没有办法加快速度?
第二个问题:我的实际关键值是计算得出的温度(两倍)。由于某种原因,双精度数在最低有效位数上似乎有点不精确。我注意到一个结果应该是25最终是25.00000000012或类似的结果。如果我使用double作为搜索值,那么当实际键值为25.00000000012或反之时,我就有寻找25的真正风险。
创建双打时我可以截断什么东西,但我担心这样做的时间。
使用双精度键作为密钥时,有什么好的策略可以解决双精度不精确问题?
答案 0 :(得分:3)
第一个问题:是否可以加快速度?
您有不必要的内存分配
byte[] pixel = new byte[3];
您可以保留空变量声明
byte[] pixel;
或使用内联变量声明
if (Lookup.TryGetValue(item, out byte[] pixel))
此更改提高了我的测试性能。
答案 1 :(得分:2)
您可以通过将Dictionary<T,byte[]>
替换为byte[][]
并将每个温度double
映射到颜色阵列中的int
索引来解决这两个问题。
因此,将温度范围划分为N个相等的分区,其中N是颜色阵列中元素的数量。取每个测得的温度并将其映射到分区号,分区号也是颜色的数组索引。
将温度映射到数组索引的函数类似于:
temp => (int)(pixelValues * (temp - minTemp) / (maxTemp - minTemp));
EG
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace ConsoleApp21
{
class Program
{
static void Main(string[] args)
{
double maxTemp = 255;
double minTemp = -35;
int pixelValues = 1200;
byte[][] Lookup = new byte[pixelValues][];
for (int i = 0; i < Lookup.Length; i++)
{
byte bValue = (byte)i;
byte[] b = new byte[3] { bValue, bValue, bValue };
Lookup[i] = b;
}
//Make proto temp readings
double[] temps = new double[640 * 512];
Random r = new Random();
for (int i = 0; i < 640 * 512; i++)
{
temps[i] = r.NextDouble() * maxTemp;
}
int size = 640 * 512 * 3;
byte[] imageValues = new byte[size];
var timings = new List<long>(50);
for (int i = 0; i < 50; i++)
{
Stopwatch sw = new Stopwatch();
sw.Start();
int index = 0;
for (int j = 0; j < temps.Length; j++)
{
var lookupVal = (int)(pixelValues * (temps[j] - minTemp) / (maxTemp - minTemp));
byte[] pixel = Lookup[lookupVal];
imageValues[index] = pixel[0];
imageValues[index + 1] = pixel[1];
imageValues[index + 2] = pixel[2];
index += 3;
}
sw.Stop();
var ms = sw.ElapsedMilliseconds;
timings.Add(ms);
//Console.WriteLine(sw.ElapsedMilliseconds);
}
Console.WriteLine($"Max {timings.Max()} Avg {timings.Average()}");
Console.ReadKey();
}
}
}
输出
Max 7 Avg 3.2
答案 2 :(得分:2)
就像伊万(Ivan)所说,移动内存分配可以节省约20%
如果使用所有可能的温度值创建查找阵列(仅使用传感器的分辨率),则可以另外节省50%。
//Create array lookup table
List<byte[]> array = new List<byte[]>(25500);
for (int i = 0; i < 25500; i++)
{
byte bValue = (byte)i;
byte[] b = new byte[3] { bValue, bValue, bValue };
array.Add(b);
}
这将使您的温度从0到255.00 然后您可以像这样访问所需的值
int size = 640 * 512 * 3;
byte[] imageValues = new byte[size];
var sw = new Stopwatch();
byte[] pixel = new byte[3];
for (int i = 0; i < 50; i++)
{
sw.Start();
int index = 0;
foreach (var item in temps)
{
pixel = array[item * 100];
imageValues[index] = pixel[0];
imageValues[index + 1] = pixel[1];
imageValues[index + 2] = pixel[2];
index += 3;
}
}
sw.Stop();
Console.WriteLine($"{sw.ElapsedMilliseconds}/{sw.ElapsedMilliseconds / 50.0}");
这会使您在5毫秒以下进行单次查找
答案 3 :(得分:0)
这可能有点不公平,因为它可以优化您的示例而不是您的实际问题,但可以应用。您知道自己正在查找byte[][]
,因此只需使用锯齿状数组:Dictionary<int,(byte,byte,byte)>
。这在我的PC上平均为0.66毫秒,而在您的原始计算机上为5.4毫秒。
注意:使用ValueTuple
大约需要4毫秒,而byte
则要保留3个var repeats = 50;
Console.WriteLine();
Console.WriteLine("byte[][3]");
//Create lookup table
var lookups = 1200;
var Lookup = new byte[lookups][];
for (int i = 0; i < lookups; i++) {
byte bValue = (byte)i;
var b = new byte[3] { bValue, bValue, bValue };
Lookup[i] = b;
}
//Make proto temp readings
int[] temps = new int[640 * 512];
Random r = new Random();
for (int i = 0; i < 640 * 512; i++) {
temps[i] = r.Next(0, 255);
}
int size = 640 * 512 * 3;
byte[] imageValues = new byte[size];
long totalMS = 0;
Stopwatch sw = new Stopwatch();
for (int i = 0; i < repeats; i++) {
sw.Restart();
int index = 0;
foreach (int item in temps) {
if (item < lookups) {
var pixel = Lookup[item];
imageValues[index] = pixel[0];
imageValues[index + 1] = pixel[1];
imageValues[index + 2] = pixel[2];
index += 3;
}
}
sw.Stop();
totalMS += sw.ElapsedMilliseconds;
//Console.WriteLine(sw.ElapsedMilliseconds);
}
Console.WriteLine($"Average: {totalMS / (double)repeats} ms");
。
{{1}}
答案 4 :(得分:0)
假设计算机具有多个内核,则可以利用计算机的更多内核。最好不要全部使用它们,而为操作系统和其他应用程序腾出一个免费的空间。下面的代码使用Parallel.ForEach
,范围为partitioner,在我的计算机中将执行速度从21毫秒提高到8毫秒。
ParallelOptions options = new ParallelOptions()
{
MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount - 1)
};
Parallel.ForEach(Partitioner.Create(0, temps.Length), options, range =>
{
for (int item = range.Item1; item < range.Item2; item++)
{
byte[] pixel = new byte[3];
if (Lookup.TryGetValue(item, out pixel))
{
int updatedIndex = Interlocked.Add(ref index, 3);
int localIndex = updatedIndex - 3;
imageValues[localIndex] = pixel[0];
imageValues[localIndex + 1] = pixel[1];
imageValues[localIndex + 2] = pixel[2];
//index += 3;
}
}
});
我没有对您的代码进行任何其他更改。例如,我没有优化不必要的数组分配。
Btw多线程处理会导致线程安全性问题。因此,我使用Interlocked.Add
而不是index
编辑了使+=
递增的答案。共享访问imageValues
数组可能是安全的。