Неизменяемые функциональные объекты в сильно изменяемой области

В настоящее время я изучаю функциональное программирование в свое свободное время в Scala, и у меня возник вопрос в новичке.

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

Но я видел блог, где у кого-то была маленькая игра в качестве примера демонстрации неизменности. Если объект существа получал урон, он не изменял свое состояние - он возвращал новый объект существа с новыми хитпоинтами и новым флагом "агро к X". Но если бы мы разработали что-то вроде MMORPG, говорят в World of Warcraft. Сотня игроков на поле битвы... возможно, тысячи атак и эффекты заклинаний "бафф / дебафф", влияющие на них по-разному. Можно ли по-прежнему проектировать систему с полностью неизменными объектами? Мне кажется, что в каждом "тике" будет огромное количество новых экземпляров. И чтобы получить действующий на данный момент экземпляр объектов, всем клиентам постоянно приходится проходить через какой-то центральный объект "игровой мир", или?

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

8 ответов

Решение

Мне кажется, что в каждом "тике" будет огромное количество новых экземпляров.

Действительно, это так. У меня есть приложение на Haskell, которое читает канал рыночных данных (около пяти миллионов сообщений в течение шестичасового торгового дня для данных, которые нас интересуют) и поддерживает "текущее состояние" для различных вещей, таких как самые последние цены и количества предложений и предложений для инструментов, то, насколько наша модель соответствует рынку и т. д. и т. д. Довольно страшно моделировать прогон этой программы с записанным фидом в режиме профилирования и смотреть, как он распределяется и GC приближается к 288 ТБ памяти (или почти в 50000 раз больше оперативной памяти моей машины) за первые 500 секунд работы. (Эта цифра была бы значительно выше без профилирования, поскольку профилирование не только замедляет работу приложения, но и заставляет его все работать на одном ядре.)

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

Обычно в функциональном программировании у вас не будет конструкторов в стиле C++. Затем, даже если концептуально вы создаете объекты постоянно, это не означает, что компилятор должен создавать код для выделения нового объекта, потому что это не может повлиять на поведение программы. Поскольку данные являются неизменяемыми, компилятор может видеть, какие значения вы только что указали, а какие были переданы в ваши функции.

Затем компилятор может создать действительно сжатый скомпилированный код, который просто вычисляет поля в определенных объектах, когда они необходимы. Насколько хорошо это работает, зависит от качества компилятора, который вы используете. Тем не менее, чистый функциональный программный код сообщает компилятору гораздо больше о вашем коде, чем может предположить компилятор C для аналогичной программы, и поэтому хороший компилятор может генерировать код лучше, чем вы ожидаете.

Так что, по крайней мере, в теории нет причин для беспокойства; Реализации функционального программирования могут масштабироваться так же, как и реализации объектно-ориентированной кучи. На практике вам нужно понимать качество языковой реализации, с которой вы работаете.

MMORPG уже является примером неизменности. Поскольку игра распространяется по серверам и игровым системам, здесь абсолютно нет центрального объекта "игровой мир". Таким образом, любой объект, который отправляется по проводам, является неизменным - потому что он не изменяется получателем. Вместо этого новый объект или сообщение отправляется в качестве ответа, если таковой имеется.

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

Например, вы играете в Command & Conquer. Ваш гигантский танк сидит в режиме готовности, охраняя вашу базу. Ваш противник приближается с легким танком, чтобы исследовать вашу базу. Ваш гигантский танк стреляет и поражает танк противника, нанося урон.

Эта игра довольно проста, поэтому я подозреваю, что многое вычисляется локально, когда это возможно. Предположим, что компьютеры двух игроков изначально синхронизированы с точки зрения состояния игры. Затем ваш противник нажимает, чтобы переместить свой легкий танк в вашу базу. Сообщение (неизменяемое) отправляется вам по проводам. Поскольку алгоритм перемещения танка (вероятно) является детерминированным, ваша копия Command & Conquer может перемещать танк противника на экране, обновляя состояние игры (может быть неизменным или изменяемым). Когда легкий танк попадает в зону действия вашего гигантского танка, ваш танк стреляет. На сервере генерируется случайное значение (в этом случае в качестве сервера произвольно выбирается один компьютер), чтобы определить, попал ли удар по вашему противнику или нет. Если предположить, что танк был поражен и необходимо обновить танк противника, то по синхронизации по играм отправляется только разница - тот факт, что новый уровень брони танка уменьшен до 22% - передается по проводам. Это сообщение является неизменным.

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

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

Следует обратить внимание на неизменность того, что (при правильной реализации) создание объекта становится относительно легким. Если поле является неизменным, оно может быть разделено между экземплярами.

При разработке функциональной программы важно учитывать, что, как вы заявляете, неизменяемые объекты будут иметь некоторые накладные расходы. Также важно помнить, что если объекты в вашей MMORPG-программе будут неизменными, это по своей природе будет более масштабируемым. Таким образом, первоначальные инвестиции в оборудование могут быть выше, но в будущем, по мере развития событий, вы сможете масштабироваться до своей базы игроков.

Еще одна важная вещь, которую стоит учесть, это то, что сейчас самые мощные машины имеют 6 ядер на процессор. Рассмотрим машину с двумя процессорами по 6 ядер. Одно из этих 12 ядер может выполнять сборку мусора, поэтому накладные расходы на снос большого количества объектов могут быть компенсированы за счет того, что приложение легко масштабируется до остальных 11 ядер.

Также помните, что не каждый объект (и его дочерние объекты) необходимо полностью перестроить на копии. Любой тип ссылки, который не изменился, будет иметь только одно назначение ссылки, когда объект "скопирован".

Сегодня я нашел блог, в котором ТОЛЬКО рассматриваются вопросы, которые я поднял в этом посте:

http://prog21.dadgum.com/23.html

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

У Эрика Липперта есть несколько интересных постов на тему неизменности, и они довольно интересное чтение.

Другие вопросы по тегам