You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2022/10/17 03:11:55 UTC

[james-mime4j] 05/14: MIME4J-318 Write single body backed by ByteArrayOutputStream, add recycling

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 <bt...@linagora.com>
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: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org