Pull to refresh

Comments 21

А большинству разработчиков эта фича ни разу не понадобится.

Спрятана же она потому, что возвращённые ссылки, фактически, инвалидируются при добавлении или удалении элементов из словаря, в то время как способа выразить это в API нету. Я бы тоже не стал держать в основном пронстранстве имён API, которое настолько легко использовать неправильно.

И вообще, нефиг делать такие большие структуры. 3-4 ссылочных поля - предел, за которым от структуры становится больше проблем чем пользы.

а какой тогда смысл делать "секретную" фичу, и не писать о ней в документации? Как разработчик, которому это потребовалось вообще должен вообще об этом узнать?

Ну автор же откуда-то про эту фичу знал? Теперь вот мы тоже знаем.

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

Этот функционал предназначен для взаимодействия с кодом, выполняющимся не под управлением .NET. И находится там же, где всё подобное - в System.InteropServices.

В обычном (управляемом) коде .NET его лучше не использовать - как например и указатели, ровно по тем же причинам: unsafe.

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

Случайно обнаружил вот это issue https://github.com/dotnet/runtime/issues/27062, когда гуглил как структуры подружить со словарём. :)

Если мы получаем от словаря ссылку на структуру и потом работаем по ссылке, то чем, собственно говоря, это отличается от класса?

Да, есть небольшой выигрыш за счёт того, что ссылки не считаются и ансейф во все поля. Но этот выигрыш чувствителен только для сценария с большим количеством поисков и не пополняющимся словарём. Для такого сценария существуют контейнеры получше, вроде FrozenDictionary. А то и бинарное дерево.

И сравнение считаю некорректным. Структуры у вас с ружьём, направленным в ногу, а классы используют стандартный сейф механизм.

И ещё. Не нашёл в коде объявления GetHash и Equals. Может у вас хэш коллизит и поиск деградировал до линейного.

Так ключом в словаре является int, у него GetHashCode и Equals свои есть

Кто-то не верит, что там ключ int? Смотрим исходники: https://github.com/alexeyfv/speed-up-the-dictionary/blob/0f6ce393fe2fb7e32caef579640749e4dc2b18f2/CodeGen/SourceGenerator.cs#L235-L245

        private readonly string _createDictionaryBenchmarkTemplate =
@"    Dictionary<int, {0}{1}> _dict{0}{1} = new(Length);
    {0}{1}[] _array{0}{1} = new {0}{1}[Length];

    [Benchmark]
    [BenchmarkCategory(""{1}"")]
    public Dictionary<int, {0}{1}> {0}{1}DictCreate()
    {{
        _dict{0}{1} = new(Length);
        return _dict{0}{1};
    }}";

Если мы получаем от словаря ссылку на структуру и потом работаем по ссылке, то чем, собственно говоря, это отличается от класса?

В таком сценарии скорее всего все данные сразу попаду на кэш-линию, по-этому код будет работать быстрее.

чем, собственно говоря, это отличается от класса

Да, есть небольшой выигрыш за счёт того, что ссылки не считаются и ансейф во все поля

Вы сами ответили на вопрос. :)

Если рассматривать только то, что приведено в статье, то да, выигрыш небольшой. Если смотреть шире, как структуры влияют на всё приложение, то за счёт их использования можно ускорить работу и снизить потребление памяти. Данные в массиве data из примера же не берутся из воздуха. CLR в какой-то момент времени 100 000 аллоцировала память под 100 000 экземпляров классов + 1 раз для словаря. В случае со структурами, аллокация произошла только для словаря. Что быстрее: аллоцировать 100 001 раз память в куче или всего 1 раз? Но, опять же, всё зависит от сценария. Я не утверждаю, что рассмотренный мною способ единственно правильный.

По поводу FrozenDictionary вы правы, работает быстрее.

| Method                       | Categories | Repeats | Mean     | Error    | StdDev   | Gen0     | Gen1     | Gen2     | Allocated |
|----------------------------- |----------- |-------- |---------:|---------:|---------:|---------:|---------:|---------:|----------:|
| FrozenDictionary             | Class      | 100000  | 24.23 ms | 0.327 ms | 0.376 ms | 437.5000 | 437.5000 | 437.5000 |   7.29 MB |
| Dictionary                   | Struct     | 100000  | 69.79 ms | 1.072 ms | 1.235 ms | 375.0000 | 375.0000 | 375.0000 |   4.14 MB |
| DictionaryCollectionsMarshal | Struct     | 100000  | 34.22 ms | 2.077 ms | 2.392 ms | 428.5714 | 428.5714 | 428.5714 |   4.14 MB |

Опять же, выбор будет зависеть от сценария. Если создавать словарь нужно относительно редко (т.е. аллоцировать память для экземпляров классов), то это хорошее решение. Если же, например, на каждый запрос нужно достать из БД какие-то данные, преобразовать в словарь, потом осуществить поиск, то структуры + CollectionsMarshall, думаю, будут быстрее.

А точно ли со всей этой махиной "обрабатываем входящий запрос -> наполняем словарь -> лезем в БД через ORM" выигрыш в class->struct будет так сильно заметен по аллокациям?

Нет, это не точно. Пока не сделать конкретные замеры и не показать результаты, нельзя сказать, что один подход быстрее другого. И то, результаты будут валидны с определёнными оговорками. Так что пока мы можем только теоретически рассуждать, что мы и делаем. :)

Я давно видел эту штуку. Но никогда не проверял, что буде если:

ref var myRef = ref CollectionsMarshal.GetValueRefOrNullRef(dict, ket);
dict.Add(...); // тут происходит рост коллекции
myRef = value; // куда сейчас указывает myRef?

Либо на актуальную ячейку, либо в мусорный массив с копией старых данных, а куда точно - не определено.

Интереснее результат можно получить если удалить ключ из словаря.

Проглядел что вы об написали об этом. С одной стороны ожидаемо, но я в тайне надеялся что есть какая-то магия.

Интереснее результат можно получить если удалить ключ из словаря.

Причем, возможно - если удалить совсем другой ключ;-). Вы не посмотрели когда туда лазили, не открытая ли адресация там используется?

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

Sign up to leave a comment.

Articles