Я хотел сохранить некоторые пиксельные местоположения, не допуская дублирования, поэтому первое, что приходит на ум, - это HashSet<Point>
или подобные классы. Однако это кажется очень медленным по сравнению с чем-то вроде HashSet<string>
.
Например, этот код:
HashSet<Point> points = new HashSet<Point>();
using (Bitmap img = new Bitmap(1000, 1000))
{
for (int x = 0; x < img.Width; x++)
{
for (int y = 0; y < img.Height; y++)
{
points.Add(new Point(x, y));
}
}
}
занимает около 22,5 секунд.
В то время как следующий код (который не является хорошим выбором по понятным причинам) занимает всего 1,6 секунды:
HashSet<string> points = new HashSet<string>();
using (Bitmap img = new Bitmap(1000, 1000))
{
for (int x = 0; x < img.Width; x++)
{
for (int y = 0; y < img.Height; y++)
{
points.Add(x + "," + y);
}
}
}
Итак, мои вопросы:
- Есть ли причина для этого? Я проверил этот ответ , но 22,5 секунды - это намного больше, чем числа, показанные в этом ответе.
- Есть ли лучший способ хранить очки без дубликатов?
c#
.net
performance
collections
hashset
Ахмед Абдельхамид
источник
источник
Ответы:
Есть две проблемы перфорирования, вызванные структурой Point. Что-то, что вы можете увидеть, когда добавляете
Console.WriteLine(GC.CollectionCount(0));
тестовый код. Вы увидите, что для теста Point требуется ~ 3720 наборов, а для строкового теста требуется всего ~ 18 наборов. Не бесплатно Когда вы видите, что тип значения вызывает так много коллекций, вам нужно заключить "э-э-э, слишком много бокса".Вопрос в том, что
HashSet<T>
нужно,IEqualityComparer<T>
чтобы сделать свою работу. Так как вы не предоставили один, он должен вернуться к одному, возвращенномуEqualityComparer.Default<T>()
. Этот метод может хорошо работать со строками, он реализует IEquatable. Но не для Point, это тип, взятый из .NET 1.0 и никогда не получавший любовь к дженерикам. Все, что он может сделать, это использовать методы Object.Другая проблема заключается в том, что Point.GetHashCode () не выполняет звездную работу в этом тесте, слишком много коллизий, поэтому он довольно сильно забивает Object.Equals (). String имеет отличную реализацию GetHashCode.
Вы можете решить обе проблемы, предоставив HashSet хороший компаратор. Как этот:
И использовать это:
И теперь это примерно в 150 раз быстрее, легко обгоняя тест строки.
источник
obj.X << 16 | obj.Y;
реализацией.|
. Для 3 чисел может иметь смысл использовать 22 и 11 в качестве смены. Для 4 чисел это будет 24, 16, 8. Однако столкновения все же будут, но только если числа станут большими. Но это также в решающей степени зависит отHashSet
реализации. Если он использует открытую адресацию с «усечением битов» (я не думаю, что это так!), Подход с левым сдвигом может быть плохим.Основная причина снижения производительности - все происходящее в боксе (как уже объяснялось в ответе Ханса Пассанта ).
Кроме того, алгоритм хеш-кода усугубляет проблему, потому что он вызывает больше вызовов,
Equals(object obj)
тем самым увеличивая количество преобразований в бокс.Также обратите внимание, что хэш-код
Point
вычисляется какx ^ y
. Это приводит к очень небольшому разбросу в вашем диапазоне данных, и, следовательно, блокиHashSet
переполнены - то, чего не происходитstring
, где разброс хэшей намного больше.Вы можете решить эту проблему, реализовав собственную
Point
структуру (тривиальную) и используя лучший алгоритм хеширования для ожидаемого диапазона данных, например, смещая координаты:Чтобы получить полезные советы по хэш-кодам, прочитайте сообщение Эрика Липперта в блоге на эту тему .
источник
GetHashCode
выполняет:unchecked(x ^ y)
покаstring
он выглядит гораздо сложнее ..HashSet<long>()
вместо этого и использовалlist.Add(unchecked(x ^ y));
для добавления значений в HashSet. Это было даже быстрее, чемHashSet<string>
(345 мс) . Это как-то отличается от того, что вы описали?list
когда вы закончили заполнять его?point
, тоHashSet
будет внутренне позвонитьGetHashCode
и для каждой из этих точек с одной и той же хэш - код, позвонит ,Equals
чтобы определить , если он уже существуетPoint
когда вы можете создать класс, который реализуетIEqualityComparer<Point>
и поддерживает совместимость с другими вещами, с которыми вы работаетеPoint
, получая при этом выгоду от отсутствия бедныхGetHashCode
и необходимости заниматься боксомEquals()
.