高度可变域中的不可变功能对象
我目前正在业余时间在Scala中学习函数式编程,但我有一个闲杂的新手问题。
当执行诸如计算Haar小波变换之类的事情时,即当对象本身表示的数据本身不变时,我可以看到拥有不可变对象的优雅。
但是我看到一个博客,其中有人在演示不变性时以一个小游戏为例。如果某个生物对象受到了伤害,它不会改变其状态,而是返回一个具有新生命值和新"朝X凝集"标志的新生物对象。但是,如果我们要设计类似MMORPG的游戏,《魔兽世界》说。一百名玩家在战场上……可能有成千上万的攻击和抛光/减益法术效果以不同的方式影响着他们。仍然可以用完全不变的对象设计系统吗?在我看来,似乎每个"勾号"都有大量新实例。为了获得对象的当前有效实例,所有客户端都将不得不不断地通过某种中央的"游戏世界"对象,或者?
函数式编程是否为此而扩展,或者这是"最佳工作的最佳工具,在这里可能并非一成不变"?
解决方案
就像编程中的几乎所有工具一样,不可变对象功能强大,但是在错误的情况下很危险。我认为游戏示例不是一个很好的例子,或者至少不是非常人为的例子。
埃里克·利珀特(Eric Lippert)有一些关于不变性的有趣文章,它们读起来很有趣。
MMORPG已经是不变性的一个例子。由于游戏分布在服务器和游戏者的系统之间,因此绝对没有中央的"游戏世界"对象。因此,通过电线发送的任何对象都是不可变的,因为接收者不会更改它。相反,如果有新对象或者消息,则将其作为响应发送。
我从来没有写过分布式游戏,所以我不知道它们是如何实现的,但是我怀疑对象的更新或者是本地计算的,或者是以差异形式发送的。
例如,我们在玩"命令与征服"。猛tank坦克正处于准备就绪状态,守卫着基地。对手带着轻型坦克接近以探索基地。猛oth坦克射击并击中对手的坦克,造成伤害。
这款游戏非常简单,因此我怀疑尽可能在本地进行大量计算。假设最初两个玩家的计算机在游戏状态方面是同步的。然后,对手单击以将其轻型坦克移入基地。一条消息(不可变)通过网络发送给我们。由于移动坦克的算法是(确定的)确定性的,因此,"命令与征服"的副本可以在屏幕上移动对手的坦克,从而更新游戏状态(可能不可变或者可变)。当轻型坦克进入猛ma坦克的射程时,坦克就会开火。服务器上会生成一个随机值(在这种情况下,服务器是任意选择一台计算机),以确定镜头是否击中了对手。假设坦克被击中,并且必须对对手的坦克进行更新,则仅当坦克的新装甲水平已降低至22%时才通过电线发送,以同步两个玩家的游戏。此消息是不可变的。
玩家的计算机上代表坦克的物体是可变的还是不可变的无关紧要;可以采用任何一种方式来实现。每个玩家都不会直接更改其他玩家的游戏状态。
关于不变性的一点要注意的是(如果正确实现的话)它使对象创建相对轻巧。如果字段是不可变的,则可以在实例之间共享它。
在设计功能程序时,请务必考虑到像我们声明的那样,不可变对象会产生一些开销。同样重要的是要记住,通过使MMORPG程序中的对象不可变,其本质上将具有更大的可扩展性。因此,设备的初始投资可能会更高,但是随着事情的发展,我们将能够扩展到玩家群。
另一个要考虑的重要问题是,目前最强大的计算机每个cpu具有6个内核。考虑一个双cpu机器,每个机器有6个内核。这12个内核之一可以进行垃圾回收,因此拆解大量对象的开销可以由应用程序轻松扩展到其他11个内核来抵消。
还请记住,并非每个对象(及其子对象)都需要在副本上完全重建。当对象被"复制"时,任何不变的引用类型将只接受一个引用分配。
通常,在函数式编程中,我们将没有C ++样式的构造函数。然后,即使从概念上讲我们一直在创建对象,但这并不意味着编译器必须编写代码来分配新对象,因为它不会影响程序的行为。由于数据是不可变的,因此编译器可以看到我们刚刚指定的值以及传递给函数的值。
然后,编译器可以创建非常紧密的编译代码,仅在需要时才计算特定对象中的字段。效果如何取决于我们使用的编译器的质量。但是,干净的函数式编程代码比类似程序的C编译器所能假定的要多得多,可以告诉编译器更多有关代码的信息,因此,好的编译器可能会生成比我们期望的更好的代码。
因此,至少在理论上,没有理由要担心。功能性编程实现可以像面向对象的堆分配实现一样扩展。在实践中,我们需要了解正在使用的语言实现的质量。
不要在连线级别上考虑对象的创建。例如,针对功能语言的优化运行时可能会"作弊",涉及替换对象和现有结构的实际do突变,如果它不知道将引用原始结构,而新结构将完全替换原始结构。想想尾递归优化,但是它适用于对象状态。
To me it would seem like there would be a ginormous swarm of new instances each 'tick'.
确实是这样。我有一个Haskell应用程序,该应用程序读取市场数据供稿(在六个小时的交易日中,大约有500万条消息用于我们感兴趣的数据),并为诸如仪器的最新出价和要约价格和数量,我们的模型如何适应市场等等。这非常令人恐惧,无法以配置文件模式记录的提要来模拟该程序的运行,并观察其分配和GC接近288在其运行的前500秒钟内,达到TB的内存(或者接近我的计算机RAM的50,000倍)。 (如果不进行概要分析,则该数字会高得多,因为进行概要分析不仅会减慢应用程序的速度,而且还会迫使它们全部运行在一个内核上。)
但是请记住,纯语言实现中的垃圾收集器已针对此类行为进行了优化。我对我的应用程序的整体速度非常满意,并且我认为它的要求很高,因为我们必须每秒从市场供稿中解析数百条消息,进行一些相当广泛的计算以建立模型,并使用该模型模型以生成订单以尽快到达交易所。
我今天找到了一个博客,该博客完全解决了我在这篇文章中提出的问题:
http://prog21.dadgum.com/23.html