【问题标题】:Efficiently update bindings in a .NET dictionary有效地更新 .NET 字典中的绑定
【发布时间】:2013-08-30 12:48:15
【问题描述】:

我使用字典来累积键的出现次数,因此,核心操作是编写一个键值对,其中的值是前一个值加一或如果没有前一个值则只加一。但是,这需要两个单独的字典操作(读取和写入),而我只能执行一个 (AddOrUpdate)。

我注意到并发字典支持AddOrUpdate,但普通的通用Dictionary 似乎不支持。

因此,对可变整数的引用字典更快。但是,这会引入不必要的引用,这意味着堆分配和写入障碍。所以我猜可能会做得更好,但如果不从头开始重写Dictionary,我看不到如何。我说的对吗?

【问题讨论】:

  • 所以您正试图消除添加或更新方案中的一个查找?
  • 并发字典在很多情况下似乎性能都不错,你检查过它是否为你的场景提供了足够的性能吗?
  • 你能对键值进行排序吗?我猜大多数将是 O(n log n) 所以你可能需要测试以获得最佳性能
  • 也许您可以尝试使用具有元素计数和键/索引到数组字典的数组 - 这将需要字典查找和数组索引 - 在极端情况下可能会更快一些跨度>
  • 听起来你是对的,你可以做得更好。从头开始编写的一种实用替代方法是从 Mono 实现开始:github.com/mono/mono/blob/master/mcs/class/corlib/…

标签: c# .net dictionary f#


【解决方案1】:

你可以这样做:

private class Counter
{
  public string Key       { get ; set ; }
  public int    Frequency { get ; set ; }
}

...

Dictionary<string,Counter> frequencyTable = new Dictionary<string,Counter>() ;

...

string someKey = GetKeyToLookup() ;
Counter item = null ;
bool hit = frequencyTable.TryGetValue( someKey,out item ) ;
if ( !hit )
{
  item = new Counter{ Key=someKey,Frequency=0 } ;
}
++ item.Frequency ;

如果这还不够好,为什么还要自己编写?使用高性能C5 Collections Library。它是免费的(实际上最初是由微软资助的),建立在微软的System.Collections.Generic 接口之上,其字典、集合和包支持FindOrAdd() 语义。

【讨论】:

  • 是的,这正是我所说的“可变整数引用字典更快”的意思,但这引入了不必要的引用,这意味着堆分配和写入障碍。
  • @JonHarrop 你试过了吗? C5 实际上对这项任务更有效吗?第二次查找还是引用类型的成本更高?
  • 我用自己的代码(不是 C5)进行了尝试,可变引用字典比对值字典的双重查找更快。第二次查找更昂贵。但是,允许就地添加的字典当然是最快的解决方案。
【解决方案2】:

正如 Jim Mischel 所提到的 - 不可能通过单次查找来更改字典的项目值。 ConcurrentDictionary.AddOrUpdate 方法做不止一个查找操作(反映来源):

public TValue AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)
{
    TValue local2;
    if (key == null)
    {
        throw new ArgumentNullException("key");
    }
    if (updateValueFactory == null)
    {
        throw new ArgumentNullException("updateValueFactory");
    }
    do
    {
        TValue local3;
        while (this.TryGetValue(key, out local3))
        {
            TValue newValue = updateValueFactory(key, local3);
            if (this.TryUpdate(key, newValue, local3))
            {
                return newValue;
            }
        }
    }
    while (!this.TryAddInternal(key, addValue, false, true, out local2));
    return local2;
}

我用并发字典和简单字典做了性能测试:

IDictionary 的 AddOrUpdate 扩展:

public static class DictionaryExtensions
{
    public static void AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue initValue, Func<TKey, TValue, TValue> updateFunc)
    {
        TValue value;
        value = dict.TryGetValue(key, out value) ? updateFunc(key, value) : initValue;

        dict[key] = value;
    }
}

测试:

static void Main(string[] args)
{
    const int dictLength = 100000;
    const int testCount = 1000000;

    var cdict = new ConcurrentDictionary<string, int>(GetRandomData(dictLength));
    var dict = GetRandomData(dictLength).ToDictionary(x => x.Key, x => x.Value);

    var stopwatch = new Stopwatch();
    stopwatch.Start();
    foreach (var pair in GetRandomData(testCount))
        cdict.AddOrUpdate(pair.Key, 1, (x, y) => y+1);          

    stopwatch.Stop();
    Console.WriteLine("Concurrent dictionary: {0}", stopwatch.ElapsedMilliseconds);

    stopwatch.Reset();
    stopwatch.Start();

    foreach (var pair in GetRandomData(testCount))
        dict.AddOrUpdate(pair.Key, 1, (x, y) => y+1);   

    stopwatch.Stop();
    Console.WriteLine("Dictionary: {0}", stopwatch.ElapsedMilliseconds);
    Console.ReadLine();
}

static IEnumerable<KeyValuePair<string, int>> GetRandomData(int count)
{
    const int constSeed = 100;
    var randGenerator = new Random(constSeed);
    return Enumerable.Range(0, count).Select((x, ind) => new KeyValuePair<string, int>(randGenerator.Next().ToString() + "_" + ind, randGenerator.Next()));
}

我的环境中的测试结果(毫秒):

ConcurrentDictionary: 2504
Dictionary: 1351

【讨论】:

    【解决方案3】:

    如果您使用引用类型,则字典更新不需要多次查找:

    假设您有一个Dictionary&lt;string, Foo&gt;,其中Foo 是一个引用类型并包含一个Count 属性:

    void UpdateCount(string key)
    {
        Foo f;
        if (dict.TryGetValue(key, out f))
        {
            // do the update
            ++f.Count;
        }
        else
        {
            dict[key] = 1;
        }
    }
    

    如果您的值是值类型……那么您必须处理值类型语义。这包括必须进行两次查找。

    也就是说,字典查找速度非常快。如果这给您带来了问题,那么您一定要计算很多次。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-19
      • 2011-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多