Как сравнить две структуры через равенство указателей в Elixir / Erlang - PullRequest
0 голосов
/ 10 сентября 2018

(Пример приведен в 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.

Как можно сравнить две структуры, используя равенство указателей, чтобы я мог определитьесли они имеют одинаковую структуру в памяти?

Спасибо

Ответы [ 4 ]

0 голосов
/ 10 сентября 2018

Позвольте мне ответить на мой вопрос:

Разработчикам не нужно явно выполнять сравнение указателей, поскольку 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.

0 голосов
/ 10 сентября 2018

Нет «официального» способа сделать это, и я бы сказал, что если вы думаете, что на самом деле нужно , чтобы сделать это, вы делаете что-то не так и должны задать еще один вопрос о том, как достичь цель, которую вы хотите достичь. Таким образом, этот ответ предлагается в духе игривости и исследования, в надежде, что он распространяет некоторые интересные знания о виртуальной машине Erlang / Elixir.


Существует функция erts_debug:size/1, которая сообщает, сколько «слов» памяти занимает термин Эрланг / Эликсир. В этой таблице указано, сколько слов используют различные термины. В частности, кортеж использует 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:

  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. Подробнее см. раздел «Сообщения процесса» в Руководстве по эффективности Erlang .

0 голосов
/ 10 сентября 2018

Кажется, вы не можете получить доступ к ячейке памяти переменной в erlang : я думаю, что это ключевое понятие в этой теме. Поэтому можно сравнивать только данные, а не указатель, указывающий на эти данные .

Кажется, что когда вы создаете несколько переменных с одинаковым значением, они создают новые данные в памяти, причем эти данные являются именем переменной и привязкой к первичным данным (выглядит сильно как указатель). Erlang VM не дублирует данные (я ищу какое-то доказательство этого .. пока я вижу это так)

0 голосов
/ 10 сентября 2018

, насколько я знаю, создает три кортежа {1, 2} в разных местах памяти.

Нет, это не правильно.Erlang VM достаточно умен, чтобы создать один кортеж и ссылаться на него.

Стоит отметить, что это возможно, потому что все неизменно.

Также, если вы обнаружите, что выполняете задачу, как указано выше, вы делаете это совершенно неправильно.

...