【问题标题】:performance penalty of message passing as opposed to shared data与共享数据相比,消息传递的性能损失
【发布时间】:2010-12-21 02:03:24
【问题描述】:

现在有很多关于不使用锁和使用像 Erlang 这样的消息传递方法的讨论。或者关于在函数式编程与 C++/Java 中使用不可变数据结构。

但我关心的是以下几点:

  1. AFAIK,Erlang 不保证消息传递。消息可能会丢失。如果您不得不担心消息丢失,算法和代码会不会再次膨胀并变得复杂?无论您使用何种分布式算法,都不得依赖于有保证的消息传递。
  2. 如果消息是一个复杂的对象怎么办?复制和发送消息与将消息保存在共享位置(例如两个进程都可以访问的数据库)相比,是否存在巨大的性能损失?
  3. 您真的可以完全取消共享状态吗?我不这么认为。例如在数据库中,您必须访问和修改相同的记录。您不能在那里使用消息传递。您需要锁定或假设乐观并发控制机制,然后对错误进行回滚。 Mnesia 是如何工作的?
  4. 此外,您不必总是担心并发问题。任何项目也将有一大段代码,它们根本不需要对并发或事务做任何事情(但它们确实需要考虑性能和速度)。许多这些算法依赖于共享状态(这就是为什么传递引用或指针如此有用)。

鉴于这个事实,用 Erlang 等编写程序是一件痛苦的事情,因为您无法做任何这些事情。可能是,它使程序变得健壮,但对于解决线性规划问题或计算凸包等问题,性能更重要,当算法与并发/事务无关时,强制不变性等是一个糟糕的决定.不是吗?

【问题讨论】:

  • 在不可变数据结构的上下文中,“共享数据”与“消息传递”的成本取决于您使用的 VM。传递消息是否涉及复制或仅传递指针只是 VM 是如何实现的问题。 BEAM VM 被设计为具有每个进程的垃圾收集,这意味着消息传递是通过实际复制发送的数据来实现的。只是我的 2 个填充物。

标签: concurrency transactions erlang distributed-computing


【解决方案1】:
  1. 这就是现实:无论语言/平台如何,您都需要考虑这种可能性。在分布式世界(现实世界)中,事情失败了:忍受它。

  2. 当然是有代价的:在我们的宇宙中没有什么是免费的。但是你不应该使用另一种媒介(例如文件、数据库)而不是在通信管道中穿梭“大对象”吗?您始终可以使用“消息”来指代存储在某处的“大对象”。

  3. 当然不是:函数式编程/Erlang OTP 背后的理念是尽可能“隔离”被“共享状态”操纵的区域。此外,明确标记共享状态发生突变的位置有助于可测试性和可追溯性。

  4. 我相信你没有抓住重点:世上没有灵丹妙药。如果您的应用程序无法使用 Erlang 成功构建,请不要这样做。您始终可以以另一种方式将整个系统的其他部分,即使用不同的语言/平台。 Erlang 在这方面与其他语言没有什么不同:为正确的工作使用正确的工具

记住:Erlang 旨在帮助解决并发异步分布式问题。例如,它没有针对在共享内存块上高效工作进行优化...除非您计算与在游戏的共享块上工作的nif 函数的接口:-)

【讨论】:

  • 我想,我之前的 Q1 应该问的问题是:哪些分布式算法更容易设计?那些依赖共享状态的人?或者那些依赖消息传递的人。我相信为了同一个目标,基于这两种不同方法的算法将要求我们设计完全不同的算法。此外,如果 (1) 进行消息传递和 (2) 在可避免时不使用锁定是规则,那么我们不能在 C++/Java 中也这样做吗?为什么需要 erlang?
  • 我在这里很困惑:如果系统要求需要算法 X,那么问题是:“X 的分布式版本变体能否支持 X,比如 D(X)”。
  • 一种编程语言(至少是一种可用的)是一种“旅行机器”:它可以计算一切。当然,你可以做 Erlang 在 C/Java 中所做的一切(Erlang 诞生于 C!)。但这不是重点:Erlang 能否帮助我更快并且更有效地(时间和美元)达到我的目标。它总是归结为
  • X 的分布式版本变体能否支持 X,比如 D(X)?嗯,是的,有点。假设我想在一组节点之间进行分布式领导选举。在这种情况下,与使用数据库中的共享记录相比,设计一个使用消息来选举领导者的分布式算法需要更聪明的思考。
  • @Continued 。最重要的是,我还必须使分布式消息传递算法对消息丢失具有鲁棒性。使用共享状态,处理失败的 SQL 更新更容易处理。不是吗?还是您认为这只是经验问题?
【解决方案2】:

