Skip to content

Commit

Permalink
improvement: don't start processes for single items in list
Browse files Browse the repository at this point in the history
chore: add some benchmarks/flame files
  • Loading branch information
zachdaniel committed Aug 15, 2024
1 parent e200b5b commit ce5c080
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 88 deletions.
5 changes: 4 additions & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,10 @@ spark_locals_without_parens = [

[
import_deps: [:spark, :reactor],
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
inputs: [
"{mix,.formatter}.exs",
"{config,lib,test,benchmarks,flames}/**/*.{ex,exs}"
],
plugins: [Spark.Formatter],
locals_without_parens: spark_locals_without_parens,
export: [
Expand Down
13 changes: 5 additions & 8 deletions benchmarks/create.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ end

changeset = Ash.Changeset.for_create(Resource, :create, %{})

Benchee.run(
%{
create: fn ->
Ash.create!(changeset)
end

}
)
Benchee.run(%{
create: fn ->
Ash.create!(changeset)
end
})
23 changes: 12 additions & 11 deletions benchmarks/embedded_resources.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,20 @@ defmodule Resource do
attributes do
uuid_primary_key :id
attribute :embeds, {:array, Embed}, public?: true

attribute :structs, {:array, :struct} do
public? true
constraints [
items: [
instance_of: Embed,
fields: [
name: [
type: :string
]
]
]
]

constraints items: [
instance_of: Embed,
fields: [
name: [
type: :string
]
]
]
end

attribute :maps, {:array, :map}, public?: true
end

Expand Down Expand Up @@ -82,5 +83,5 @@ Benchee.run(
Ash.bulk_create!(resource_structs_input, Resource, :create)
end
},
memory_time: 2
memory_time: 2
)
80 changes: 80 additions & 0 deletions benchmarks/read.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
defmodule Domain do
use Ash.Domain, validate_config_inclusion?: false

resources do
allow_unregistered? true
end
end

defmodule Destination do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
domain: Domain

actions do
defaults [:read, :destroy, create: :*, update: :*]
end

attributes do
uuid_primary_key :id
attribute :name, :string, allow_nil?: false, public?: true
end

relationships do
belongs_to :source, Source, public?: true
end
end

defmodule Source do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
domain: Domain

actions do
defaults [:read, :destroy, create: :*, update: :*]
end

attributes do
uuid_primary_key :id
attribute :first_name, :string, allow_nil?: false, public?: true
attribute :last_name, :string, allow_nil?: false, public?: true
end

calculations do
calculate :full_name, :string, expr(first_name <> " " <> last_name)
end

aggregates do
first :first_destination_name, :destination, :name
end

relationships do
has_many :destination, Destination
end
end

source =
Source
|> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"})
|> Ash.create!()

for _ <- 1..2 do
Destination
|> Ash.Changeset.for_create(:create, %{source_id: source.id, name: "Destination"})
|> Ash.create!()
end

query =
Source
|> Ash.Query.for_read(:read, %{})
|> Ash.Query.load([:first_destination_name, :full_name, :destination])

Ash.read!(query)

Logger.configure(level: :error)

Benchee.run(%{
"read" => fn ->
Ash.read!(query)
end
})
61 changes: 40 additions & 21 deletions benchmarks/sat_solver.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ list = Enum.to_list(1..10_000)
map_fun = fn i -> [i, i * i] end

mixed = fn count ->
Enum.reduce(1..count, 0, fn var, expr ->
cond do
rem(var, 4) == 0 ->
{:or, var, expr}
Enum.reduce(1..count, 0, fn var, expr ->
cond do
rem(var, 4) == 0 ->
{:or, var, expr}

rem(var, 3) == 0 ->
{:and, expr, var}
rem(var, 3) == 0 ->
{:and, expr, var}

rem(var, 2) == 0 ->
{:and, -var, expr}
rem(var, 2) == 0 ->
{:and, -var, expr}

true ->
{:or, -var, expr}
end
end) |> Ash.Policy.SatSolver.solve()
true ->
{:or, -var, expr}
end
end)
|> Ash.Policy.SatSolver.solve()
end

Benchee.run(
Expand All @@ -26,14 +27,32 @@ Benchee.run(
end
},
inputs: %{
"3 conjunctive" => Enum.to_list(1..3) |> Enum.reduce(0, fn var, expr -> {:and, var, expr} end) |> Ash.Policy.SatSolver.solve(),
"3 disjunctive" => Enum.to_list(1..3) |> Enum.reduce(0, fn var, expr -> {:or, var, expr} end) |> Ash.Policy.SatSolver.solve(),
"3 mixed" => mixed.(3),
"5 conjunctive" => Enum.to_list(1..5) |> Enum.reduce(0, fn var, expr -> {:and, var, expr} end) |> Ash.Policy.SatSolver.solve(),
"5 disjunctive" => Enum.to_list(1..5) |> Enum.reduce(0, fn var, expr -> {:or, var, expr} end) |> Ash.Policy.SatSolver.solve(),
"5 mixed" => mixed.(5),
"7 conjunctive" => Enum.to_list(1..7) |> Enum.reduce(0, fn var, expr -> {:and, var, expr} end) |> Ash.Policy.SatSolver.solve(),
"7 disjunctive" => Enum.to_list(1..7) |> Enum.reduce(0, fn var, expr -> {:or, var, expr} end) |> Ash.Policy.SatSolver.solve(),
"7 mixed" => mixed.(7),
"3 conjunctive" =>
Enum.to_list(1..3)
|> Enum.reduce(0, fn var, expr -> {:and, var, expr} end)
|> Ash.Policy.SatSolver.solve(),
"3 disjunctive" =>
Enum.to_list(1..3)
|> Enum.reduce(0, fn var, expr -> {:or, var, expr} end)
|> Ash.Policy.SatSolver.solve(),
"3 mixed" => mixed.(3),
"5 conjunctive" =>
Enum.to_list(1..5)
|> Enum.reduce(0, fn var, expr -> {:and, var, expr} end)
|> Ash.Policy.SatSolver.solve(),
"5 disjunctive" =>
Enum.to_list(1..5)
|> Enum.reduce(0, fn var, expr -> {:or, var, expr} end)
|> Ash.Policy.SatSolver.solve(),
"5 mixed" => mixed.(5),
"7 conjunctive" =>
Enum.to_list(1..7)
|> Enum.reduce(0, fn var, expr -> {:and, var, expr} end)
|> Ash.Policy.SatSolver.solve(),
"7 disjunctive" =>
Enum.to_list(1..7)
|> Enum.reduce(0, fn var, expr -> {:or, var, expr} end)
|> Ash.Policy.SatSolver.solve(),
"7 mixed" => mixed.(7)
}
)
21 changes: 10 additions & 11 deletions benchmarks/uuids.exs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
Benchee.run(
%{
"uuid_v7 raw" => fn ->
Ash.UUIDv7.bingenerate()
end,
"uuid_v7 string" => fn ->
Ash.UUIDv7.generate()
end,
"uuid_v4 string" => fn ->
Ash.UUID.generate()
end
Benchee.run(%{
"uuid_v7 raw" => fn ->
Ash.UUIDv7.bingenerate()
end,
"uuid_v7 string" => fn ->
Ash.UUIDv7.generate()
end,
"uuid_v4 string" => fn ->
Ash.UUID.generate()
end
})
81 changes: 81 additions & 0 deletions flames/read.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
defmodule Domain do
use Ash.Domain, validate_config_inclusion?: false

resources do
allow_unregistered? true
end
end

defmodule Destination do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
domain: Domain

actions do
defaults [:read, :destroy, create: :*, update: :*]
end

attributes do
uuid_primary_key :id
attribute :name, :string, allow_nil?: false, public?: true
end

relationships do
belongs_to :source, Source, public?: true
end
end

defmodule Source do
use Ash.Resource,
data_layer: Ash.DataLayer.Ets,
domain: Domain

actions do
defaults [:read, :destroy, create: :*, update: :*]
end

attributes do
uuid_primary_key :id
attribute :first_name, :string, allow_nil?: false, public?: true
attribute :last_name, :string, allow_nil?: false, public?: true
end

calculations do
calculate :full_name, :string, expr(first_name <> " " <> last_name)
end

aggregates do
first :first_destination_name, :destination, :name
end

relationships do
has_many :destination, Destination
end
end

source =
Source
|> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"})
|> Ash.create!()

for _ <- 1..2 do
Destination
|> Ash.Changeset.for_create(:create, %{source_id: source.id, name: "Destination"})
|> Ash.create!()
end

query =
Source
|> Ash.Query.for_read(:read, %{})
|> Ash.Query.load([:first_destination_name, :full_name, :destination])

Ash.read!(query)

Logger.configure(level: :error)

:eflame.apply(
fn ->
Ash.read!(query)
end,
[]
)
5 changes: 3 additions & 2 deletions lib/ash/actions/read/async_limiter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ defmodule Ash.Actions.Read.AsyncLimiter do
def async_or_inline(
%{resource: resource, context: %{private: %{async_limiter: async_limiter}}} = query,
opts,
last?,
func
)
when not is_nil(async_limiter) do
when not is_nil(async_limiter) and last? != true do
if Application.get_env(:ash, :disable_async?) do
func.()
else
Expand Down Expand Up @@ -54,7 +55,7 @@ defmodule Ash.Actions.Read.AsyncLimiter do
end
end

def async_or_inline(_, _opts, func) do
def async_or_inline(_, _opts, _, func) do
func.()
end

Expand Down
32 changes: 22 additions & 10 deletions lib/ash/actions/read/calculations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -264,16 +264,8 @@ defmodule Ash.Actions.Read.Calculations do

{newly_done, remaining} =
do_now
|> Enum.map(fn calculation ->
Ash.Actions.Read.AsyncLimiter.async_or_inline(
ash_query,
Ash.Context.to_opts(calculation.context),
fn ->
{calculation.name, calculation, run_calculation(calculation, ash_query, records)}
end
)
end)
|> Enum.concat(tasks)
|> do_run_calcs(ash_query, records)
|> Stream.concat(tasks)
|> Ash.Actions.Read.AsyncLimiter.await_at_least_one()

records =
Expand Down Expand Up @@ -309,6 +301,26 @@ defmodule Ash.Actions.Read.Calculations do
end
end

defp do_run_calcs(calcs, ash_query, records, acc \\ [])

defp do_run_calcs([], _ash_query, _records, acc) do
acc
end

defp do_run_calcs([calculation | rest], ash_query, records, acc) do
result =
Ash.Actions.Read.AsyncLimiter.async_or_inline(
ash_query,
Ash.Context.to_opts(calculation.context),
Enum.empty?(rest),
fn ->
{calculation.name, calculation, run_calculation(calculation, ash_query, records)}
end
)

do_run_calcs(rest, ash_query, records, [result | acc])
end

defp attach_calculation_results(calculation, records, nil) do
if calculation.load do
Enum.map(records, fn record ->
Expand Down
Loading

0 comments on commit ce5c080

Please sign in to comment.