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

adelbene pushed a commit to branch wicket-9.x
in repository https://gitbox.apache.org/repos/asf/wicket.git


The following commit(s) were added to refs/heads/wicket-9.x by this push:
     new f14aa98da2 WICKET-6992 Reduce object creation and wasted memory in Url 
toString() methods
f14aa98da2 is described below

commit f14aa98da2839e027a31e7cc36459438016afe4a
Author: Alan Stange <[email protected]>
AuthorDate: Wed May 4 22:07:24 2022 -0400

    WICKET-6992 Reduce object creation and wasted memory in Url toString() 
methods
    
    The Url class generates a lot of StringBuilders and temporary buffers.  
It's a bit frustrating form a performance
    standpoint as the builders often need to resize, further compounding the 
performance impact.
    
    - QueryParameter.toString(...) can be updated to not generate any 
extraneous garbage objects using JEP 280 String
    concatenation.   This is faster than the original code and avoids extra 
garbage object creation.
    
    - enhance Url.getPath() by using getPathInternal() which allows the code to 
return an optimal result.  "" for no segments;
    the String for 1 segment, and the resulting StringBuilder for more than 2 
segments.  This avoids copying the internal byte[]/char[]
    array yet again and wasting more memory.  The callers of this method can 
then use the returned object in the way that best
    suits them.   NB the case for two segments should probably also be handled 
by explicit invocation of
       seg1 + / + seg2 as is would be faster and save memory.
    Note that we now compute the size of the StringBuilder in advance, avoiding 
the repeated resizing of the builder that had been
    happening in the original code.
    
    - enhance getQueryString() in a similar manner.  Handle the n=0 and n=1 
common cases without generating any extra garbage.
    Size the StringBuilder a little better in this case as well.  Again, the 
n=2 case should probably with String concatenation.
    
    The Url.toString(mode, charset) method is a bit more complicated.  It is 
rarely called (if ever?) with StringMode=FULL and it
    appears as if fragment is commonly unset as well, so handle that 99% code 
path at the top efficiently without any
    wasted memory.   If not, then fall through into the original code.    The 
call to getPathInternal here avoids making a copy of
    that array.
    
    Note that it might be much more efficient to allow this code to pass a 
StringBuilder instance around.  This would avoid some
    repeated copying of byte[]/char[] arrays.   These changes here greatly 
reduce the memory waste, but more can still be done.
    
    I'm a bit surprised about the missing ).  Must have fat fingered it at the 
last second.
    
    I'm a bit surprised about the missing ).  Must have fat fingered it at the 
last second.
    
    I'm not a fan of the brace style used in this project... ;-)
    
    Two changes in response to the feedback.
    - cache the result of a function call
    - use charAt() and avoid a toString() call.
    
    Fix code formatting.
---
 .../main/java/org/apache/wicket/request/Url.java   | 96 ++++++++++++++++------
 .../apache/wicket/util/encoding/UrlEncoder.java    |  2 +-
 2 files changed, 70 insertions(+), 28 deletions(-)

diff --git a/wicket-request/src/main/java/org/apache/wicket/request/Url.java 
b/wicket-request/src/main/java/org/apache/wicket/request/Url.java
index 8ccc8e8d99..ab505f5497 100755
--- a/wicket-request/src/main/java/org/apache/wicket/request/Url.java
+++ b/wicket-request/src/main/java/org/apache/wicket/request/Url.java
@@ -747,8 +747,28 @@ public class Url implements Serializable
         */
        public String toString(StringMode mode, Charset charset)
        {
-               StringBuilder result = new StringBuilder();
-               final String path = getPath(charset);
+               // this method is rarely called with StringMode == FULL.
+
+               final CharSequence path = getPathInternal(charset);
+               final String queryString = getQueryString(charset);
+               String _fragment = getFragment();
+
+               // short circuit all the processing in the most common cases
+               if (StringMode.FULL != mode && Strings.isEmpty(_fragment))
+               {
+                       if (queryString == null)
+                       {
+                               return path.toString();
+                       }
+                       else
+                       {
+                               return path + "?" + queryString;
+                       }
+               }
+
+               // fall through into the traditional code path
+
+               StringBuilder result = new StringBuilder(64);
 
                if (StringMode.FULL == mode)
                {
@@ -781,22 +801,19 @@ public class Url implements Serializable
                                        StringMode.FULL.name() + " mode because 
it has a `..` segment: " + toString());
                        }
 
-                       if (!path.startsWith("/"))
+                       if (!path.isEmpty() && !(path.charAt(0) == '/'))
                        {
                                result.append('/');
                        }
-
                }
 
                result.append(path);
 
