如何在Elixir / Erlang

时间:2018-09-10 13:06:28

标签: erlang elixir

(在Elixir中给出的示例。)

假设我有以下代码,

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

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

使用运算符=====比较任何a变量总是返回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)也会返回true

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

谢谢

5 个答案:

答案 0 :(得分:14)

没有做到这一点的“官方”方法,我想说的是,如果您认为自己确实需要这样做,那说明您做错了,应该问另一个问题实现您想要实现的目标。因此,本着嬉戏和探索的精神提供了这个答案,希望它能传播一些有关Erlang / Elixir VM的有趣知识。


有一个函数erts_debug:size/1,它告诉您Erlang / Elixir术语占用多少个内存“单词”。 This table告诉您各种术语使用多少个单词。特别是,一个元组使用1个单词,每个元素使用1个单词,再加上“非立即”元素的存储空间。我们使用小整数作为元素,它们是“立即数”,因此是“自由”的。因此,此检查:

> :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表中或从中检索时,这种术语共享就会丢失。有关详细信息,请参见the Process Messages section of the Erlang Efficiency Guide

答案 1 :(得分: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}

在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,因为它内部共享子结构s

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

现在,如果要考虑跨两个结构的结构重复,例如当它们从具有相似内容的两个大文件中加载时,则必须将它们压缩为两个新结构,它们共享内容相同的部分。 a1a2就是这种情况,它们被压缩为b1b2

答案 2 :(得分:3)

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

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

答案 3 :(得分:2)

  

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

不,那是不正确的。 Erlang VM足够聪明,可以创建一个元组并对其进行引用。

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

此外,如果您发现自己完成了上述任务,则说明这样做是错误的。

答案 4 :(得分:2)

您似乎无法接触memory location of a variable in erlang:我认为这是该主题的关键概念。因此,您只能比较数据,而不能指向这些数据的指针

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