This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-mime4j.git
commit cd9f63be2b784028e8b5602f8436b660793ee6e4 Author: Benoit Tellier <[email protected]> AuthorDate: Mon Jun 20 23:24:27 2022 +0700 MIME4J-318 Write single body backed by ByteArrayOutputStream, add recycling Reduce allocation rate by ~30% which allow to fasten Mime message parsing from 45 us to 39us, which represents a ~12% speedup. Recycling those output stream drops the GC churn rate below 500MB/s while yielding a message parsing in 34us. --- .../james/mime4j/LongMultipartReadBench.java | 4 +- .../mime4j/util/ByteArrayOutputStreamRecycler.java | 65 ++++++++++++++++++++++ .../org/apache/james/mime4j/util/ContentUtil.java | 19 ++++++- dom/pom.xml | 1 - .../james/mime4j/message/BasicBodyFactory.java | 17 +++--- 5 files changed, 94 insertions(+), 12 deletions(-) diff --git a/benchmark/src/main/java/org/apache/james/mime4j/LongMultipartReadBench.java b/benchmark/src/main/java/org/apache/james/mime4j/LongMultipartReadBench.java index 21f9348e..9dbbd525 100644 --- a/benchmark/src/main/java/org/apache/james/mime4j/LongMultipartReadBench.java +++ b/benchmark/src/main/java/org/apache/james/mime4j/LongMultipartReadBench.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import org.apache.james.mime4j.dom.Header; +import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.dom.MessageBuilder; import org.apache.james.mime4j.message.DefaultMessageBuilder; import org.apache.james.mime4j.message.SimpleContentHandler; @@ -174,7 +175,8 @@ public class LongMultipartReadBench { MessageBuilder builder = new DefaultMessageBuilder(); for (int i = 0; i < repetitions; i++) { - builder.parseMessage(new ByteArrayInputStream(content)); + Message message = builder.parseMessage(new ByteArrayInputStream(content)); + message.dispose(); } } } diff --git a/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java b/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java new file mode 100644 index 00000000..d99dda7e --- /dev/null +++ b/core/src/main/java/org/apache/james/mime4j/util/ByteArrayOutputStreamRecycler.java @@ -0,0 +1,65 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.mime4j.util; + +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.commons.io.output.ByteArrayOutputStream; + +public class ByteArrayOutputStreamRecycler { + public static class Wrapper { + private final ByteArrayOutputStreamRecycler recycler; + private final ByteArrayOutputStream value; + + public Wrapper(ByteArrayOutputStreamRecycler recycler, ByteArrayOutputStream value) { + this.recycler = recycler; + this.value = value; + } + + public void release() { + recycler.release(value); + } + + public ByteArrayOutputStream getValue() { + return value; + } + } + + protected final ConcurrentLinkedQueue<ByteArrayOutputStream> buffers; + + public ByteArrayOutputStreamRecycler() { + buffers = new ConcurrentLinkedQueue<>(); + } + + public Wrapper allocOutputStream() { + ByteArrayOutputStream result = buffers.poll(); + if (result == null) { + result = new ByteArrayOutputStream(); + } + return new Wrapper(this, result); + } + + private void release(ByteArrayOutputStream value) { + if (value != null) { + value.reset(); + buffers.offer(value); + } + } +} diff --git a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java index ce4d4121..0e25ce28 100644 --- a/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java +++ b/core/src/main/java/org/apache/james/mime4j/util/ContentUtil.java @@ -39,6 +39,7 @@ import org.apache.james.mime4j.Charsets; */ public class ContentUtil { protected static final ThreadLocal<SoftReference<BufferRecycler>> _recyclerRef = new ThreadLocal<>(); + protected static final ThreadLocal<SoftReference<ByteArrayOutputStreamRecycler>> _outputStreamRecyclerRef = new ThreadLocal<>(); public static BufferRecycler getBufferRecycler() { SoftReference<BufferRecycler> ref = _recyclerRef.get(); @@ -52,6 +53,18 @@ public class ContentUtil { return br; } + public static ByteArrayOutputStreamRecycler getOutputStreamRecycler() { + SoftReference<ByteArrayOutputStreamRecycler> ref = _outputStreamRecyclerRef.get(); + ByteArrayOutputStreamRecycler br = (ref == null) ? null : ref.get(); + + if (br == null) { + br = new ByteArrayOutputStreamRecycler(); + ref = new SoftReference<>(br); + _outputStreamRecyclerRef.set(ref); + } + return br; + } + private ContentUtil() { } @@ -98,12 +111,12 @@ public class ContentUtil { return buf.toByteArray(); } - public static ByteArrayOutputStream bufferEfficient(final InputStream in) throws IOException { + public static ByteArrayOutputStreamRecycler.Wrapper bufferEfficient(final InputStream in) throws IOException { if (in == null) { throw new IllegalArgumentException("Input stream may not be null"); } - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - copy(in, buf); + ByteArrayOutputStreamRecycler.Wrapper buf = getOutputStreamRecycler().allocOutputStream(); + copy(in, buf.getValue()); return buf; } diff --git a/dom/pom.xml b/dom/pom.xml index 1dae3046..7e92c191 100644 --- a/dom/pom.xml +++ b/dom/pom.xml @@ -63,7 +63,6 @@ <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> - <scope>test</scope> </dependency> </dependencies> diff --git a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java index 9bb57ab6..f3c74043 100644 --- a/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java +++ b/dom/src/main/java/org/apache/james/mime4j/message/BasicBodyFactory.java @@ -35,6 +35,7 @@ import org.apache.james.mime4j.dom.BinaryBody; import org.apache.james.mime4j.dom.SingleBody; import org.apache.james.mime4j.dom.TextBody; import org.apache.james.mime4j.io.InputStreams; +import org.apache.james.mime4j.util.ByteArrayOutputStreamRecycler; import org.apache.james.mime4j.util.ContentUtil; /** @@ -223,10 +224,10 @@ public class BasicBodyFactory implements BodyFactory { static class StringBody3 extends TextBody { - private final ByteArrayOutputStream content; + private final ByteArrayOutputStreamRecycler.Wrapper content; private final Charset charset; - StringBody3(final ByteArrayOutputStream content, final Charset charset) { + StringBody3(final ByteArrayOutputStreamRecycler.Wrapper content, final Charset charset) { super(); this.content = content; this.charset = charset; @@ -239,16 +240,17 @@ public class BasicBodyFactory implements BodyFactory { @Override public Reader getReader() throws IOException { - return new InputStreamReader(this.content.toInputStream(), this.charset); + return new InputStreamReader(this.content.getValue().toInputStream(), this.charset); } @Override public InputStream getInputStream() throws IOException { - return this.content.toInputStream(); + return this.content.getValue().toInputStream(); } @Override public void dispose() { + this.content.release(); } @Override @@ -285,20 +287,21 @@ public class BasicBodyFactory implements BodyFactory { static class BinaryBody3 extends BinaryBody { - private final ByteArrayOutputStream content; + private final ByteArrayOutputStreamRecycler.Wrapper content; - BinaryBody3(ByteArrayOutputStream content) { + BinaryBody3(ByteArrayOutputStreamRecycler.Wrapper content) { super(); this.content = content; } @Override public InputStream getInputStream() throws IOException { - return content.toInputStream(); + return content.getValue().toInputStream(); } @Override public void dispose() { + content.release(); } @Override --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
