This is an automated email from the ASF dual-hosted git repository.

davydotcom pushed a commit to branch patch-gsp-performance
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 85148d461234e0ed066d562c71f0fa4a01e8633c
Author: David Estes <[email protected]>
AuthorDate: Fri Oct 3 14:43:54 2025 -0400

    First Step in optimizing GSP Performance on Grails 7. Safe small 
optimizations to start
---
 .../src/main/groovy/org/grails/gsp/GroovyPage.java | 31 ++++++++++------
 .../groovy/org/grails/gsp/GroovyPageMetaInfo.java  | 41 ++++++++++++++++++++++
 .../groovy/org/grails/gsp/GroovyPageWritable.java  | 20 +++++------
 .../grails/gsp/compiler/GroovyPageCompiler.groovy  | 14 +++++---
 .../org/grails/gsp/compiler/GroovyPageParser.java  |  7 ++--
 5 files changed, 85 insertions(+), 28 deletions(-)

diff --git a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPage.java 
b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPage.java
index d59a5d54d2..cb720573b7 100644
--- a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPage.java
+++ b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPage.java
@@ -130,14 +130,19 @@ public abstract class GroovyPage extends Script {
         throw new IllegalStateException("Setting out in page isn't allowed.");
     }
 
-    public void initRun(Writer target, OutputContext outputContext, 
GroovyPageMetaInfo metaInfo) {
-        OutputEncodingStackAttributes.Builder attributesBuilder = new 
OutputEncodingStackAttributes.Builder();
+    public void initCommonRun(GroovyPageMetaInfo metaInfo) {
         if (metaInfo != null) {
+            setHtmlParts(metaInfo.getHtmlParts());
+            setHtmlPartsSet(metaInfo.getHtmlPartsSet());
             setJspTags(metaInfo.getJspTags());
             setJspTagLibraryResolver(metaInfo.getJspTagLibraryResolver());
             setGspTagLibraryLookup(metaInfo.getTagLibraryLookup());
-            setHtmlParts(metaInfo.getHtmlParts());
             setPluginContextPath(metaInfo.getPluginPath());
+        }
+    }
+    public void initRun(Writer target, OutputContext outputContext, 
GroovyPageMetaInfo metaInfo) {
+        OutputEncodingStackAttributes.Builder attributesBuilder = new 
OutputEncodingStackAttributes.Builder();
+        if (metaInfo != null) {
             attributesBuilder.outEncoder(metaInfo.getOutEncoder());
             attributesBuilder.staticEncoder(metaInfo.getStaticEncoder());
             
attributesBuilder.expressionEncoder(metaInfo.getExpressionEncoder());
@@ -507,6 +512,14 @@ public abstract class GroovyPage extends Script {
         staticOut.write(htmlParts[partNumber]);
     }
 
+    /**
+     * Shorthand for printHtmlPart to reduce class size
+     * @param partNumber
+     */
+    public final void h(final int partNumber) {
+        staticOut.write(htmlParts[partNumber]);
+    }
+
     /**
      * Sets the JSP tags used by this GroovyPage instance
      *
@@ -527,14 +540,10 @@ public abstract class GroovyPage extends Script {
 
     public void setHtmlParts(String[] htmlParts) {
         this.htmlParts = htmlParts;
-        this.htmlPartsSet = new HashSet<>();
-        if (htmlParts != null) {
-            for (String htmlPart : htmlParts) {
-                if (htmlPart != null) {
-                    htmlPartsSet.add(System.identityHashCode(htmlPart));
-                }
-            }
-        }
+    }
+
+    public void setHtmlPartsSet(Set<Integer> htmlPartsSet) {
+        this.htmlPartsSet = htmlPartsSet;
     }
 
     public final OutputEncodingStack getOutputStack() {
diff --git 
a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageMetaInfo.java 
b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageMetaInfo.java
index 0cd8efd298..310a49af04 100644
--- a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageMetaInfo.java
+++ b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageMetaInfo.java
@@ -22,7 +22,10 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Writer;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Modifier;
 import java.net.URL;
 import java.net.URLConnection;
@@ -67,14 +70,17 @@ public class GroovyPageMetaInfo implements 
GrailsApplicationAware {
     private static final Log LOG = LogFactory.getLog(GroovyPageMetaInfo.class);
     private TagLibraryLookup tagLibraryLookup;
     private TagLibraryResolver jspTagLibraryResolver;
+    private ThreadLocal<SoftReference<GroovyPage>> pageInstance = new 
ThreadLocal<>();
 
     private boolean precompiledMode = false;
     private Class<?> pageClass;
+    private Constructor<?> pageClassConstructor;
     private long lastModified;
     private InputStream groovySource;
     private String contentType;
     private int[] lineNumbers;
     private String[] htmlParts;
+    private Set<Integer> htmlPartsSet;
     @SuppressWarnings("rawtypes")
     private Map jspTags = Collections.emptyMap();
     private GroovyPagesException compilationException;
@@ -115,6 +121,11 @@ public class GroovyPageMetaInfo implements 
GrailsApplicationAware {
         this();
         precompiledMode = true;
         this.pageClass = pageClass;
+        try {
+            this.pageClassConstructor = pageClass.getConstructor();
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
         contentType = (String) 
ReflectionUtils.getField(ReflectionUtils.findField(pageClass, 
GroovyPageParser.CONSTANT_NAME_CONTENT_TYPE), null);
         jspTags = (Map) 
ReflectionUtils.getField(ReflectionUtils.findField(pageClass, 
GroovyPageParser.CONSTANT_NAME_JSP_TAGS), null);
         lastModified = (Long) 
ReflectionUtils.getField(ReflectionUtils.findField(pageClass, 
GroovyPageParser.CONSTANT_NAME_LAST_MODIFIED), null);
@@ -282,8 +293,26 @@ public class GroovyPageMetaInfo implements 
GrailsApplicationAware {
         return pageClass;
     }
 
+    public GroovyPage getPageClassInstance() throws InstantiationException, 
IllegalAccessException, NoSuchMethodException, InvocationTargetException {
+        SoftReference<GroovyPage> pageSoftRef = pageInstance.get();
+
+        GroovyPage pageCacheEntry = pageSoftRef != null ? pageSoftRef.get() : 
null;
+        if(pageCacheEntry == null) {
+            LOG.info("Loading Page: " + pageClass.getName());
+            pageCacheEntry = (GroovyPage) pageClassConstructor.newInstance();
+            pageCacheEntry.initCommonRun(this);
+            pageInstance.set(new SoftReference<>(pageCacheEntry));
+        }
+        return pageCacheEntry;
+    }
+
     public void setPageClass(Class<?> pageClass) {
         this.pageClass = pageClass;
+        try {
+            this.pageClassConstructor = pageClass.getConstructor();
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
         initializePluginPath();
     }
 
@@ -359,6 +388,18 @@ public class GroovyPageMetaInfo implements 
GrailsApplicationAware {
 
     public void setHtmlParts(String[] htmlParts) {
         this.htmlParts = htmlParts;
+        this.htmlPartsSet = new HashSet<>();
+        if (htmlParts != null) {
+            for (String htmlPart : htmlParts) {
+                if (htmlPart != null) {
+                    htmlPartsSet.add(System.identityHashCode(htmlPart));
+                }
+            }
+        }
+    }
+
+    Set<Integer> getHtmlPartsSet() {
+        return this.htmlPartsSet;
     }
 
     public void applyLastModifiedFromResource(Resource resource) {
diff --git 
a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageWritable.java 
b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageWritable.java
index 6fb297db3c..4f04b9e043 100644
--- a/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageWritable.java
+++ b/grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageWritable.java
@@ -26,13 +26,10 @@ import java.io.Reader;
 import java.io.Writer;
 import java.util.LinkedHashMap;
 import java.util.Map;
-
 import groovy.lang.Binding;
 import groovy.lang.Writable;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import grails.util.Environment;
 import org.grails.taglib.AbstractTemplateVariableBinding;
 import org.grails.taglib.TemplateVariableBinding;
@@ -47,7 +44,8 @@ import org.grails.taglib.encoder.OutputContextLookup;
  * @since 0.5
  */
 public class GroovyPageWritable implements Writable {
-    private static final Log LOG = LogFactory.getLog(GroovyPageWritable.class);
+    //get SLF4J logger instead
+    private static final Logger LOG = 
LoggerFactory.getLogger(GroovyPageWritable.class);
     private static final String GSP_NONE_CODEC_NAME = "none";
     private GroovyPageMetaInfo metaInfo;
     private OutputContextLookup outputContextLookup;
@@ -125,9 +123,7 @@ public class GroovyPageWritable implements Writable {
                 // only try to set content type when evaluating top level GSP
                 boolean contentTypeAlreadySet = 
outputContext.isContentTypeAlreadySet();
                 if (!contentTypeAlreadySet) {
-                    if (LOG.isDebugEnabled()) {
-                        LOG.debug("Writing output with content type: " + 
metaInfo.getContentType());
-                    }
+                    LOG.debug("Writing output with content type: {}", 
metaInfo.getContentType());
                     outputContext.setContentType(metaInfo.getContentType()); 
// must come before response.getWriter()
                 }
             }
@@ -139,7 +135,9 @@ public class GroovyPageWritable implements Writable {
 
             GroovyPage page = null;
             try {
-                page = (GroovyPage) metaInfo.getPageClass().newInstance();
+
+                page = metaInfo.getPageClassInstance();
+
             } catch (Exception e) {
                 throw new GroovyPagesException("Problem instantiating page 
class", e);
             }
@@ -294,4 +292,6 @@ public class GroovyPageWritable implements Writable {
             in.close();
         }
     }
+
+
 }
diff --git 
a/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy
 
b/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy
index 3a9917a415..ba91149922 100644
--- 
a/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy
+++ 
b/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy
@@ -31,8 +31,10 @@ import org.codehaus.groovy.control.CompilationUnit
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.control.Phases
 
-import org.apache.commons.logging.Log
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.apache.commons.logging.LogFactory
+import org.slf4j.Logger
 
 import org.springframework.core.CollectionFactory
 
@@ -42,9 +44,12 @@ import org.grails.config.CodeGenConfig
 import org.grails.gsp.GroovyPageMetaInfo
 import org.grails.gsp.compiler.transform.GroovyPageInjectionOperation
 import org.grails.taglib.encoder.OutputEncodingSettings
-
+import org.grails.gsp.GroovyPage
 /**
- * Used to compile GSP files into a specified target directory.
+ * Used to compile GSP files into a specified target directory. The compiler 
creates 3 files per page.
+ * Firstly, it generates a {@link GroovyPage} derived class which is then 
compiled to a .class file.
+ * It also will generate a "_html.data" and a "_linenumbers.data" file which 
contain the static HTML parts of the page.
+ * These are read at runtime by the {@link 
org.grails.gsp.GroovyPagesTemplateEngine} class.
  *
  * @author Graeme Rocher
  * @since 1.2
@@ -52,7 +57,7 @@ import org.grails.taglib.encoder.OutputEncodingSettings
 @CompileStatic
 class GroovyPageCompiler {
 
-    private static final Log LOG = LogFactory.getLog(GroovyPageCompiler)
+    private static final Logger LOG = 
LoggerFactory.getLogger(GroovyPageCompiler)
 
     private Map compileGSPRegistry = [:]
     private Object mutexObject = new Object()
@@ -238,7 +243,6 @@ class GroovyPageCompiler {
                 CompilationUnit unit = new CompilationUnit(compilerConfig, 
null, classLoader)
                 unit.addPhaseOperation(operation, Phases.CANONICALIZATION)
                 unit.addSource(gspgroovyfile.name, gsptarget.toString())
-                // unit.addSource(gspgroovyfile)
                 unit.compile()
             }
         }
diff --git 
a/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageParser.java 
b/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageParser.java
index 2f7040d24a..92b5672033 100644
--- 
a/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageParser.java
+++ 
b/grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageParser.java
@@ -66,7 +66,10 @@ import org.grails.taglib.encoder.OutputEncodingSettings;
 /**
  * NOTE: Based on work done by the GSP standalone project 
(https://gsp.dev.java.net/).
  * <p>
- * Parsing implementation for GSP files
+ * Parsing implementation for GSP files. This class is responsible for parsing 
.gsp extension files
+ * and converting them to Groovy source code that extends the {@link 
GroovyPage} base class. It also gathers
+ * taglib references and html parts (contants with no modification) and writes 
them to a separate file.
+ * For improved debugging, line number references are also stored for easier 
exception tracing.
  *
  * @author Troy Heninger
  * @author Graeme Rocher
@@ -643,7 +646,7 @@ public class GroovyPageParser implements Tokens {
     }
 
     private void htmlPartPrintlnRaw(int partNumber) {
-        out.print("printHtmlPart(");
+        out.print("h(");
         out.print(String.valueOf(partNumber));
         out.print(")");
         out.println();

Reply via email to