Возможно вы искали: 'Club Football 2005: Ma...'

May 31 2025 02:07:32
  • Как сделать 8Gamers.Ru домашней страницей?
  • Игры
    • База данных по играх
    • Игровые новости
    • Игровая индустрия
    • Обзоры на игры
    • Прохождения игр
    • Гайды к играм
    • Превью о играх
    • Игровые тизеры
    • Игровые арты
    • Игровые обои
    • Игровые скриншоты
    • Игровые обложки
    • Игровые трейлеры
    • Игровое видео
    • Вышедшие игры
    • Ближайшие релизы игр
  • Кино и ТВ
    • База данных по кино
    • Статьи о кино
    • Постеры
    • Кадры из кино
    • Кино трейлеры
    • Сегодня в кино
    • Скоро в кино
  • Комиксы и манга
    • Манга по алфавиту
    • База данных по комиксах
    • Читать онлайн комиксы
    • Читать онлайн манга
    • База персонажей
  • Читы и коды
    • Чит-коды для PC игр
    • Чит-коды для консольных игр
    • Трейнеры
    • Коды Game Genie
  • Моддинг
    • Модификации
    • Карты к играм
    • Программы для моддинга
    • Статьи о моддинге
  • Геймдев
    • Всё о создании игр
    • Список движков
    • Утилиты в помощь игроделу
    • Конструкторы игр
    • Игровые движки
    • Библиотеки разработки
    • 3D-модели
    • Спрайты и тайлы
    • Музыка и звуки
    • Текстуры и фоны
  • Рецензии
    • Игры
    • Кино
    • Аниме
    • Комиксы
    • Мангу
    • Саундтреки
  • Саундтреки
    • Лирика
  • Файлы
    • Патчи к играм
    • Русификаторы к играм
    • Сохранения к играм
    • Субтитры к кино
  • Медиа
    • Видео
    • Фото
    • Аудио
    • Фан-арты
    • Косплей
    • Фото с виставок
    • Девушки из игр
    • Рисунки
    • Рисуем онлайн
    • Фотохостинг
  • Юмор
    • Анекдоты
    • Афоризмы
    • Истории
    • Стишки и эпиграммы
    • Тосты
    • Цитаты
  • Флеш
    • Азартные
    • Аркады
    • Бродилки
    • Гонки
    • Для девочек
    • Для мальчиков
    • Драки
    • Квесты
    • Леталки
    • Логические
    • Мультфильмы
    • Открытки
    • Приколы
    • Разное
    • Спорт
    • Стратегии
    • Стрелялки
Статистика

Статей: 87772
Просмотров: 96425698
Игры
Injustice:  Gods Among Us
Injustice: Gods Among Us
...
Dark Souls 2
Dark Souls 2
Dark Souls II - вторая часть самой хардкорной ролевой игры 2011-2012 года, с новым героем, сюжето...
Battlefield 4
Battlefield 4
Battlefield 4 - продолжение венценосного мультиплеер-ориентированного шутера от первого ли...
Кино
Steins;Gate
Steins;Gate
Любители японской анимации уже давно поняли ,что аниме сериалы могут дать порой гораздо больше пи...
Ку! Кин-дза-дза
Ку! Кин-дза-дза
Начинающий диджей Толик и всемирно известный виолончелист Владимир Чижов встречают на шумной моск...
Обзоры на игры
• Обзор Ibara [PCB/PS2] 18407
• Обзор The Walking ... 18853
• Обзор DMC: Devil M... 19921
• Обзор на игру Valk... 15921
• Обзор на игру Stars! 17810
• Обзор на Far Cry 3 18000
• Обзор на Resident ... 16063
• Обзор на Chivalry:... 17561
• Обзор на игру Kerb... 18021
• Обзор игры 007: Fr... 16667
Превью о играх
• Превью к игре Comp... 18003
• Превью о игре Mage... 14502
• Превью Incredible ... 14763
• Превью Firefall 13523
• Превью Dead Space 3 16378
• Превью о игре SimC... 14772
• Превью к игре Fuse 15479
• Превью Red Orche... 15589
• Превью Gothic 3 16388
• Превью Black & W... 17402
Главная » Статьи » Всё о XNA » Использование перечисления в качестве ключа для словаря

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

