1
\$\begingroup\$

In our current project, we use Photon room properties to sync entities. and i noticed some heavy GC Allocs while deep profiling the game and it always ends down to this method.

enter image description here

Have anyone faced this issue before ? or attempted to fix it ? here is the source code of that method.

/// <summary>
        /// This removes all key-value pairs that have a null-reference as value.
        /// Photon properties are removed by setting their value to null.
        /// Changes the original passed IDictionary!
        /// </summary>
        /// <param name="original">The IDictionary to strip of keys with null-values.</param>
        public static void StripKeysWithNullValues(this IDictionary original)
        {
            object[] keys = new object[original.Count];
            original.Keys.CopyTo(keys, 0);

            for (int index = 0; index < keys.Length; index++)
            {
                var key = keys[index];
                if (original[key] == null)
                {
                    original.Remove(key);
                }
            }
        }
\$\endgroup\$
3
\$\begingroup\$

I don't know anything about Photon, but I suspect your keys array is very large. The CopyTo method is allocating all of that memory, and then it must be cleaned up.

Instead of duplicating the array, make a new list, and add all of the keys to remove to it. Then loop back through, and clean up. Something along the lines of:

/// <summary>
/// This removes all key-value pairs that have a null-reference as value.
/// Photon properties are removed by setting their value to null.
/// Changes the original passed IDictionary!
/// </summary>
/// <param name="original">The IDictionary to strip of keys with null-values.</param>
public static void StripKeysWithNullValues(this IDictionary original)
{
    var keysToRemove = new List<object>();
    foreach(var entry in original)
    {
        if(entry.Value == null)
        {
            keysToRemove.Add(entry.Key);
        }
    }
    foreach(var key in keysToRemove)
    {
        original.Remove(key);
    }
}

You can further reduce memory allocation by implementing @Ed Marty's and @DMGregory's suggestions:

private static List<object> keysToRemove = new List<object>();
public static void StripKeysWithNullValues(this IDictionary original)
{
    if(original.Values.Count(v => v == null) == 0)
    {
        return;
    }
    keysToRemove.Clear();
    foreach(var entry in original)
    {
        if(entry.Value == null)
        {
            keysToRemove.Add(entry.Key);
        }
    }
    foreach(var key in keysToRemove)
    {
        original.Remove(key);
    }
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ One minor adjustment I’d make is to not even assign keysToRemove to a new list until it needs to contain at least one element so that if there is nothing to remove, no allocation occurs at all \$\endgroup\$
    – Ed Marty
    Mar 8 '20 at 15:22
  • 3
    \$\begingroup\$ You can reduce the allocation thrash even further by making keysToRemove a static variable that you clear before using. It will trigger allocations only during the warm-up phase as the list needs to grow. Once it's hit the high water mark for keys you need to remove, it will not allocate further during steady-state operation. \$\endgroup\$
    – DMGregory
    Mar 8 '20 at 15:34

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .