From 2e46a1e9ecbd9a2996aa84601ab6721bb4a11bf7 Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Thu, 21 Nov 2024 13:32:12 +0100 Subject: [PATCH] LDEV-5156 allow configuring the default capacity for local and arguments scopes https://luceeserver.atlassian.net/browse/LDEV-5156 --- .../runtime/type/scope/ArgumentImpl.java | 8 +- .../lucee/runtime/type/scope/LocalImpl.java | 9 +- .../java/resource/setting/sysprop-envvar.json | 8 ++ test/tickets/LDEV5156.cfc | 105 ++++++++++++++++++ 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 test/tickets/LDEV5156.cfc diff --git a/core/src/main/java/lucee/runtime/type/scope/ArgumentImpl.java b/core/src/main/java/lucee/runtime/type/scope/ArgumentImpl.java index 647d3ca416..37fefbf780 100755 --- a/core/src/main/java/lucee/runtime/type/scope/ArgumentImpl.java +++ b/core/src/main/java/lucee/runtime/type/scope/ArgumentImpl.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Set; +import lucee.commons.io.SystemUtil; import lucee.commons.lang.CFTypes; import lucee.runtime.PageContext; import lucee.runtime.config.NullSupportHelper; @@ -63,6 +64,11 @@ public final class ArgumentImpl extends ScopeSupport implements Argument, ArrayP private boolean bind; private Set functionArgumentNames; + private static int argumentInitialCapacity; + + static { + argumentInitialCapacity = Caster.toIntValue(SystemUtil.getSystemPropOrEnvVar("lucee.scope.arguments.capacity", "16"), 16); + } // private boolean supportFunctionArguments; /** @@ -70,7 +76,7 @@ public final class ArgumentImpl extends ScopeSupport implements Argument, ArrayP */ public ArgumentImpl() { // super("arguments", SCOPE_ARGUMENTS, StructImpl.TYPE_LINKED, 4); - super("arguments", SCOPE_ARGUMENTS, StructImpl.TYPE_LINKED_NOT_SYNC, 4); + super("arguments", SCOPE_ARGUMENTS, StructImpl.TYPE_LINKED_NOT_SYNC, argumentInitialCapacity); } @Override diff --git a/core/src/main/java/lucee/runtime/type/scope/LocalImpl.java b/core/src/main/java/lucee/runtime/type/scope/LocalImpl.java index 9c2e2978d3..3a40435bbd 100755 --- a/core/src/main/java/lucee/runtime/type/scope/LocalImpl.java +++ b/core/src/main/java/lucee/runtime/type/scope/LocalImpl.java @@ -18,17 +18,24 @@ **/ package lucee.runtime.type.scope; +import lucee.commons.io.SystemUtil; import lucee.runtime.PageContext; +import lucee.runtime.op.Caster; import lucee.runtime.type.Struct; public final class LocalImpl extends ScopeSupport implements Scope, Local { private static final long serialVersionUID = -7155406303949924403L; private boolean bind; + private static int localInitialCapacity; + + static { + localInitialCapacity = Caster.toIntValue(SystemUtil.getSystemPropOrEnvVar("lucee.scope.local.capacity", "16"), 16); + } public LocalImpl() { // super("local", Scope.SCOPE_LOCAL, Struct.TYPE_SYNC, 4); - super("local", Scope.SCOPE_LOCAL, Struct.TYPE_REGULAR, 4); + super("local", Scope.SCOPE_LOCAL, Struct.TYPE_REGULAR, localInitialCapacity); } @Override diff --git a/core/src/main/java/resource/setting/sysprop-envvar.json b/core/src/main/java/resource/setting/sysprop-envvar.json index 2b1526d7a9..25ba8c6a70 100644 --- a/core/src/main/java/resource/setting/sysprop-envvar.json +++ b/core/src/main/java/resource/setting/sysprop-envvar.json @@ -378,5 +378,13 @@ "sysprop": "lucee.dump.threads", "envvar": "LUCEE_DUMP_THREADS", "desc": "Used for debugging, when enabled, it will dump out running threads to the console via the background controller thread" + "sysprop": "lucee.scope.local.capacity", + "envvar": "LUCEE_SCOPE_LOCAL_CAPACITY", + "desc": "Sets the initial capacity (size) for the local scope hashmap" + }, + { + "sysprop": "lucee.scope.arguments.capacity", + "envvar": "LUCEE_SCOPE_ARGUMENTS_CAPACITY", + "desc": "Sets the initial capacity (size) for the arguments scope hashmap" } ] \ No newline at end of file diff --git a/test/tickets/LDEV5156.cfc b/test/tickets/LDEV5156.cfc new file mode 100644 index 0000000000..51db1c6cca --- /dev/null +++ b/test/tickets/LDEV5156.cfc @@ -0,0 +1,105 @@ +component extends = "org.lucee.cfml.test.LuceeTestCase" skip="true" { + + /* + run with + + -DtestFilter="5156" -DUseEpsilonGC="true" + + so it runs alone and without any GC for better memory info + */ + + function testStructSize() localmode=true { + rounds = 25000; + TYPE_LINKED_NOT_SYNC = 100; //args + TYPE_REGULAR = 3; // local + systemOutput(" ", true); + _testStructSize(fill=0, rounds=rounds); + systemOutput(" ---warm up complete--- ", true); + systemOutput(" --- test TYPE_REGULAR / local scope --- ", true); + _testStructSize(fill=0, rounds=rounds, structType=TYPE_REGULAR); + _testStructSize(fill=2, rounds=rounds, structType=TYPE_REGULAR); + _testStructSize(fill=4, rounds=rounds, structType=TYPE_REGULAR); + _testStructSize(fill=8, rounds=rounds, structType=TYPE_REGULAR); + + systemOutput(" --- test TYPE_LINKED_NOT_SYNC / arguments scope --- ", true); + _testStructSize(fill=0, rounds=rounds, structType=TYPE_LINKED_NOT_SYNC); + _testStructSize(fill=2, rounds=rounds, structType=TYPE_LINKED_NOT_SYNC); + _testStructSize(fill=4, rounds=rounds, structType=TYPE_LINKED_NOT_SYNC); + _testStructSize(fill=8, rounds=rounds, structType=TYPE_LINKED_NOT_SYNC); + } + + private function _testStructSize(fill, rounds, structType) localmode=true { + loop list="4,8,16,32" item="initialCapacity"{ + //createObject( "java", "java.lang.System" ).gc(); + //systemOutput(" ", true); + //systemOutput("testing initialCapacity: #initialCapacity#, fill: #arguments.fill# ", true); + s = getTickCount(); + start = reportMem( "", {}, "start" ); + a=[]; + while(true){ + st = createObject("java","lucee.runtime.type.StructImpl").init(int(arguments.structType), initialCapacity); + for ( i=1; i < arguments.fill ; i++ ) + st["fill#i#"]=1; // populating the struct, potentially beyond fill factor, triggering a resize + for ( i=1; i < arguments.fill ; i++ ) + st["fill#i#"]=2; // also test accessing the struct + arrayAppend(a, st); + /* + if (arrayLen(a) mod 5000 eq 0){ + systemOutput(a.len() & " "); + // echo(a.len() & ", "); + flush; + } + */ + if (len(a) == rounds) break; + } + //systemOutput(" ", true); + if (initialCapacity < 10) initialCapacity = " #initialCapacity#"; // + systemOutput("initialCapacity: #initialCapacity#, fill: #arguments.fill#, rounds: #numberformat(rounds)#, took #numberformat(getTickCount()-s)# ms, "); + r = reportMem( "", start.usage, "st" ); + for (rr in r.report){ + if (rr contains "G1" || rr contains "Epsilon") + systemOutput(rr, true); + } + //systemOutput(r.report, true); + + //dump(getTickCount()-s & "ms"); + //dump(url.size); + //dump(r.report); + //systemOutput(" ", true); + } + + systemOutput("----", true); + + } + + private function reportMem ( string type, struct prev={}, string name="" ) { + var qry = getMemoryUsage( type ); + var report = []; + var used = { name: arguments.name }; + querySort(qry,"type,name"); + loop query=qry { + if (qry.max == -1) + var perc = 0; + else + var perc = int( ( qry.used / qry.max ) * 100 ); + //if(qry.max<0 || qry.used<0 || perc<90) continue; + //if(qry.max<0 || qry.used<0 || perc<90) continue; + var rpt = replace(ucFirst(qry.type), '_', ' ') + & " " & qry.name & ": " & numberFormat(perc) & "%, " & numberFormat( qry.used / 1024 / 1024 ) & " Mb"; + if ( structKeyExists( arguments.prev, qry.name ) ) { + var change = numberFormat( (qry.used - arguments.prev[ qry.name ] ) / 1024 / 1024 ); + if ( change gt 0 ) { + rpt &= ", (+ " & change & "Mb )"; + } else if ( change lt 0 ) { + rpt &= ", ( " & change & "Mb )"; + } + } + arrayAppend( report, rpt ); + used[ qry.name ] = qry.used; + } + return { + report: report, + usage: used + }; + } +}