Неявная генерация объектов в процессе работы .NET приложения является неоспоримым злом, а в процессе работы .NET приложения, которые по совместительству еще и являются играми реального времени, такое поведение и вообще смерти подобно. Сам по себе вызов сборки мусора к краху приложения конечно ни в коем случае не приведет, но желательно, чтобы их в процессе жизни нашего приложения происходило, как можно меньше. В данной статье я хочу рассмотреть довольно частый прием, который применяется программистами .NET – использование перечисления (enum) в качестве ключа для объекта словаря (Dictionary). Во-первых, применение данного метода удобно, объекты одного типа собраны в один контейнер и доступ к ним осуществляется фактически по имени (значение перечисления), IntelliSince тоже вносит свою немалую лепту в написании кода при таком подходе. Во-вторых, данный подход помогает избежать ошибок, в отличие от применения скажем string в качестве ключа.

Но как всегда найдется ложка с дегтем, нависающая над нашей бочкой с прополисом. Использование перечисления, как ключа для словаря ведет к генерации мусора, в бизнес приложении можно конечно забить на сей факт (что конечно тоже нежелательно), но в играх XNA, где фреймрейт показатель довольно значительный с этим мериться никак нельзя.
Images: XNASpeed.jpg
Если вкратце, то при использовании в качестве ключа ссылочного типа мы ограничиваемся только вызовом метода GetHashCode и по вычисленному хэшу получаем значение из HashTable, то при использовании в качестве ключа типа значение, для получения хэша нам приходится неявно приводить значение в ссылочный тип, в нашем случае – это object и уже затем вычислять хэш. А этот объект отбрасывается в виде мусора за ненадобностью, при накоплении достаточного количества таких мусорных объектов будет инициирована сборка, которая сама по себе является не легкой операцией. В своем докладе Иван обошел это применением кэширования, он просто закэшировал ссылку на объект текстуры, которую до этого получал из словаря по ключу для каждого объекта звезды.

Теперь давайте более подробно разберемся в сложившейся ситуации, а точнее покопаемся внутри объекта Dictionary.
К моему удивлению reflectior не показал мне объекта Dictionary в пространстве имен System.Collections.Generic, где он по теории обитает, поэтому я взял исходники .NET Framework.

Итак, что у нас происходит, когда мы объявляем и инициализируем словарь?
public static Dictionary<TextureEnum, Texture2D> Textures = new Dictionary<TextureEnum, Texture2D>();

 

Не параметризированный конструктор вызывает свою реализацию, но уже с двумя параметрами, передавая 0 и null.
public Dictionary(): this(0, null) {}

 

В которой уже происходит нечто интересное
public Dictionary(int capacity, IEqualityComparer<TKey> comparer) {
            if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); 
            if (capacity > 0) Initialize(capacity); 
            if (comparer == null) comparer = EqualityComparer<TKey>.Default;
            this.comparer = comparer; 
        }

 

Во-первых, передается количество элементов словаря, под которое будет сразу же выделена память, но более интересен для нас сейчас – это объект comparer. Если мы нечего не скармливаем в конструктор, то будет создан экземпляр стандартной реализации EqualityComparer.Default;  Не буду вдаваться в подробности его реализации, но если пройтись по коду, то станет видно, что в случае с перечислениями он инициализируется реализацией internal class ObjectEqualityComparer: EqualityComparer. Собственно данный класс и подписывает нам приговор при использовании перечисления, а точнее его универсальность, в связке со спецификой реализации перечислений дают просто убийственный эффект в производительности.

Нас в нем интересует два метода
public override bool Equals(T x, T y) {
            if (x != null) { 
                if (y != null) return x.Equals(y);
                return false; 
            } 
            if (y != null) return false;
            return true; 
        }
public override int GetHashCode(T obj) {
            if (obj == null) return 0; 
            return obj.GetHashCode();
        }

 

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

public override int GetHashCode()
{
    return this.GetValue().GetHashCode();
}
 
