Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 把一本大词典分成多个部分是明智的吗?_C#_.net_Performance_Dictionary - Fatal编程技术网

C# 把一本大词典分成多个部分是明智的吗?

C# 把一本大词典分成多个部分是明智的吗?,c#,.net,performance,dictionary,C#,.net,Performance,Dictionary,我用字典来存放大量(>10^7)的条目。如果它被拆分成多个单独的字典,每个字典都保存一部分/分区的数据,它能提高查找和/或插入性能吗 举个例子,假设我们有一本字典。我们可以将其替换为: var ds = new Dictionary<int,int> [256]; // ... void Add (int key, int value) { // We can assume key is an evenly distributed hash ds[key &

我用字典来存放大量(>10^7)的条目。如果它被拆分成多个单独的字典,每个字典都保存一部分/分区的数据,它能提高查找和/或插入性能吗

举个例子,假设我们有一本
字典
。我们可以将其替换为:

var ds = new Dictionary<int,int> [256];
// ...
void Add (int key, int value) {
    // We can assume key is an evenly distributed hash
    ds[key & 0xFF].Add (key, value);
}
// Lookup similar
var-ds=新字典[256];
// ...
无效添加(整型键,整型值){
//我们可以假设密钥是均匀分布的散列
ds[key&0xFF]。添加(key,value);
}
//查找相似
当然,这需要进行基准测试,但我也对这种情况下的一般建议感兴趣。令人惊讶的是,我在这里找不到真正类似的问题


我知道一本词典所能容纳的词条数量是有限的。这个问题假设这个限制不是问题——否则,无论如何只有一个解决方案。

我已经考虑了更多。虽然许多数据结构显示插入或查找操作的对数成本,但在字典中,这些成本(摊销)假定为O(1)

在前一种情况下,通过手动索引(O(1)操作)拆分一部分功可以通过减少对数参数来减少剩余功。实际上,我们将在另一个结构之上实现一个字典

当然,这也意味着,当基本结构本身已经是一个字典时,这不应该有任何显著的影响。有许多方法可以实现这些功能,但据我所知,没有一种方法可以通过减小它们的大小而逐渐受益:它们的平均案例行为(即处理重复项)在时间上是恒定的,并且不会增长

另一方面,手工工作会带来开销。因此,我们预计这样一个切片字典的性能会更差

为了检查这一点,我写了一个小测试

Console.WriteLine ("Times in seconds per 10m merged/sliced operations");
foreach (var init in new[] { "empty", "size", "spare" }) {
    for (int n = 10 * 1000 * 1000; n <= 40 * 1000 * 1000; n += 10 * 1000 * 1000) {
        for (int repeat = 0; repeat < 3; repeat++) {
            Stopwatch wmi, wml, wsi, wsl;

            {
                GC.Collect ();
                var r = new Random (0);
                Dictionary<int, object> d;

                if (init == "empty") {
                    d = new Dictionary<int, object> ();
                }
                else if (init == "size") {
                    d = new Dictionary<int, object> (n);
                }
                else {
                    d = new Dictionary<int, object> (2 * n);
                }

                wmi = Stopwatch.StartNew ();
                for (int i = 0; i < n; i++) {
                    var key = r.Next ();
                    d[key] = null;
                }
                wmi.Stop ();

                wml = Stopwatch.StartNew ();
                var dummy = false;
                for (int i = 0; i < n; i++) {
                    dummy ^= d.ContainsKey (i);
                }
                wml.Stop ();
            }

            {
                GC.Collect ();
                var r = new Random (0);
                var ds = new Dictionary<int, object>[256];

                for (int i = 0; i < 256; i++) {
                    if (init == "empty") {
                        ds[i] = new Dictionary<int, object> ();
                    }
                    else if (init == "size") {
                        ds[i] = new Dictionary<int, object> (n / 256);
                    }
                    else {
                        ds[i] = new Dictionary<int, object> (2 * n / 256);
                    }
                }

                wsi = Stopwatch.StartNew ();
                for (int i = 0; i < n; i++) {
                    var key = r.Next ();
                    var d = unchecked(ds[key & 0xFF]);
                    d[key] = null;
                }
                wsi.Stop ();

                wsl = Stopwatch.StartNew ();
                var dummy = false;
                for (int i = 0; i < n; i++) {
                    var d = unchecked(ds[i & 0xFF]);
                    dummy ^= d.ContainsKey (i);
                }
                wsl.Stop ();
            }

            string perM (Stopwatch w) => $"{w.Elapsed.TotalSeconds / n * 10 * 1000 * 1000,5:0.00}";
            Console.WriteLine ($"init = {init,-5}, n = {n,8};"
                + $" insert = {perM (wmi)}/{perM (wsi)},"
                + $" lookup = {perM (wml)}/{perM (wsl)}");
        }
    }
    Console.WriteLine ();
}
一致地,在切片字典中插入和查找都不会更快。我相信在大多数情况下都是这样

然而,在并行操作中仍然存在这样一个切片字典的可能用例。字典的不同部分可以并行处理批处理操作数据的插入、查找等


无论字典作为一个整体是否同时使用,这都是正确的。但是,如果是,那么切片将只允许锁定所需的部分,而不是所有部分(在一个幼稚的实现中)。但是,其他为从头开始的并发操作而设计的字典(例如.NET的
ConcurrentDictionary
)可以免于这一缺点。

将字典保持在一个整体中将为您提供最佳的平均性能,但每次需要它的内部数组时,您都会获得巨大的点击率

// Create the dictionary
var dict = new Dictionary<int, int>(19998337); // 90 msec

