我有一个包含100万到1亿整数的整数列表。
我想根据其出现次数对其进行排名,然后选择热门K
(此处K=10
)结果。
我已经尝试了4种不同的方式来使Method1
最快。并行化并没有在Method1
中击败我自己的分组代码,并且由于线程竞争条件导致排名不准确。
Method1
和Method4
结果准确无误,Method2
和Method3
因种族条件而排名可能不准确。
现在,我正在寻找比Method1
更快的代码,或修复并行化方法,使其准确然后比Method1
更快。
class Benchmark
{
static List<int> input = new List<int>();
static void Main(string[] args)
{
int count = int.Parse(args[0]);
Random rnd = new Random();
for (int i = 0; i < count; i++)
input.Add(rnd.Next(1, count));
DoBench();
Console.ReadKey();
}
private static void DoBench()
{
for (int i = 1; i <= 4; i++)
{
DateTime start = DateTime.Now;
List<KeyValuePair<int, int>> results = null;
switch (i)
{
case 1:
results = Method1();
break;
case 2:
results = Method2();
break;
case 3:
results = Method3();
break;
case 4:
results = Method4();
break;
}
int resultsCount = 10;
var topResults = results.Take(resultsCount).OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToArray();
for (int j = 0; j < resultsCount; j++)
Console.WriteLine("No {0,2}: {1,8}, Score {2,4}", j + 1, topResults[j].Key, topResults[j].Value);
Console.WriteLine("Time of Method{0}: {1} ms", i, (long)DateTime.Now.Subtract(start).TotalMilliseconds);
Console.WriteLine();
}
}
private static List<KeyValuePair<int, int>> Method1()
{
Dictionary<int, int> dic = new Dictionary<int, int>();
for (int i = 0; i < input.Count; i++)
{
int number = input[i];
if (dic.ContainsKey(number))
dic[number]++;
else
dic.Add(number, 1);
}
var sorted_results = dic.OrderByDescending(x => x.Value).ToList();
return sorted_results;
}
private static List<KeyValuePair<int, int>> Method2()
{
var sorted_results = input.AsParallel().GroupBy(x => x)
.Select(g => new KeyValuePair<int, int>(g.Key, g.Count()))
.OrderByDescending(x => x.Value).ToList();
return sorted_results;
}
private static List<KeyValuePair<int, int>> Method3()
{
ConcurrentDictionary<int, int> dic = new ConcurrentDictionary<int, int>();
input.AsParallel<int>().ForAll((number) =>
{
dic.AddOrUpdate(number, 1, new Func<int, int, int>((key, oldValue) => oldValue + 1));
});
var sorted_results = dic.OrderByDescending(x => x.Value).ToList();
return sorted_results;
}
private static List<KeyValuePair<int, int>> Method4()
{
var sorted_results = input.GroupBy(x => x)
.Select(g => new KeyValuePair<int, int>(g.Key, g.Count()))
.OrderByDescending(x => x.Value).ToList();
return sorted_results;
}
}
答案 0 :(得分:1)
我认为你可以大大加快算法中使用选择排序找到10个最高计数的部分。
最快的方法是使用QuickSelect算法,但这对我来说有点太复杂了。
相反,我将演示使用Partial Selection Sort。
这是一个使用它的Method5()
。它与Method1()
基本相同,只是它在最后阶段使用部分排序而不是完整排序:
private static List<KeyValuePair<int, int>> Method5()
{
Dictionary<int, int> dic = new Dictionary<int, int>();
for (int i = 0; i < input.Count; i++)
{
int number = input[i];
if (dic.ContainsKey(number))
dic[number]++;
else
dic.Add(number, 1);
}
var result = dic.ToList();
partialSort(result, 10);
return result;
}
private static void partialSort(List<KeyValuePair<int, int>> list, int k)
{
for (int i = 0; i < k; ++i)
{
int maxIndex = i;
int maxValue = list[i].Value;
for (int j = i+1; j < list.Count; ++j)
{
if (list[j].Value > maxValue)
{
maxIndex = j;
maxValue = list[j].Value;
}
}
var temp = list[i];
list[i] = list[maxIndex];
list[maxIndex] = temp;
}
}
我定时了一个RELEASE(而非调试)版本,K == 10
时我的电脑上的速度几乎要快三倍。
较小的K相对于集合的大小将按比例更快。对于K
的较大值(特别是K
接近集合大小的地方),此算法可能比普通排序慢。
K
预计很大, 请勿使用此算法。它的效率很大程度上取决于K
相对于集合大小的小值。
答案 1 :(得分:1)
我找到了一个更快的字典实现,用于分组部分。我使用了Adam Horvath(FastDictionary)编写的MapReduce.NET项目中的link类。
使用FastDictionary
我更新了我的Method1
并写了一个更快的新Method6
。然后我从@Mattew Watson添加快速排序并编写Method7
。最后,我把它们完全填满了。
修改强>
我想出了一个很棒的主意,可以在Method8
中更快地加速分组。我的想法是使用一个预定义的数组,其大小最大为输入数组,因此我可以用数组替换字典。当Method8
接近Maximum-Element-of-Input/Size-Of-Input
和1
整数的比例适合内存时,Maximum-Element-of-Input
是有效的。
修改2
我写了另外3个名为Method9
,Method10
和Method11
的方法。
我注意到收集分组结果并将它们添加到Method8
中的列表需要大量不必要的内存,所以我稍微修改了partialSort
并删除了内存开销。
同样,我注意到我在Int32
的4个字节中持有相对较少的金额。为什么?!因此,假设输入列表中的元素不超过65535
(short.MaxValue
),我只需将Int32
数组替换为占用内存一半的short
数组。
再一次,我注意到如果我们有一个内存关键场景并再次假设输入数组中的大多数元素都小于255
(byte.MaxValue
),我们可以同时使用byte
用于具有高发生率的元素的普通字典。由于在分组操作期间进行了多次检查,这种技术有点慢,并且还需要在结束时合并结果,但正如您在图表中所看到的,其内存使用量远远小于其他基于字典的初始方法。
<强>时序:强>
内存使用:
时间比较
内存比较
方法6:
private static List<KeyValuePair<int, int>> Method6()
{
FastDictionary<int, int> dic = new FastDictionary<int, int>();
for (int i = 0; i < input.Count; i++)
{
int number = input[i];
int pos = dic.InitOrGetPosition(number);
int curr = dic.GetAtPosition(pos);
dic.StoreAtPosition(pos, ++curr);
}
var sorted_results = dic.OrderByDescending(x => x.Value).ToList();
return sorted_results;
}
方法7:
private static List<KeyValuePair<int, int>> Method7()
{
FastDictionary<int, int> dic = new FastDictionary<int, int>();
for (int i = 0; i < input.Count; i++)
{
int number = input[i];
int pos = dic.InitOrGetPosition(number);
int curr = dic.GetAtPosition(pos);
dic.StoreAtPosition(pos, ++curr);
}
var result = dic.ToList();
partialSort(result, 10);
return result;
}
方法8
private static List<KeyValuePair<int, int>> Method8()
{
int[] dic = new int[input.Max() + 1];
for (int i = 0; i < input.Count; i++)
{
dic[input[i]]++;
}
List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>();
for (int i = 0; i < dic.Length; i++)
{
if (dic[i] > 0)
list.Add(new KeyValuePair<int, int>(i, dic[i]));
}
partialSort(list, 10);
return list;
}
方法9
private static List<KeyValuePair<int, int>> Method9()
{
int[] dic = new int[input.Max() + 1];
for (int i = 0; i < input.Count; i++)
{
dic[input[i]]++;
}
List<KeyValuePair<int, int>> list = partialSort(dic, 10);
return list;
}
方法10
private static List<KeyValuePair<int, int>> Method10()
{
short[] dic = new short[input.Max() + 1];
for (int i = 0; i < input.Count; i++)
{
dic[input[i]]++;
}
List<KeyValuePair<int, int>> list = partialSort(dic, 10);
return list;
}
方法11
private static List<KeyValuePair<int, int>> Method11()
{
byte[] dic = new byte[input.Max() + 1];
Dictionary<int, int> largeDic = new Dictionary<int, int>();
int index = 0;
int val = 0;
for (int i = 0; i < input.Count; i++)
{
index = input[i];
val = dic[index];
if (val < 255)
dic[index] = (byte)(val + 1); // casting to byte
else
{
if (largeDic.ContainsKey(index))
{
largeDic[index]++;
}
else
{
largeDic.Add(index, 255 + 1);
}
}
}
List<KeyValuePair<int, int>> list = new List<KeyValuePair<int, int>>();
if (largeDic.Count == 0)
{
list = partialSort(dic, 10);
}
else
{
if (largeDic.Count < 10)
{
list.AddRange(largeDic.OrderByDescending(x => x.Value));
var tempList = partialSort(dic, 50);
list.AddRange(tempList.Except(list, new KeyValueComparer()).Take(10 - list.Count));
}
else
{
list = largeDic.OrderByDescending(x => x.Value).Take(10).ToList();
}
}
return list;
}
部分排序
不同的重载只有不同的参数类型(int[]
,short[]
,byte[]
)
private static List<KeyValuePair<int, int>> partialSort(int[] list, int k)
{
int[] topIndexes = new int[k];
for (int i = 0; i < k; ++i)
{
int maxIndex = i;
int maxValue = list[i];
for (int j = i + 1; j < list.Length; ++j)
{
if (list[j] > maxValue)
{
maxIndex = j;
maxValue = list[j];
}
}
var temp = list[i];
list[i] = list[maxIndex];
list[maxIndex] = temp;
topIndexes[i] = maxIndex;
}
List<KeyValuePair<int, int>> top = new List<KeyValuePair<int, int>>();
for (int i = 0; i < k; i++)
top.Add(new KeyValuePair<int, int>(topIndexes[i], list[i]));
return top;
}