private object GetValue()
{
    return this.InternalGetValue();
}
 
[MethodImpl(MethodImplOptions.InternalCall)]
private extern object InternalGetValue();
 
[MethodImpl(MethodImplOptions.InternalCall)]
public override extern bool Equals(object obj);

 

Метод GetValue() возвращает нам object, по которому у нас и высчитывается хэш, по окончанию расчетов он будут отброшен, как мусорный объект.

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

За основу для кода я взял исходники Ивана Андреева из его доклада, который упоминался выше.

Простейшая реализация класса для сравнения значений ключей для перечисления текстур:
    public class TextureEqualityComparer : IEqualityComparer<TextureEnum>
    {
        private static TextureEqualityComparer defaultInstance;
        public static TextureEqualityComparer Default
        {
            get
            {
                if (defaultInstance == null)
                    defaultInstance = new TextureEqualityComparer();
                return defaultInstance;
            }
        }
 
        public bool Equals(TextureEnum x, TextureEnum y)
        {
            return x == y;
        }
 
        public int GetHashCode(TextureEnum obj)
        {
            return (int)obj;
        }
    }

 

Если кому-нибуть непонятно, что мы этим добились, ведь у нас все так-же продолжает вызываться GetHashCode, поясняю, реализация методов GetHashCode и Equals у типов значений (наследованные от ValueType), в частности наше перечисление TextureEnum отличается от реализации у объектов ссылочного типа, которые напрямую наследуют от object, в них учитывается именно значение, которое находится в объекте.

Далее мы просто скармливаем наш класс для сравнения в конструктор словаря.
Textures = new Dictionary<TextureEnum, Texture2D>(TextureEqualityComparer.Default);

 

Ну говорить я тут могу все что угодно, но неплохо бы провести тестирование…


Создадим простое консольное приложение
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
 
namespace TestDictionaryEnum
{
    class Program
    {
        public enum TextureEnum
        {
            Star,
            Ship,
            Asteroid,
            Bullet,
            Enemy,
            Logo
        }
 
        static void Main(string[] args)
        {
            // Создаем словарь
            Dictionary<TextureEnum, string> testDictionary = new Dictionary<TextureEnum, string>();
 
            // Заполняем
            testDictionary.Add(TextureEnum.Asteroid, TextureEnum.Asteroid.ToString());
            testDictionary.Add(TextureEnum.Bullet, TextureEnum.Bullet.ToString());
            testDictionary.Add(TextureEnum.Enemy, TextureEnum.Enemy.ToString());
            testDictionary.Add(TextureEnum.Logo, TextureEnum.Logo.ToString());
            testDictionary.Add(TextureEnum.Ship, TextureEnum.Ship.ToString());
            testDictionary.Add(TextureEnum.Star, TextureEnum.Star.ToString());
 
            const int counter = 10000000; // кол-во итераций 
            Stopwatch timer = Stopwatch.StartNew();
            string testData;
            for (int i = 0; i < counter; i++)
            {
                testData = testDictionary[TextureEnum.Enemy]; // получаем наше значение
            }
 
            TimeSpan timeSpan = timer.Elapsed;
 
            // Вывод статистики
            Console.WriteLine("Затраченное время {0}", timeSpan);
            Console.WriteLine("Вызовов сборщика мусора \r\nПоколение 0 - {0} \r\nПоколение 1 - {1} \r\nПоколение 2 - {2}", 
                GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
 
            Console.ReadLine();
        }
    }
}

 


При запуске приложения в среднем мне на моем «железе» выдает:
Затраченное время 00:00:02.3281013
Вызовов сборщика мусора
Поколение 0 - 458
Поколение 1 - 1
Поколение 2 – 0

 

Значения более менее средние и от количества запусков значения меняются не очень сильно.
Теперь допишем класс для сравнения и пропишем его экземпляр в конструктор нашего словаря:

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
 
namespace TestDictionaryEnum
{
    class Program
    {
        public class TextureEqualityComparer : IEqualityComparer<TextureEnum>
        {
            private static TextureEqualityComparer defaultInstance;
            public static TextureEqualityComparer Default
            {
                get
                {
                    if (defaultInstance == null)
                        defaultInstance = new TextureEqualityComparer();
                    return defaultInstance;
                }
            }
 
