Saltar al contenido

Cómo comparar dos estructuras a través de la igualdad de punteros en Elixir / Erlang

Solución:

No hay una forma “oficial” de hacer esto, y diría que si crees que realmente necesitar para hacer esto, está haciendo algo mal y debe hacer otra pregunta sobre cómo lograr el objetivo que desea lograr. Entonces, esta respuesta se ofrece con un espíritu de diversión y exploración, con la esperanza de que difunda algún conocimiento interesante sobre Erlang / Elixir VM.


Hay una función, erts_debug:size/1, que te dice cuántas “palabras” de memoria ocupa un término de Erlang / Elixir. Esta tabla le indica cuántas palabras utilizan varios términos. En particular, una tupla usa 1 palabra, más 1 palabra para cada elemento, más el espacio de almacenamiento para cualquier elemento que sea “no inmediato”. Estamos usando pequeños enteros como elementos, y son “inmediatos” y, por lo tanto, “gratuitos”. Entonces esto verifica:

> :erts_debug.size({1,2})
3

Ahora hagamos una tupla que contenga dos de esas tuplas:

> :erts_debug.size({{1,2}, {1,2}})
9

Eso tiene sentido: las dos tuplas internas son 3 palabras cada una, y la tupla externa es 1 + 2 palabras, para un total de 9 palabras.

Pero, ¿y si ponemos la tupla interna en una variable?

> x = {1, 2}
{1, 2}
> :erts_debug.size({x, x})
6

¡Mira, guardamos 3 palabras! Eso es porque el contenido de x solo cuenta una vez; la tupla exterior apunta a la misma tupla interior dos veces.

Así que escribamos una pequeña función que haga esto por nosotros:

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

¿Funciona el sistema? Parece ser:

> Test.same? x, {1,2}
false
> Test.same? x, x
true

¡Objetivo cumplido!


Sin embargo, digamos que estamos intentando llamar a esta función desde otra función en un módulo compilado, no desde el shell 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

Eso imprime:

> Test.try_it
a1 and a2 same? true
a1 and a3 same? true
a3 and a2 same? true

Esto se debe a que el compilador es lo suficientemente inteligente como para ver que esos literales son iguales y los fusiona en un término durante la compilación.


Tenga en cuenta que este intercambio de términos se pierde cuando los términos se envían a otro proceso o se almacenan / recuperan de una tabla ETS. Consulte la sección Procesar mensajes de la Guía de eficiencia de Erlang para obtener más detalles.

Déjame responder mi pregunta:

No es necesario que los desarrolladores realicen una comparación de punteros explícitamente, porque Elixir ya lo hace internamente, en la coincidencia de patrones y en los operadores. == y === (a través de los operadores de Erlang correspondientes).

Por ejemplo, dado

a1 = {0, {1, 2}}
a2 = {1, {1, 2}}
x = {a1, a2}
s = {1, 2}
b1 = {0, s}
b2 = {1, s}
y = {b1, b2}

en IEx tenemos

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

Es decir, x y y tienen el mismo contenido, pero la memoria es diferente, porque y ocupa menos memoria que x ya que comparte internamente la subestructura s.

En breve, == y === hacer comparaciones tanto de contenido como de puntero. La comparación de punteros es la forma más eficiente para que Erlang evite atravesar la misma subestructura en ambos lados de la comparación, ahorrando así mucho tiempo para grandes subestructuras compartidas.

Ahora bien, si la duplicación estructural en dos estructuras es una preocupación, como cuando se cargan desde dos archivos grandes con contenido similar, entonces uno debe comprimirlos en dos nuevas estructuras compartiendo las partes en las que tienen el mismo contenido. Este fue el caso de a1 y a2 que fueron comprimidos como b1 y b2.

Erlang / OTP 22 (y posiblemente anterior) proporciona :erts_debug.same/2, que le permitirá realizar la prueba de puntero de memoria deseada. Sin embargo, tenga en cuenta que la función no está documentada y en un módulo llamado erts_debug, por lo que solo debe confiar en él para depurar y probar, y nunca en el código de producción.

En mis casi 9 años usando Erlang / Elixir, solo lo he usado una vez, que es para probar que no estamos asignando estructuras innecesarias en Ecto. Aquí está el compromiso como referencia.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *