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