基于输入(目前使用大量 if 语句完成)编程输出的最有效方法是什么?

What is the most efficient way of programming an output based on input (currently done with lots of if-statements)?

在单片机运行过程中float output_value会给我不同的值(控制算法)。我想利用某种 "lookup table" 比较从 float output_value 获得的每个值以找到 float final_output_value.

我现在有很多 if 语句,例如:

if(output_value > 3.0 && output_value < 5.0)
{
 final_output_value = 3.2;
} 
if(output_value > 5.0 && output_value < 8.0)
{
 final_output_value = 5.7
} 

这就是我的想法。我正在寻找更有效的方法,有人知道更好的方法吗?

这里有很多你没有提到的决定因素,所以让我概述一些:

  • 可维护性(即正确编写代码和维护代码的难易程度)
  • 波动性(即范围值及其输出多久改变一次?)
  • 代码大小(您提到微控制器场景,您是否对需要克服的代码大小有限制?)

我的另一个问题可能会改变我给出的建议(我这么说是因为我只测试了一个变体,我不知道另一个的效果)是是否可以优化范围检查。

你有这两个:

3.0 < v < 5.0
5.0 < v < 8.0

但是 5.0 呢完全?

你能像这样重写范围吗:

3.0 <= v < 5.0
5.0 <= v < 8.0

(或者把等号放在另一个),这样区间是连续的,中间没有空洞?我假设是的,你可以。

无论如何,我所做的是:

  1. 编写一组纯 "linear" 基于代码的 if 语句,其中 300 个
  2. 编写一个 table 驱动的方法,通过一次从点 1 调用方法构建 table,然后在每次查找时重新使用 table 进行二进制搜索
  3. 根据第 1 点编写一个稍微更好的基于代码的方法。

基本上,"linear" 代码方法如下所示:

