cad.net 排序和搜索
基础
众所周知,我们很经常找到一个目标,然后中断搜索,
传统方式:
var array = int[] { 1,5,6,3,2,8,9,6,5,4 };
var target = 8;
int i;
for(i = 0; i < array.Length; i++) {
if (array[i] == target)
break;
}
Debug.WriteLine($"目标索引: {i}");
下面有几个搜索并返回的方法:
1: list.FindIndex会找到第一个索引位置,等价传统方式.
2: list.Find是list专用的,性能比3高一点,找到返回对象,找不到:class返回null,struct返回default.
3: list.FirstOrDefault是迭代器用的,找到返回对象,找不到:class返回null,struct返回default.
4: list.First找到第一个,找不到会报错,最好不要用它咯.
5: list.LastOrDefault 找到最后一个满足,API对称设计,IList是倒序遍历,迭代器需要遍历全部.
当这个数组很长的时候,你会发现这种排序之后再搜索,可以快6倍!
原理就是CPU分支预测功能可以对其加速.
但是,我们已经排序了,那么为什么不尝试二分法呢?
int result = Array.BinarySearch(array, target);
if (result >= 0) {
Console.WriteLine($"目标值 {target} 在数组中的索引是 {result}");
} else {
Console.WriteLine($"目标值 {target} 不在数组中.查找结果为 {result},该值表示如果要插入元素应插入的位置(取反减1)");
}
写个main测试
using System;
using System.Collections.Generic;
using System.Diagnostics;
class Program
{
static void Main()
{
const long TicksPerNanosecond = 10;
var list = new List<int>();
Random random = new Random();
for (int j = 0; j < 100000; j++) list.Add(random.Next(1, 1000000));
var target = list[random.Next(0, list.Count)];
Stopwatch stopwatch1 = Stopwatch.StartNew();
int i;
for (i = 0; i < list.Count; i++)
if (list[i] == target) break;
stopwatch1.Stop();
long elapsedNs1 = stopwatch1.ElapsedTicks / TicksPerNanosecond;
Console.WriteLine($"传统for方式耗时: {elapsedNs1} 纳秒,目标值: {list[i]},目标索引: {i}");
Stopwatch stopwatch2a = Stopwatch.StartNew();
int findIndex = list.FindIndex(element => element == target);
stopwatch2a.Stop();
long elapsedNs2a = stopwatch2a.ElapsedTicks / TicksPerNanosecond;
Console.WriteLine($"排序前FindIndex方法耗时: {elapsedNs2a} 纳秒,目标值: {list[findIndex]},目标索引: {findIndex}");
Stopwatch stopwatch2 = Stopwatch.StartNew();
var findResult = list.Find(element => element == target);
stopwatch2.Stop();
long elapsedNs2 = stopwatch2.ElapsedTicks / TicksPerNanosecond;
Console.WriteLine($"排序前Find方法耗时: {elapsedNs2} 纳秒,目标值: {findResult}");
// 新副本并排序
var sortedList = new List<int>(list);
sortedList.Sort();
Stopwatch stopwatch3 = Stopwatch.StartNew();
var sortedFindResult = sortedList.Find(element => element == target);
stopwatch3.Stop();
long elapsedNs3 = stopwatch3.ElapsedTicks / TicksPerNanosecond;
Console.WriteLine($"排序后Find方法耗时: {elapsedNs3} 纳秒,目标值: {sortedFindResult}");
Stopwatch stopwatch4 = Stopwatch.StartNew();
int binarySearchResult = sortedList.BinarySearch(target);
stopwatch4.Stop();
long elapsedNs4 = stopwatch4.ElapsedTicks / TicksPerNanosecond;
if (binarySearchResult >= 0)
Console.WriteLine($"二分查找耗时: {elapsedNs4} 纳秒,目标值 {target} 在数组中的索引是 {binarySearchResult}");
else Console.WriteLine($"二分查找耗时: {elapsedNs4} 纳秒,目标值 {target} 不在数组中.查找结果为 {binarySearchResult},该值表示如果要插入元素应插入的位置(取反减1)");
}
}
测试结果
测试版本net8.0 release模式 控制台:
第一次跑:
传统for方式耗时: 5 纳秒,目标值: 103802,目标索引: 10134
排序前FindIndex方法耗时: 40 纳秒,目标值: 103802,目标索引: 10134
排序前Find方法耗时: 23 纳秒,目标值: 103802
排序后Find方法耗时: 34 纳秒,目标值: 103802
二分查找耗时: 8 纳秒,目标值 103802 在数组中的索引是 10366
第二次跑:
传统for方式耗时: 37 纳秒,目标值: 553015,目标索引: 84828
排序前FindIndex方法耗时: 124 纳秒,目标值: 553015,目标索引: 84828
排序前Find方法耗时: 110 纳秒,目标值: 553015
排序后Find方法耗时: 343 纳秒,目标值: 553015
二分查找耗时: 23 纳秒,目标值 553015 在数组中的索引是 55200
换一个人的电脑跑,
netnet4.8 release模式 控制台 改成一百万.
第一次跑:
传统for方式耗时: 234 纳秒,目标值: 900769,目标索引: 49318
排序前FindIndex方法耗时: 225 纳秒,目标值: 900769,目标索引: 49318
排序前Find方法耗时: 158 纳秒,目标值: 900769
排序后Find方法耗时: 341 纳秒,目标值: 900769
二分查找耗时: 48 纳秒,目标值 900769 在数组中的索引是 90171
第二次跑:
传统for方式耗时: 49 纳秒,目标值: 383880,目标索引: 10351
排序前FindIndex方法耗时: 104 纳秒,目标值: 383880,目标索引: 10351
排序前Find方法耗时: 64 纳秒,目标值: 383880
排序后Find方法耗时: 129 纳秒,目标值: 383880
二分查找耗时: 38 纳秒,目标值 383880 在数组中的索引是 38494
第三次跑:
传统for方式耗时: 262 纳秒,目标值: 708638,目标索引: 54940
排序前FindIndex方法耗时: 226 纳秒,目标值: 708638,目标索引: 54940
排序前Find方法耗时: 166 纳秒,目标值: 708638
排序后Find方法耗时: 244 纳秒,目标值: 708638
二分查找耗时: 49 纳秒,目标值 708638 在数组中的索引是 71069
结论
从平均期望来说,二分法还是牛逼.
LINQ排序前后的差距不大,只有3倍.
我没有测试传统for的排序前后,大概是6倍,因为网上有人测试过..
NET8.0的传统for和FindIndex居然有些许差距是怎么肥四?
被拒绝内联了吗?
还是委托链的延迟绑定函数其实无法展开?
委托要命中委托链,再查询虚函数表,然后才能命中函数再调用.
多次之后才能特化重写.
二分法必须要排序再用.那么排序耗时不记录吗?
很多时候是允许排序耗时的,因为排序一次,搜索n次.
所以不要在乎这点时间,要在乎搜索的单次时间.
之所以这么死扣性能,
主要是因为扫描线算法还真是log2(n)*2*100w
http://bbs.mjtd.com/thread-192041-1-1.html
浙公网安备 33010602011771号