-               final String queryString = getQueryString(charset);
                if (queryString != null)
                {
                        result.append('?').append(queryString);
                }
 
-               String _fragment = getFragment();
                if (Strings.isEmpty(_fragment) == false)
                {
                        result.append('#').append(_fragment);
@@ -1021,14 +1038,15 @@ public class Url implements Serializable
                 */
                public String toString(final Charset charset)
                {
-                       StringBuilder result = new StringBuilder();
-                       result.append(encodeParameter(getName(), charset));
-                       if (!Strings.isEmpty(getValue()))
+                       String value = getValue();
+                       if (Strings.isEmpty(value))
                        {
-                               result.append('=');
-                               result.append(encodeParameter(getValue(), 
charset));
+                               return encodeParameter(getName(), charset);
+                       }
+                       else
+                       {
+                               return encodeParameter(getName(), charset) + 
"=" + encodeParameter(value, charset);
                        }
-                       return result.toString();
                }
        }
 
@@ -1202,10 +1220,35 @@ public class Url implements Serializable
         * @return path string
         */
        public String getPath(Charset charset)
+       {
+               return getPathInternal(charset).toString();
+       }
+
+       /**
+        * return path for current url in given encoding, with optimizations 
for common use to avoid excessive object creation
+        * and resizing of StringBuilders.   Used internally by Url
+        *
+        * @param charset
+        *            character set for encoding
+        *
+        * @return path string
+        */
+       private CharSequence getPathInternal(Charset charset)
        {
                Args.notNull(charset, "charset");
 
-               StringBuilder path = new StringBuilder();
+               List<String> segments = getSegments();
+               // these two common cases can be handled with no additional 
overhead, so do that.
+               if (segments.isEmpty())
+                       return "";
+               if (segments.size() == 1)
+                       return encodeSegment(segments.get(0), charset);
+
+               int length = 0;
+               for (String segment : getSegments())
+                       length += segment.length() + 4;
+
+               StringBuilder path = new StringBuilder(length);
                boolean slash = false;
 
                for (String segment : getSegments())
@@ -1217,7 +1260,7 @@ public class Url implements Serializable
                        path.append(encodeSegment(segment, charset));
                        slash = true;
                }
-               return path.toString();
+               return path;
        }
 
        /**
@@ -1243,24 +1286,23 @@ public class Url implements Serializable
        {
                Args.notNull(charset, "charset");
 
-               String queryString = null;
                List<QueryParameter> queryParameters = getQueryParameters();
+               if (queryParameters.isEmpty())
+                       return null;
+               if (queryParameters.size() == 1)
+                       return queryParameters.get(0).toString(charset);
 
-               if (queryParameters.size() != 0)
+               // make a reasonable guess at a size for this builder
+               StringBuilder query = new StringBuilder(16 * parameters.size());
+               for (QueryParameter parameter : queryParameters)
                {
-                       StringBuilder query = new StringBuilder();
-
-                       for (QueryParameter parameter : queryParameters)
-                       {
-                               if (query.length() != 0)
-                               {
-                                       query.append('&');
-                               }
-                               query.append(parameter.toString(charset));
+                       if (query.length() != 0) {
+                               query.append('&');
                        }
-                       queryString = query.toString();
+                       query.append(parameter.toString(charset));
                }
-               return queryString;
+
+               return query.toString();
        }
 
        /**
diff --git 
a/wicket-util/src/main/java/org/apache/wicket/util/encoding/UrlEncoder.java 
b/wicket-util/src/main/java/org/apache/wicket/util/encoding/UrlEncoder.java
index 7fa4b6709f..299fb8b693 100644
--- a/wicket-util/src/main/java/org/apache/wicket/util/encoding/UrlEncoder.java
+++ b/wicket-util/src/main/java/org/apache/wicket/util/encoding/UrlEncoder.java
@@ -249,7 +249,7 @@ public class UrlEncoder
                                }
                        }
                }
-               return new String(bos.toByteArray(), charset);
+               return bos.toString(charset);
        }
 
 }

Reply via email to