无论如何,现实世界的系统总是混合的:我不相信现代范式在实践中试图摆脱可变数据和共享状态。

然而,目标是不需要对这个共享状态进行并发访问。程序可以分为并发和顺序,并发部分使用消息传递和新范式。

并非每个代码都会获得相同的投资:人们担心线程从根本上“被认为是有害的”。像 Apache 这样的东西可能需要传统的并发线程,而像这样的关键技术可能会在几年内经过精心改进,这样它就可以在完全并发的共享状态下爆发。操作系统内核是另一个例子,“解决问题,不管它多么昂贵”可能是有意义的。

fast-but-broken 没有任何好处:但是对于新代码,或者没有引起太多关注的代码,它可能根本不是线程-安全,并且它不会处理真正的并发,因此相对“效率”是无关紧要的。一种方法有效,一种方法无效。

不要忘记可测试性: 另外,您对测试有什么价值?基于线程的共享内存并发根本无法测试。消息传递并发是。因此,现在您遇到了可以测试一种范式但不能测试另一种范式的情况。那么,知道代码已经过测试有什么价值呢?甚至不知道其他代码是否适用于所有情况的危险?

【讨论】:

    【解决方案3】:

    关于你对 Erlang 的误解的几点意见:

    • Erlang 保证消息不会丢失,并且它们会按照发送的顺序到达。 一个基本的错误情况是机器 A 无法与机器 B 通话。当这种情况发生时,进程监视器和链接将触发,并且系统节点关闭消息将被发送到为其注册的进程。什么都不会被默默地丢弃。进程将“崩溃”并且主管(如果有)会尝试重新启动它们。
    • 对象不能被改变,因此它们总是被复制。 确保不变性的一种方法是将值复制到其他 erlang 进程的堆中。另一种方法是在共享堆中分配对象,对它们进行消息引用,并且根本没有任何改变它们的操作。 Erlang 性能第一!如果您需要停止所有进程以对共享堆进行垃圾收集,那么实时性就会受到影响。询问 Java。
    • Erlang 中有共享状态。 Erlang 并不以此为荣,但它是务实的。一个例子是本地进程注册表,它是一个将名称映射到进程的全局映射,以便系统进程可以重新启动并声明其旧名称。 Erlang 只是尽可能避免共享状态。另一个示例是公开的 ETS 表。
    • 是的,有时 Erlang 太慢了。 这发生在所有语言中。有时Java太慢了。有时 C++ 太慢了。仅仅因为游戏中的一个紧密循环必须下降到汇编来启动一些严肃的基于 SIMD 的矢量数学,你不能推断一切都应该用汇编编写,因为它是唯一在重要时快速的语言。重要的是能够编写具有良好性能的系统,而 Erlang 管理得很好。查看 yaws 或 rabbitmq 的基准测试。

    你的事实不是关于 Erlang 的事实。即使您认为 Erlang 编程很痛苦,您也会发现其他人也因此而开发了一些很棒的软件。你应该尝试在 Erlang 中编写一个 IRC 服务器,或者其他非常并发的东西。即使你再也不会使用 Erlang,你也会学会以另一种方式考虑并发性。当然,你会的,因为 Erlang 非常简单。

    那些不了解 Erlang 的人注定要糟糕地重新实现它。

    好吧,原来是关于 Lisp 的,但是……它是真的!

    【讨论】:

      【解决方案4】:

      您的问题中有一些隐含的假设 - 您假设所有数据都可以拟合 在一台机器上,并且应用程序本质上本地化到一个地方。

      如果应用程序太大而无法在一台机器上容纳会发生什么?如果应用程序超出一台机器会怎样?

      如果应用程序适合在一台机器上并且 一种完全不同的编程方式,一旦它超过一台机器。

      如果你想创建一个容错的应用程序会发生什么?要使某些东西具有容错性,您至少需要两台物理上分开的机器并且不能共享。 当您谈论共享和数据库时,您会忽略诸如 mySQL 之类的东西 集群通过维护同步的副本精确地实现容错 物理分离的机器中的数据 - 有很多消息传递和 复制你在表面上看不到的东西——Erlang 只是暴露了这一点。

      您的编程方式不应突然改变以适应容错性和可扩展性。

      Erlang 主要是为构建容错应用程序而设计的。

      多核上的共享数据有其自身的一系列问题 - 当您访问共享数据时 您需要获取锁 - 如果您使用全局锁(最简单的方法),您最终可能会 在您访问共享数据时停止所有核心。多核上的共享数据访问 由于缓存问题可能会出现问题,如果内核具有本地数据缓存,那么访问“远处”数据(在某些其他处理器缓存中)可能会非常昂贵。

      许多问题本质上是分布的,数据永远不会在一个地方获得 同时,这类问题非常适合 Erlang 的思维方式。

      在分布式设置中,“保证消息传递”不可能 - 目标计算机可能已经崩溃。因此,Erlang 不能保证消息传递 - 它采用不同的方法 - 系统会告诉您它是否未能传递消息 (但前提是您使用了链接机制)-然后您可以编写自己的自定义错误 恢复。)

      对于纯数字运算 Erlang 是不合适的 - 但在混合系统中 Erlang 擅长管理如何将计算分配给可用的处理器,因此我们看到很多系统在其中 Erlang 管理问题的分配和容错方面,但问题本身是用不同的语言解决的。

      使用其他语言

      【讨论】:

        【解决方案5】:

        例如在数据库中,您必须访问和修改相同的记录

        但这由数据库处理。作为数据库的用户,您只需执行查询,数据库会确保它是独立执行的。

        就性能而言,消除共享状态的最重要的事情之一是它可以实现新的优化。共享状态并不是特别有效。内核会争夺相同的缓存行,并且必须将数据写入内存,否则它可能会保留在寄存器或 CPU 缓存中。

        许多编译器优化也依赖于没有副作用和共享状态。

        您可以说,与 C 之类的语言相比,保证这些事情的更严格的语言需要更多的优化才能提高性能,但它也使编译器更容易实现这些优化。

        许多类似于并发问题的问题出现在单线程代码中。现代 CPU 是流水线的,无序执行指令,每个周期可以运行 3-4 个指令。因此,即使在单线程程序中,编译器和 CPU 也必须能够确定哪些指令可以交错并并行执行。

        【讨论】:

          【解决方案6】:
          1. Erlang 为同步调用提供了 supervisor 和 gen_server 回调,所以如果消息没有传递,你就会知道:gen_server 调用返回超时,或者如果 supervisor 被触发,你的整个节点将被关闭和启动.
          2. 通常如果进程在同一个节点上,消息传递语言会优化数据复制,所以它几乎就像共享内存,除非对象在之后被两者使用,这也不能使用共享内存来完成反正
          3. 有一些状态由进程通过在递归尾调用中传递给它们自己来保持,当然也可以通过消息传递一些状态。我很少使用 mnesia,但它是一个事务性数据库,所以一旦你将操作传递给 mnesia(并且它已经返回),你几乎可以保证它会通过..
          4. 这就是为什么使用端口或驱动程序很容易将此类应用程序绑定到 erlang 的原因。最简单的是端口,它很像一个 unix 管道,虽然我认为性能不是那么好......正如所说,消息传递通常最终只是指针传递,因为 VM/编译器优化了内存复制.

          【讨论】:

            【解决方案7】:

            为了正确起见,共享是要走的路,并尽可能保持数据标准化。为了即时性,发送消息以通知更改,但始终通过轮询来备份它们。消息会被丢弃、重复、重新排序、延迟 - 不要依赖它们。

            如果您担心速度,请先使用单线程和tune the daylights out of it。然后,如果您有多个内核并且知道如何拆分工作,请使用并行性。

            【讨论】:

            • 我正在做一个项目,几年前有人实现了单线程并调整了白天。现在我们想将它移植到 CUDA 中,一次可能有几千个线程。直接移植是不可能的,我们不得不从头开始重写,因为“调整”使代码完全无法使用。我认为这不是解决任何并发问题的好方法。
            • @Andrew:可悲的是,调优可能做得很糟糕。我不认为调优必须使代码无法维护或变得邪恶。我仍然认为没有充分的理由任何给定的线程应该做一些不是真正必要的事情。而且我看到太多代码主要依赖于消息传递,然后由于难以确保消息传递架构完全可靠,因此从来没有完全没有问题。
            • 我同意调优可能做得很糟糕,我不是反对你的人,因为有时候调优是正确的方法。我只是认为,如果您真的受到性能限制,那么如果您的平台合理,则应该首先考虑并行性。诚然,这是我个人的偏见,我更喜欢尽可能干净的并发代码,而不是经过大量调整的串行代码。我还认为,如果您的算法可以很好地并行化,这是一种更容易看到大幅加速的方法,但这是针对特定问题的考虑,在某些情况下,调整可能是唯一的方法。
            • @Andrew:对我来说听起来像是常识。我不认为它是非此即彼的,我相信你也不会。只是,如果单线程代码非常复杂,通常可以通过难以置信的因素加速它,比如 40 (stackoverflow.com/questions/926266/…),而无需特别修改它。然后,如果您可以通过并行性获得另一个因素,那就更好了。
            猜你喜欢
            • 2012-06-01
            • 1970-01-01
            • 2010-12-20
            • 2011-02-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-12-03
            相关资源
            最近更新 更多