понедельник, 27 апреля 2009 г.

Открытие сайта и анонс проекта MMT Online

Думаю, многим будет интересна причина малой активности в этом блоге за последние полгода. Как ни странно, заключается она не в потере интереса, а в активной работе над собственным игровым проектом.
Наш сайт был введен в действие несколько дней назад (http://runserver.net) и сегодня команда RunServer оффициально анонсирует проект MMT Online. Детальне читайте в новостях на сайте: http://runserver.net/ru/mmt/19-mmt-anounce


В двух словах расскажу, почему проект называется "демонстрационным".
Основная цель проекта - показать возможности нашей серверной платформы, да и нашей команды в целом. Это означает, что все усилия направлены на разработку одной-двух локаций и показ как можно большего количества серверных возможностей. Мы пока не планируем сотни континентов, различные расы, десятки подземелий и пр.
С другой стороны, наш гейм-дизайнер Ипполит придумал достаточно необычный мир (точнее два мира) и было бы интересно реализовать его идеи в будующем. Возможно, ситуация изменится, если будет найден инвестор.

Немного истории.
В гейм-деве мы достаточно давно (с 2004го года как минимум), но основным направлением была серверная часть, хотя и были эксперименты над клиентской (замороженный ныне проект Heroes Offline, различные аддоны к WoW, наработки на Ogre 3D и пр.). Не смотря на это, первый наш блин в 3D игростроении вышел, как водится, комом - проект на движке Unigine (http://unigine.com) стартовал в начале 2007го года, но через полгода был заморожен и закрыт. Сказался недостаток опыта и контента.
В конце 2007го года фирма МСМ Онлайн (дочерняя компания провайдера WNet) начала разработку проекта Syndicate Online (http://syndicate-online.com) c использованием платформы RunServer и понемногу начала снабжать нас 3д контентом. Это позволило осенью 2008го года начать разработку собственной игры. Движок взяли тот же - Unigine, но программную часть написали заново. Так и родился MMT Online. К предоставленному высококачественному контенту мы начали дорисовывать своими силами собственные поделия. Местами это смотрится хорошо, местами - убого :)
Надеюсь, участие в КРИ 2009 покажет, насколько мы продвинулись в нашей цели, и получилось ли привлечь внимание к RunServer и нашей команде.

Дальше..

четверг, 11 декабря 2008 г.

Поддержка Mono

Сегодня мы начали работать над возобновлением поддержки Mono (http://www.mono-project.com/) платформой RunServer. Причин для этого несколько, но самая первая из них - кросс-платформенность. Три года назад такая мысль меня уже посещала и результат был неутешителен: на Mono 1.14 мы получали примерно десятикратное падение производительности по сравнению с Microsoft .Net Framework 2.0 в RunWoW. Сейчас же различные источники (например этот) сообщают, что скорость Mono вплотную приблизилась к показателям .Net.

После некоторых упражнений с напильником и бубном, RunWoW запустился, но самый первый процесс - загрузка данных из БД - занял около 10 минут против 2х минут в .Net. Вывод напрашивался сам собой, но все-же стало интеерсно, откуда такая существенная разница в скорости.


Первые же тесты показали, что при загрузке небольших таблиц скорость практически идентична, но чем больше возвращается данных, тем больше разрыв. К примеру, 2 и 5 секунд при загрузке 4000 записей против 5 и 20 секунд при загрузке 40000.

Дополнительные проверки показали следующее:
- реализация System.Data.SqlClient в Mono приблизительно на 40% медленнее, чем в . Net;
- время создания обьекта отличается в Mono и .Net на доли процента;
- различные коллекции (связные списки, словари, группы массивов, да и просто обычные списки) на время загрузки данных из БД не влияют, т.к. время выборки, приведения типов, да и вцелом обработки загруженных данных, на порядок больше времени, которое тратится на перебор коллекций;
- в случае, если грузится больше N элементов, выполняется не обычный select * from <..> where <..>, а просто select * from <..> и затем результат фильтруется;

Последняя особенность привлекла мое внимание. С одной стороны, все корректно - я сам писал этот код и отлаживал на различных БД, сравнивая производительность такого решения. С другой стороны оказалось, что если убрать проверку и никогда не загружать все элементы, то разрыв в скорости на Mono и .Net уменьшается до 30-40%.
Копнув чуть глубже я нашел, что сама фильтрация результатов делается не очень оптимально: есть некий массив с ID, которые должны быть в результируещем списке и для каждого элемента таблицы выполняется проверка Array.IndexOf(id) != -1.
Этот метод имеет право на жизнь, если сам IndexOf базируется на каком-нибудь оптимизированном алгоритме (хотя бы на двоичном поиске), но совершенно неприемлим в случае последовательного перебора.

Докопаться до истины стало делом принципа. Я нашел реализацию IndexOf в Mono:

public static int IndexOf (Array array, object value, int startIndex, int count)
{
if (array == null)
throw new ArgumentNullException ("array");

if (array.Rank > 1)
throw new RankException (Locale.GetText ("Only single dimension arrays are supported."));

// re-ordered to avoid possible integer overflow
if (count < 0 || startIndex < array.GetLowerBound (0) || startIndex - 1 > array.GetUpperBound (0) - count)
throw new ArgumentOutOfRangeException ();

int max = startIndex + count;
for (int i = startIndex; i < max; i++) {
if (Object.Equals (value, array.GetValueImpl (i)))
return i;
}

return array.GetLowerBound (0) - 1;
}


Как мы видим, тут имеет место последовательный перебор. Я не стал искать реализацию этого метода в .Net, но подозреваю, что он вызывает Array.BinarySearch, что делает его в разы быстрее. Как бы там ни было, если необходимо проверить наличие записи в коллекции, самым быстрым вариантом является использование Dictionary<>, на котором я и остановился. Результат достаточно приемлим: сервер на Mono потребляет немного больше памяти и загружается на ~40% медленее. К тому же, после отказа от Array.IndexOf и .Net версия стала грузиться быстрее на пару десятков секунд.

Вывод у меня лишь один: Premature optimization is the root of all evil.

Дальше..

среда, 10 декабря 2008 г.

Баг со структурами и readonly

Сегодня столкнулся с любобытнейшим багом в C#. Вполне возможно, что это не баг, а предусмотренное поведение, но выглядит оно необычно.
В двух словах: readonly переменная изменяема только в конструкторе класса и даже если конструктор вызовет метод, изменяющий ее - эти изменения будут отброшены.


Рассмотрим такой код с вспомогательной структурой и классом.

public struct TestStruct
{
private int m_count;
private int m_value;

public int Count
{
get { return m_count; }
}

public int Value
{
get { return m_value; }
}

public TestStruct(int value)
{
m_value = value;
m_count = 0;
}

public void Increment()
{
m_count++;
}
}

public class TestClass
{
private TestStruct m_struct;

public int Value
{
get { return m_struct.Value; }
}

public int Count
{
get { return m_struct.Count; }
}

public TestClass(int value)
{
m_struct = new TestStruct(value);

for (int i = 0; i < value; i++)
Increment();
}

private void Increment()
{
m_struct.Increment();
}
}


Если создать экземпляр TestClass с каким-либо числом, то значения Value и Count будут равны этому числу. Вполне нормальное и логичное поведение.
Картина меняется, если мы добавим слово readonly:

private readonly TestStruct m_struct;


После этого изменения код

TestClass test = new TestClass(11);
Console.WriteLine("Test result: value {0}, count {1}",
test.Value, test.Count);

выдает такой результат:

Test result: value 11, count 0

Верно такое поведение или нет?
Мы знаем, что структуры являются value-type и для каждого члена структуры, вложенной в класс, память выделяется в самом классе. Потому логично предположить, что readonly распостраняется и на члены структуры. Неприятно, но компилятор нам об этом не сообщает и никак не предупреждает, что этот модификатор приведет к потере данных.
Более того, логично было бы предположить, что если метод Increment() обьявлен как private и используется только в конструкторе, то метод будет inline и на него будут распостранятся те же правила, что и для конструктора. К сожалению, это предположение не оправдывается и нам просто надо помнить, что вызов методов в конструкторе может привести к "необычным" последствиям, не говоря о том, что может быть при вызове виртуальных методов.

Дальше..

четверг, 13 ноября 2008 г.

Знание языка

Прошел тест на знание русского языка.

Я прошел "Тест на определение словарного запаса"



ВАШ СЛОВАРНЫЙ ЗАПАС - Результаты теста
Ваш словарный запас на очень высоком уровне! Превосходный результат! Вы правильно ответили на 32 вопроса из 35! Поздравляем!
Пройти "Тест на определение словарного запаса" здесь

Дальше..