[memory] 共享内存与消息传递如何处理大型数据结构?



Answers

需要注意的一点是,Erlang并发模型并没有真正指定消息中的数据必须在进程之间复制,它指出发送消息是唯一的通信方式,并且没有共享状态。 由于所有数据都是不可变的,这基础,因此实现很可能不会复制数据,而只是发送对它的引用。 或者可以使用两种方法的组合。 与往常一样,没有最佳解决方案,在选择如何做时需要权衡利弊。

BEAM使用复制,除了发送引用的大型二进制文件。

Question

在查看Go和Erlang的并发方法时,我注意到它们都依赖于消息传递。

这种方法显然减轻了对复杂锁的需求,因为没有共享状态。

但是,请考虑许多客户端希望对内存中的单个大型数据结构进行并行只读访问的情况 - 如后缀数组。

我的问题:

  • 使用共享状态会比消息传递更快并且使用更少的内存,因为锁是大多数不必要的,因为数据是只读的,只需要存在于一个位置?

  • 如何在消息传递上下文中处理此问题? 是否有一个访问数据结构的进程,客户端只需要顺序请求数据? 或者,如果可能的话,数据是否会被分块以创建几个保存块的进程?

  • 鉴于现代CPU和内存的架构,两种解决方案之间是否存在很大差异 - 即,共享内存可以由多个内核并行读取 - 这意味着没有硬件瓶颈可能会使两个实现大致执行相同的操作?




大多数现代处理器使用MESI协议的变体。 由于共享状态,在不同线程之间传递只读数据非常便宜。 但是,修改后的共享数据非常昂贵,因为存储此缓存行的所有其他缓存必须使其无效。

因此,如果您具有只读数据,则在线程之间共享它而不是使用消息进行复制非常便宜。 如果您具有读取主要数据,则在线程之间共享可能会很昂贵,部分原因是需要同步访问,部分原因是写入破坏了共享数据的缓存友好行为。

不可变数据结构在这里是有益的。 您只需创建一个共享大部分旧数据的新数据,而不是更改实际的数据结构,但需要更改您需要更改的内容。 共享它的单个版本很便宜,因为所有数据都是不可变的,但您仍然可以有效地更新到新版本。




这里没有提出的一个解决方案是主从复制。 如果您具有大型数据结构,则可以将更改复制到对其副本执行更新的所有从属服务器。

如果想要扩展到几台甚至无法在没有非常人为的设置的情况下共享内存的机器(从远程计算机的内存中读取/写入的块设备的mmap?),这一点尤其有用。

它的一个变体是有一个事务管理器,一个人很好地请求更新复制的数据结构,它将确保它同时提供一个并且只提供更新请求。 这更像是用于mnesia table-data的主 - 主复制的mnesia模型,它被称为“大数据结构”。







目前的问题确实是锁定和缓存线一致性可能与复制更简单的数据结构(例如几百个字节)一样昂贵。

大多数时候,一个巧妙编写的新的多线程算法试图消除大部分锁定总是会更快 - 而且现代无锁数据结构要快得多。 特别是当您拥有设计良好的缓存系统,如Sun的Niagara芯片级多线程。

如果您的系统/问题不容易分解为一些简单的数据访问,那么您就遇到了问题。 并非所有问题都可以通过消息传递来解决。 这就是为什么还有一些基于Itanium的超级计算机的销售,因为它们拥有TB的共享RAM和最多128个CPU在同一个共享内存上运行。 它们比具有相同CPU功率的主流x86群集贵一个数量级,但您不需要分解数据。

到目前为止未提及的另一个原因是,当您使用多线程时,程序可以更容易编写和维护。 消息传递和无共享方法使其更易于维护。

作为一个例子,Erlang从未被设计为使事情更快,而是使用大量线程来构建复杂的数据和事件流。

我想这是设计中的主要观点之一。 在谷歌的网络世界中,你通常不关心性能 - 只要它可以在云中并行运行。 通过消息传递,理想情况下可以添加更多计算机而无需更改源代码。






Related