Page MenuHomeWildfire Games

D4758.id20787.diff
No OneTemporary

Size
10 KB
Referenced Files
None
Subscribers
None

D4758.id20787.diff

Index: source/scriptinterface/ScriptContext.h
===================================================================
--- source/scriptinterface/ScriptContext.h
+++ source/scriptinterface/ScriptContext.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -22,10 +22,11 @@
#include "ScriptExtraHeaders.h"
#include <list>
+#include <memory>
// Those are minimal defaults. The runtime for the main game is larger and GCs upon a larger growth.
-constexpr int DEFAULT_CONTEXT_SIZE = 16 * 1024 * 1024;
-constexpr int DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER = 2 * 1024 * 1024;
+constexpr u32 DEFAULT_CONTEXT_SIZE = 16 * 1024 * 1024;
+constexpr u32 DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER = 2 * 1024 * 1024;
/**
* Abstraction around a SpiderMonkey JSContext.
@@ -40,7 +41,7 @@
class ScriptContext
{
public:
- ScriptContext(int contextSize, int heapGrowthBytesGCTrigger);
+ ScriptContext(u32 contextSize, u32 heapGrowthBytesGCTrigger);
~ScriptContext();
/**
@@ -51,8 +52,8 @@
* @param heapGrowthBytesGCTrigger Size in bytes of cumulated allocations after which a GC will be triggered
*/
static std::shared_ptr<ScriptContext> CreateContext(
- int contextSize = DEFAULT_CONTEXT_SIZE,
- int heapGrowthBytesGCTrigger = DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER);
+ u32 contextSize = DEFAULT_CONTEXT_SIZE,
+ u32 heapGrowthBytesGCTrigger = DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER);
/**
@@ -60,12 +61,12 @@
* be worth the amount of time it would take. It does this with our own logic and NOT some predefined JSAPI logic because
* such functionality currently isn't available out of the box.
* It does incremental GC which means it will collect one slice each time it's called until the garbage collection is done.
- * This can and should be called quite regularly. The delay parameter allows you to specify a minimum time since the last GC
- * in seconds (the delay should be a fraction of a second in most cases though).
- * It will only start a new incremental GC or another GC slice if this time is exceeded. The user of this function is
- * responsible for ensuring that GC can run with a small enough delay to get done with the work.
+ * This can and should be called quite regularly. The timeBudget paramater specifies the maximum time the GC is allowed to run.
*/
- void MaybeIncrementalGC(double delay);
+ void MaybeIncrementalGC(double timeBudget);
+ /**
+ * ShrinkingGC runs a full stop-the-world GC. Used only in testing.
+ */
void ShrinkingGC();
/**
@@ -90,9 +91,8 @@
void PrepareZonesForIncrementalGC() const;
std::list<JS::Realm*> m_Realms;
- int m_ContextSize;
- int m_HeapGrowthBytesGCTrigger;
- int m_LastGCBytes;
+ u32 m_HeapGrowthBytesGCTrigger;
+ u32 m_LastGCBytes;
double m_LastGCCheck;
};
Index: source/scriptinterface/ScriptContext.cpp
===================================================================
--- source/scriptinterface/ScriptContext.cpp
+++ source/scriptinterface/ScriptContext.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -19,6 +19,7 @@
#include "ScriptContext.h"
+#include "lib/debug.h"
#include "lib/alignment.h"
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
@@ -26,6 +27,8 @@
#include "scriptinterface/ScriptEngine.h"
#include "scriptinterface/ScriptInterface.h"
+#include <algorithm>
+
void GCSliceCallbackHook(JSContext* UNUSED(cx), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc))
{
/**
@@ -77,16 +80,15 @@
#endif
}
-std::shared_ptr<ScriptContext> ScriptContext::CreateContext(int contextSize, int heapGrowthBytesGCTrigger)
+std::shared_ptr<ScriptContext> ScriptContext::CreateContext(u32 contextSize, u32 heapGrowthBytesGCTrigger)
{
return std::make_shared<ScriptContext>(contextSize, heapGrowthBytesGCTrigger);
}
-ScriptContext::ScriptContext(int contextSize, int heapGrowthBytesGCTrigger):
+ScriptContext::ScriptContext(u32 contextSize, u32 heapGrowthBytesGCTrigger):
m_LastGCBytes(0),
m_LastGCCheck(0.0f),
- m_HeapGrowthBytesGCTrigger(heapGrowthBytesGCTrigger),
- m_ContextSize(contextSize)
+ m_HeapGrowthBytesGCTrigger(heapGrowthBytesGCTrigger)
{
ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be initialized before constructing any ScriptContexts!");
@@ -106,7 +108,7 @@
JS::SetGCSliceCallback(m_cx, GCSliceCallbackHook);
- JS_SetGCParameter(m_cx, JSGC_MAX_BYTES, m_ContextSize);
+ JS_SetGCParameter(m_cx, JSGC_MAX_BYTES, contextSize);
JS_SetGCParameter(m_cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
JS_SetOffthreadIonCompilationEnabled(m_cx, true);
@@ -148,7 +150,7 @@
}
#define GC_DEBUG_PRINT 0
-void ScriptContext::MaybeIncrementalGC(double delay)
+void ScriptContext::MaybeIncrementalGC(double timeBudget)
{
PROFILE2("MaybeIncrementalGC");
@@ -160,91 +162,51 @@
// The sweeping actually frees memory and it does this in a background thread (if JS_USE_HELPER_THREADS is set).
// While the sweeping is happening we already run scripts again and produce new garbage.
- const int GCSliceTimeBudget = 30; // Milliseconds an incremental slice is allowed to run
-
- // Have a minimum time in seconds to wait between GC slices and before starting a new GC to distribute the GC
- // load and to hopefully make it unnoticeable for the player. This value should be high enough to distribute
- // the load well enough and low enough to make sure we don't run out of memory before we can start with the
- // sweeping.
- if (timer_Time() - m_LastGCCheck < delay)
- return;
-
m_LastGCCheck = timer_Time();
- int gcBytes = JS_GetGCParameter(m_cx, JSGC_BYTES);
+ const u32 gcBytes = JS_GetGCParameter(m_cx, JSGC_BYTES);
+ const u32 gcMaxBytes = JS_GetGCParameter(m_cx, JSGC_MAX_BYTES);
+ const double gcPressure = static_cast<double>(gcBytes) / gcMaxBytes;
+ double minimumTimeBugdget = 20.0;
+ if (gcPressure > 0.5)
+ // Add 50% minimum time budget for each 0.1 of pressure over 0.5.
+ minimumTimeBugdget *= 1.0 + ((gcPressure - 0.5) * 5.0);
+ timeBudget = std::max(minimumTimeBugdget, timeBudget);
#if GC_DEBUG_PRINT
- std::cout << "gcBytes: " << gcBytes / 1024 << " KB" << std::endl;
+ debug_printf("gcBytes: %u KB\n", gcBytes / 1024);
#endif
if (m_LastGCBytes > gcBytes || m_LastGCBytes == 0)
{
#if GC_DEBUG_PRINT
- printf("Setting m_LastGCBytes: %d KB \n", gcBytes / 1024);
+ debug_printf("Setting m_LastGCBytes: %u KB \n", gcBytes / 1024);
#endif
m_LastGCBytes = gcBytes;
}
- // Run an additional incremental GC slice if the currently running incremental GC isn't over yet
+ // Run an additional incremental GC slice if the currently running incremental GC isn't over yet.
// ... or
- // start a new incremental GC if the JS heap size has grown enough for a GC to make sense
+ // Start a new incremental GC if the JS heap size has grown enough for a GC to make sense.
if (JS::IsIncrementalGCInProgress(m_cx) || (gcBytes - m_LastGCBytes > m_HeapGrowthBytesGCTrigger))
{
#if GC_DEBUG_PRINT
if (JS::IsIncrementalGCInProgress(m_cx))
- printf("An incremental GC cycle is in progress. \n");
+ debug_printf("An incremental GC cycle is in progress. \n");
else
- printf("GC needed because JSGC_BYTES - m_LastGCBytes > m_HeapGrowthBytesGCTrigger \n"
- " JSGC_BYTES: %d KB \n m_LastGCBytes: %d KB \n m_HeapGrowthBytesGCTrigger: %d KB \n",
+ debug_printf("GC needed because JSGC_BYTES - m_LastGCBytes > m_HeapGrowthBytesGCTrigger \n"
+ " JSGC_BYTES: %u KB \n m_LastGCBytes: %u KB \n m_HeapGrowthBytesGCTrigger: %u KB \n gcPressure: %lf% ",
gcBytes / 1024,
m_LastGCBytes / 1024,
- m_HeapGrowthBytesGCTrigger / 1024);
+ m_HeapGrowthBytesGCTrigger / 1024,
+ gcPressure);
#endif
- // A hack to make sure we never exceed the context size because we can't collect the memory
- // fast enough.
- if (gcBytes > m_ContextSize / 2)
- {
- if (JS::IsIncrementalGCInProgress(m_cx))
- {
-#if GC_DEBUG_PRINT
- printf("Finishing incremental GC because gcBytes > m_ContextSize / 2. \n");
-#endif
- PrepareZonesForIncrementalGC();
- JS::FinishIncrementalGC(m_cx, JS::GCReason::API);
- }
- else
- {
- if (gcBytes > m_ContextSize * 0.75)
- {
- ShrinkingGC();
-#if GC_DEBUG_PRINT
- printf("Running shrinking GC because gcBytes > m_ContextSize * 0.75. \n");
-#endif
- }
- else
- {
-#if GC_DEBUG_PRINT
- printf("Running full GC because gcBytes > m_ContextSize / 2. \n");
-#endif
- JS_GC(m_cx);
- }
- }
- }
+ PrepareZonesForIncrementalGC();
+ if (!JS::IsIncrementalGCInProgress(m_cx))
+ JS::StartIncrementalGC(m_cx, GC_NORMAL, JS::GCReason::API, timeBudget);
else
- {
-#if GC_DEBUG_PRINT
- if (!JS::IsIncrementalGCInProgress(m_cx))
- printf("Starting incremental GC \n");
- else
- printf("Running incremental GC slice \n");
-#endif
- PrepareZonesForIncrementalGC();
- if (!JS::IsIncrementalGCInProgress(m_cx))
- JS::StartIncrementalGC(m_cx, GC_NORMAL, JS::GCReason::API, GCSliceTimeBudget);
- else
- JS::IncrementalGCSlice(m_cx, JS::GCReason::API, GCSliceTimeBudget);
- }
+ JS::IncrementalGCSlice(m_cx, JS::GCReason::API, timeBudget);
m_LastGCBytes = gcBytes;
}
}
Index: source/simulation2/Simulation2.cpp
===================================================================
--- source/simulation2/Simulation2.cpp
+++ source/simulation2/Simulation2.cpp
@@ -363,6 +363,8 @@
PROFILE3("sim update");
PROFILE2_ATTR("turn %d", (int)m_TurnNumber);
+ const double turnRealLengthStart = timer_Time();
+
fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000;
/*
@@ -492,20 +494,15 @@
}
}
- // Run the GC occasionally
- // No delay because a lot of garbage accumulates in one turn and in non-visual replays there are
- // much more turns in the same time than in normal games.
- // Every 500 turns we run a shrinking GC, which decommits unused memory and frees all JIT code.
- // Based on testing, this seems to be a good compromise between memory usage and performance.
- // Also check the comment about gcPreserveCode in the ScriptInterface code and this forum topic:
- // http://www.wildfiregames.com/forum/index.php?showtopic=18466&p=300323
- //
+ // Calculate how much time we have this thread idle for before the next turn is scheduled to start
+
// (TODO: we ought to schedule this for a frame where we're not
// running the sim update, to spread the load)
- if (m_TurnNumber % 500 == 0)
- scriptInterface.GetContext()->ShrinkingGC();
- else
- scriptInterface.GetContext()->MaybeIncrementalGC(0.0f);
+
+ const double turnRealLength = timer_Time() - turnRealLengthStart;
+ // Reserve 10 ms for overhead, may be excessive / unneeded.
+ const double timeBudget = turnLength - 10.0 - turnRealLength;
+ scriptInterface.GetContext()->MaybeIncrementalGC(timeBudget);
if (m_EnableOOSLog)
DumpState();

File Metadata

Mime Type
text/plain
Expires
Mon, Sep 23, 2:53 AM (21 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3374417
Default Alt Text
D4758.id20787.diff (10 KB)

Event Timeline