Skip to content

Commit

Permalink
HN-237 improve benchmarks format
Browse files Browse the repository at this point in the history
  • Loading branch information
lubiepiwo committed Oct 7, 2019
1 parent b57e0d8 commit 02edd56
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 74 deletions.
9 changes: 5 additions & 4 deletions bench/delete_benchmark.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
alias ElixirRtree.Node
import IO.ANSI
generate = fn n,s ->
BoundingBoxGenerator.generate(n,s,[]) |> Enum.map(fn x -> {x,ElixirRtree.Node.new()} end)
BoundingBoxGenerator.generate(n,s,[]) |> Enum.map(fn x -> {x,Node.new()} end)
end

new_tree = fn boxes,s ->
boxes |> Enum.slice(0..s-1) |> Enum.reduce(Drtree.new,fn {b,i},acc ->
acc |> ElixirRtree.insert({i,b})
acc |> Drtree.insert({i,b})
end)
end

boxes = generate.(100000,1)

delete = fn t,id ->
ElixirRtree.delete(t,id)
Drtree.delete(t,id)
end

random_leaf = fn leafs,limit ->
Expand All @@ -24,7 +25,7 @@ reinsert_cache = fn t,{box,id} ->
end

Benchee.run(%{
red() <>"delete "<> cyan() <>"["<> color(195) <>"random leaf"<> cyan() <>"]" <> reset() =>
"delete [random leaf]" =>
{fn {t,{_box,id} = leaf} ->
delete.(t,id)
{t,leaf}
Expand Down
15 changes: 8 additions & 7 deletions bench/insert_benchmark.ex
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
alias ElixirRtree.Node
import IO.ANSI
generate = fn n,s ->
BoundingBoxGenerator.generate(n,s,[]) |> Enum.map(fn x -> {x,ElixirRtree.Node.new()} end)
BoundingBoxGenerator.generate(n,s,[]) |> Enum.map(fn x -> {x,Node.new()} end)
end

new_tree = fn leafs ->
generate.(leafs,1)
|> Enum.reduce(Drtree.new,fn {b,i},acc ->
acc |> ElixirRtree.insert({i,b})
acc |> Drtree.insert({i,b})
end)
end

insert = fn t,data ->
data
|> Enum.reduce(t,fn {b,i},acc ->
acc |> ElixirRtree.insert({i,b})
acc |> Drtree.insert({i,b})
end)
end

Expand All @@ -22,25 +23,25 @@ flush_cache = fn t ->
end

Benchee.run(%{
green() <>"tree "<> cyan() <>"["<> color(195) <>"100000"<> cyan() <>"]" <> reset() =>
"tree [100000]" =>
{fn {t,data} ->
insert.(t,data)
end,
before_each: fn boxes -> {new_tree.(100000),boxes} end,
after_each: fn rt -> flush_cache.(rt) end},
green() <>"tree "<> cyan() <>"["<> color(195) <>"10000"<> cyan() <>"]" <> reset() =>
"tree [10000]" =>
{fn {t,data} ->
insert.(t,data)
end,
before_each: fn boxes -> {new_tree.(10000),boxes} end,
after_each: fn rt -> flush_cache.(rt) end},
green() <>"tree "<> cyan() <>"["<> color(195) <>"1000"<> cyan() <>"]" <> reset() =>
"tree [1000]" =>
{fn {t,data} ->
insert.(t,data)
end,
before_each: fn boxes -> {new_tree.(1000),boxes} end,
after_each: fn rt -> flush_cache.(rt) end},
green() <>"tree "<> cyan() <>"["<> color(195) <>"empty"<> cyan() <>"]" <> reset() =>
"tree [empty]" =>
{ fn {t,data} ->
insert.(t,data)
end,
Expand Down
28 changes: 13 additions & 15 deletions bench/query_benchmark.ex
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
alias ElixirRtree.Node
import IO.ANSI
generate = fn n,s ->
BoundingBoxGenerator.generate(n,s,[]) |> Enum.map(fn x -> {x,ElixirRtree.Node.new()} end)
BoundingBoxGenerator.generate(n,s,[]) |> Enum.map(fn x -> {x,Node.new()} end)
end

new_tree = fn boxes,s ->
boxes |> Enum.slice(0..s-1) |> Enum.reduce(Drtree.new,fn {b,i},acc ->
acc |> ElixirRtree.insert({i,b})
acc |> Drtree.insert({i,b})
end)
end

boxes = generate.(100000,1)

Benchee.run(%{
cyan() <>"tree "<> cyan() <>"["<> color(195) <>"1000 leafs"<> cyan() <>"]" <> reset() =>
{fn {t,b} ->
ElixirRtree.query(t,b)
"tree [1000 leafs]" =>
{fn {t,b} -> Drtree.query(t,b)
end,
before_scenario: fn b -> {new_tree.(boxes,1000),b} end},
cyan() <>"tree "<> cyan() <>"["<> color(195) <>"10000 leafs"<> cyan() <>"]" <> reset() =>
{fn {t,b} ->
ElixirRtree.query(t,b)
"tree [10000 leafs]" =>
{fn {t,b} -> Drtree.query(t,b)
end,
before_scenario: fn b -> {new_tree.(boxes,10000),b} end},
cyan() <>"tree "<> cyan() <>"["<> color(195) <>"100000 leafs"<> cyan() <>"]" <> reset() =>
{fn {t,b} ->
ElixirRtree.query(t,b)
"tree [100000 leafs]" =>
{fn {t,b} -> Drtree.query(t,b)
end,
before_scenario: fn b -> {new_tree.(boxes,100000),b} end},

}, inputs: %{
yellow() <> "1x1"<> cyan() <> " box query" => [{0,1},{0,1}],
yellow() <> "10x10"<> cyan() <> " box query" => [{0,10},{0,10}],
yellow() <> "100x100"<> cyan() <> " box query" => [{-50,50},{0,100}],
yellow() <> "world"<> cyan() <> " box query" => [{-180,180},{-90,90}]
yellow() <> "1x1"<> cyan() <> " box query" <> reset() => [{0,1},{0,1}],
yellow() <> "10x10"<> cyan() <> " box query" <> reset() => [{0,10},{0,10}],
yellow() <> "100x100"<> cyan() <> " box query" <> reset() => [{-50,50},{0,100}],
yellow() <> "world"<> cyan() <> " box query" <> reset() => [{-180,180},{-90,90}]
})
27 changes: 17 additions & 10 deletions bench/update_benchmark.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
alias ElixirRtree.Utils
alias ElixirRtree.Node
import IO.ANSI
size = 1

generate = fn n,s ->
BoundingBoxGenerator.generate(n,s,[]) |> Enum.map(fn x -> {x,ElixirRtree.Node.new()} end)
BoundingBoxGenerator.generate(n,s,[]) |> Enum.map(fn x -> {x,Node.new()} end)
end

new_tree = fn leafs ->
boxes = generate.(leafs,size)
tree = boxes |> Enum.reduce(Drtree.new,fn {b,i},acc ->
acc |> ElixirRtree.insert({i,b})
acc |> Drtree.insert({i,b})
end)
{boxes,tree}
end
Expand All @@ -19,27 +21,32 @@ end


Benchee.run(%{
#TODO : update discreto (?)
"continuous update" =>
{fn {t,ml} ->
ml |> Enum.reduce(t,fn {id,ob,nb},acc ->
acc |> ElixirRtree.update_leaf(id,{ob,nb})
acc |> Drtree.update_leaf(id,{ob,nb})
end)
end,
before_each: fn _i -> [{_atom,t}] = :ets.lookup(:bench_tree,:tree)
[{atom,ml}] = :ets.lookup(:move_list,:move)
[{_atom,ml}] = :ets.lookup(:move_list,:move)
{t,ml}
end,
after_each: fn t -> :ets.insert(:bench_tree,{:tree,t})
[{atom,ml}] = :ets.lookup(:move_list,:move)
new_ml = ml |> Enum.map(fn {id,ob,nb} -> {id,nb,Utils.box_move(nb,unit_move.())} end)
:ets.insert(:move_list,{:move,ml})
[{_atom,ml}] = :ets.lookup(:move_list,:move)
new_ml = ml |> Enum.map(fn {id,_ob,nb} -> {id,nb,Utils.box_move(nb,unit_move.())} end)
:ets.insert(:move_list,{:move,new_ml})
end,
before_scenario: fn _i ->
{b,t} = new_tree.(10000)
before_scenario: fn n ->
{b,t} = new_tree.(n)
move_list = b |> Enum.map(fn {box,id} -> {id,box,Utils.box_move(box,unit_move.())} end)
:ets.new(:bench_tree,[:set,:named_table])
:ets.new(:move_list,[:set,:named_table])
:ets.insert(:bench_tree,{:tree,t})
:ets.insert(:move_list,{:move,move_list})
end}
})
}, inputs: %{
cyan() <>"tree ["<> color(195) <>"1000" <> cyan() <> "]" <> reset() => 1000,
cyan() <>"tree ["<> color(195) <>"10000" <> cyan() <> "]" <> reset() => 10000,
cyan() <>"tree ["<> color(195) <>"100000" <> cyan() <> "]" <> reset() => 100000
}, time: 60)
11 changes: 11 additions & 0 deletions benchmarks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
echo "Insertion benchmark..."
mix run bench/insert_benchmark.ex

echo "Query benchmark..."
mix run bench/query_benchmark.ex

echo "Update benchmark..."
mix run bench/update_benchmark.ex

echo "Erase benchmark..."
mix run bench/delete_benchmark.ex
7 changes: 5 additions & 2 deletions lib/drtree.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
defmodule Drtree do
@moduledoc false
defdelegate insert(tree,leaf), to: ElixirRtree
defdelegate query(tree,box), to: ElixirRtree
defdelegate delete(tree,id), to: ElixirRtree
defdelegate update_leaf(tree,id,update), to: ElixirRtree

@defopts %{
width: 6,
type: :standalone,
verbose: false,
verbose: false, #TODO: This crazy american prefers Logger comparison than the verbose flag ùwú
database: false,
}

Expand All @@ -17,5 +21,4 @@ defmodule Drtree do
acc |> Map.put(k,opts[k])
end) |> ElixirRtree.new
end

end
88 changes: 52 additions & 36 deletions lib/elixir_rtree.ex
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,16 @@ defmodule ElixirRtree do
def insert(tree,{id,box} = leaf)do
rbundle = get_rbundle(tree)
if rbundle.ets |> :ets.member(id) do
if rbundle.verbose,do: Logger.info(cyan <>"["<>green<>"Insertion"<>cyan<>"] failed:" <> yellow <> " [#{id}] " <> cyan <> "already exists at tree." <> yellow <> " [Tip]"<> cyan <> " use " <> yellow <>"update_leaf/3")
if rbundle.verbose,do: Logger.info(cyan() <>"["<>green<>"Insertion"<>cyan()<>"] failed:" <> yellow() <> " [#{id}] " <> cyan() <> "already exists at tree." <> yellow() <> " [Tip]"<> cyan() <> " use " <> yellow() <>"update_leaf/3")
tree
else
t1 = :os.system_time(:microsecond)
path = best_subtree(rbundle,leaf)
r = insertion(rbundle,path,leaf)
|> recursive_update(tl(path),leaf,:insertion)
t2 = :os.system_time(:microsecond)
if rbundle.verbose,do: Logger.info(cyan <>"["<>green<>"Insertion"<>cyan<>"] success: "<> yellow <> "[#{id}]" <> cyan <> " was inserted at" <> yellow <>" ['#{hd(path)}']")
if rbundle.verbose,do: Logger.info(cyan <>"["<>green<>"Insertion"<>cyan<>"] took" <> yellow <> " #{t2-t1} µs")
if rbundle.verbose,do: Logger.info(cyan() <>"["<>green<>"Insertion"<>cyan()<>"] success: "<> yellow() <> "[#{id}]" <> cyan() <> " was inserted at" <> yellow() <>" ['#{hd(path)}']")
if rbundle.verbose,do: Logger.info(cyan() <>"["<>green<>"Insertion"<>cyan()<>"] took" <> yellow() <> " #{t2-t1} µs")
r
end
end
Expand All @@ -102,52 +102,35 @@ defmodule ElixirRtree do
t1 = :os.system_time(:microsecond)
r = find_match_leafs(rbundle,box,[get_root(rbundle)],[],[])
t2 = :os.system_time(:microsecond)
if rbundle.verbose,do: Logger.info(cyan <> "["<>color(201)<>"Query"<>cyan<>"] box " <> yellow <> "#{box |> Kernel.inspect} " <> cyan <> "took " <> yellow <> "#{t2-t1} µs")
if rbundle.verbose,do: Logger.info(cyan() <> "["<>color(201)<>"Query"<>cyan()<>"] box " <> yellow() <> "#{box |> Kernel.inspect} " <> cyan() <> "took " <> yellow() <> "#{t2-t1} µs")
r
end

def delete(tree,id)do
rbundle = get_rbundle(tree)
t1 = :os.system_time(:microsecond)
r = remove(rbundle,id)
r = if(rbundle.ets |> :ets.member(id)) do
remove(rbundle,id)
else
tree
end
t2 = :os.system_time(:microsecond)
if rbundle.verbose,do: Logger.info(cyan <>"["<>color(124)<>"Delete"<>cyan<>"] leaf "<>yellow<>"[#{id}]"<>cyan<>" took "<>yellow<>"#{t2-t1} µs")
if rbundle.verbose,do: Logger.info(cyan() <>"["<>color(124)<>"Delete"<>cyan()<>"] leaf "<>yellow()<>"[#{id}]"<>cyan()<>" took "<>yellow()<>"#{t2-t1} µs")
r
end

def update_leaf(tree,id,{old_box,new_box})do
def update_leaf(tree,id,{old_box,new_box} = boxes)do
rbundle = get_rbundle(tree)
t1 = :os.system_time(:microsecond)
parent = rbundle.tree |> Map.get(:parents) |> Map.get(id)
parent_box = rbundle.ets |> :ets.lookup(parent) |> Utils.ets_value(:bbox)
rbundle |> update_crush(id,{Utils.ets_index(:bbox),new_box})

r = if Utils.contained?(parent_box,new_box)do
if Utils.in_border?(parent_box,old_box)do
if rbundle.verbose,do: Logger.info(cyan<>"["<>color(195)<>"Update"<>cyan<>"] Good case: new box "<>yellow<>"(#{new_box |> Kernel.inspect})"<>cyan<>" of "<>yellow<>"[#{id}]"<>cyan<>" reduce the parent "<>yellow<>"(['#{parent}'])"<>cyan<>" box")
rbundle |> recursive_update(parent,old_box,:deletion)
else
if rbundle.verbose,do: Logger.info(cyan<>"["<>color(195)<>"Update"<>cyan<>"] Best case: new box "<>yellow<>"(#{new_box |> Kernel.inspect})"<>cyan<>" of "<>yellow<>"[#{id}]"<>cyan<>" was contained by his parent "<>yellow<>"(['#{parent}'])")
tree
end
if rbundle.ets |> :ets.member(id) do
t1 = :os.system_time(:microsecond)
r = update(rbundle,id,boxes)
t2 = :os.system_time(:microsecond)
if rbundle.verbose,do: Logger.info(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] "<>yellow()<>"[#{id}]"<>cyan()<>" from "<>yellow()<>"#{old_box |> Kernel.inspect}"<>cyan()<>" to "<>yellow()<>"#{new_box |> Kernel.inspect}"<>cyan()<>" took "<>yellow()<>"#{t2-t1} µs")
r
else
case rbundle |> node_brothers(parent) |> (fn b -> good_slot?(rbundle,b,new_box) end).() do
{new_parent,_new_brothers,_new_parent_box} ->
if rbundle.verbose,do: Logger.info(cyan<>"["<>color(195)<>"Update"<>cyan<>"] Neutral case: new box "<>yellow<>"(#{new_box |> Kernel.inspect})"<>cyan<>" of "<>yellow<>"[#{id}]"<>cyan<>" increases the parent box but there is an available slot at one uncle "<>yellow<>"(['#{new_parent}'])")
triple_s(rbundle,parent,new_parent,{id,old_box})

nil -> if Utils.area(parent_box) >= @max_area do
if rbundle.verbose,do: Logger.info(cyan<>"["<>color(195)<>"Update"<>cyan<>"] Worst case: new box "<>yellow<>"(#{new_box |> Kernel.inspect})"<>cyan<>" of "<>yellow<>"[#{id}]"<>cyan<>" increases the parent box which was so big "<>yellow<>"#{(((Utils.area(parent_box) |> Kernel.trunc)/@max_area) * 100) |> Kernel.trunc } %. "<>cyan<>"So we proceed to delete "<>yellow<>"[#{id}]"<>cyan<>" and reinsert at tree")
rbundle |> top_down({id,new_box})
else
if rbundle.verbose,do: Logger.info(cyan<>"["<>color(195)<>"Update"<>cyan<>"] Bad case: new box "<>yellow<>"(#{new_box |> Kernel.inspect})"<>cyan<>" of "<>yellow<>"[#{id}]"<>cyan<>" increases the parent box which isn't that big yet "<>yellow<>"#{(((Utils.area(parent_box) |> Kernel.trunc)/@max_area) * 100) |> Kernel.trunc} %. "<>cyan<>"So we proceed to increase parent "<>yellow<>"(['#{parent}'])"<>cyan<>" box")
rbundle |> recursive_update(parent,new_box,:insertion)
end
end
if rbundle.verbose,do: Logger.warn(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] "<>yellow()<>"[#{id}] doesn't exists"<>cyan())
tree
end
t2 = :os.system_time(:microsecond)
if rbundle.verbose,do: Logger.info(cyan<>"["<>color(195)<>"Update"<>cyan<>"] "<>yellow<>"[#{id}]"<>cyan<>" from "<>yellow<>"#{old_box |> Kernel.inspect}"<>cyan<>" to "<>yellow<>"#{new_box |> Kernel.inspect}"<>cyan<>" took "<>yellow<>"#{t2-t1} µs")
r
end

# You dont need to know old_box but is a BIT slower
Expand Down Expand Up @@ -451,6 +434,39 @@ defmodule ElixirRtree do
end
end

## Hard update

defp update(rbundle,id,{old_box,new_box})do

parent = rbundle.tree |> Map.get(:parents) |> Map.get(id)
parent_box = rbundle.ets |> :ets.lookup(parent) |> Utils.ets_value(:bbox)
rbundle |> update_crush(id,{Utils.ets_index(:bbox),new_box})

if Utils.contained?(parent_box,new_box)do
if Utils.in_border?(parent_box,old_box)do
if rbundle.verbose,do: Logger.info(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] Good case: new box "<>yellow()<>"(#{new_box |> Kernel.inspect})"<>cyan()<>" of "<>yellow()<>"[#{id}]"<>cyan()<>" reduce the parent "<>yellow()<>"(['#{parent}'])"<>cyan()<>" box")
rbundle |> recursive_update(parent,old_box,:deletion)
else
if rbundle.verbose,do: Logger.info(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] Best case: new box "<>yellow()<>"(#{new_box |> Kernel.inspect})"<>cyan()<>" of "<>yellow()<>"[#{id}]"<>cyan()<>" was contained by his parent "<>yellow()<>"(['#{parent}'])")
rbundle.tree
end
else
case rbundle |> node_brothers(parent) |> (fn b -> good_slot?(rbundle,b,new_box) end).() do
{new_parent,_new_brothers,_new_parent_box} ->
if rbundle.verbose,do: Logger.info(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] Neutral case: new box "<>yellow()<>"(#{new_box |> Kernel.inspect})"<>cyan()<>" of "<>yellow()<>"[#{id}]"<>cyan()<>" increases the parent box but there is an available slot at one uncle "<>yellow()<>"(['#{new_parent}'])")
triple_s(rbundle,parent,new_parent,{id,old_box})

nil -> if Utils.area(parent_box) >= @max_area do
if rbundle.verbose,do: Logger.info(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] Worst case: new box "<>yellow()<>"(#{new_box |> Kernel.inspect})"<>cyan()<>" of "<>yellow()<>"[#{id}]"<>cyan()<>" increases the parent box which was so big "<>yellow()<>"#{(((Utils.area(parent_box) |> Kernel.trunc)/@max_area) * 100) |> Kernel.trunc } %. "<>cyan()<>"So we proceed to delete "<>yellow()<>"[#{id}]"<>cyan()<>" and reinsert at tree")
rbundle |> top_down({id,new_box})
else
if rbundle.verbose,do: Logger.info(cyan()<>"["<>color(195)<>"Update"<>cyan()<>"] Bad case: new box "<>yellow()<>"(#{new_box |> Kernel.inspect})"<>cyan()<>" of "<>yellow()<>"[#{id}]"<>cyan()<>" increases the parent box which isn't that big yet "<>yellow()<>"#{(((Utils.area(parent_box) |> Kernel.trunc)/@max_area) * 100) |> Kernel.trunc} %. "<>cyan()<>"So we proceed to increase parent "<>yellow()<>"(['#{parent}'])"<>cyan()<>" box")
rbundle |> recursive_update(parent,new_box,:insertion)
end
end
end
end

## Common updates

defp top_down(rbundle,{id,box})do
Expand Down
2 changes: 2 additions & 0 deletions test/elixir_rtree_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ defmodule ElixirRtreeTest do
use ExUnit.Case
doctest ElixirRtree



test "greets the world" do
assert ElixirRtree.hello() == :world
end
Expand Down

0 comments on commit 02edd56

Please sign in to comment.