ARTEMIS-5048 use java.util.Base64

We've traditionally used org.apache.activemq.artemis.utils.Base64 for
Base64 encoding/decoding. This implementation is based on public domain
code from http://iharder.net/base64.

In Java 8 java.util.Base64 was introduced. I assumed we hadn't switched
to this implementation for performance reasons so I created a simple
JMH-based test to compare the two implementations and it appears to me
that java.util.Base64 is significantly faster than our current
implementation. Using the JDK's class will simplify our code and
improve performance. Also, it should be 100% backwards compatible
since Base64 encoding/decoding is standardized.
This commit is contained in:
Justin Bertram 2024-09-17 22:59:51 -05:00 committed by Timothy Bish
parent 6d84f3741e
commit d9d84f814c
11 changed files with 63 additions and 1693 deletions

View File

@ -292,7 +292,7 @@ public class DecodeJournal extends LockAbstract {
}
private static byte[] decode(final String data) {
return Base64.decode(data, Base64.DONT_BREAK_LINES | Base64.URL_SAFE);
return Base64.decode(data, true);
}
}

View File

@ -185,7 +185,7 @@ public class EncodeJournal extends LockAbstract {
}
private static String encode(final byte[] data) {
return Base64.encodeBytes(data, 0, data.length, Base64.DONT_BREAK_LINES | Base64.URL_SAFE);
return Base64.encodeBytes(data, true);
}
}

View File

@ -288,7 +288,7 @@ public class XMLMessageImporter {
}
private static byte[] decode(String data) {
return Base64.decode(data, Base64.DONT_BREAK_LINES | Base64.URL_SAFE);
return Base64.decode(data, true);
}
private interface MessageBodyBytesProcessor {

View File

@ -100,6 +100,6 @@ public class XmlDataExporterUtil {
}
protected static String encode(final byte[] data) {
return Base64.encodeBytes(data, 0, data.length, Base64.DONT_BREAK_LINES | Base64.URL_SAFE);
return Base64.encodeBytes(data, true);
}
}

View File

@ -173,8 +173,8 @@
<xsd:element type="bodyType" name="body">
<xsd:annotation>
<xsd:documentation>
the body of the message (Base64 encoded using the Base64.DONT_BREAK_LINES and Base64.URL_SAFE options;
see org.apache.activemq.artemis.utils.Base64.encodeBytes(byte[], int, int, int)); stored in a CDATA
the body of the message (Base64 URL-safe encoded;
see java.util.Base64.getUrlEncoder().encodeToString(String); stored in a CDATA
</xsd:documentation>
</xsd:annotation>
</xsd:element>

View File

@ -16,13 +16,6 @@
*/
package org.apache.activemq.artemis.api.core;
import org.apache.activemq.artemis.json.JsonArray;
import org.apache.activemq.artemis.json.JsonArrayBuilder;
import org.apache.activemq.artemis.json.JsonNumber;
import org.apache.activemq.artemis.json.JsonObject;
import org.apache.activemq.artemis.json.JsonObjectBuilder;
import org.apache.activemq.artemis.json.JsonString;
import org.apache.activemq.artemis.json.JsonValue;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import java.io.ByteArrayInputStream;
@ -34,6 +27,13 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.activemq.artemis.json.JsonArray;
import org.apache.activemq.artemis.json.JsonArrayBuilder;
import org.apache.activemq.artemis.json.JsonNumber;
import org.apache.activemq.artemis.json.JsonObject;
import org.apache.activemq.artemis.json.JsonObjectBuilder;
import org.apache.activemq.artemis.json.JsonString;
import org.apache.activemq.artemis.json.JsonValue;
import org.apache.activemq.artemis.utils.Base64;
import org.apache.activemq.artemis.utils.JsonLoader;
import org.apache.activemq.artemis.utils.ObjectInputStreamWithClassLoader;

View File

@ -42,7 +42,7 @@ public class XidImpl implements Xid, Serializable {
public static String toBase64String(final Xid xid) {
byte[] data = XidImpl.toByteArray(xid);
return Base64.encodeBytes(data, 0, data.length, Base64.DONT_BREAK_LINES | Base64.URL_SAFE);
return Base64.encodeBytes(data, true);
}
private static byte[] toByteArray(final Xid xid) {

View File

@ -72,7 +72,7 @@ public class CoreMessageTest {
@BeforeEach
public void before() {
BYTE_ENCODE = Unpooled.wrappedBuffer(Base64.decode(STRING_ENCODE, Base64.DONT_BREAK_LINES | Base64.URL_SAFE));
BYTE_ENCODE = Unpooled.wrappedBuffer(Base64.decode(STRING_ENCODE, true));
// some extra caution here, nothing else, to make sure we would get the same encoding back
assertEquals(STRING_ENCODE, encodeString(BYTE_ENCODE.array()));
BYTE_ENCODE.readerIndex(0).writerIndex(BYTE_ENCODE.capacity());
@ -421,7 +421,7 @@ public class CoreMessageTest {
}
private String encodeString(byte[] bytes) {
return Base64.encodeBytes(bytes, 0, bytes.length, Base64.DONT_BREAK_LINES | Base64.URL_SAFE);
return Base64.encodeBytes(bytes, true);
}
}

View File

@ -576,7 +576,7 @@ public final class DescribeJournal {
}
private static String encode(final byte[] data) {
return Base64.encodeBytes(data, 0, data.length, Base64.DONT_BREAK_LINES | Base64.URL_SAFE);
return Base64.encodeBytes(data, true);
}
private static Xid toXid(final byte[] data) {

View File

@ -1,72 +0,0 @@
/*
* 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.activemq.artemis.tests.performance.utils;
import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.utils.Base64;
import org.apache.activemq.artemis.utils.RandomUtil;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.infra.Blackhole;
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS)
public class Base64Benchmark {
@Param({"1", "10", "100"})
private static int length;
@State(Scope.Benchmark)
public static class Input {
public byte[] bytesToEncode = RandomUtil.randomString().repeat(length).getBytes();
public String stringToDecode = java.util.Base64.getUrlEncoder().encodeToString(bytesToEncode);
}
@Benchmark
public void benchmarkCustomBase64Encoding(Blackhole blackhole, Input input) {
blackhole.consume(Base64.encodeBytes(input.bytesToEncode));
}
@Benchmark
public void benchmarkJavaBase64Encoding(Blackhole blackhole, Input input) {
blackhole.consume(java.util.Base64.getUrlEncoder().encodeToString(input.bytesToEncode));
}
@Benchmark
public void benchmarkCustomBase64Decoding(Blackhole blackhole, Input input) {
blackhole.consume(Base64.decode(input.stringToDecode));
}
@Benchmark
public void benchmarkJavaBase64Decoding(Blackhole blackhole, Input input) {
blackhole.consume(java.util.Base64.getUrlDecoder().decode(input.stringToDecode));
}
}