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

mlj*_*jrg 5 erlang elixir

(在Elixir中给出的例子.)

假设我有以下代码,

x = {1, 2}
a1 = {"a", {1, 2}}
a2 = {"a", {1, 2}}
a3 = {"a", x}
Run Code Online (Sandbox Code Playgroud)

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

使用运算符=====比较任何a变量总是返回true.这是可以预料的,因为这两个运算符仅在比较数字类型时有所不同(即,与之1 == 1.0不同1 === 1.0).

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

defmodule Test do
  def same?({x, y}, {x, y}), do: true
  def same?(_, _), do: false
end
Run Code Online (Sandbox Code Playgroud)

但是召唤Test.same?(a1, a3)也会回来true.

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

谢谢

leg*_*cia 14

没有"正式"的方法可以做到这一点,我会说,如果你认为你真的要这样做,你做错了什么,应该问另一个问题,关于如何实现你想要实现的目标.因此,这个答案是以游戏和探索的精神提供的,希望它传播一些关于Erlang/Elixir VM的有趣知识.


有一个函数,erts_debug:size/1告诉你Erlang/Elixir术语占用多少内存"单词". 此表告诉您各个术语使用了多少个单词.特别是,元组使用1个字,每个元素加1个字,加上任何"非立即"元素的存储空间.我们使用小整数作为元素,它们是"即时的",因此是"自由的".所以这个检查:

> :erts_debug.size({1,2})
3
Run Code Online (Sandbox Code Playgroud)

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

> :erts_debug.size({{1,2}, {1,2}})
9
Run Code Online (Sandbox Code Playgroud)

这是有道理的:两个内部元组各3个字,外部元组是1 + 2个单词,总共9个单词.

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

> x = {1, 2}
{1, 2}
> :erts_debug.size({x, x})
6
Run Code Online (Sandbox Code Playgroud)

看,我们保存了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
Run Code Online (Sandbox Code Playgroud)

系统工作?似乎是:

> Test.same? x, {1,2}
false
> Test.same? x, x
true
Run Code Online (Sandbox Code Playgroud)

目标完成了!


但是,假设我们试图从编译模块中的另一个函数调用此函数,而不是从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
Run Code Online (Sandbox Code Playgroud)

打印:

> Test.try_it
a1 and a2 same? true
a1 and a3 same? true
a3 and a2 same? true
Run Code Online (Sandbox Code Playgroud)

这是因为编译器足够智能,可以看到这些文字是相同的,并在编译时将它们合并为一个术语.


请注意,当术语发送到另一个进程,或存储在ETS表中/从ETS表中检索时,这种术语共享将丢失.有关详细信息,请参阅"Erlang效率指南"的"处理消息"部分.


Jos*_*lim 8

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

在我使用 Erlang/Elixir 的近 9 年中,我只使用过一次,这是为了测试我们没有在 Ecto 中不必要地分配结构。这是供参考的提交


mlj*_*jrg 6

让我回答我的问题:

开发人员无需明确地进行指针比较,因为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}
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

也就是说,x并且y内容相等,但内存不同,因为y占用的内存少于x内部共享子结构的内存s.

总之,==并且===做内容和指针比较.指针比较是Erlang避免在比较两侧遍历相同子结构的最有效方式,从而为大型共享子结构节省了大量时间.

现在,如果跨两个结构的结构复制是一个问题,就像它们是从具有相似内容的两个大文件加载时那么,必须将它们压缩成两个新的结构,共享它们内容相等的部分.这是的情况a1a2压缩为b1b2.

  • @mudasobwa 这不是完全正确的描述:例如,这意味着 `{1.0, 1.0} === {1, 1}` 为真,因为它们都是元组,并且它们的 `==` 为真。相反,它就像 `==` 一样递归,除了有一个不同的基本情况:它从不将浮点数和整数视为相等。 (3认同)
  • 仅供参考:'===`映射到erlang的`=:=`,如果类型相同,则_compares类型和委托给`==`.由于它执行一个额外的操作,因此无论如何都不能更有效. (2认同)
  • @AlexeyRomanov所以这两个运算符在浮点数和整数方面只有不同. (2认同)
  • @mljrg是的,这是唯一的区别. (2认同)