Uploaded image for project: 'Log4j 2'
  1. Log4j 2
  2. LOG4J2-154

ThreadContext performance improvement: shallow copies for reads, deep copies for writes

    XMLWordPrintableJSON

Details

    • Improvement
    • Status: Resolved
    • Major
    • Resolution: Fixed
    • 2.0-beta3
    • 2.0-beta7
    • Core
    • None

    Description

      Currently, every time a Log4jLogEvent object is created, a deep copy is made of both the context map and the context stack. However, expected usage is that only a few objects are pushed onto the stack or put in the context map, while the number of LogEvents is in the thousands or millions.
      Essentially, there are far more reads than writes, so using a copy-on-write mechanism should give us better performance.

      Example context map put: deep copy (expensive but rare)
      public static void put(String key, String value) {
      if (!useMap)

      { return; }
      Map<String, String> map = localMap.get();
      Map<String, String> copy = null;
      if (map == null) { copy = new HashMap<String, String>(); } else { copy = new HashMap<String, String>(map); }
      copy.put(key, value);
      localMap.set(copy);
      }

      Example context stack push: deep copy (expensive but rare)
      public static void push(String message) {
      if (!useStack) { return; }

      ContextStack stack = localStack.get();
      ContextStack copy = null;
      if (stack == null)

      { copy = new ThreadContextStack(); }

      else

      { copy = stack.copy(); }

      copy.push(message);
      localStack.set(copy);
      }

      Now, when the Log4jLogEvents are created, they just call ThreadContext.getImmutableContext and getImmutableStack. These methods return an unmodifiable wrapper around the most recent copy.

      Example for context map:
      public static Map<String, String> getImmutableContext()

      { Map<String, String> map = localMap.get(); return map == null ? EMPTY_MAP : Collections.unmodifiableMap(map); }

      Example for context stack:
      public static ContextStack getImmutableStack()

      { ContextStack stack = localStack.get(); return stack == null ? EMPTY_STACK : new ImmutableStack(stack.asList()); }

      Note that ThreadContext.ThreadContextStack needs to be changed to contain an ArrayList rather than extend it, to facilitate making both deep mutable copies and shallow immutable copies.

      private static class ThreadContextStack implements ContextStack {

      private static final long serialVersionUID = 5050501L;

      private List<String> list;

      public ThreadContextStack()

      { list = new ArrayList<String>(); }

      /**

      • This constructor uses the specified list as its internal data
      • structure unchanged. It does not make a defensive copy.
        */
        public ThreadContextStack(List<String> aList) { list = aList; // don't copy! }

      /**

      • This constructor copies the elements of the specified collection into
      • a new list. Changes to the specified collection will not affect this
      • {@code ThreadContextStack}

        .
        */
        public ThreadContextStack(Collection<String> collection)

        { list = new ArrayList<String>(collection); }

      public void clear()

      { list.clear(); }

      public String pop() {
      int index = list.size() - 1;
      if (index >= 0)

      { String result = list.get(index); list.remove(index); return result; }

      throw new NoSuchElementException("The ThreadContext stack is empty");
      }

      public String peek() {
      int index = list.size() - 1;
      if (index >= 0)

      { return list.get(index); }

      return null;
      }

      public void push(String message)

      { list.add(message); }

      public int getDepth()

      { return list.size(); }

      public List<String> asList()

      { return list; }

      public void trim(int depth) {
      if (depth < 0)

      { throw new IllegalArgumentException( "Maximum stack depth cannot be negative"); }

      while (list.size() > depth)

      { list.remove(list.size() - 1); }

      }

      /**

      • Returns a deep copy of this stack.
        */
        public ContextStack copy() { return new ThreadContextStack(new ArrayList<>(list)); }

        }

      Attachments

        1. LOG4J2-154.patch
          31 kB
          Ralph Goers
        2. LOG4J2-154-patch-ThreadContextMap.txt
          0.7 kB
          Remko Popma
        3. LOG4J2-154-patch-ThreadContext.txt
          9 kB
          Remko Popma
        4. LOG4J2-154-patch-DefaultThreadContextMap.txt
          2 kB
          Remko Popma

        Issue Links

          Activity

            People

              rpopma Remko Popma
              rpopma Remko Popma
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: