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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.