Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Dictionary instead of Hashtable in EventMap #4731

Merged
merged 1 commit into from
Aug 17, 2021

Conversation

stephentoub
Copy link
Member

Description

It's currently using a Hashtable with Int32 keys, which means every operation on the Hashtable boxes the int. Switching to a Dictionary removes those costs. It also lets use use TryGetValue to perform a single lookup to get a value rather than first calling ContainsKey and then using the indexer, and avoiding multiple virtual calls (most APIs on Hashtable are virtual) when we're only using the base class.

Customer Impact

Less unnecessary boxing means less pauses due to GC.

Regression

No

Testing

Just CI

Risk

Minimal. In general we prefer Dictionary over Hashtable, and the only places we're generally keeping Hashtable is when we benefit from its thread-safety guarantees, but all accesses here are protected by a lock.

You can see some of the associated costs with a microbenchmark like this:

Method Count Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
WithHashtable 1 326.98 ns 0.813 ns 0.635 ns 0.0114 - - 72 B
WithDictionary 1 21.29 ns 0.108 ns 0.096 ns - - - -
WithHashtable 3 456.96 ns 4.039 ns 3.778 ns 0.0343 - - 216 B
WithDictionary 3 49.60 ns 0.220 ns 0.195 ns - - - -
WithHashtable 5 573.64 ns 1.306 ns 1.091 ns 0.0572 - - 360 B
WithDictionary 5 71.76 ns 0.280 ns 0.248 ns - - - -
WithHashtable 7 735.64 ns 7.836 ns 6.946 ns 0.0801 - - 504 B
WithDictionary 7 91.98 ns 0.594 ns 0.527 ns - - - -
WithHashtable 9 811.44 ns 6.010 ns 5.622 ns 0.1030 - - 648 B
WithDictionary 9 117.27 ns 0.813 ns 0.720 ns - - - -
WithHashtable 20 1,417.64 ns 10.259 ns 9.596 ns 0.2289 - - 1,440 B
WithDictionary 20 290.11 ns 3.202 ns 2.838 ns - - - -
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

[MemoryDiagnoser]
public class Program
{
    static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);

    private readonly Hashtable _table = new Hashtable(20, .1f);
    private readonly Dictionary<int, object> _dictionary = new Dictionary<int, object>(20);
    private readonly object[] _objects = Enumerable.Range(0, 20).Select(_ => new object()).ToArray();

    [Params(1, 3, 5, 7, 9, 20)]
    public int Count { get; set; }

    [Benchmark]
    public void WithHashtable()
    {
        for (int i = 0; i < Count; i++)
        {
            _table.Add(i, _objects[i]);
        }
        for (int i = 0; i < Count; i++)
        {
            if (_table.ContainsKey(i))
            {
                object o = _table[i];
            }
        }
        _table.Clear();
    }

    [Benchmark]
    public void WithDictionary()
    {
        for (int i = 0; i < Count; i++)
        {
            _dictionary.Add(i, _objects[i]);
        }
        for (int i = 0; i < Count; i++)
        {
            if (_dictionary.TryGetValue(i, out object o))
            {
            }
        }
        _dictionary.Clear();
    }
}

It's currently using a Hashtable with Int32 keys, which means every operation on the Hashtable boxes the int.  Switching to a Dictionary removes those costs.  It also lets use use TryGetValue to perform a single lookup to get a value rather than first calling ContainsKey and then using the indexer, and avoiding multiple virtual calls (most APIs on Hashtable are virtual) when we're only using the base class.
@stephentoub stephentoub requested a review from a team as a code owner June 23, 2021 14:35
@ghost ghost added the PR metadata: Label to tag PRs, to facilitate with triage label Jun 23, 2021
@ghost ghost requested review from fabiant3, ryalanms and SamBent June 23, 2021 14:35
@ryalanms ryalanms merged commit 3ca050a into dotnet:main Aug 17, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Apr 8, 2022
@stephentoub stephentoub deleted the eventmaphashtable branch April 11, 2022 19:58
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
PR metadata: Label to tag PRs, to facilitate with triage
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants