Skip to content
This repository has been archived by the owner on May 3, 2021. It is now read-only.

Commit

Permalink
Feature/optimize string key (#4)
Browse files Browse the repository at this point in the history
* Optimized the string key.

- Implemented the StringKey type that precalculate the hash.

* Add a description of StringKey.
  • Loading branch information
troubear authored Oct 25, 2017
1 parent 836c9e3 commit cd0c267
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 89 deletions.
1 change: 0 additions & 1 deletion Assets/Plugins/BetterDictionary/Dictionary.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using UnsafeGeneric;

#if BETTER_PATCH
namespace Better
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
using System;
using System.Collections.Generic;
using UnsafeGeneric;

namespace UnsafeGeneric
namespace Better
{
/// <summary>
/// Provides the factory method of the unsafe EqualityComparers.
/// Provides the factory method of the better EqualityComparers.
/// </summary>
public static class EqualityComparerFactory
{
private static readonly object StringEqualityComparer = new StringEqualityComparer();
private static readonly object StringKeyEqualityComparer = new StringKeyEqualityComparer();

public static IEqualityComparer<T> Create<T>()
{
Expand Down Expand Up @@ -36,9 +38,29 @@ public static IEqualityComparer<T> Create<T>()
}
if (keyType == typeof(string))
{
return (IEqualityComparer<T>) StringEqualityComparer;
return (IEqualityComparer<T>)StringEqualityComparer;
}
if (keyType == typeof(StringKey))
{
return (IEqualityComparer<T>)StringKeyEqualityComparer;
}
return null; // float, double, structs
}
}

/// <summary>
/// An implementation of <see cref="IEqualityComparer{T}" /> for the <see cref="StringKey" /> type.
/// </summary>
internal struct StringKeyEqualityComparer : IEqualityComparer<StringKey>
{
public bool Equals(StringKey x, StringKey y)
{
return x.Equals(y);
}

public int GetHashCode(StringKey obj)
{
return obj.HashCode;
}
}
}
12 changes: 12 additions & 0 deletions Assets/Plugins/BetterDictionary/EqualityComparerFactory.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 54 additions & 55 deletions Assets/Plugins/BetterDictionary/HashSet.cs
Original file line number Diff line number Diff line change
@@ -1,71 +1,70 @@
using System.Collections.Generic;
using UnsafeGeneric;

#if !BETTER_PATCH
namespace Better
{
#endif
/// <summary>
/// A generic hash set class optimized for the Unity.
/// </summary>
/// <typeparam name="T">The type of elements in the hash set.</typeparam>
public class HashSet<T> : System.Collections.Generic.HashSet<T>
{
private static readonly IEqualityComparer<T> EqualityComparer = EqualityComparerFactory.Create<T>();

/// <summary>
/// Initializes a new instance of the <see cref="T:Better.HashSet`1" /> class that is empty
/// and uses the default equality comparer for the set type.
/// A generic hash set class optimized for the Unity.
/// </summary>
public HashSet() : this((IEqualityComparer<T>) null)
/// <typeparam name="T">The type of elements in the hash set.</typeparam>
public class HashSet<T> : System.Collections.Generic.HashSet<T>
{
}
private static readonly IEqualityComparer<T> EqualityComparer = EqualityComparerFactory.Create<T>();

/// <summary>
/// Initializes a new instance of the <see cref="T:Better.HashSet`1" /> class that is empty
/// and uses the specified equality comparer for the set type.
/// </summary>
/// <param name="comparer">
/// The <see cref="T:System.Collections.Generic.IEqualityComparer`1" /> implementation to use when
/// comparing values in the set, or null to use the default
/// <see cref="T:System.Collections.Generic.EqualityComparer`1" /> implementation for the set type.
/// </param>
public HashSet(IEqualityComparer<T> comparer) : base(comparer ?? EqualityComparer)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="T:Better.HashSet`1" /> class that is empty
/// and uses the default equality comparer for the set type.
/// </summary>
public HashSet() : this((IEqualityComparer<T>)null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="T:Better.HashSet`1" /> class that uses the
/// default equality comparer for the set type, contains elements copied from the specified collection, and has
/// sufficient capacity to accommodate the number of elements copied.
/// </summary>
/// <param name="collection">The collection whose elements are copied to the new set.</param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="collection" /> is null.
/// </exception>
public HashSet(IEnumerable<T> collection) : this(collection, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="T:Better.HashSet`1" /> class that is empty
/// and uses the specified equality comparer for the set type.
/// </summary>
/// <param name="comparer">
/// The <see cref="T:System.Collections.Generic.IEqualityComparer`1" /> implementation to use when
/// comparing values in the set, or null to use the default
/// <see cref="T:System.Collections.Generic.EqualityComparer`1" /> implementation for the set type.
/// </param>
public HashSet(IEqualityComparer<T> comparer) : base(comparer ?? EqualityComparer)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="T:Better.HashSet`1" /> class that uses the
/// specified equality comparer for the set type, contains elements copied from the specified collection, and has
/// sufficient capacity to accommodate the number of elements copied.
/// </summary>
/// <param name="collection">The collection whose elements are copied to the new set.</param>
/// <param name="comparer">
/// The <see cref="T:System.Collections.Generic.IEqualityComparer`1" /> implementation to use when
/// comparing values in the set, or null to use the default
/// <see cref="T:System.Collections.Generic.EqualityComparer`1" /> implementation for the set type.
/// </param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="collection" /> is null.
/// </exception>
public HashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
: base(collection, comparer ?? EqualityComparer)
{
/// <summary>
/// Initializes a new instance of the <see cref="T:Better.HashSet`1" /> class that uses the
/// default equality comparer for the set type, contains elements copied from the specified collection, and has
/// sufficient capacity to accommodate the number of elements copied.
/// </summary>
/// <param name="collection">The collection whose elements are copied to the new set.</param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="collection" /> is null.
/// </exception>
public HashSet(IEnumerable<T> collection) : this(collection, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="T:Better.HashSet`1" /> class that uses the
/// specified equality comparer for the set type, contains elements copied from the specified collection, and has
/// sufficient capacity to accommodate the number of elements copied.
/// </summary>
/// <param name="collection">The collection whose elements are copied to the new set.</param>
/// <param name="comparer">
/// The <see cref="T:System.Collections.Generic.IEqualityComparer`1" /> implementation to use when
/// comparing values in the set, or null to use the default
/// <see cref="T:System.Collections.Generic.EqualityComparer`1" /> implementation for the set type.
/// </param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="collection" /> is null.
/// </exception>
public HashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
: base(collection, comparer ?? EqualityComparer)
{
}
}
}
#if !BETTER_PATCH
}
#endif
75 changes: 75 additions & 0 deletions Assets/Plugins/BetterDictionary/StringKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using UnsafeGeneric;

#if !BETTER_PATCH
namespace Better
{
#endif
/// <summary>
/// A string key class optimized for the Dictionary and HashSet.
/// </summary>
public struct StringKey : IEquatable<StringKey>
{
/// <summary>
/// The string of the key.
/// </summary>
public readonly string Value;

/// <summary>
/// The hash code of the string.
/// </summary>
public readonly int HashCode;

public StringKey(string str)
{
Value = str;
HashCode = StringHashCode.Calculate(str);
}

public bool Equals(StringKey key)
{
return Value.Equals(key.Value);
}

public override bool Equals(object obj)
{
if (obj is StringKey)
{
return Equals((StringKey)obj);
}

// exception will be thrown later for null this
return this == null;
}

public override int GetHashCode()
{
return HashCode;
}

public static bool operator ==(StringKey a, StringKey b)
{
return a.Equals(b);
}

public static bool operator !=(StringKey a, StringKey b)
{
return !a.Equals(b);
}

// StringKey key = "Foo";
public static implicit operator StringKey(string str)
{
return new StringKey(str);
}

// StringKey key;
// string str = key;
public static implicit operator string(StringKey str)
{
return str.Value;
}
}
#if !BETTER_PATCH
}
#endif
12 changes: 12 additions & 0 deletions Assets/Plugins/BetterDictionary/StringKey.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified Assets/Plugins/BetterDictionary/UnsafeGeneric.dll
Binary file not shown.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,48 @@ BetterDictionary for Unity
1. または、`PlayerSettings`の`Scripting Define Symbols`に`BETTER_PATCH`を追加し、プロジェクトソース内で使用されているSystem.Collections.Generic名前空間のDictionary/HashSetクラスを本ライブラリのクラスで一括置換※します。
- ※`BETTER_PATCH`シンボルの追加によって、本ライブラリのDictionary/HashSetクラスがグローバル名前空間で定義されます。

StringKey (ハッシュ計算済み文字列キー)
---
Better.StringKeyを使用することで、Dictionary/HashSetへのアクセスを高速化することができます。

~~~csharp
using Better;

// Typical extension method of the Dictionary class
public static void AddOrUpdate<TKey, TValue>(
this Dictionary<TKey, TValue> dict, TKey key, TValue value)
{
if (dict.ContainsKey(key))
{
dict[key] = value;
}
else
{
dict.Add(key, value);
}
}

Dictionary<string, int> dict1;
Dictionary<StringKey, int> dict2;

dict1.AddOrUpdate("hogehoge", 1234);

// Depending on the length of the string,
// several percent to tens of percent faster than the raw string key.
// 文字列の長さによって、stringキーより数パーセント~数十パーセント速くなります。
dict2.AddOrUpdate("hogehoge", 1234);

// Attention!!
// However, in the case of a single operation it will slow down about 30%.
// ※但し、単一オペレーションでは約30%程度遅くなります。
dict2["hogehoge"] = 5678;

// Therefore, it is strongly recommended to prepare StringKey in advance.
// 従って、StringKeyを事前に用意しておくことを強く推奨します。
static StringKey HogeHoge = "hogehoge";
dict2[HogeHoge] = 5678; // Faster than dict1["hogehoge"]
~~~

TODO
---
- 詳細なパフォーマンス計測&グラフ化
Expand Down
Loading

0 comments on commit cd0c267

Please sign in to comment.