我们的应用程序使用大量字典,这些字典具有不经常更改的多级查找。我们正在研究转换使用字典进行大量查找的一些关键代码,以使用替代结构 - 更快的查找,点亮内存/ gc。这使我们比较了各种可用的词典/库 -
Dictionary
(System.Collections.Generics.Dictionary
- SCGD),ImmutableDictionary
,C5.HashDictionary
,FSharpMap
。
运行包含各种项目的以下程序--100,1000,10000,100000 - 表示词典在大多数范围内仍然是赢家。第一行表示集合中的项目。 MS / Ticks将随机执行x查找所花费的时间(代码如下)。
项目 - 100
SCGD - 0 MS - 50 Ticks
C5 - 1 MS - 1767蜱
Imm - 4 MS - 5951 Ticks
FS - 0 MS - 240蜱
项目 - 1000
SCGD - 0 MS - 230 Ticks
C5 - 0 MS - 496 Ticks
Imm - 0 MS - 1046 Ticks
FS - 1 MS - 1870 Ticks
项目 - 10000
SCGD - 3 MS - 4722蜱虫
C5 - 4 MS - 6370蜱虫
Imm - 9 MS - 13119蜱虫
FS - 15 MS - 22050蜱虫
项目--100000
SCGD - 62 MS - 89276蜱虫
C5 - 72 MS - 103658蜱虫
Imm - 172 MS - 246247蜱虫
FS - 249 MS - 356176蜱虫
这是否意味着,我们已经使用最快并且不必更改?我曾假设不可变结构应位于表格顶部,但事实并非如此。我们做错了比较还是我错过了什么?坚持这个问题,但觉得,问题更好。任何链接或注释或任何引用光线的引用都会很棒。
完整的测试代码 -
namespace CollectionsTest
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Runtime;
using Microsoft.FSharp.Collections;
/// <summary>
///
/// </summary>
class Program
{
static Program()
{
ProfileOptimization.SetProfileRoot(@".\Jit");
ProfileOptimization.StartProfile("Startup.Profile");
}
/// <summary>
/// Mains the specified args.
/// </summary>
/// <param name="args">The args.</param>
static void Main(string[] args)
{
// INIT TEST DATA ------------------------------------------------------------------------------------------------
foreach (int MAXITEMS in new int[] { 100, 1000, 10000, 100000 })
{
Console.WriteLine("\n# - {0}", MAXITEMS);
List<string> accessIndex = new List<string>(MAXITEMS);
List<KeyValuePair<string, object>> listofkvps = new List<KeyValuePair<string, object>>();
List<Tuple<string, object>> listoftuples = new List<Tuple<string, object>>();
for (int i = 0; i < MAXITEMS; i++)
{
listoftuples.Add(new Tuple<string, object>(i.ToString(), i));
listofkvps.Add(new KeyValuePair<string, object>(i.ToString(), i));
accessIndex.Add(i.ToString());
}
// Randomize for lookups
Random r = new Random(Environment.TickCount);
List<string> randomIndexesList = new List<string>(MAXITEMS);
while (accessIndex.Count > 0)
{
int index = r.Next(accessIndex.Count);
string value = accessIndex[index];
accessIndex.RemoveAt(index);
randomIndexesList.Add(value);
}
// Convert to array for best perf
string[] randomIndexes = randomIndexesList.ToArray();
// LOAD ------------------------------------------------------------------------------------------------
// IMMU
ImmutableDictionary<string, object> idictionary = ImmutableDictionary.Create<string, object>(listofkvps);
//Console.WriteLine(idictionary.Count);
// SCGD
Dictionary<string, object> dictionary = new Dictionary<string, object>();
for (int i = 0; i < MAXITEMS; i++)
{
dictionary.Add(i.ToString(), i);
}
//Console.WriteLine(dictionary.Count);
// C5
C5.HashDictionary<string, object> c5dictionary = new C5.HashDictionary<string, object>();
for (int i = 0; i < MAXITEMS; i++)
{
c5dictionary.Add(i.ToString(), i);
}
//Console.WriteLine(c5dictionary.Count);
// how to change to readonly?
// F#
FSharpMap<string, object> fdictionary = new FSharpMap<string, object>(listoftuples);
//Console.WriteLine(fdictionary.Count);
// TEST ------------------------------------------------------------------------------------------------
Stopwatch sw = Stopwatch.StartNew();
for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
{
string i = randomIndexes[index];
object value;
dictionary.TryGetValue(i, out value);
}
sw.Stop();
Console.WriteLine("SCGD - {0,10} MS - {1,10} Ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks);
Stopwatch c5sw = Stopwatch.StartNew();
for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
{
string key = randomIndexes[index];
object value;
c5dictionary.Find(ref key, out value);
}
c5sw.Stop();
Console.WriteLine("C5 - {0,10} MS - {1,10} Ticks", c5sw.ElapsedMilliseconds, c5sw.ElapsedTicks);
Stopwatch isw = Stopwatch.StartNew();
for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
{
string i = randomIndexes[index];
object value;
idictionary.TryGetValue(i, out value);
}
isw.Stop();
Console.WriteLine("Imm - {0,10} MS - {1,10} Ticks", isw.ElapsedMilliseconds, isw.ElapsedTicks);
Stopwatch fsw = Stopwatch.StartNew();
for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
{
string i = randomIndexes[index];
fdictionary.TryFind(i);
}
fsw.Stop();
Console.WriteLine("FS - {0,10} MS - {1,10} Ticks", fsw.ElapsedMilliseconds, fsw.ElapsedTicks);
}
}
}
}
答案 0 :(得分:10)
标准字典已经很好地优化了。当你进行查找时它真正做的就是计算提供的密钥的哈希值(其速度取决于密钥的类型及其如何实现GetHashCode
),对哈希值进行模运算以查找右桶然后它遍历桶的内容,直到找到正确的值(其速度取决于GetHashCode
函数的质量,所以如果桶很平衡并且不包含太多项目,以及类型的Equals
方法的速度。
总而言之,它对查找没有那么多,所以我认为你不会找到一个明显更快的通用数据结构。但是,根据您计划使用词典的方式,您可以构建更多专用解决方案。例如,我需要一个非常快速的查找,其中键是一个类型。我没有使用字典并执行dictionary[typeof(T)]
,而是像这样创建了一个泛型类:
class ValueStore<T>
{
public static T Value;
}
所以我可以用ValueStore<T>.Value
做几乎零查询开销。
你是否可以做类似的事情(以及它是否值得)真的取决于你的用例;结构将容纳多少项,读取和写入的频率,是否需要线程安全,写入速度有多重要等等。例如,如果写入速度完全没有关系,但是如果需要线程安全性,您需要进行写时复制,其中数据结构永远不会被写入而是被复制,从而确保线程安全和无锁(因此:快速)读取,代价是写入速度。专门研究可以对写入结构进行重新排序以优化它,因此散列桶不包含多于N个项目。
PS:如果你真的非常渴望速度但无法构建更专业的数据结构,那么你可能从复制Dictionary<TKey,TValue>
和删除各种健全性检查(空检查等)和虚拟中获得微小的收益/ interface方法调用。但是,我怀疑这会给你带来超过20%的收益,如果那样的话。
答案 1 :(得分:6)
F#映射结构实现为二叉树,因此实际上不是字典。如上所述here,标准.net字典是您获得的最快字典。
答案 2 :(得分:6)
你推测不可变字典会允许更快的查找是错误的,因为几乎所有不可变集合都避免复制整个结构的方式修改&#39; 是将数据存储在树中,仅复制“修改”中的某些节点,共享所有其他节点。
我知道您对读取性能(冻结词典)感兴趣,但不可变集合的树特征在write performance as documented中显示相似:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Mutable (amortized) Mutable (worst) Immutable
───────────────────────────────────────────────────────────────────────
Stack.Push O(1) O(n) O(1)
Queue.Enqueue O(1) O(n) O(1)
List.Add O(1) O(n) O(log n)
HashSet.Add O(1) O(n) O(log n)
SortedSet.Add O(log n) O(n) O(log n)
Dictionary.Add O(1) O(n) O(log n)
SortedDictionary.Add O(log n) O(n log n) O(log n)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
顺便说一下,如果按原样运行示例代码,至少不可变字典的值在正常(运行时间较长)的应用程序中将非常不典型,因为由于某种原因,不可变字典显然需要时间来加热向上即可。性能差异巨大。只需看看前3次运行的输出:
Items Dict Conc Immu
===========================
100 1.90 1.00 361.81
1000 1.07 1.00 4.33
10000 1.24 1.00 1.74
100000 1.00 1.33 2.71
---------------------------
100 1.06 1.00 2.56
1000 1.03 1.00 4.34
10000 1.00 1.06 3.54
100000 1.00 1.17 2.76
---------------------------
100 1.06 1.00 2.50
1000 1.66 1.00 4.16
10000 1.00 1.02 3.67
100000 1.00 1.26 3.13
Fwiw,我根据您的代码比较了Dictionary<,>
,ConcurrentDictionary<,>
和ImmutableDictionary<,>
的单线程读取性能。
预热之后30次运行的平均结果:
为了感受写性能,我还运行了一个测试,它为字典增加了50个条目。同样,在热身之后<30>运行的平均结果:
经过测试
N.B。应该注意的是,不可变字典是如此快得多和/或在许多现实生活中的多线程应用程序中允许更高级别的并发性,否则将不得不诉诸缓慢或容易出错的技术,如防御性复制,锁定等等,以应对线程中的可变性。如果您需要快照,例如乐观并发,MVCC,则尤其如此。
答案 3 :(得分:0)
这里还有一些基准。
.NET 4.5版本的
System.Collections.Immutable
软件包,64位。
Dictionary
vs ImmutableDictionary
:
BuilderBenchmark elapsed time
Mutable : Avg= 15.213, Stdev 4.591 [ms] ( 10.2x)
Immutable : Avg=155.883, Stdev 15.145 [ms]
BuilderBenchmark per op
Mutable : Avg= 0.152, Stdev 0.046 [us] ( 10.2x)
Immutable : Avg= 1.559, Stdev 0.151 [us]
SetItemBenchmark elapsed time
Mutable : Avg= 13.100, Stdev 2.975 [ms] ( 30.4x)
Immutable : Avg=397.932, Stdev 18.551 [ms]
SetItemBenchmark per op
Mutable : Avg= 0.131, Stdev 0.030 [us] ( 30.4x)
Immutable : Avg= 3.979, Stdev 0.186 [us]
LookupBenchmark elapsed time
Mutable : Avg= 9.439, Stdev 0.942 [ms] ( 3.6x)
Immutable : Avg= 34.250, Stdev 3.457 [ms]
LookupBenchmark per op
Mutable : Avg= 0.094, Stdev 0.009 [us] ( 3.6x)
Immutable : Avg= 0.343, Stdev 0.035 [us]
Dictionary
vs ImmutableSortedDictionary
:
BuilderBenchmark elapsed time
Mutable : Avg= 13.654, Stdev 5.124 [ms] ( 34.5x)
Immutable : Avg=471.574, Stdev 20.719 [ms]
BuilderBenchmark per op
Mutable : Avg= 0.137, Stdev 0.051 [us] ( 34.5x)
Immutable : Avg= 4.716, Stdev 0.207 [us]
SetItemBenchmark elapsed time
Mutable : Avg= 11.838, Stdev 0.530 [ms] ( 37.6x)
Immutable : Avg=444.964, Stdev 11.125 [ms]
SetItemBenchmark per op
Mutable : Avg= 0.118, Stdev 0.005 [us] ( 37.6x)
Immutable : Avg= 4.450, Stdev 0.111 [us]
LookupBenchmark elapsed time
Mutable : Avg= 9.354, Stdev 0.542 [ms] ( 4.4x)
Immutable : Avg= 40.988, Stdev 3.242 [ms]
LookupBenchmark per op
Mutable : Avg= 0.094, Stdev 0.005 [us] ( 4.4x)
Immutable : Avg= 0.410, Stdev 0.032 [us]
我想知道不可变集合会有多慢。请注意,整个运行适用于100,000个插入操作。我很高兴地报告,查找性能下降仅为4倍,而插入性能下降为10倍,仍然相当不错。除非您绝对需要排序数据结构,否则ImmutableDictionary
明显优于ImmutableSortedDictionary
。
关于写行为副本的附注。这些持久性数据结构比写入实现上的任何天真副本快几个数量级。这就是我使用的。使用比较和交换
CAS
指令提交更改(使用数据竞争检测)也非常容易。
的Program.cs:
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using ImmutableDictionary = System.Collections.Immutable.ImmutableDictionary; // select implementation to benchmark here
namespace DictPerf
{
class Program
{
static string alphaNum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static string NextString(Random r, char[] buf)
{
int i = 0, len = r.Next(buf.Length) + 1;
for (; i < len; i++)
{
buf[i] = alphaNum[r.Next(alphaNum.Length)];
}
return new string(buf, 0, len);
}
static HashSet<string> strings = new HashSet<string>();
private static void Seed()
{
var r = new Random();
var buf = new char[64];
for (int i = 0; i < 100000; i++)
{
strings.Add(NextString(r, buf));
}
}
static void Main(string[] args)
{
Seed();
Benchmark(RunDictionaryBuilderBenchmark, RunImmutableDictionaryBuilderBenchmark, "BuilderBenchmark");
Benchmark(RunDictionarySetItemBenchmark, RunImmutableDictionarySetItemBenchmark, "SetItemBenchmark");
Benchmark(RunDictionaryLookupBenchmark, RunImmutableDictionaryLookupBenchmark, "LookupBenchmark");
}
private static string Stats(IEnumerable<double> source)
{
var avg = source.Average();
var variance = source.Select(val => (val - avg) * (val - avg)).Sum();
var stdev = Math.Sqrt(variance / (source.Count() - 1));
return $"Avg={avg,7:0.000}, Stdev{stdev,7:0.000}";
}
private static void Benchmark(Action<ICollection<string>, Stopwatch> benchmark1, Action<ICollection<string>, Stopwatch> benchmark2, string benchmarkName)
{
var xs = new List<double>();
var ys = new List<double>();
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
sw.Restart();
benchmark1(strings, sw);
xs.Add(sw.Elapsed.TotalMilliseconds);
sw.Restart();
benchmark2(strings, sw);
ys.Add(sw.Elapsed.TotalMilliseconds);
}
var x = xs.Average();
var y = ys.Average();
var a = xs.Select(v => v / 100).Average();
var b = ys.Select(v => v / 100).Average();
Console.WriteLine($"{benchmarkName} elapsed time");
Console.WriteLine($" Mutable : {Stats(xs)} [ms] ({y / x,7:0.0}x)");
Console.WriteLine($" Immutable : {Stats(ys)} [ms]");
Console.WriteLine($"{benchmarkName} per op");
Console.WriteLine($" Mutable : {Stats(xs.Select(v => v / 100))} [us] ({b / a,7:0.0}x)");
Console.WriteLine($" Immutable : {Stats(ys.Select(v => v / 100))} [us]");
}
private static void RunDictionaryBuilderBenchmark(ICollection<string> strings, Stopwatch sw)
{
var d = new Dictionary<string, object>();
foreach (var s in strings)
{
d[s] = null;
}
}
private static void RunImmutableDictionaryBuilderBenchmark(ICollection<string> strings, Stopwatch sw)
{
var d = ImmutableDictionary.CreateBuilder<string, object>();
foreach (var s in strings)
{
d[s] = null;
}
d.ToImmutableDictionary();
}
private static void RunDictionarySetItemBenchmark(ICollection<string> strings, Stopwatch sw)
{
var d = new Dictionary<string, object>();
foreach (var s in strings)
{
d[s] = null;
}
}
private static void RunImmutableDictionarySetItemBenchmark(ICollection<string> strings, Stopwatch sw)
{
var d = ImmutableDictionary.Create<string, object>();
foreach (var s in strings)
{
d = d.SetItem(s, null);
}
}
private static void RunDictionaryLookupBenchmark(ICollection<string> strings, Stopwatch timer)
{
timer.Stop();
var d = new Dictionary<string, object>();
foreach (var s in strings)
{
d[s] = null;
}
timer.Start();
foreach (var s in strings)
{
object v;
d.TryGetValue(s, out v);
}
}
private static void RunImmutableDictionaryLookupBenchmark(ICollection<string> strings, Stopwatch timer)
{
timer.Stop();
var d = ImmutableDictionary.CreateBuilder<string, object>();
foreach (var s in strings)
{
d[s] = null;
}
var x = d.ToImmutableDictionary();
timer.Start();
foreach (var s in strings)
{
object v;
x.TryGetValue(s, out v);
}
}
}
}
答案 4 :(得分:0)
在.NET 3.1中,它们看起来非常相似。
| Method | MAXITEMS | Mean | Error | StdDev |
|---------- |--------- |----------:|----------:|----------:|
| imm | 10 | 0.9921 ns | 0.0630 ns | 0.1837 ns |
| scg | 10 | 0.9699 ns | 0.0571 ns | 0.1683 ns |
| scgsorted | 10 | 1.0136 ns | 0.0577 ns | 0.1684 ns |
| imm | 100 | 1.5296 ns | 0.1153 ns | 0.3327 ns |
| scg | 100 | 1.3151 ns | 0.0694 ns | 0.1990 ns |
| scgsorted | 100 | 1.4516 ns | 0.0855 ns | 0.2426 ns |
| imm | 1000 | 0.8514 ns | 0.0905 ns | 0.2582 ns |
| scg | 1000 | 1.0898 ns | 0.0552 ns | 0.1416 ns |
| scgsorted | 1000 | 1.0302 ns | 0.0533 ns | 0.1001 ns |
| imm | 10000 | 1.0280 ns | 0.0530 ns | 0.1175 ns |
| scg | 10000 | 0.9929 ns | 0.0523 ns | 0.1253 ns |
| scgsorted | 10000 | 1.0531 ns | 0.0534 ns | 0.1248 ns |
测试源代码:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
namespace CollectionsTest
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Runtime;
/// <summary>
///
/// </summary>
public class Program
{
private static ImmutableDictionary<string, object> idictionary;
private static Dictionary<string, object> dictionary;
private static SortedDictionary<string, object> sorteddictionary;
private static string[] randomIndexes;
[Params(10, 100, 1000, 10000)] public int MAXITEMS { get; set; }
public Program()
{
Console.WriteLine("\n# - {0}", MAXITEMS);
List<string> accessIndex = new List<string>(MAXITEMS);
List<KeyValuePair<string, object>> listofkvps = new List<KeyValuePair<string, object>>();
List<Tuple<string, object>> listoftuples = new List<Tuple<string, object>>();
for (int i = 0; i < MAXITEMS; i++)
{
listoftuples.Add(new Tuple<string, object>(i.ToString(), i));
listofkvps.Add(new KeyValuePair<string, object>(i.ToString(), i));
accessIndex.Add(i.ToString());
}
// Randomize for lookups
Random r = new Random(Environment.TickCount);
List<string> randomIndexesList = new List<string>(MAXITEMS);
while (accessIndex.Count > 0)
{
int index = r.Next(accessIndex.Count);
string value = accessIndex[index];
accessIndex.RemoveAt(index);
randomIndexesList.Add(value);
}
// Convert to array for best perf
randomIndexes = randomIndexesList.ToArray();
// LOAD ------------------------------------------------------------------------------------------------
// IMMU
idictionary = listofkvps.ToImmutableDictionary();
//Console.WriteLine(idictionary.Count);
// SCGD
dictionary = new Dictionary<string, object>();
for (int i = 0; i < MAXITEMS; i++)
{
dictionary.Add(i.ToString(), i);
}
sorteddictionary = new SortedDictionary<string, object>();
for (int i = 0; i < MAXITEMS; i++)
{
sorteddictionary.Add(i.ToString(), i);
}
//Console.WriteLine(dictionary.Count);
//scg(randomIndexes, dictionary);
//imm(randomIndexes, idictionary);
}
/// <summary>
/// Mains the specified args.
/// </summary>
/// <param name="args">The args.</param>
public static void Main(string[] args)
{
// INIT TEST DATA ------------------------------------------------------------------------------------------------
Console.WriteLine(BenchmarkRunner.Run<Program>());
}
[Benchmark]
public void imm()
{
for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
{
string i = randomIndexes[index];
object value;
idictionary.TryGetValue(i, out value);
}
}
[Benchmark]
public void scg()
{
// TEST ------------------------------------------------------------------------------------------------
for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
{
string i = randomIndexes[index];
object value;
dictionary.TryGetValue(i, out value);
}
}
[Benchmark]
public void scgsorted()
{
// TEST ------------------------------------------------------------------------------------------------
for (int index = 0, indexMax = randomIndexes.Length; index < indexMax; index++)
{
string i = randomIndexes[index];
object value;
sorteddictionary.TryGetValue(i, out value);
}
}
}
}