Различное количество результатов сравнения изменений параметров структуры - PullRequest
1 голос
/ 04 июня 2019

Я использую BenchmarkDotNet для тестирования структуры связанного кода и заметил, что производительность моего теста зависит от количества параметров, содержащихся в моей структуре.

[MemoryDiagnoser]
public class Runner
{
    [Params(1000)]
    public int N;
    [Benchmark]
    public void StructKey()
    {
        var dictionary = new Dictionary<BoxingStruct, int>(); //only difference
        for (int i = 0; i < N; i++)
        {
            var boxingStruct = MakeBoxingStruct(i);
            if (!dictionary.ContainsKey(boxingStruct))
                dictionary.Add(boxingStruct, i);
        }
    }
    [Benchmark]
    public void ObjectKey()
    {
        var dictionary = new Dictionary<object, int>(); //only difference
        for (int i = 0; i < N; i++)
        {
            var boxingStruct = MakeBoxingStruct(i);
            if (!dictionary.ContainsKey(boxingStruct))
                dictionary.Add(boxingStruct, i);
        }
    }        

    public BoxingStruct MakeBoxingStruct(int id)
    {
        var boxingStruct = new BoxingStruct()
        {
            Id = id,
            User = new UserStruct()
            {
                name = "Test User"
            }
        };
        return boxingStruct;
    }
}
public struct BoxingStruct
{
    public int Id { get; set; }
    public UserStruct User { get; set; }


    public override bool Equals(object obj)
    {
        if (!(obj is BoxingStruct))
            return false;

        BoxingStruct mys = (BoxingStruct)obj;
        return mys.Id == Id;
    }

    public override int GetHashCode()
    {
        return Id;
    }
}
public struct UserStruct
{
    public string name { get; set; }
}
public class Program
{
    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<Runner>();
    }
}

Этот простой тест создает структуры и добавляет их в словарь, если словарь еще не содержит их. Единственная разница между StructKey () и ObjectKey () - это тип ключа словаря, один из которых представляет собой BoxingStruct, а другой - объект. В этом примере мой UserStruct имеет только одно поле. Если я запускаю это, я получаю следующие результаты:

|    Method |    N |     Mean | Allocated |
|---------- |----- |---------:|----------:|
| StructKey | 1000 | 54.85 us | 128.19 KB |
| ObjectKey | 1000 | 61.50 us | 162.32 KB |

Теперь, если я добавлю еще несколько элементов в UserStruct, мои результаты производительности перевернутся.

public struct UserStruct
{
    public string name { get; set; }
    public string email { get; set; }
    public string phone { get; set; }
    public int age { get; set; }
}
public BoxingStruct MakeBoxingStruct(int id)
{
    var boxingStruct = new BoxingStruct()
    {
        Id = id,
        User = new UserStruct()
        {
            name = "Test User",
            email = "testemail@gmail.com",
            phone = "8293839283",
            age = 11110,
        }
    };
    return boxingStruct;
}

Результаты:

|    Method |    N |      Mean | Allocated |
|---------- |----- |----------:|----------:|
| StructKey | 1000 | 112.00 us |  213.2 KB |
| ObjectKey | 1000 |  90.97 us |  209.2 KB |

Теперь метод StructKey занимает больше времени и выделяет больше памяти. Но я не знаю почему? Я запускал это несколько раз, и запуск с 8 и 16 параметрами дает схожие результаты.

Я прочитал о различиях между структурами и объектами , значение v. Ссылочный тип. Со структурами данные копируются, но объекты просто передают элементы по ссылке. String является ссылочным типом, поэтому я вполне уверен, что он не хранится в стеке. Эти стеки имеют ограниченную емкость хранилища, но я не думаю, что приближаюсь к этому. Имею ли словарный ключ объект, я помещаю в бокс тип значения?

Несмотря на все сказанное, независимо от различий в производительности между двумя словарями, я ожидаю, что число параметров структуры не изменится, какой метод является более производительным. Я был бы рад, если бы кто-нибудь смог рассказать, что происходит, что влияет на производительность этих тестов.

Я на Windows-машине с ядром dotnet 2.2.300, выполняю тесты в режиме релиза, вот Github repo , содержащий мой тест.

EDIT

Я реализовал и IEquatable, и IEqualityComparer, производительность фактически ухудшилась, и те же отношения все еще существуют. С 1 свойством StructKey () работает быстрее и использует меньше памяти, а с 4 свойствами ObjectKey () работает быстрее и использует меньше памяти.

public struct BoxingStruct : IEqualityComparer<BoxingStruct>, IEquatable<BoxingStruct>
{
    public int Id { get; set; }
    public UserStruct User { get; set; }
    public override bool Equals(object obj)
    {
        if (!(obj is BoxingStruct))
            return false;

        BoxingStruct mys = (BoxingStruct)obj;
        return Equals(mys);
    }

    public bool Equals(BoxingStruct x, BoxingStruct y)
    {
        return x.Id == y.Id;
    }

    public bool Equals(BoxingStruct other)
    {
        return Id == other.Id;
    }

    public override int GetHashCode()
    {
        return Id;
    }

    public int GetHashCode(BoxingStruct obj)
    {
        return obj.Id;
    }
}

1 Результат собственности:

|    Method |    N |     Mean | Allocated |
|---------- |----- |---------:|----------:|
| StructKey | 1000 | 62.32 us | 128.19 KB |
| ObjectKey | 1000 | 71.11 us | 162.32 KB |

4 Свойства Результат:

|    Method |    N |     Mean | Allocated |
|---------- |----- |---------:|----------:|
| StructKey | 1000 | 155.5 us | 213.29 KB |
| ObjectKey | 1000 | 109.1 us |  209.2 KB |

1 Ответ

1 голос
/ 07 июня 2019

Как упоминали в комментариях и Ганс, и Иван, я упускал из виду ценность склонности к использованию структур.В C # есть две основные категории типов: ссылочные типы и типы значений.

Когда создается ссылочный тип, локальная переменная указывает на область памяти в куче, где хранится объект.Когда вы передаете ссылочный тип методу, только ссылка передается, пока объект в куче остается там.

Когда создается тип значения, он сохраняется в стеке.При передаче типа значения методу создается полная копия этого типа значения, и эта копия передается методу.

Очевидно, что чем больше данных в структуре, тем больше данных нужно копировать при каждом перемещении.Объясняя, почему мой структурный бенчмарк показал худшие результаты по мере роста.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...