如何在 Elixir / Erlang 中通过指针相等比较两个结构

How to compare two structures via pointer equality in Elixir / Erlang

(Elixir 中给出的示例。)

假设我有以下代码,

x = {1, 2}
a1 = {"a", {1, 2}}
a2 = {"a", {1, 2}}
a3 = {"a", x}

据我所知,它在不同的内存位置创建了三个元组 {1, 2}

使用运算符 ===== 来比较任何 a 变量总是 returns true。这是意料之中的,因为这两个运算符仅在比较数字类型时有所不同(即 1 == 1.01 === 1.0 不同)。

所以,然后我尝试通过模式匹配比较结构,使用以下模块(严格创建以测试我的案例),

defmodule Test do
  def same?({x, y}, {x, y}), do: true
  def same?(_, _), do: false
end

但也调用 Test.same?(a1, a3) returns true.

如何使用指针相等性来比较两个结构,以便确定它们在内存中是否是相同的结构?

谢谢

as far as I know creates three tuples {1, 2} at different memory locations.

不,那是不正确的。 Erlang VM 足够智能,可以创建单个元组并引用它。

值得一提的是,这是可能的,因为一切都是不可变的。

此外,如果您发现自己完成了上述任务,那您就大错特错了。

没有 "official" 方法可以做到这一点,我想说的是,如果你认为你确实 需要 这样做,那你就做错了,应该再问一个问题,关于如何实现你想要实现的目标。所以这个答案是本着好玩和探索的精神提供的,希望它能传播一些关于 Erlang/Elixir VM 的有趣知识。


有一个函数,erts_debug:size/1,它告诉你"words"一个Erlang/Elixir项占用了多少内存。 This table 告诉您各种术语使用了多少个单词。特别是,一个元组使用 1 个单词,每个元素加上 1 个单词,再加上 space 的存储 space 任何 "non-immediate" 的元素。我们使用小整数作为元素,它们是 "immediates",因此是 "free"。所以这检查出来:

> :erts_debug.size({1,2})
3

现在让我们创建一个包含其中两个元组的元组:

> :erts_debug.size({{1,2}, {1,2}})
9

有道理:两个内元组各3个词,外元组1+2个词,共9个词。

但是如果我们把内部元组放在一个变量中呢?

> x = {1, 2}
{1, 2}
> :erts_debug.size({x, x})
6

看,我们省了3个字!那是因为 x 的内容只计算一次;外部元组两次指向同一个内部元组。

所以让我们写一个小函数来为我们做这个:

defmodule Test do
  def same?(a, b) do
    a_size = :erts_debug.size(a)
    b_size = :erts_debug.size(b)
    # Three words for the outer tuple; everything else is shared
    a_size == b_size and :erts_debug.size({a,b}) == a_size + 3
  end
end

系统正常吗?似乎是:

> Test.same? x, {1,2}
false
> Test.same? x, x
true

目标完成!


但是,假设我们试图从已编译模块中的另一个函数调用此函数,而不是从 iex shell:

  def try_it() do
    x = {1, 2}
    a1 = {"a", {1, 2}}
    a2 = {"a", {1, 2}}
    a3 = {"a", x}

    IO.puts "a1 and a2 same? #{same?(a1,a2)}"
    IO.puts "a1 and a3 same? #{same?(a1,a3)}"
    IO.puts "a3 and a2 same? #{same?(a3,a2)}"
  end

打印:

> Test.try_it
a1 and a2 same? true
a1 and a3 same? true
a3 and a2 same? true

那是因为编译器足够聪明,可以看出这些文字是相等的,并在编译时将它们合并为一个项。


请注意,当术语被发送到另一个进程,或存储在 ETS 中/从 ETS table 中检索时,这种术语共享就会丢失。有关详细信息,请参阅 the Process Messages section of the Erlang Efficiency Guide

看来你无法到达:我认为这是本主题的关键概念。因此,只能比较数据,不能比较指向那些数据的指针

看起来,当您创建多个具有相同值的变量时,它会在内存中创建新数据,这些数据是变量的名称和与主要数据的绑定(看起来很像指针)。 Erlang VM 不复制数据(我正在寻找一些证明......到目前为止,这只是我看到的方式)

让我回答我的问题:

开发人员不需要显式地进行指针比较,因为 Elixir 已经在内部,在模式匹配和运算符 ===== 中(通过相应的Erlang 运算符).

例如给定

a1 = {0, {1, 2}}
a2 = {1, {1, 2}}
x = {a1, a2}
s = {1, 2}
b1 = {0, s}
b2 = {1, s}
y = {b1, b2}

在 IEx 中我们有

Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> a1 = {0, {1, 2}}
{0, {1, 2}}
iex(2)> a2 = {1, {1, 2}}
{1, {1, 2}}
iex(3)> x = {a1, a2}
{{0, {1, 2}}, {1, {1, 2}}}
iex(4)> s = {1, 2}
{1, 2}
iex(5)> b1 = {0, s}
{0, {1, 2}}
iex(6)> b2 = {1, s}
{1, {1, 2}}
iex(7)> y = {b1, b2}
{{0, {1, 2}}, {1, {1, 2}}}
iex(8)> :erts_debug.size(x)
15
iex(9)> :erts_debug.size(y)
12
iex(10)> x == y
true
iex(11)> x === y
true

xy内容相同,但内存不同,因为y占用的内存比x少,因为它内部共享子结构[=18] =].

简而言之,=====都进行了内容比较和指针比较。指针比较是 Erlang 避免在比较两边遍历相同子结构的最有效方式,从而为大型共享子结构节省了大量时间。

现在,如果两个结构之间的结构重复是一个问题,比如当它们从两个具有相似内容的大文件加载时,那么必须将它们压缩到两个新结构中,共享它们内容相同的部分。这是 a1a2 的情况,它们被压缩为 b1b2.

Erlang/OTP 22(可能更早)提供了:erts_debug.same/2,这将允许您进行所需的内存指针测试。但是,请注意该函数未记录在一个名为 erts_debug 的模块中,因此您应该只依赖它进行调试和测试,切勿在生产代码中使用它。

在我使用 Erlang/Elixir 将近 9 年的时间里,我只使用过一次,那是为了测试我们是否在 Ecto 中没有不必要地分配结构。这里是 the commit for reference.