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