Почему Dictionary.ContainsKey () & ToString () вызывает GC Alloc? - PullRequest
0 голосов
/ 12 мая 2018

Я не могу предложить больше ничего с точки зрения деталей, кроме того факта, что есть около 1 тыс. Экземпляров, которые довольно часто запускают свои ContainsKey() и ToString().

Местоположение - только моя личная замена для Vector3 Unity, чтобы соответствовать моим потребностям:

[Serializable] public struct Location
{
    public double X;
    public double Y;
    public double Z;

    public Location(double x, double y, double z) : this()
    {
        X = x;
        Y = y;
        Z = z;
    }

    public override string ToString()
    {
        return String.Format("{0}, {1}, {2}", X, Y, Z);
    }
}

(я знаю, что нарушаю какое-то правило с Structs .. просто не знаю, как удовлетворить мои потребности другим способом.)

Вот скриншот запущенного Profiler:

Unity_Profiler_ToString_ContainsKey_GC_Alloc

Как вы можете видеть, для большей части временной шкалы она стабильна, затем внезапно после того, как мои экземпляры достигают около 1 КБ (количество), (они начинаются около 100-250), ЦП и Память становятся дикими из-за того, что кажется быть распределением GC. Я пытался найти то, что я могу почистить немного лучше, но все, что я вижу, это даже вызывает ЛЮБОЕ выделение GC, это когда я запускаю:

if (_dictionary.ContainsKey(key)) {...}

и при переименовании Unity GameObjects с помощью:

part.name = "Part: " + part.Location.ToString();

Если это относится только к неизбежному времени, которое требуется для поиска, то есть ли альтернативы Dictionaries, которые имеют тенденцию функционировать еще медленнее, но вызывают меньшее выделение GC, и есть более эффективный способ override ToString() метод?

Добавление: Мой словарь является ключевым: (моя личная структура) Местоположение, значение: экземпляр класса.

1 Ответ

0 голосов
/ 12 мая 2018

Превращение комментариев в ответ ...

Ваш метод ToString() всегда будет создавать новую строку, так что это не удивительно.Однако вы также используете конкатенацию строк, поэтому вы создаете две новые строки.Вы можете уменьшить это значение до одного, просто вставив метод ToString().Например, для краткости используйте интерполированные строки C # 6:

var location = part.Location;
part.name = $"Part: {location.X}, {location.Y}, {location.Z}";

Для аспекта словаря есть две проблемы:

  • Вы не переопределяете Equals и GetHashCode, что может означать, что значение упаковывается в коробку для вызова реализаций в ValueType.Я не уверен на 100% в этом;правила бокса могут быть сложными.
  • Вы не используете IEquatable<T>, поэтому очень вполне вероятно, что любые Equals вызовы будут боксировать.

Вы можете легко исправить оба из них:

[Serializable] public struct Location : IEquatable<Location>
{
    public double X;
    public double Y;
    public double Z;

    public Location(double x, double y, double z) : this()
    {
        X = x;
        Y = y;
        Z = z;
    }

    public override string ToString() => $"{X}, {Y}, {Z}";

    public override bool Equals(object obj) =>
        obj is Location loc && Equals(loc);

    public bool Equals(Location other) =>
        X == other.X && Y == other.Y && Z == other.Z;

    public override int GetHashCode()
    {
        // Replace with whatever implementation you want
        int hash = 17;
        hash = hash * 23 + X.GetHashCode();
        hash = hash * 23 + Y.GetHashCode();
        hash = hash * 23 + Z.GetHashCode();
        return hash;
    };
}

(Это использует синтаксис C # 7, но я ожидаю, что все будет в порядке, если вы используете современную версию Unity с VS2017. Есливы используете более старую версию, вы должны быть в состоянии реализовать те же методы просто более длинным способом.)

...