Elixir Struct Access效率

时间:2016-10-29 19:40:50

标签: elixir

我正在实现一种排序算法来对结构集合进行排序。我需要对每个结构中特定键的值进行排序。我可以将密钥硬编码到函数中,但我想概括它。如下所示,专门的实现比通用的快3倍。

是否有更有效的方法来获取结构中的键值而不是defp get_value(x, key), do: get_in(x, [Access.key(key)])?这来自docs

专门

defmodule QuickSort do
  def qsort([]) do
    []
  end
  def qsort([pivot | rest]) do
    {left, right} = Enum.partition(rest, fn(x) -> x.key < pivot.key end)
    qsort(left) ++ [pivot] ++ qsort(right)
  end
end




iex(74)> Benchwarmer.benchmark fn -> QuickSort.qsort(collection, :key) end                                            
*** #Function<20.52032458/0 in :erl_eval.expr/5> ***
2.5 sec      3 iterations   863742.34 μs/op

广义

defmodule QuickSort do
  def qsort(collection, key \\ nil)
  def qsort([], _) do
    []
  end
  def qsort([pivot | rest], key) do
    {left, right} = Enum.partition(rest, fn(x) -> get_value(x, key) < get_value(pivot, key) end)
    qsort(left, key) ++ [pivot] ++ qsort(right, key)
  end

  defp get_value(x, key), do: get_in(x, [Access.key(key)])
end




iex(79)> Benchwarmer.benchmark fn -> QuickSort.qsort(collection, :key) end
*** #Function<20.52032458/0 in :erl_eval.expr/5> ***
3.1 sec      1 iterations   3180784.0 μs/op

2 个答案:

答案 0 :(得分:2)

在这个简单的例子中,

get_in(..., [Access.key(key)])完成了很多你不需要的东西。它用于访问嵌套结构,因此需要迭代一组函数(更不用说你需要用Access.key创建这些函数)。毫不奇怪,它要慢得多。

Map.get首先调用Map.fetch,然后检查是否存在一个元素,如果不存在,则将其替换为默认值。再一次,一些额外的代码。

在我的观察中,你可以到达.key的最近的事情是Map.fetch。或者,如果你想进入低级:maps.find(key, map)(虽然没有比Map.fetch更多的优势)。

.key如此高效的原因是因为它直接编译成BEAM字节码而不调用外部函数。

答案 1 :(得分:1)

Map.get会让你非常接近硬编码密钥,但它不像硬编码密钥那么快,因为Erlang最有可能对这些密钥进行一些额外的优化。这是针对Map.get的两个版本:

defmodule Thing do
  defstruct [:key]
end

defmodule QuickSortDotKey do
  def qsort([]), do: []
  def qsort([pivot | rest]) do
    {left, right} = Enum.partition(rest, fn(x) -> x.key < pivot.key end)
    qsort(left) ++ [pivot] ++ qsort(right)
  end
end

defmodule QuickSortAccessKey do
  def qsort([], _), do: []
  def qsort([pivot | rest], key) do
    {left, right} = Enum.partition(rest, fn(x) -> get_value(x, key) < get_value(pivot, key) end)
    qsort(left, key) ++ [pivot] ++ qsort(right, key)
  end

  defp get_value(x, key), do: get_in(x, [Access.key(key)])
end

defmodule QuickSortMapGet do
  def qsort([], _), do: []
  def qsort([pivot | rest], key) do
    {left, right} = Enum.partition(rest, fn(x) -> Map.get(x, key) < Map.get(pivot, key) end)
    qsort(left, key) ++ [pivot] ++ qsort(right, key)
  end
end

defmodule Bench do
  use Benchfella

  bench ".key", [list: gen()] do
    QuickSortDotKey.qsort(list)
  end

  bench "Access.key", [list: gen()] do
    QuickSortAccessKey.qsort(list, :key)
  end

  bench "Map.get", [list: gen()] do
    QuickSortMapGet.qsort(list, :key)
  end

  defp gen, do: for _ <- 1..10000, do: %Thing{key: :rand.uniform}

  # Tests
  list = for _ <- 1..10000, do: %Thing{key: :rand.uniform}
  sorted = Enum.sort_by(list, &(&1.key))
  true = sorted == QuickSortDotKey.qsort(list)
  true = sorted == QuickSortAccessKey.qsort(list, :key)
  true = sorted == QuickSortMapGet.qsort(list, :key)
end

输出:

## Bench
benchmark n iterations   average time
.key               100   15572.13 µs/op
Map.get            100   23042.38 µs/op
Access.key          50   60898.12 µs/op