From 339f456401b3a029a573425311c839fb5facea34 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Wed, 3 Apr 2019 16:37:19 -0700 Subject: [PATCH] Bugfix: Performance - Memory climb on startup (#432) * Remove garbage collector tuning code * Factor to include onIdle function * Print idle message in example app * Formatting --- examples/Examples.re | 3 +- src/Core/App.re | 108 +++++++++++++++++------------------ src/Core/GarbageCollector.re | 26 --------- 3 files changed, 53 insertions(+), 84 deletions(-) diff --git a/examples/Examples.re b/examples/Examples.re index 4b839ff64..8cd47c325 100644 --- a/examples/Examples.re +++ b/examples/Examples.re @@ -282,4 +282,5 @@ let init = app => { UI.start(win, render); }; -App.startWithState(state, reducer, init); +let onIdle = () => print_endline("Example: idle callback triggered"); +App.startWithState(~onIdle, state, reducer, init); diff --git a/src/Core/App.re b/src/Core/App.re index d7249af81..c83f0de42 100644 --- a/src/Core/App.re +++ b/src/Core/App.re @@ -2,18 +2,20 @@ open Reglfw; type reducer('s, 'a) = ('s, 'a) => 's; -type idleState = - | Running - | Idle; +type idleFunc = unit => unit; +let noop = () => (); type t('s, 'a) = { reducer: reducer('s, 'a), mutable state: 's, mutable windows: list(Window.t), mutable needsRender: bool, - mutable idleState, + mutable idleCount: int, + onIdle: idleFunc, }; +let framesToIdle = 10; + /* If no state is specified, just use unit! */ let defaultState = (); @@ -22,8 +24,6 @@ let defaultReducer: reducer(unit, unit) = (_s, _a) => (); type appInitFunc('s, 'a) = t('s, 'a) => unit; -type startFunc('s, 'a) = ('s, reducer('s, 'a), appInitFunc('s, 'a)) => unit; - let getWindows = (app: t('s, 'a)) => app.windows; let getState = (app: t('s, 'a)) => app.state; @@ -62,61 +62,55 @@ let _checkAndCloseWindows = (app: t('s, 'a)) => { app.windows = windowsToKeep; }; -let startWithState: startFunc('s, 'a) = - ( - initialState: 's, - reducer: reducer('s, 'a), - initFunc: appInitFunc('s, 'a), - ) => { - let appInstance: t('s, 'a) = { - reducer, - state: initialState, - windows: [], - needsRender: true, - idleState: Running, - }; +let startWithState = + ( + ~onIdle=noop, + initialState: 's, + reducer: reducer('s, 'a), + initFunc: appInitFunc('s, 'a), + ) => { + let appInstance: t('s, 'a) = { + reducer, + state: initialState, + windows: [], + needsRender: true, + idleCount: 0, + onIdle, + }; - GarbageCollector.tune(); - - let _ = Glfw.glfwInit(); - let _ = initFunc(appInstance); - - let appLoop = (_t: float) => { - Glfw.glfwPollEvents(); - Tick.Default.pump(); - - _checkAndCloseWindows(appInstance); - - if (appInstance.needsRender || _anyWindowsDirty(appInstance)) { - Performance.bench("renderWindows", () => { - List.iter(w => Window.render(w), getWindows(appInstance)); - appInstance.needsRender = false; - }); - /* We're taking path 2 of the garbage collector route to nirvana: - * https://blogs.msdn.microsoft.com/shawnhar/2007/07/02/twin-paths-to-garbage-collector-nirvana/ - */ - appInstance.idleState = Running; - Performance.bench("gc: minor", () => GarbageCollector.minor()); - } else if (appInstance.idleState == Running) { - /* If the we're transitioning from Running to Idle, this is the - * perfect time to do a full memory collection, so that - * we're in a clear memory state, so we're ready to go on the next - * tick - */ - Performance.bench("gc: full", () => GarbageCollector.full()); - appInstance.idleState = Idle; - } else { - Environment.sleep(Milliseconds(1.)); - }; + let _ = Glfw.glfwInit(); + let _ = initFunc(appInstance); + + let appLoop = (_t: float) => { + Glfw.glfwPollEvents(); + Tick.Default.pump(); - if (Environment.isNative) { - Thread.yield(); + _checkAndCloseWindows(appInstance); + + if (appInstance.needsRender || _anyWindowsDirty(appInstance)) { + Performance.bench("renderWindows", () => { + List.iter(w => Window.render(w), getWindows(appInstance)); + appInstance.needsRender = false; + appInstance.idleCount = 0; + }); + } else { + appInstance.idleCount = appInstance.idleCount + 1; + + if (appInstance.idleCount === framesToIdle) { + appInstance.onIdle(); }; - List.length(getWindows(appInstance)) == 0; + + Environment.sleep(Milliseconds(1.)); }; - Glfw.glfwRenderLoop(appLoop); + if (Environment.isNative) { + Thread.yield(); + }; + List.length(getWindows(appInstance)) == 0; }; -let start = (initFunc: appInitFunc(unit, unit)) => - startWithState(defaultState, defaultReducer, initFunc); + Glfw.glfwRenderLoop(appLoop); +}; + +let start = (~onIdle=noop, initFunc: appInitFunc(unit, unit)) => + startWithState(~onIdle, defaultState, defaultReducer, initFunc); diff --git a/src/Core/GarbageCollector.re b/src/Core/GarbageCollector.re index d1ff9834d..69bc21ab5 100644 --- a/src/Core/GarbageCollector.re +++ b/src/Core/GarbageCollector.re @@ -6,32 +6,6 @@ * Revery app model. */ -/* Increase minor heap size: 256kb -> 8MB */ -/* - * Some more info on why this is helpful: - * https://md.ekstrandom.net/blog/2010/06/ocaml-memory-tuning - */ -let minorHeapSize = 8 * 1024 * 1024; - -/* - * Amortize major collections across frames - */ -let defaultSliceSize = minorHeapSize / 60; - -let tune = () => - /* Gc tuning is only applicable in the native space */ - if (Environment.isNative) { - let settings = Gc.get(); - - Gc.set({...settings, minor_heap_size: minorHeapSize}); - }; - -let minor = () => - if (Environment.isNative) { - let _ = Gc.major_slice(defaultSliceSize); - (); - }; - let full = () => if (Environment.isNative) { Gc.full_major();