在C#中完成查找的最快方法以及如何处理双精度值的不精确性?

时间:2019-07-16 19:55:26

标签: c# double lookup lookup-tables

我有一个双精度数组(通过计算),具有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的真正风险。

创建双打时我可以截断什么东西,但我担心这样做的时间。

使用双精度键作为密钥时,有什么好的策略可以解决双精度不精确问题?

5 个答案:

答案 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数组可能是安全的。