Skip to content

Commit

Permalink
add game of life benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
JaggerJo committed Jun 23, 2023
1 parent 9f5bf5f commit a15dc81
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="Scenarios\Diffing\GameOfLife.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

Expand Down
13 changes: 1 addition & 12 deletions src/Avalonia.FuncUI.Benchmarks/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,9 @@
open BenchmarkDotNet.Attributes
open BenchmarkDotNet.Running

[<MemoryDiagnoser; >]
type Bench () =

[<Benchmark(Baseline = true)>]
member this.Baseline () : unit =
()

[<Benchmark>]
member this.Champion () : unit =
()

module Program =

[<EntryPoint>]
let main (args: string array) : int =
let _ = BenchmarkRunner.Run<Bench>()
let _ = BenchmarkRunner.Run<Scenarios.Diffing.GameOfLifeBench>()
0
230 changes: 230 additions & 0 deletions src/Avalonia.FuncUI.Benchmarks/Scenarios/Diffing/GameOfLife.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
namespace Avalonia.FuncUI.Benchmarks.Scenarios.Diffing

open System
open Avalonia.Controls
open Avalonia.Controls.Primitives
open Avalonia.FuncUI.DSL

open Avalonia.FuncUI.Types
open Avalonia.Layout
open Avalonia.Media
open BenchmarkDotNet.Attributes

[<RequireQualifiedAccess>]
module GameOfLife =

let random = Random(Seed = 42)

let randomView (cells: int, extendedAttrs: bool) =
UniformGrid.create [
UniformGrid.columns cells
UniformGrid.rows cells
UniformGrid.children [
for x = 0 to cells - 1 do
for y = 0 to cells - 1 do
let alive = random.Next(100) < 50

Button.create [
Button.row y
Button.column x
Button.cornerRadius 5.0
Button.borderThickness 1.0
Button.onClick ignore

if extendedAttrs then
Button.verticalAlignment VerticalAlignment.Center
Button.horizontalAlignment HorizontalAlignment.Center
Button.padding 0.0
Button.margin 0.0
Button.borderBrush Brushes.Black
Button.foreground Brushes.Black
Button.content (if alive then "X" else " ")

if alive then
Button.background "green"
else
Button.background "gray"
]
]
]


open Avalonia.FuncUI.VirtualDom.Delta

module internal rec DifferChampionA =

let private update (last: IAttr, next: IAttr) : AttrDelta =
match next with
| Property' property ->
AttrDelta.Property
{ Accessor = property.Accessor
Value = Some property.Value
DefaultValueFactory = property.DefaultValueFactory }

| Content' content ->
AttrDelta.Content
{ Accessor = content.Accessor;
Content = diffContent (last, next) }

| Subscription' subscription ->
AttrDelta.Subscription (SubscriptionDelta.From subscription)

| InitFunction init ->
AttrDelta.SetupFunction init

| _ -> failwithf "no update operation is defined for '%A' next" next

let private reset (last: IAttr) : AttrDelta =
match last with
| Property' property ->
AttrDelta.Property
{ Accessor = property.Accessor;
Value = None
DefaultValueFactory = property.DefaultValueFactory }

| Content' content ->
let empty =
match content.Content with
| ViewContent.Single _ -> ViewContentDelta.Single None
| ViewContent.Multiple _ -> ViewContentDelta.Multiple []

AttrDelta.Content
{ Accessor = content.Accessor;
Content = empty }

| Subscription' subscription ->
AttrDelta.Subscription
{ Name = subscription.Name
Subscribe = subscription.Subscribe
Func = None }

| InitFunction init ->
AttrDelta.SetupFunction init

| _ -> failwithf "no reset operation is defined for last '%A'" last

let private diffContentSingle (last: IView option, next: IView option) : ViewDelta option =
match next with
| Some next ->
match last with
| Some last -> Some (diff(last, next))
| None -> Some (ViewDelta.From next)
| None -> None

