(在Elixir中给出的示例。)
假设我有以下代码,
x = {1, 2}
a1 = {"a", {1, 2}}
a2 = {"a", {1, 2}}
a3 = {"a", x}
据我所知,它在不同的存储位置创建了三个元组{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
但调用Test.same?(a1, a3)
也会返回true
。
如何使用指针相等性比较两个结构,以便确定它们在内存中是否是相同的结构?
谢谢
答案 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
也就是说,x
和y
的内容相等,但是内存不同,因为y
占用的内存少于x
,因为它内部共享子结构s
简而言之,==
和===
进行内容和指针比较。指针比较是Erlang避免在比较的两端遍历相同子结构的最有效方法,从而为大型共享子结构节省了大量时间。
现在,如果要考虑跨两个结构的结构重复,例如当它们从具有相似内容的两个大文件中加载时,则必须将它们压缩为两个新结构,它们共享内容相同的部分。 a1
和a2
就是这种情况,它们被压缩为b1
和b2
。
答案 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不会复制数据(我正在寻找证明。.到目前为止,这只是我看到的方式)