private double IfStatementLookup(double input)
{
    if (input < 01.0) return 01.0;
    if (input < 02.0) return 02.0;
    if (input < 03.0) return 03.0;
    if (input < 04.0) return 04.0;
    if (input < 05.0) return 05.0;

等等直到 < 300,基本上是一组值的简单 "ceiling" 函数。所以0 <= input < 1returns1,依此类推

table 驱动的方法如下所示:

private double TableLookup(double input)
{
    if (input >= 300)
        return 0;

    int lower = 0;
    int upper = _Table.Length - 1;

    while (lower <= upper)
    {
        int middle = lower + (upper - lower) / 2;
        if (input < _Table[middle][0])
            upper = middle - 1;
        else
            lower = middle + 1;
    }

    return _Table[upper][1];
}

稍微好一点的 if 语句版本如下所示:

double SlightlyOptimizedIfStatementLookup(double input)
{
    if (input < 100)
    {
        if (input < 50)
        {
            if (input < 01.0) return 01.0;
            if (input < 02.0) return 02.0;
            if (input < 03.0) return 03.0;
        }
        // if-statements for 50-100
    }
    if (input < 200)
        // same here for 200-299
    if (input < 300)
        // same here for 300-399

现在,在我展示基准测试结果之前,这里有一些要点:

  1. if 语句方法可能比 table 驱动方法需要更多 代码,因此您需要考虑这是否重要
  2. 可以通过将每个新级别的 if 语句的范围大致减半来进一步更改稍微好一点的 if 语句版本,例如通过为 < 25 和 [=21 添加 if 语句=] 等在各自的级别内
  3. if 语句方法(任何一种)都运行无法维护。您无法从文件中动态加载 table 范围,或通过用户界面或下载 table 进行维护,您将需要发布新版本的软件来更改它.
  4. 还可以对 table 驱动的方法进行进一步优化。

整个LINQPad程序可以在这个答案的底部找到。请务必 运行 打开优化(右下角的小 /o+ 按钮)。

注意! 我可能在程序中犯了很多错误,这些错误有可能使我的整个答案无效。请独立核实结果。

无论如何,这里是缩写的基准测试结果:

                        Method |      Mean |     Error |    StdDev |
------------------------------ |----------:|----------:|----------:|
                   TableDriven | 163.95 us | 0.9125 us | 0.7620 us |
            LinearIfStatements | 314.41 us | 2.3511 us | 2.0842 us |
 SlightlyOptimizedIfStatements |  64.43 us | 0.7900 us | 0.7390 us |

结论:基于代码的基于 if 语句的方法优于(我的简单)table 驱动方法。

这是整个程序,请注意,由于 if 语句的数量,它有点长。

void Main()
{
    new LookupTest().Validate();
    BenchmarkRunner.Run<LookupTest>();
}

public class LookupTest
{
    public LookupTest()
    {
        _Inputs = Enumerable.Range(0, 3000).Select(idx => idx / 10.0).ToArray();
        _Table = _Inputs.Select(input => new[] { input, IfStatementLookup(input) }).ToArray();

        TableDriven();
    }

    public void Validate()
    {
        bool anyMismatch = false;
        foreach (var input in _Inputs)
        {
            var ifStatements = IfStatementLookup(input);
            var slightlyOptimizedIfStatements = SlightlyOptimizedIfStatementLookup(input);
            var tableDriven = TableLookup(input);

            if (ifStatements != tableDriven || ifStatements != slightlyOptimizedIfStatements)
            {
                $"{input:0.00} => if: {ifStatements}, lookup: {tableDriven}, if+: {slightlyOptimizedIfStatements}".Dump();
                anyMismatch = true;
            }
        }
        if (anyMismatch)
            throw new InvalidOperationException();
    }

    private double[] _Inputs;
    private double[][] _Table;

    [Benchmark]
    public void TableDriven()
    {
        double sum = 0;
        for (int index = 0; index < _Inputs.Length; index++)
        {
            sum += TableLookup(_Inputs[index]);
        }
        if (sum < 0)
            throw new InvalidOperationException();
    }

    private double TableLookup(double input)
    {
        if (input >= 300)
            return 0;

        int lower = 0;
        int upper = _Table.Length - 1;

        while (lower <= upper)
        {
            int middle = lower + (upper - lower) / 2;
            if (input < _Table[middle][0])
                upper = middle - 1;
            else
                lower = middle + 1;
        }

        return _Table[upper][1];
    }

    [Benchmark]
    public void LinearIfStatements()
    {
        double sum = 0;
        for (int index = 0; index < _Inputs.Length; index++)
        {
            sum += IfStatementLookup(_Inputs[index]);
        }
        if (sum < 0)
            throw new InvalidOperationException();
    }

    [Benchmark]
    public void SlightlyOptimizedIfStatements()
    {
        double sum = 0;
        for (int index = 0; index < _Inputs.Length; index++)
        {
            sum += SlightlyOptimizedIfStatementLookup(_Inputs[index]);
        }
        if (sum < 0)
            throw new InvalidOperationException();
    }

    private double IfStatementLookup(double input)
    {
        if (input < 01.0) return 01.0;
        if (input < 02.0) return 02.0;
        if (input < 03.0) return 03.0;
        if (input < 04.0) return 04.0;
        if (input < 05.0) return 05.0;
        if (input < 06.0) return 06.0;
        if (input < 07.0) return 07.0;
        if (input < 08.0) return 08.0;
        if (input < 09.0) return 09.0;
        if (input < 10.0) return 10.0;
        if (input < 11.0) return 11.0;
        if (input < 12.0) return 12.0;
        if (input < 13.0) return 13.0;
        if (input < 14.0) return 14.0;
        if (input < 15.0) return 15.0;
        if (input < 16.0) return 16.0;
        if (input < 17.0) return 17.0;
        if (input < 18.0) return 18.0;
        if (input < 19.0) return 19.0;
        if (input < 20.0) return 20.0;
        if (input < 21.0) return 21.0;
        if (input < 22.0) return 22.0;
        if (input < 23.0) return 23.0;
        if (input < 24.0) return 24.0;
        if (input < 25.0) return 25.0;
        if (input < 26.0) return 26.0;
        if (input < 27.0) return 27.0;
        if (input < 28.0) return 28.0;
        if (input < 29.0) return 29.0;
        if (input < 30.0) return 30.0;
        if (input < 31.0) return 31.0;
        if (input < 32.0) return 32.0;
        if (input < 33.0) return 33.0;
        if (input < 34.0) return 34.0;
        if (input < 35.0) return 35.0;
        if (input < 36.0) return 36.0;
        if (input < 37.0) return 37.0;
        if (input < 38.0) return 38.0;
        if (input < 39.0) return 39.0;
        if (input < 40.0) return 40.0;
        if (input < 41.0) return 41.0;
        if (input < 42.0) return 42.0;
        if (input < 43.0) return 43.0;
        if (input < 44.0) return 44.0;
        if (input < 45.0) return 45.0;
        if (input < 46.0) return 46.0;
        if (input < 47.0) return 47.0;
        if (input < 48.0) return 48.0;
        if (input < 49.0) return 49.0;
        if (input < 50.0) return 50.0;
        if (input < 51.0) return 51.0;
        if (input < 52.0) return 52.0;
        if (input < 53.0) return 53.0;
        if (input < 54.0) return 54.0;
        if (input < 55.0) return 55.0;
        if (input < 56.0) return 56.0;
        if (input < 57.0) return 57.0;
        if (input < 58.0) return 58.0;
        if (input < 59.0) return 59.0;
        if (input < 60.0) return 60.0;
        if (input < 61.0) return 61.0;
        if (input < 62.0) return 62.0;
        if (input < 63.0) return 63.0;
        if (input < 64.0) return 64.0;
        if (input < 65.0) return 65.0;
        if (input < 66.0) return 66.0;
        if (input < 67.0) return 67.0;
        if (input < 68.0) return 68.0;
        if (input < 69.0) return 69.0;
        if (input < 70.0) return 70.0;
        if (input < 71.0) return 71.0;
        if (input < 72.0) return 72.0;
        if (input < 73.0) return 73.0;
        if (input < 74.0) return 74.0;
        if (input < 75.0) return 75.0;
        if (input < 76.0) return 76.0;
        if (input < 77.0) return 77.0;
        if (input < 78.0) return 78.0;
        if (input < 79.0) return 79.0;
        if (input < 80.0) return 80.0;
        if (input < 81.0) return 81.0;
        if (input < 82.0) return 82.0;
        if (input < 83.0) return 83.0;
        if (input < 84.0) return 84.0;
        if (input < 85.0) return 85.0;
        if (input < 86.0) return 86.0;
        if (input < 87.0) return 87.0;
        if (input < 88.0) return 88.0;
        if (input < 89.0) return 89.0;
        if (input < 90.0) return 90.0;
        if (input < 91.0) return 91.0;
        if (input < 92.0) return 92.0;
        if (input < 93.0) return 93.0;
        if (input < 94.0) return 94.0;
        if (input < 95.0) return 95.0;
        if (input < 96.0) return 96.0;
        if (input < 97.0) return 97.0;
        if (input < 98.0) return 98.0;
        if (input < 99.0) return 99.0;
        if (input < 100.0) return 100.0;
        if (input < 101.0) return 101.0;
        if (input < 102.0) return 102.0;
        if (input < 103.0) return 103.0;
        if (input < 104.0) return 104.0;
        if (input < 105.0) return 105.0;
        if (input < 106.0) return 106.0;
        if (input < 107.0) return 107.0;
        if (input < 108.0) return 108.0;
        if (input < 109.0) return 109.0;
        if (input < 110.0) return 110.0;
        if (input < 111.0) return 111.0;
        if (input < 112.0) return 112.0;
        if (input < 113.0) return 113.0;
        if (input < 114.0) return 114.0;
        if (input < 115.0) return 115.0;
        if (input < 116.0) return 116.0;
        if (input < 117.0) return 117.0;
        if (input < 118.0) return 118.0;
        if (input < 119.0) return 119.0;
        if (input < 120.0) return 120.0;
        if (input < 121.0) return 121.0;
        if (input < 122.0) return 122.0;
        if (input < 123.0) return 123.0;
        if (input < 124.0) return 124.0;
        if (input < 125.0) return 125.0;
        if (input < 126.0) return 126.0;
        if (input < 127.0) return 127.0;
        if (input < 128.0) return 128.0;
        if (input < 129.0) return 129.0;
        if (input < 130.0) return 130.0;
        if (input < 131.0) return 131.0;
        if (input < 132.0) return 132.0;
        if (input < 133.0) return 133.0;
        if (input < 134.0) return 134.0;
        if (input < 135.0) return 135.0;
        if (input < 136.0) return 136.0;
        if (input < 137.0) return 137.0;
        if (input < 138.0) return 138.0;
        if (input < 139.0) return 139.0;
        if (input < 140.0) return 140.0;
        if (input < 141.0) return 141.0;
        if (input < 142.0) return 142.0;
        if (input < 143.0) return 143.0;
        if (input < 144.0) return 144.0;
        if (input < 145.0) return 145.0;
        if (input < 146.0) return 146.0;
        if (input < 147.0) return 147.0;
        if (input < 148.0) return 148.0;
        if (input < 149.0) return 149.0;
        if (input < 150.0) return 150.0;
        if (input < 151.0) return 151.0;
        if (input < 152.0) return 152.0;
        if (input < 153.0) return 153.0;
        if (input < 154.0) return 154.0;
        if (input < 155.0) return 155.0;
        if (input < 156.0) return 156.0;
        if (input < 157.0) return 157.0;
        if (input < 158.0) return 158.0;
        if (input < 159.0) return 159.0;
        if (input < 160.0) return 160.0;
        if (input < 161.0) return 161.0;
        if (input < 162.0) return 162.0;
        if (input < 163.0) return 163.0;
        if (input < 164.0) return 164.0;
        if (input < 165.0) return 165.0;
        if (input < 166.0) return 166.0;
        if (input < 167.0) return 167.0;
        if (input < 168.0) return 168.0;
        if (input < 169.0) return 169.0;
        if (input < 170.0) return 170.0;
        if (input < 171.0) return 171.0;
        if (input < 172.0) return 172.0;
        if (input < 173.0) return 173.0;
        if (input < 174.0) return 174.0;
        if (input < 175.0) return 175.0;
        if (input < 176.0) return 176.0;
        if (input < 177.0) return 177.0;
        if (input < 178.0) return 178.0;
        if (input < 179.0) return 179.0;
        if (input < 180.0) return 180.0;
        if (input < 181.0) return 181.0;
        if (input < 182.0) return 182.0;
        if (input < 183.0) return 183.0;
        if (input < 184.0) return 184.0;
        if (input < 185.0) return 185.0;
        if (input < 186.0) return 186.0;
        if (input < 187.0) return 187.0;
        if (input < 188.0) return 188.0;
        if (input < 189.0) return 189.0;
        if (input < 190.0) return 190.0;
        if (input < 191.0) return 191.0;
        if (input < 192.0) return 192.0;
        if (input < 193.0) return 193.0;
        if (input < 194.0) return 194.0;
        if (input < 195.0) return 195.0;
        if (input < 196.0) return 196.0;
        if (input < 197.0) return 197.0;
        if (input < 198.0) return 198.0;
        if (input < 199.0) return 199.0;

        # error Stack Overflow limited my answer
        // Copy the 200-299-block above and just
        // Alt+Shift Selection to mark the 2's and change them to 3's
        // BOTH PLACES

        return 0;
    }

    double SlightlyOptimizedIfStatementLookup(double input)
    {
        if (input < 100)
        {
            if (input < 50)
            {
                if (input < 01.0) return 01.0;
                if (input < 02.0) return 02.0;
                if (input < 03.0) return 03.0;
                if (input < 04.0) return 04.0;
                if (input < 05.0) return 05.0;
                if (input < 06.0) return 06.0;
                if (input < 07.0) return 07.0;
                if (input < 08.0) return 08.0;
                if (input < 09.0) return 09.0;
                if (input < 10.0) return 10.0;
                if (input < 11.0) return 11.0;
                if (input < 12.0) return 12.0;
                if (input < 13.0) return 13.0;
                if (input < 14.0) return 14.0;
                if (input < 15.0) return 15.0;
                if (input < 16.0) return 16.0;
                if (input < 17.0) return 17.0;
                if (input < 18.0) return 18.0;
                if (input < 19.0) return 19.0;
                if (input < 20.0) return 20.0;
                if (input < 21.0) return 21.0;
                if (input < 22.0) return 22.0;
                if (input < 23.0) return 23.0;
                if (input < 24.0) return 24.0;
                if (input < 25.0) return 25.0;
                if (input < 26.0) return 26.0;
                if (input < 27.0) return 27.0;
                if (input < 28.0) return 28.0;
                if (input < 29.0) return 29.0;
                if (input < 30.0) return 30.0;
                if (input < 31.0) return 31.0;
                if (input < 32.0) return 32.0;
                if (input < 33.0) return 33.0;
                if (input < 34.0) return 34.0;
                if (input < 35.0) return 35.0;
                if (input < 36.0) return 36.0;
                if (input < 37.0) return 37.0;
                if (input < 38.0) return 38.0;
                if (input < 39.0) return 39.0;
                if (input < 40.0) return 40.0;
                if (input < 41.0) return 41.0;
                if (input < 42.0) return 42.0;
                if (input < 43.0) return 43.0;
                if (input < 44.0) return 44.0;
                if (input < 45.0) return 45.0;
                if (input < 46.0) return 46.0;
                if (input < 47.0) return 47.0;
                if (input < 48.0) return 48.0;
                if (input < 49.0) return 49.0;
                return 50.0;
            }

            if (input < 51.0) return 51.0;
            if (input < 52.0) return 52.0;
            if (input < 53.0) return 53.0;
            if (input < 54.0) return 54.0;
            if (input < 55.0) return 55.0;
            if (input < 56.0) return 56.0;
            if (input < 57.0) return 57.0;
            if (input < 58.0) return 58.0;
            if (input < 59.0) return 59.0;
            if (input < 60.0) return 60.0;
            if (input < 61.0) return 61.0;
            if (input < 62.0) return 62.0;
            if (input < 63.0) return 63.0;
            if (input < 64.0) return 64.0;
            if (input < 65.0) return 65.0;
            if (input < 66.0) return 66.0;
            if (input < 67.0) return 67.0;
            if (input < 68.0) return 68.0;
            if (input < 69.0) return 69.0;
            if (input < 70.0) return 70.0;
            if (input < 71.0) return 71.0;
            if (input < 72.0) return 72.0;
            if (input < 73.0) return 73.0;
            if (input < 74.0) return 74.0;
            if (input < 75.0) return 75.0;
            if (input < 76.0) return 76.0;
            if (input < 77.0) return 77.0;
            if (input < 78.0) return 78.0;
            if (input < 79.0) return 79.0;
            if (input < 80.0) return 80.0;
            if (input < 81.0) return 81.0;
            if (input < 82.0) return 82.0;
            if (input < 83.0) return 83.0;
            if (input < 84.0) return 84.0;
            if (input < 85.0) return 85.0;
            if (input < 86.0) return 86.0;
            if (input < 87.0) return 87.0;
            if (input < 88.0) return 88.0;
            if (input < 89.0) return 89.0;
            if (input < 90.0) return 90.0;
            if (input < 91.0) return 91.0;
            if (input < 92.0) return 92.0;
            if (input < 93.0) return 93.0;
            if (input < 94.0) return 94.0;
            if (input < 95.0) return 95.0;
            if (input < 96.0) return 96.0;
            if (input < 97.0) return 97.0;
            if (input < 98.0) return 98.0;
            if (input < 99.0) return 99.0;
            return 100.0;
        }
        if (input < 200)
        {
            if (input < 150)
            {
                if (input < 101.0) return 101.0;
                if (input < 102.0) return 102.0;
                if (input < 103.0) return 103.0;
                if (input < 104.0) return 104.0;
                if (input < 105.0) return 105.0;
                if (input < 106.0) return 106.0;
                if (input < 107.0) return 107.0;
                if (input < 108.0) return 108.0;
                if (input < 109.0) return 109.0;
                if (input < 110.0) return 110.0;
                if (input < 111.0) return 111.0;
                if (input < 112.0) return 112.0;
                if (input < 113.0) return 113.0;
                if (input < 114.0) return 114.0;
                if (input < 115.0) return 115.0;
                if (input < 116.0) return 116.0;
                if (input < 117.0) return 117.0;
                if (input < 118.0) return 118.0;
                if (input < 119.0) return 119.0;
                if (input < 120.0) return 120.0;
                if (input < 121.0) return 121.0;
                if (input < 122.0) return 122.0;
                if (input < 123.0) return 123.0;
                if (input < 124.0) return 124.0;
                if (input < 125.0) return 125.0;
                if (input < 126.0) return 126.0;
                if (input < 127.0) return 127.0;
                if (input < 128.0) return 128.0;
                if (input < 129.0) return 129.0;
                if (input < 130.0) return 130.0;
                if (input < 131.0) return 131.0;
                if (input < 132.0) return 132.0;
                if (input < 133.0) return 133.0;
                if (input < 134.0) return 134.0;
                if (input < 135.0) return 135.0;
                if (input < 136.0) return 136.0;
                if (input < 137.0) return 137.0;
                if (input < 138.0) return 138.0;
                if (input < 139.0) return 139.0;
                if (input < 140.0) return 140.0;
                if (input < 141.0) return 141.0;
                if (input < 142.0) return 142.0;
                if (input < 143.0) return 143.0;
                if (input < 144.0) return 144.0;
                if (input < 145.0) return 145.0;
                if (input < 146.0) return 146.0;
                if (input < 147.0) return 147.0;
                if (input < 148.0) return 148.0;
                if (input < 149.0) return 149.0;
                return 150.0;
            }
            if (input < 151.0) return 151.0;
            if (input < 152.0) return 152.0;
            if (input < 153.0) return 153.0;
            if (input < 154.0) return 154.0;
            if (input < 155.0) return 155.0;
            if (input < 156.0) return 156.0;
            if (input < 157.0) return 157.0;
            if (input < 158.0) return 158.0;
            if (input < 159.0) return 159.0;
            if (input < 160.0) return 160.0;
            if (input < 161.0) return 161.0;
            if (input < 162.0) return 162.0;
            if (input < 163.0) return 163.0;
            if (input < 164.0) return 164.0;
            if (input < 165.0) return 165.0;
            if (input < 166.0) return 166.0;
            if (input < 167.0) return 167.0;
            if (input < 168.0) return 168.0;
            if (input < 169.0) return 169.0;
            if (input < 170.0) return 170.0;
            if (input < 171.0) return 171.0;
            if (input < 172.0) return 172.0;
            if (input < 173.0) return 173.0;
            if (input < 174.0) return 174.0;
            if (input < 175.0) return 175.0;
            if (input < 176.0) return 176.0;
            if (input < 177.0) return 177.0;
            if (input < 178.0) return 178.0;
            if (input < 179.0) return 179.0;
            if (input < 180.0) return 180.0;
            if (input < 181.0) return 181.0;
            if (input < 182.0) return 182.0;
            if (input < 183.0) return 183.0;
            if (input < 184.0) return 184.0;
            if (input < 185.0) return 185.0;
            if (input < 186.0) return 186.0;
            if (input < 187.0) return 187.0;
            if (input < 188.0) return 188.0;
            if (input < 189.0) return 189.0;
            if (input < 190.0) return 190.0;
            if (input < 191.0) return 191.0;
            if (input < 192.0) return 192.0;
            if (input < 193.0) return 193.0;
            if (input < 194.0) return 194.0;
            if (input < 195.0) return 195.0;
            if (input < 196.0) return 196.0;
            if (input < 197.0) return 197.0;
            if (input < 198.0) return 198.0;
            if (input < 199.0) return 199.0;
            return 200.0;
        }
        if (input < 300)
        {
            # error Stack Overflow limited my answer
            // Copy the 200-block above and just
            // Alt+Shift Selection to mark the 2's and change them to 3's
            // BOTH PLACES
        }
        return 0;
    }
}

由于 Stack Overflow 的答案有 30.000 个字符的限制,我不得不缩短上面的程序。您可以下载完整的 LINQPad 脚本 here.