let private diffContentMultiple (lastList: IView list, nextList: IView list) : ViewDelta list =
let lastListLength = lastList.Length

nextList |> List.mapi (fun index next ->
if index + 1 <= lastListLength then
diff(lastList.[index], next)
else
ViewDelta.From next
)

let private diffContent (last: IAttr, next: IAttr) : ViewContentDelta =
match next with
| Content' nextContent ->
match last with
| Content' lastContent ->
match nextContent.Content with

// Single Content
| ViewContent.Single nextSingleContent ->
match lastContent.Content with
| ViewContent.Single lastSingleContent ->
ViewContentDelta.Single (diffContentSingle (lastSingleContent, nextSingleContent))
| _ ->
ViewContentDelta.Single None

// Multiple Content
| ViewContent.Multiple nextMultipleContent ->
match lastContent.Content with
| ViewContent.Multiple lastMultipleContent ->
ViewContentDelta.Multiple (diffContentMultiple (lastMultipleContent, nextMultipleContent))
| _ ->
ViewContentDelta.Multiple (diffContentMultiple (List.empty, nextMultipleContent))

| _ -> invalidOp "'last' must be of type content"

| _ -> invalidOp "'next' must be of type content"

let internal diffAttributes (lastAttrs: IAttr list, nextAttrs: IAttr list) : AttrDelta list =

let delta = ResizeArray<AttrDelta>()

(* check if there is a corresponding new attribute for all old attributes *)
for lastAttr in lastAttrs do

let isAttrStillPresent =
nextAttrs
|> List.tryFind (fun nextAttr -> nextAttr.UniqueName = lastAttr.UniqueName)

(* check if attribute is still present *)
match isAttrStillPresent with

(* attribute is still there, but it's value changed. -> update value *)
| Some nextAttr when not (nextAttr.Equals lastAttr) ->
let attrDelta = update (lastAttr, nextAttr)
delta.Add attrDelta

(* attribute is still there. It hasn't changed -> do nothing *)
| Some _ -> ()

(* attribute is no longer present. -> reset value *)
| None ->
let attrDelta = reset lastAttr
delta.Add attrDelta

(* check if new attributes need to be added *)
for nextAttr in nextAttrs do

let attrIsAlreadyKnown =
lastAttrs
|> List.exists (fun lastAttr -> lastAttr.UniqueName = nextAttr.UniqueName)

(* attribute is new - create delta from it -> set value *)
if not attrIsAlreadyKnown then
delta.Add (AttrDelta.From nextAttr)

List.ofSeq delta

let diff (last: IView, next: IView) : ViewDelta =
// only diff attributes if viewType/viewKey matches
if last.ViewKey <> next.ViewKey then
ViewDelta.From (next, true)
elif last.ViewType <> next.ViewType then
ViewDelta.From next
else
{ ViewType = next.ViewType
Attrs = diffAttributes (last.Attrs, next.Attrs)
ConstructorArgs = next.ConstructorArgs
KeyDidChange = false
Outlet = next.Outlet }

[<MemoryDiagnoser; >]
type GameOfLifeBench () =

let mutable viewLast: IView option = None

let mutable viewNext: IView option = None

[<Params(10, 100)>]
member val Cells: int = 0 with get, set

[<Params(true, false)>]
member val ExtendedAttrs: bool = false with get, set

[<GlobalSetup>]
member this.GlobalSetup () =
viewLast <- Some (GameOfLife.randomView (this.Cells, this.ExtendedAttrs))
viewNext <- Some (GameOfLife.randomView (this.Cells, this.ExtendedAttrs))

[<Benchmark(Baseline = true)>]
member this.Baseline () : unit =
let _ = Avalonia.FuncUI.VirtualDom.Differ.diff (viewLast.Value, viewNext.Value)
()

[<Benchmark>]
member this.ChampionA () : unit =
let _ = DifferChampionA.diff (viewLast.Value, viewNext.Value)
()

0 comments on commit a15dc81

Please sign in to comment.