来自 Flat Concurrent Prolog 的 Logix 实现的一个想法可以被应用于 Erlang:对用户不可见的是,可以有两种 '原子' 的实现,解决一个主要的系统完整性问题,并消除为了绕过它而扭曲数据结构设计的需要。
Erlang 语言或库没有用户可见的更改。Erlang 和其他语言(如 C)之间的接口可能需要更改。
我们将原子分为两类:“全局”原子是那些出现在某些已加载模块的后预处理文本中的原子,或者是任何进程的注册名称;“局部”原子是进程创建的所有其他原子。
局部原子由 SUCH AS 的数据结构表示
+----------+
| size+tag | boxed object header; see below
+----------+
| hashcode | a 32-bit hash code
+----------+
| equivrep | points to Union/Find representative
+----------+
| bytes of |
| name ... |
+----------+
像往常一样,大小+标签包含一个 2 位标签,表示它是一个 IMMED2 对象,一个 4 位子标签表示其类型(我建议使用 1011),以及一个 26 位元数。但是,元数字段分为两个子字段
+--------------+------------+----+--+
| byte count | char count |LATM|BX|
+--------------+------------+----+--+
14 12 4 2 size in bits
字符计数表示名称中有多少 Unicode 字符。字节计数表示这些字符存储在多少字节中。为了紧凑性和向后兼容性,名称仅由 Latin-1 字符组成的原子具有字节计数 = 字符计数,名称表示为 Latin-1;名称超出该范围的原子以其他形式存储,例如 UTF-8、SCSU、BOCU 或其他形式。此提案不是专门关于编码方案的;我在这里要说的是,它对于所有原子都应该相同,并且它应该至少和 UTF-8 一样好。
哈希码字段是一个 32 位哈希码。同样,我对于原子哈希本身没有什么可说的,只是要说该方法对于节点上所有进程中的所有原子都应相同,并且它应该是一个好的方法。关于良好哈希函数的建议很难找到。hashpjw()
可以改进。我衷心推荐 Valloud 的书。
equivrep 字段是一个指针。它始终指向一个原子,该原子可能是全局原子或局部原子。最初,它指向局部原子本身。当一个局部原子与另一个局部原子进行比较时,
但这与 Union/Find 结合使用,非常像 Prolog 中的绑定变量。因此,我们在第二步之后“解引用”(追逐 equivrep 字段),如果我们最终到达同一位置,则两个局部原子相等。如果两个物理上不同的局部原子确实相等,我们会使较新的那个(最近创建的那个)指向较旧的那个。
全局原子应具有类似的表示形式;我建议将局部原子的表示形式嵌入到全局原子的表示形式中,以便可以将局部原子与全局原子进行比较,就像它们都是局部原子一样。
list_to_existing_atom/1
返回的原子始终是全局原子。list_to_atom/1
或 binary_to_term/1
返回的原子当且仅当它们是已经存在的全局原子时才是全局原子,否则它们是局部原子。
提供给其他语言(如 C 或 Java)的接口应保留现有的原子创建操作返回全局原子,并应添加用于创建局部原子操作。
当进程被垃圾回收时,指向局部原子的指针将被该局部原子的 equivrep 替换,以便曾经注意到它们有重复局部原子的进程不会永远保留它们。
存在许多限制 Erlang 原子用途的问题。
第一个是原子大小限制为 255 个字节,这使得 Erlang 原子对于文件名几乎没有用处,因为现在 C 的 FILENAME_MAX
通常为 1024。
第二个是原子限制为 Latin-1 字符。我们确实希望为它们提供完整的 Unicode 支持,这不仅是为了让程序员在源代码中使用奇怪的脚本编写原子,而且是为了允许信息以原子的形式通过 Erlang 系统流动。
这两个是次要问题。
主要问题是原子表。
它是一个全局资源,这意味着在 SMP 系统上必须进行大量的锁定和解锁。此提案不包括新的“始终返回局部原子”操作,但它为需要无锁的新操作创造了可能性。
在 atom.c 中,原子表限制为 ATOM_LIMIT=1024*1024
个条目。即使在 32 位系统上,这也小于机器可以支持的条目数;这是一个任意的限制,而这种限制始终是一个问题。
原子表不会进行垃圾回收。一旦创建了一个原子,它就表示已创建。像 Quintus Prolog 这样的历史 Prolog 系统也做了同样的事情。早在 1984 年,这就被认为是一个问题,特别是对于那些想要访问大量存储数据的程序。像 SWI Prolog 这样的现代 Prolog 系统确实会收集原子;如果不是这样,SWI Prolog 在操作大量 RDF 数据时就不会那么有用。此提案不会为原子表添加垃圾回收;它所做的是阻止大多数将被收集的原子首先进入该表。
填满原子表会使整个节点崩溃或挂起。
这意味着通过向 Erlang 软件输入过多的原子,很容易使 Erlang 软件崩溃或哈希。
而这意味着想要在数据结构中使用原子(例如,作为字典中的键)的 Erlang 程序员改为使用二进制文件:二进制文件在大小或数量上没有限制,如果需要,可以保存 UTF-8,可以进行垃圾回收,并且通常更安全使用。
尽管此提案使原子更方便使用(它们可以更长、更多,并且可以包含 Unicode),但真正的重点是使原子更安全使用。如果您可以从源流式传输数据通过 Erlang 进程,将外部“字符串”映射到二进制文件,那么您将能够安全地将其映射到原子。
Erlang 不是第一个面临这些问题的语言。它甚至不是第一个面临这些问题的并发语言。Flat Concurrent Prolog 是第一个,虽然我没有看过 Logix 源代码,但这个想法在多年前的 Logix 文档中进行了解释。我知道这是可以工作的,因为它确实奏效了。
Logix 对所有原子都使用了这种方法;最终,我相信 Erlang 也需要这样做,以便在没有大量锁的情况下处理数千个处理器。现在,对于相当“静态”的原子,继续使用旧的表示形式是有意义的。特别是,我们希望模块和函数名称(以及我们拥有帧键时)保持现在的样子。如果在创建局部原子之后加载应用程序,我们可能会发现它毕竟是模块名称或函数名称;这是使用 equivrep 字段的原因之一。一旦注意到,重复就不会在另一次垃圾回收中幸存下来。
当前的“全局原子”表示形式有一个技巧可以加快术语比较。为了简单起见,我没有在上面描述它,因为它与此 EEP 关注的问题是正交的。我注意到 (a) 为了使 ord0 字段继续保持其当前形式,编码最好是 UTF-8 或 BOCU,并且 (b) 为了保持 Latin-1 原子的紧凑性,ord0 字段应该是如果原子存储在所选的 UTF-8 或 BOCU 中,则本来会存储的前 31 位。我还注意到 (c) 如果您不允许“原生”字节顺序决定存储原子名称的字节顺序,则您不需要特殊的 ord0 字段。
我应该承认,这个提案并不能完全避免崩溃和挂起的问题。如果可以劝说 Erlang 系统从不可信的来源加载模块,仍然可以使其尝试创建足够的原子来陷入困境。这就是我认为 Erlang 最终将不得不放弃全局原子表的原因之一。但是,任何加载模块的人
来自不可信的来源的人应该知道他们正在这样做;这样做显然很危险。list_to_atom/1
不是一个明显危险的函数,它不应该比 list_to_binary/1
更危险。
不应影响任何现有代码(Erlang 实现之外)。
无。这个改变在概念上很简单,但会影响系统核心中的几个原子。
本文档已放入公共领域。