[ https://issues.apache.org/jira/browse/LOG4J2-154?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13586642#comment-13586642 ]
Remko Popma commented on LOG4J2-154: ------------------------------------ Ralph, sorry for taking so long to get back. Looks good, very clean! Below are some things I noticed. ThreadContext: EMPTY_STACK is currently mutable Before: public static final ThreadContextStack EMPTY_STACK = new MutableThreadContextStack(new ArrayList<String>()); After: public static final ThreadContextStack EMPTY_STACK = new MutableThreadContextStack(Collections.emptyList()); ---- DefaultThreadContextStack: 1. avoid multiple calls to stack.get() within one method as value may have been changed/removed by another thread between calls. for example: "if (stack.get() != null) {return stack.get().size();}" may result in NullPointerExceptions 2. I would use Collections.emptyList and Collections.emptyIterator to avoid creating ArrayList objects where possible DefaultThreadContextStack#copy() Before: if (!useStack || stack.get() == null) { return new MutableThreadContextStack(new ArrayList<String>()); } return new MutableThreadContextStack(stack.get()); After: List<String> list = null; if (!useStack || (list = stack.get()) == null) { return new MutableThreadContextStack(new ArrayList<String>()); } return new MutableThreadContextStack(list); DefaultThreadContextStack#size() Before: return stack.get() == null ? 0 : stack.get().size(); After: List<String> list = stack.get(); return list == null ? 0 : list.size(); DefaultThreadContextStack#isEmpty() Before: return stack.get() == null ? 0 : stack.get().isEmpty(); After: List<String> list = stack.get(); return list == null ? 0 : list.isEmpty(); DefaultThreadContextStack#contains(Object o) Before: return stack.get() == null ? 0 : stack.get().contains(o); After: List<String> list = stack.get(); return list == null ? 0 : list.contains(o); DefaultThreadContextStack#iterator() Before: if (stack.get() == null) { return new ArrayList<String>().iterator(); } else { return stack.get().iterator(); } After: List<String> list = stack.get(); if (list == null) { return Collections.emptyIterator(); // avoid creating ArrayList } else { return list.iterator(); } DefaultThreadContextStack#toArray() Before: if (stack.get() == null) { return new String[0]; } else { List<String> list = stack.get(); return stack.get().toArray(new Object[list.size()]); } After: List<String> list = stack.get(); if (list == null) { return new String[0]; } else { return list.toArray(new Object[list.size()]); } DefaultThreadContextStack#toArray(T[] ts) Before: if (stack.get() == null) { return new ArrayList<String>().toArray(ts); } return stack.get().toArray(ts); After: List<String> list = stack.get(); if (list == null) { return Collections.emptyList().toArray(ts); // avoid creating new ArrayList } return list.toArray(ts); > ThreadContext performance improvement: shallow copies for reads, deep copies > for writes > --------------------------------------------------------------------------------------- > > Key: LOG4J2-154 > URL: https://issues.apache.org/jira/browse/LOG4J2-154 > Project: Log4j 2 > Issue Type: Improvement > Components: Core > Affects Versions: 2.0-beta3 > Reporter: Remko Popma > Attachments: LOG4J2-154.patch, > LOG4J2-154-patch-DefaultThreadContextMap.txt, > LOG4J2-154-patch-ThreadContextMap.txt, LOG4J2-154-patch-ThreadContext.txt > > > 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)); > } > } -- This message is automatically generated by JIRA. If you think it was sent incorrectly, please contact your JIRA administrators For more information on JIRA, see: http://www.atlassian.com/software/jira --------------------------------------------------------------------- To unsubscribe, e-mail: log4j-dev-unsubscr...@logging.apache.org For additional commands, e-mail: log4j-dev-h...@logging.apache.org