Skip to content

Commit

Permalink
fix #6080
Browse files Browse the repository at this point in the history
add knobs
  • Loading branch information
rt committed Nov 26, 2018
1 parent 5845552 commit 82a205f
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 27 deletions.
2 changes: 2 additions & 0 deletions rts/Lua/LuaAllocState.h
@@ -1,3 +1,5 @@
/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */

#ifndef SPRING_LUA_ALLOC_STATE_H
#define SPRING_LUA_ALLOC_STATE_H

Expand Down
2 changes: 2 additions & 0 deletions rts/Lua/LuaContextData.h
Expand Up @@ -4,6 +4,7 @@
#define LUA_CONTEXT_DATA_H

#include "Lua/LuaAllocState.h"
#include "Lua/LuaGarbageCollectCtrl.h"
#include "LuaMemPool.h"
#if (!defined(UNITSYNC) && !defined(DEDICATED))
#include "LuaShaders.h"
Expand Down Expand Up @@ -96,6 +97,7 @@ struct luaContextData {
int selectTeam;

SLuaAllocState allocState;
SLuaGarbageCollectCtrl gcCtrl;

#if (!defined(UNITSYNC) && !defined(DEDICATED))
// NOTE:
Expand Down
26 changes: 26 additions & 0 deletions rts/Lua/LuaGarbageCollectCtrl.h
@@ -0,0 +1,26 @@
/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */

#ifndef SPRING_LUA_GARBAGE_COLLECT_CTRL_H
#define SPRING_LUA_GARBAGE_COLLECT_CTRL_H

#include <limits>

struct SLuaGarbageCollectCtrl {
// maximum number of lua_gc calls made in each CollectGarbage loop
int itersPerBatch = std::numeric_limits<int>::max();

// number of steps executed by a single lua_gc call
int numStepsPerIter = 10;
int minStepsPerIter = 1;
int maxStepsPerIter = 10000;

// CollectGarbage loop runtime bounds, in milliseconds
float minLoopRunTime = 0.0f;
float maxLoopRunTime = 100.0f;

float baseRunTimeMult = 0.0f;
float baseMemLoadMult = 0.0f;
};

#endif

52 changes: 28 additions & 24 deletions rts/Lua/LuaHandle.cpp
Expand Up @@ -52,6 +52,10 @@
#include <string>


CONFIG(float, LuaGarbageCollectionMemLoadMult).defaultValue(1.33f).minimumValue(1.0f).maximumValue(100.0f);
CONFIG(float, LuaGarbageCollectionRunTimeMult).defaultValue(5.0f).minimumValue(1.0f).description("in milliseconds");


static spring::unsynced_set<const lua_State*> SYNCED_LUAHANDLE_STATES;
static spring::unsynced_set<const lua_State*> UNSYNCED_LUAHANDLE_STATES;
const spring::unsynced_set<const lua_State*>* LUAHANDLE_STATES[2] = {&UNSYNCED_LUAHANDLE_STATES, &SYNCED_LUAHANDLE_STATES};
Expand Down Expand Up @@ -94,13 +98,17 @@ CLuaHandle::CLuaHandle(const string& _name, int _order, bool _userMode, bool _sy
, killMe(false)
// no shared pool for LuaIntro to protect against LoadingMT=1
// do not use it for LuaMenu either; too many blocks allocated
// by *other* states end up not being recycled so we clear the
// shared pool on reload
// by *other* states end up not being recycled which presently
// forces clearing the shared pool on reload
, D(_name != "LuaIntro" && name != "LuaMenu", true)
, callinErrors(0)
{
D.owner = this;
D.synced = _synced;

D.gcCtrl.baseMemLoadMult = configHandler->GetFloat("LuaGarbageCollectionMemLoadMult");
D.gcCtrl.baseRunTimeMult = configHandler->GetFloat("LuaGarbageCollectionRunTimeMult");

L = LUA_OPEN(&D);

LUA_INSERT_STATE(L, LUAHANDLE_STATES[D.synced]);
Expand Down Expand Up @@ -2475,54 +2483,49 @@ void CLuaHandle::DownloadProgress(int ID, long downloaded, long total)
/******************************************************************************/
/******************************************************************************/

CONFIG(float, LuaGarbageCollectionMemLoadMult).defaultValue(1.33f).minimumValue(1.0f).maximumValue(100.0f);
CONFIG(float, LuaGarbageCollectionRunTimeMult).defaultValue(5.0f).minimumValue(1.0f).description("in milliseconds");


void CLuaHandle::CollectGarbage()
{
static const float gcMemLoadMult = configHandler->GetFloat("LuaGarbageCollectionMemLoadMult");
static const float gcRunTimeMult = configHandler->GetFloat("LuaGarbageCollectionRunTimeMult");
const float gcMemLoadMult = D.gcCtrl.baseMemLoadMult;
const float gcRunTimeMult = D.gcCtrl.baseRunTimeMult;

if (spring_lua_alloc_skip_gc(gcMemLoadMult))
return;

lua_lock(L_GC);
SetHandleRunning(L_GC, true);

// note: total footprint INCLUDING garbage
int luaMemFootPrintKB = lua_gc(L_GC, LUA_GCCOUNT, 0);
int gcItersInBatch = 0;

static int gcStepsPerIter = 10;
// note: total footprint INCLUDING garbage, in KB
int gcMemFootPrint = lua_gc(L_GC, LUA_GCCOUNT, 0);
int gcItersInBatch = 0;
int& gcStepsPerIter = D.gcCtrl.numStepsPerIter;

// if gc runs at a fixed rate, the upper limit to base runtime will
// quickly be reached since Lua's footprint can easily exceed 100MB
// and OOM exceptions become a concern when catching up
// OTOH if gc is tied to sim-speed the increased number of calls can
// mean too much time is spent on it, must weigh the per-call period
const float gcSpeedFactor = Clamp(gs->speedFactor * (1 - gs->PreSimFrame()) * (1 - gs->paused), 1.0f, 50.0f);
const float gcBaseRunTime = smoothstep(10.0f, 100.0f, luaMemFootPrintKB / 1024);
const float gcLoopRunTime = (gcBaseRunTime * gcRunTimeMult) / gcSpeedFactor;
const float gcBaseRunTime = smoothstep(10.0f, 100.0f, gcMemFootPrint / 1024);
const float gcLoopRunTime = Clamp((gcBaseRunTime * gcRunTimeMult) / gcSpeedFactor, D.gcCtrl.minLoopRunTime, D.gcCtrl.maxLoopRunTime);

const spring_time startTime = spring_gettime();
const spring_time endTime = startTime + spring_msecs(gcLoopRunTime);

// collect garbage until time runs out
while (spring_gettime() < endTime) {
// perform GC cycles until time runs out or iteration-limit is reached
while (gcItersInBatch < D.gcCtrl.itersPerBatch && spring_gettime() < endTime) {
gcItersInBatch++;

if (!lua_gc(L_GC, LUA_GCSTEP, gcStepsPerIter))
continue;

// garbage-collection cycle finished
const int luaMemFootPrintNow = lua_gc(L_GC, LUA_GCCOUNT, 0);
const int luaMemFootPrintDif = luaMemFootPrintNow - luaMemFootPrintKB;
const int gcMemFootPrintNow = lua_gc(L_GC, LUA_GCCOUNT, 0);
const int gcMemFootPrintDif = gcMemFootPrintNow - gcMemFootPrint;

luaMemFootPrintKB = luaMemFootPrintNow;
gcMemFootPrint = gcMemFootPrintNow;

// early-exit if cycle didn't free any memory
if (luaMemFootPrintDif == 0)
if (gcMemFootPrintDif == 0)
break;
}

Expand All @@ -2536,10 +2539,11 @@ void CLuaHandle::CollectGarbage()

if (gcStepsPerIter > 1 && gcItersInBatch > 0) {
// runtime optimize number of steps to process in a batch
const float avgTimePerLoopIter = (finishTime - startTime).toMilliSecsf() / gcItersInBatch;
const float avgLoopIterTime = (finishTime - startTime).toMilliSecsf() / gcItersInBatch;

gcStepsPerIter -= (avgTimePerLoopIter > (gcRunTimeMult * 0.150f));
gcStepsPerIter += (avgTimePerLoopIter < (gcRunTimeMult * 0.075f));
gcStepsPerIter -= (avgLoopIterTime > (gcRunTimeMult * 0.150f));
gcStepsPerIter += (avgLoopIterTime < (gcRunTimeMult * 0.075f));
gcStepsPerIter = Clamp(gcStepsPerIter, D.gcCtrl.minStepsPerIter, D.gcCtrl.maxStepsPerIter);
}

eventHandler.DbgTimingInfo(TIMING_GC, startTime, finishTime);
Expand Down
20 changes: 20 additions & 0 deletions rts/Lua/LuaUnsyncedCtrl.cpp
Expand Up @@ -257,6 +257,7 @@ bool LuaUnsyncedCtrl::PushEntries(lua_State* L)
REGISTER_LUA_CFUNC(SetLogSectionFilterLevel);

REGISTER_LUA_CFUNC(ClearWatchDogTimer);
REGISTER_LUA_CFUNC(GarbageCollectCtrl);

REGISTER_LUA_CFUNC(PreloadUnitDefModel);
REGISTER_LUA_CFUNC(PreloadFeatureDefModel);
Expand Down Expand Up @@ -2899,6 +2900,25 @@ int LuaUnsyncedCtrl::ClearWatchDogTimer(lua_State* L) {
return 0;
}

int LuaUnsyncedCtrl::GarbageCollectCtrl(lua_State* L) {
luaContextData* ctxData = GetLuaContextData(L);
SLuaGarbageCollectCtrl& gcCtrl = ctxData->gcCtrl;

gcCtrl.itersPerBatch = std::max(0, luaL_optint(L, 1, gcCtrl.itersPerBatch));

gcCtrl.numStepsPerIter = std::max(0, luaL_optint(L, 2, gcCtrl.numStepsPerIter));
gcCtrl.minStepsPerIter = std::max(0, luaL_optint(L, 3, gcCtrl.minStepsPerIter));
gcCtrl.maxStepsPerIter = std::max(0, luaL_optint(L, 4, gcCtrl.maxStepsPerIter));

gcCtrl.minLoopRunTime = std::max(0.0f, luaL_optfloat(L, 5, gcCtrl.minLoopRunTime));
gcCtrl.maxLoopRunTime = std::max(0.0f, luaL_optfloat(L, 6, gcCtrl.maxLoopRunTime));

gcCtrl.baseRunTimeMult = std::max(0.0f, luaL_optfloat(L, 7, gcCtrl.baseRunTimeMult));
gcCtrl.baseMemLoadMult = std::max(0.0f, luaL_optfloat(L, 8, gcCtrl.baseMemLoadMult));

return 0;
}

/******************************************************************************/
/******************************************************************************/

Expand Down
1 change: 1 addition & 0 deletions rts/Lua/LuaUnsyncedCtrl.h
Expand Up @@ -163,6 +163,7 @@ class LuaUnsyncedCtrl {
static int SetLogSectionFilterLevel(lua_State* L);

static int ClearWatchDogTimer(lua_State* L);
static int GarbageCollectCtrl(lua_State* L);

static int PreloadUnitDefModel(lua_State* L);
static int PreloadFeatureDefModel(lua_State* L);
Expand Down
3 changes: 2 additions & 1 deletion rts/lib/gflags/src/gflags.cc
Expand Up @@ -136,7 +136,8 @@ using std::string;
using std::vector;

static void flush_exit(int ec) {
// exit(1) is not a normal termination, force a flush
// exit(1) is supposed to be a normal termination, but force a flush
std::fprintf(stdout, "[gflags::%s]\n", __func__);
std::fflush(stdout);
std::exit(ec);
}
Expand Down
6 changes: 4 additions & 2 deletions rts/lib/lua/include/LuaUser.cpp
Expand Up @@ -304,8 +304,10 @@ void spring_lua_alloc_get_stats(SLuaAllocState* state)

bool spring_lua_alloc_skip_gc(float gcLoadMult)
{
// if memory load is smaller than 1/gcLoadMult run the GC less frequently
return (lguRNG.NextFloat() > (gcLoadMult * float(gLuaAllocState.allocedBytes.load()) / float(SLuaAllocState::maxAllocedBytes)));
// randomly skip a GC cycle with probability 1 - (weighted memory load ratio)
const float rawLoadRatio = float(gLuaAllocState.allocedBytes.load()) / float(SLuaAllocState::maxAllocedBytes);
const float modLoadRatio = gcLoadMult * rawLoadRatio;
return (lguRNG.NextFloat() > modLoadRatio);
}

bool spring_lua_alloc_get_error(SLuaAllocError* error)
Expand Down

0 comments on commit 82a205f

Please sign in to comment.