You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by "Thomas Heigl (Jira)" <ji...@apache.org> on 2020/05/05 09:37:00 UTC

[jira] [Commented] (WICKET-6780) Improve performance of resource mapping

    [ https://issues.apache.org/jira/browse/WICKET-6780?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17099716#comment-17099716 ] 

Thomas Heigl commented on WICKET-6780:
--------------------------------------

This is the current implementation:
{code:java}
import org.apache.wicket.core.request.mapper.ResourceMapper;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.util.string.Strings;

import java.util.List;
import java.util.function.Predicate;

/**
 * Extension of {@link ResourceMapper} that does not attempt to do expensive URL parsing if the request
 * does not contain all required segments at the start of the mapped resource.
 */
public final class PathMatchingResourceMapper extends ResourceMapper {

   private static final String[] EMPTY_SEGMENTS = {};

   private final String[] requiredSegments;

   public PathMatchingResourceMapper(String path, ResourceReference reference) {
      super(path, reference);
      requiredSegments = getRequiredSegments(getMountSegments(path), this::isPlaceholder);
   }

   private static String[] getRequiredSegments(String[] segments, Predicate<String> isPlaceholder) {
      if (segments.length <= 1) {
         return EMPTY_SEGMENTS;
      }
      final String[] res = new String[segments.length - 1];
      for (int i = 0; i < segments.length - 1; i++) {
         final String segment = segments[i];
         if (Strings.isEmpty(segment) || isPlaceholder.test(segment)) {
            break;
         }
         res[i] = segment;
      }
      return res;
   }

   private boolean isPlaceholder(String segment) {
      return getPlaceholder(segment) != null || getOptionalPlaceholder(segment) != null;
   }

   @Override
   public int getCompatibilityScore(Request request) {
      if (!hasRequiredSegments(request, requiredSegments)) {
         return -1;
      }
      return super.getCompatibilityScore(request);
   }

   private static boolean hasRequiredSegments(Request request, String[] requiredSegments) {
      if (requiredSegments.length == 0) {
         return true;
      }
      final Url url = request.getUrl();
      final List<String> segments = url.getSegments();
      if (segments.size() < requiredSegments.length) {
         return false;
      }
      for (int i = 0; i < requiredSegments.length; i++) {
         final String requiredSegment = requiredSegments[i];
         if (requiredSegment != null && !requiredSegment.equals(segments.get(i))) {
            return false;
         }
      }
      return true;
   }

}{code}

> Improve performance of resource mapping
> ---------------------------------------
>
>                 Key: WICKET-6780
>                 URL: https://issues.apache.org/jira/browse/WICKET-6780
>             Project: Wicket
>          Issue Type: Improvement
>          Components: wicket-core
>    Affects Versions: 8.7.0, 9.0.0-M5
>            Reporter: Thomas Heigl
>            Priority: Major
>         Attachments: image-2020-05-05-10-59-50-624.png
>
>
> {{ResourceMapper}} showed up very prominently in my production profiler:
> !image-2020-05-05-10-59-50-624.png|width=952,height=551!
> For mapping an incoming request to a handler, the {{CompoundRequestMapper}} iterates over all registered mappers and calculates a compatibility score. For resources, this involves extracting the component and page info from the URL. This seems to be quite an expensive operation.
> If a request comes in, Wicket parses the component info for *every* registered resource. In my case, several hundred. It does this, *before* it checks if the request path would even match the requested resource, which would be a much cheaper operation. It has to do so, because it has to remove potential caching information from the URL before applying url matching.
> I have implemented a heuristic that bypasses this check if the initial segments of the resource path do not match the incoming request. E.g.
> A resource is mounted under {{/static/css/my.css}}. The initial segments would be {{static}} and {{css}}. They contain no parameters and do not contain caching information because this information is either encoded in the file name or a query parameter.
> This is currently implemented as a custom {{ResourceMapper}} that I use for all my resources, but it might be a worthy improvement for the default mapper implementation:
> ||Benchmark||Mode||Cnt||Score||Error||Units||
> |MapperBenchmark.compatibilityScore|thrpt|5|  6251028,198|± 1110953,287|ops/s|
> |MapperBenchmark.compatibilityScoreWithPrefixMatching|thrpt|5|13154340,419|± 1435077,659|ops/s|



--
This message was sent by Atlassian Jira
(v8.3.4#803005)