            public bool Equals(TextureEnum x, TextureEnum y)
            {
                return x == y;
            }
 
            public int GetHashCode(TextureEnum obj)
            {
                return (int)obj;
            }
        }
 
        public enum TextureEnum
        {
            Star,
            Ship,
            Asteroid,
            Bullet,
            Enemy,
            Logo
        }
 
        static void Main(string[] args)
        {
            // Создаем словарь
            Dictionary<TextureEnum, string> testDictionary = new Dictionary<TextureEnum, string>(TextureEqualityComparer.Default);
 
            // Заполняем
            testDictionary.Add(TextureEnum.Asteroid, TextureEnum.Asteroid.ToString());
            testDictionary.Add(TextureEnum.Bullet, TextureEnum.Bullet.ToString());
            testDictionary.Add(TextureEnum.Enemy, TextureEnum.Enemy.ToString());
            testDictionary.Add(TextureEnum.Logo, TextureEnum.Logo.ToString());
            testDictionary.Add(TextureEnum.Ship, TextureEnum.Ship.ToString());
            testDictionary.Add(TextureEnum.Star, TextureEnum.Star.ToString());
 
            const int counter = 10000000; // кол-во итераций 
            Stopwatch timer = Stopwatch.StartNew();
            string testData;
            for (int i = 0; i < counter; i++)
            {
                testData = testDictionary[TextureEnum.Enemy]; // получаем наше значение
            }
 
            TimeSpan timeSpan = timer.Elapsed;
 
            // Вывод статистики
            Console.WriteLine("Затраченное время {0}", timeSpan);
            Console.WriteLine("Вызовов сборщика мусора \r\nПоколение 0 - {0} \r\nПоколение 1 - {1} \r\nПоколение 2 - {2}", 
                GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
 
            Console.ReadLine();
        }
    }
}

 



Результат:
Затраченное время 00:00:00.2425216
Вызовов сборщика мусора
Поколение 0 - 0
Поколение 1 - 0
Поколение 2 – 0

 


674 Прочтений •  [Использование перечисления в качестве ключа для словаря] [08.08.2012] [Комментариев: 0]
Добавил: Ukraine Vova
Ссылки
HTML: 
[BB Url]: 
Похожие статьи
Название Добавил Добавлено
• Использование перечисления в качест... Ukraine Vova 08.08.2012
Ни одного комментария? Будешь первым :).
Пожалуйста, авторизуйтесь для добавления комментария.

Проект входит в сеть сайтов «8Gamers Network»

Все права сохранены. 8Gamers.NET © 2011 - 2025

Статьи
Рецензия на Pressure
Рецензия на Pressure
Чтобы обратить на себя внимание, начинающие маленькие разработчики, как правило, уходят в жанры, ...
Рецензия на Lost Chronicles of Zerzura
Рецензия на Lost Chron...
Игры, сделанные без любви и старания, похожи на воздушный шар – оболочка есть, а внутри пусто. Lo...
Рецензия на The Bridge
Рецензия на The Bridge
«Верх» и «низ» в The Bridge — понятия относительные. Прогуливаясь под аркой, можно запросто перей...
Рецензия на SimCity
Рецензия на SimCity
Когда месяц назад состоялся релиз SimCity, по Сети прокатилось цунами народного гнева – глупые ош...
Рецензия на Strategy & Tactics: World War 2
Рецензия на Strategy &...
Название Strategy & Tactics: World War II вряд ли кому-то знакомо. Зато одного взгляда на ее скри...
Рецензия на игру Scribblenauts Unlimited
Рецензия на игру Scrib...
По сложившейся традиции в информационной карточке игры мы приводим в пример несколько похожих игр...
Рецензия на игру Walking Dead: Survival Instinct, The
Рецензия на игру Walki...
Зомби и продукция-по-лицензии — которые и сами по себе не лучшие представители игровой биосферы —...
Обратная связь | RSS | Донейт | Статистика | Команда | Техническая поддержка