// Populate the dictionary
for (int i = 0; i < 19998337; i++) dict.Add(i, i); // 850 msec

// Add one more entry that requires resize
dict.Add(-1, -1); // 850 msec

这不要紧-一个.NET字典已经实现为一个散列容器,所以您的拆分只是做第二个散列来细分为其他散列容器。除非你真的幸运地使用第一个散列函数来分发数据,否则(乍一看)你提出的解决方案并不比仅仅使用字典好。要判断拥有多个字典是否更好,唯一的方法就是,正如你所建议的,自己对它进行基准测试。我们不知道你现在是如何输入字典的,所以我们没什么可以帮你的。如果您有一种细分数据的方法,它可能会有所帮助。不过,有这么多的项目,我可能会建议使用第三方服务,比如Elasticsearch。根据mi测试,32位的48*10^6项目和64位的96*10^6项目在内存中崩溃。所以这是一个严格的理论问题。我认为提高的机会在更高的层次上-你需要这个做什么?@AntonínLejsek很好,错别字减少了一个数量级我需要一个便宜的内存KVS,以便在我正在处理的一些相当大的统计计算中进行批处理。但问题的范围确实更广。如果您将主要哈希函数更改为
ds[(uint)i>>24]
,则会有一个可测量的差异。这可能是因为它改进了测试中的内存局部性。问题是,真实案例的测试代表性是多少。这一点很好,但我完全没有谈到这一点。为了解释您的想法:在回答中,我默默地使用了几乎最坏的内存位置进行查找:由于
索引&0xFF
,几乎每个项都引用了不同的子字典。另一方面,将其更改为
索引>>24
是最好的情况,因为后续索引引用相同的切片。显然,后一种情况很有可能更快。衡量这一点,我的结果与你的一致:速度明显加快(我的盒子上的系数为1.5到2.5)。因此,如果可以假设内存的局部性,那么切片可能是有用的,因为每次调整大小都会使大小增加一倍。这意味着,在调整大小时复制的项目数大致等于字典的实际容量。一个大字典和多个小字典的总调整开销大致相同。@AntonínLejsek true。但是如果你正在做一些实时的事情,比如流式传输视频帧,你可能希望避免这些不常见的大打嗝。
// Create the dictionary
var dict = new Dictionary<int, int>(19998337); // 90 msec

// Populate the dictionary
for (int i = 0; i < 19998337; i++) dict.Add(i, i); // 850 msec

// Add one more entry that requires resize
dict.Add(-1, -1); // 850 msec
public class SegmentedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private class CachedComparer : IEqualityComparer<TKey>
    {
        private readonly IEqualityComparer<TKey> _source;
        private int? _cachedHashCode;

        public CachedComparer(IEqualityComparer<TKey> source)
        {
            _source = source ?? EqualityComparer<TKey>.Default;
        }

        public bool Equals(TKey x, TKey y) => _source.Equals(x, y);

        public int GetHashCodeAndCache(TKey key)
        {
            int hashCode = _source.GetHashCode(key);
            _cachedHashCode = hashCode;
            return hashCode;
        }

        public int GetHashCode(TKey key)
        {
            if (_cachedHashCode.HasValue)
            {
                int hashCode = _cachedHashCode.Value;
                _cachedHashCode = null; // Use the cache only once
                return hashCode;
            }
            return _source.GetHashCode(key);
        }
    }

    private readonly CachedComparer _comparer;
    private readonly Dictionary<TKey, TValue>[] _segments;

    public SegmentedDictionary(int segmentsCount, int capacityPerSegment,
        IEqualityComparer<TKey> comparer)
    {
        _comparer = new CachedComparer(comparer);
        _segments = new Dictionary<TKey, TValue>[segmentsCount];
        for (int i = 0; i < segmentsCount; i++)
        {
            _segments[i] = new Dictionary<TKey, TValue>(
                capacityPerSegment, _comparer);
        }
    }

    private Dictionary<TKey, TValue> GetSegment(TKey key)
    {
        var hashCode = _comparer.GetHashCodeAndCache(key);
        var index = Math.Abs(hashCode) % _segments.Length;
        return _segments[index];
    }

    public int Count => _segments.Sum(d => d.Count);

    public TValue this[TKey key]
    {
        get => GetSegment(key)[key];
        set => GetSegment(key)[key] = value;
    }

    public void Add(TKey key, TValue value) => GetSegment(key).Add(key, value);

    public bool ContainsKey(TKey key) => GetSegment(key).ContainsKey(key);

    public bool TryGetValue(TKey key, out TValue value)
        => GetSegment(key).TryGetValue(key, out value);

    public bool Remove(TKey key) => GetSegment(key).Remove(key);

    public void Clear() => Array.ForEach(_segments, d => d.Clear());

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        => _segments.SelectMany(d => d).GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    ICollection<TKey> IDictionary<TKey, TValue>.Keys
        => throw new NotImplementedException();

    ICollection<TValue> IDictionary<TKey, TValue>.Values
        => throw new NotImplementedException();

    void ICollection<KeyValuePair<TKey, TValue>>.Add(
        KeyValuePair<TKey, TValue> item)
        => throw new NotImplementedException();

    bool ICollection<KeyValuePair<TKey, TValue>>.Contains(
        KeyValuePair<TKey, TValue> item)
        => throw new NotImplementedException();

    void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(
        KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        => throw new NotImplementedException();

    bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
        KeyValuePair<TKey, TValue> item)
        => throw new NotImplementedException();

    bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
        => throw new NotImplementedException();
}