自定义选项可比纯二进制搜索更快地搜索排序列表

时间:2019-06-05 11:00:25

标签: c# .net performance binary-search sorteddictionary

  

以下是用例

  • DateTime类型的排序列表,粒度以毫秒为单位
  • 搜索最接近的DateTime,满足提供的predicate委托
  • 性能是一个问题,因为List拥有100K +条记录,从最小索引到最大索引的总时间跨度为10小时,并且频繁调用(50次以上/运行)会影响性能
  

我们目前正在做什么,自定义二进制搜索如下?

 public static int BinaryLastOrDefault<T>(this IList<T> list, Predicate<T> predicate)
 {
            var lower = 0;
            var upper = list.Count - 1;

            while (lower < upper)
            {
                var mid = lower + ((upper - lower + 1) / 2);
                if (predicate(list[mid]))
                {
                    lower = mid;
                }
                else
                {
                    upper = mid - 1;
                }
            }

            if (lower >= list.Count) return -1;
            return !predicate(list[lower]) ? -1 : lower;
}
  

我可以使用字典使其变为O(1)吗?

  • 我的理解是“否”,因为输入值可能不存在,在这种情况下,我们需要返回最接近的值,如果在上面的代码中返回-1,则排序后的列表中的最后一个元素就是预期的结果
  

以下是我正在考虑的选项

  • Dictionary<int,SortedDictionary<DateTime,int>>之类的数据结构
  • 总持续时间DateTime最高值与最低值之间的持续时间为10小时〜10 * 3600 * 1000毫秒= 3600万毫秒
  • 创建的每个存储桶为60秒,元素总数〜36 million / 60 K = 600
  • 对于任何提供的DateTime值,现在都可以轻松找到存储桶,其中可以将有限数量的值存储为SortedDictionary,其中键为DateTime值,原始索引为值,因此如果需要,则可以枚举数据找到最接近的索引

在我的理解中,此实现将使搜索比上面详细介绍的二进制搜索快得多,因为将大大减少搜索到的数据。任何建议都可以做得更多,以进一步缩短搜索时间,从而进一步改善算法,我可以分别为各种独立呼叫尝试“并行”选项

1 个答案:

答案 0 :(得分:1)

我使用List<T>的本机BinarySearch方法进行了一些性能测试。查找最接近的DateTime的逻辑如下所示:

public static DateTime GetNearest(List<DateTime> source, DateTime date)
{
    var index = source.BinarySearch(date);
    if (index >= 0) return source[index];
    index = ~index;
    if (index == 0) return source[0];
    if (index == source.Count) return source[source.Count - 1];
    var d1 = source[index - 1];
    var d2 = source[index];
    return (date - d1 < d2 - date) ? d1 : d2;
}

我创建了一个随机列表,其中包含1,000,000个排序日期,从最小到最大的时间跨度为10小时。然后,我创建了一个大小相等的列表,其中包含未排序的随机日期以进行搜索,覆盖的时间跨度略长。然后将内部版本更改为Release并开始测试。结果表明,仅使用相对较慢的机器的单个内核,就可以在不到一秒钟的时间内完成80万次搜索。

然后,我通过搜索包含1,000,000个元素的List<(DateTime, object)>来增加测试的复杂性,因此每个比较都需要对dateSelector函数进行两次额外的调用,该函数返回DateTime属性每个ValueTuple中的每个。 结果:每秒每个线程350,000次搜索。

我通过使用引用类型作为元素,并向List<Tuple<DateTime, object>>填充了1,000,000个元组,进一步增加了复杂性。性能仍然相当不错:每秒每线程270,000次搜索。

我的结论是BinarySearch方法快如闪电,如果发现它是应用程序的瓶颈,那将是令人惊讶的事情。