如何在 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.0
与 1 === 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
即x
和y
内容相同,但内存不同,因为y
占用的内存比x
少,因为它内部共享子结构[=18] =].
简而言之,==
和===
都进行了内容比较和指针比较。指针比较是 Erlang 避免在比较两边遍历相同子结构的最有效方式,从而为大型共享子结构节省了大量时间。
现在,如果两个结构之间的结构重复是一个问题,比如当它们从两个具有相似内容的大文件加载时,那么必须将它们压缩到两个新结构中,共享它们内容相同的部分。这是 a1
和 a2
的情况,它们被压缩为 b1
和 b2
.
Erlang/OTP 22(可能更早)提供了:erts_debug.same/2
,这将允许您进行所需的内存指针测试。但是,请注意该函数未记录在一个名为 erts_debug
的模块中,因此您应该只依赖它进行调试和测试,切勿在生产代码中使用它。
在我使用 Erlang/Elixir 将近 9 年的时间里,我只使用过一次,那是为了测试我们是否在 Ecto 中没有不必要地分配结构。这里是 the commit for reference.
(Elixir 中给出的示例。)
假设我有以下代码,
x = {1, 2}
a1 = {"a", {1, 2}}
a2 = {"a", {1, 2}}
a3 = {"a", x}
据我所知,它在不同的内存位置创建了三个元组 {1, 2}
。
使用运算符 ==
或 ===
来比较任何 a
变量总是 returns 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)
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
即x
和y
内容相同,但内存不同,因为y
占用的内存比x
少,因为它内部共享子结构[=18] =].
简而言之,==
和===
都进行了内容比较和指针比较。指针比较是 Erlang 避免在比较两边遍历相同子结构的最有效方式,从而为大型共享子结构节省了大量时间。
现在,如果两个结构之间的结构重复是一个问题,比如当它们从两个具有相似内容的大文件加载时,那么必须将它们压缩到两个新结构中,共享它们内容相同的部分。这是 a1
和 a2
的情况,它们被压缩为 b1
和 b2
.
Erlang/OTP 22(可能更早)提供了:erts_debug.same/2
,这将允许您进行所需的内存指针测试。但是,请注意该函数未记录在一个名为 erts_debug
的模块中,因此您应该只依赖它进行调试和测试,切勿在生产代码中使用它。
在我使用 Erlang/Elixir 将近 9 年的时间里,我只使用过一次,那是为了测试我们是否在 Ecto 中没有不必要地分配结构。这里是 the commit for reference.