使用双键高效缓存整数

时间:2017-09-04 10:13:25

标签: c++ optimization

我的代码从不同范围之间具有不同精度的设备获取测量值。测量结果以double的形式收到,我希望将其转换为int,这是我的内部数组的索引,它存储所有可能的值,而它们之间没有间隙。

目前代码:

struct Range
{
    double  FirstVal;
    double  Precision;
}

class Measure
{
    std::vector<Range> mRangeList;   // Gets filled with ascending ranges
    int* mStartIdxAtRange = nullptr; // Gets filled with starting incides for each range

    int ValToIdx(double val) const
    {
        // Find the precision for the current range
        int rangeAffinity = static_cast<int>(mRangeList.size()) - 1;
        for (int i = rangeAffinity - 1; i >= 0; i--)
        {
            if (val< mRangeList[i+ 1].FirstVal)
            {
                rangeAffinity = i;
            }
            else
            {
                break;
            }
        }

        double howMuchBigger = val- mRangeList[rangeAffinity].FirstVal;
        int retIdx = mStartIdxAtRange[rangeAffinity] + howMuchBigger / mRangeList[rangeAffinity].Precision + 0.5;
        return retIdx;
    }
}

问题是ValToIdx被调用很多,并且是分析后最昂贵的功能。我想缓存这些值,所以我尝试了这个:

mutable std::unordered_map<double, int> Measure::mValToIdxCache;

int Measure::ValToIdx(double val) const
{
    auto it = mValToIdxCache.find(val);
    if (it != mValToIdxCache.end())
    {
        return it->second;
    }

    // Find the precision for the current range
    int rangeAffinity = static_cast<int>(mRangeList.size()) - 1;
    for (int i = rangeAffinity - 1; i >= 0; i--)
    {
        if (val< mRangeList[i+ 1].FirstVal)
        {
            rangeAffinity = i;
        }
        else
        {
            break;
        }
    }

    double howMuchBigger = val- mRangeList[rangeAffinity].FirstVal;
    int retIdx = mStartIdxAtRange[rangeAffinity] + howMuchBigger / mRangeList[rangeAffinity].Precision + 0.5;
    mValToIdxCache[val] = retIdx;
    return retIdx;
}

但是,这使得功能慢了3倍。如何缓存每个值的索引,以便缓存比从头开始计算更快? 对于软件的任何给定运行,可以假设测量值在vector的~10个可用范围中的1到3个范围内。

2 个答案:

答案 0 :(得分:1)

你可以添加“int startIdx;”到Range结构并从Measure中删除mStartIndexAtRange?我怀疑这会提高缓存性能。

此外,你可以存储1.0 /精度范围而不是精度。这样你就可以使用乘法而不是除法,例如:

int retIdx = mStartIdxAtRange[rangeAffinity] + howMuchBigger *
                mRangeList[rangeAffinity].ReciprocalPrecision + 0.5;

答案 1 :(得分:1)

根据我对这个问题的理解,我会这样解决:

1)假设值正在平滑变化,范围的边界将一次改变一个索引,我将使用左或右顺序搜索,从当前范围开始

if V < R[i]:
    i-= 1
    while V < R[i]:
        i-= 1
else
    while V >= R[i+1]:
        i+= 1

从一个值V到下一个值,保留索引i。例如,可以使用中间索引初始化它。

当你保持相同的范围时,这需要进行2次比较。如果你进入下一个较低的范围,2个比较,并进入下一个更高的范围,进行3次比较。

2)原则上你需要检查索引i是否仍然在允许的范围内(0rangeAffinity除外)。

这可以通过使用标记值来避免:如果需要,使用非常小且非常大的值扩展bounds数组,以确保V位于其中一个区间中,并且上面的范围搜索片段将保持安全。

3)预先计算每个间隔的两个系数,以便用

进行整个转换
int(Ai + Bi * V)

请注意,与mStartIdxAtRange相反,系数Ai是双精度,并且已包含0.5舍入项。

我们对V的变体知之甚少。但是如果事实证明舍入值经常在一段时间内保持不变,那么计算与该相同舍入值相对应的范围并在其他任何事物之前检测该范围​​的偏移可能是有益的。在这种情况下,无需重新计算舍入表达式。