Page Menu
Home
Wildfire Games
Search
Configure Global Search
Log In
Files
F5216707
D4758.id20787.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
D4758.id20787.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D4758: Rearrange GC scheduling for reduced lag
Attached
Detach File
Event Timeline
Log In to Comment