Add 'io.netty.tryAllocateUninitializedArray' system property which allows to allocate byte[] without memset in Java9+

Motivation:

Java9 added a new method to Unsafe which allows to allocate a byte[] without memset it. This can have a massive impact in allocation times when the byte[] is big. This change allows to enable this when using Java9 with the io.netty.tryAllocateUninitializedArray property when running Java9+. Please note that you will need to open up the jdk.internal.misc package via '--add-opens java.base/jdk.internal.misc=ALL-UNNAMED' as well.

Modifications:

Allow to allocate byte[] without memset on Java9+

Result:

Better performance when allocate big heap buffers and using java9.
This commit is contained in:
Norman Maurer 2017-03-25 19:06:31 -07:00
parent fb113dce3a
commit e482d933f7
5 changed files with 174 additions and 4 deletions

View File

@ -672,6 +672,10 @@ abstract class PoolArena<T> implements PoolArenaMetric {
directMemoryCacheAlignment);
}
private static byte[] newByteArray(int size) {
return PlatformDependent.allocateUninitializedArray(size);
}
@Override
boolean isDirect() {
return false;
@ -679,12 +683,12 @@ abstract class PoolArena<T> implements PoolArenaMetric {
@Override
protected PoolChunk<byte[]> newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) {
return new PoolChunk<byte[]>(this, new byte[chunkSize], pageSize, maxOrder, pageShifts, chunkSize, 0);
return new PoolChunk<byte[]>(this, newByteArray(chunkSize), pageSize, maxOrder, pageShifts, chunkSize, 0);
}
@Override
protected PoolChunk<byte[]> newUnpooledChunk(int capacity) {
return new PoolChunk<byte[]>(this, new byte[capacity], capacity, 0);
return new PoolChunk<byte[]>(this, newByteArray(capacity), capacity, 0);
}
@Override

View File

@ -29,6 +29,11 @@ class UnpooledUnsafeHeapByteBuf extends UnpooledHeapByteBuf {
super(alloc, initialCapacity, maxCapacity);
}
@Override
byte[] allocateArray(int initialCapacity) {
return PlatformDependent.allocateUninitializedArray(initialCapacity);
}
@Override
public byte getByte(int index) {
checkIndex(index);

View File

@ -97,6 +97,7 @@ public final class PlatformDependent {
private static final long DIRECT_MEMORY_LIMIT;
private static final ThreadLocalRandomProvider RANDOM_PROVIDER;
private static final Cleaner CLEANER;
private static final int UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD;
public static final boolean BIG_ENDIAN_NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
@ -160,7 +161,13 @@ public final class PlatformDependent {
}
}
DIRECT_MEMORY_LIMIT = maxDirectMemory;
logger.debug("io.netty.maxDirectMemory: {} bytes", maxDirectMemory);
logger.debug("-Dio.netty.maxDirectMemory: {} bytes", maxDirectMemory);
int tryAllocateUninitializedArray =
SystemPropertyUtil.getInt("io.netty.uninitializedArrayAllocationThreshold", 1024);
UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD = javaVersion() >= 9 && PlatformDependent0.hasAllocateArrayMethod() ?
tryAllocateUninitializedArray : -1;
logger.debug("-Dio.netty.uninitializedArrayAllocationThreshold: {}", UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD);
MAYBE_SUPER_USER = maybeSuperUser0();
@ -177,6 +184,11 @@ public final class PlatformDependent {
}
}
public static byte[] allocateUninitializedArray(int size) {
return UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD < 0 || UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD > size ?
new byte[size] : PlatformDependent0.allocateUninitializedArray(size);
}
/**
* Returns {@code true} if and only if the current platform is Android
*/

View File

@ -40,7 +40,9 @@ final class PlatformDependent0 {
private static final long BYTE_ARRAY_BASE_OFFSET;
private static final Constructor<?> DIRECT_BUFFER_CONSTRUCTOR;
private static final boolean IS_EXPLICIT_NO_UNSAFE = explicitNoUnsafe0();
private static final Method ALLOCATE_ARRAY_METHOD;
private static final Object INTERNAL_UNSAFE;
static final Unsafe UNSAFE;
// constants borrowed from murmur3
@ -59,12 +61,15 @@ final class PlatformDependent0 {
static {
final ByteBuffer direct;
Field addressField = null;
Method allocateArrayMethod = null;
Unsafe unsafe;
Object internalUnsafe = null;
if (isExplicitNoUnsafe()) {
direct = null;
addressField = null;
unsafe = null;
internalUnsafe = null;
} else {
direct = ByteBuffer.allocateDirect(1);
@ -187,6 +192,7 @@ final class PlatformDependent0 {
BYTE_ARRAY_BASE_OFFSET = -1;
UNALIGNED = false;
DIRECT_BUFFER_CONSTRUCTOR = null;
ALLOCATE_ARRAY_METHOD = null;
} else {
Constructor<?> directBufferConstructor;
long address = -1;
@ -237,7 +243,6 @@ final class PlatformDependent0 {
}
}
DIRECT_BUFFER_CONSTRUCTOR = directBufferConstructor;
ADDRESS_FIELD_OFFSET = objectFieldOffset(addressField);
BYTE_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
boolean unaligned;
@ -279,8 +284,65 @@ final class PlatformDependent0 {
}
UNALIGNED = unaligned;
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
// Java9 has jdk.internal.misc.Unsafe and not all methods are propergated to
// sun.misc.Unsafe
Class<?> internalUnsafeClass = getClassLoader(PlatformDependent0.class)
.loadClass("jdk.internal.misc.Unsafe");
Method method = internalUnsafeClass.getDeclaredMethod("getUnsafe");
return method.invoke(null);
} catch (Throwable e) {
return e;
}
}
});
if (!(maybeException instanceof Throwable)) {
internalUnsafe = maybeException;
final Object finalInternalUnsafe = internalUnsafe;
maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return finalInternalUnsafe.getClass().getDeclaredMethod(
"allocateUninitializedArray", Class.class, int.class);
} catch (NoSuchMethodException e) {
return e;
} catch (SecurityException e) {
return e;
}
}
});
if (maybeException instanceof Method) {
try {
Method m = (Method) maybeException;
byte[] bytes = (byte[]) m.invoke(finalInternalUnsafe, byte.class, 8);
assert bytes.length == 8;
allocateArrayMethod = m;
} catch (IllegalAccessException e) {
maybeException = e;
} catch (InvocationTargetException e) {
maybeException = e;
}
}
}
ALLOCATE_ARRAY_METHOD = allocateArrayMethod;
if (maybeException instanceof Throwable) {
logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable",
(Throwable) maybeException);
} else {
logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): available");
}
}
INTERNAL_UNSAFE = internalUnsafe;
logger.debug("java.nio.DirectByteBuffer.<init>(long, int): {}",
DIRECT_BUFFER_CONSTRUCTOR != null ? "available" : "unavailable");
}
@ -343,6 +405,20 @@ final class PlatformDependent0 {
return newDirectBuffer(UNSAFE.allocateMemory(capacity), capacity);
}
static boolean hasAllocateArrayMethod() {
return ALLOCATE_ARRAY_METHOD != null;
}
static byte[] allocateUninitializedArray(int size) {
try {
return (byte[]) ALLOCATE_ARRAY_METHOD.invoke(INTERNAL_UNSAFE, byte.class, size);
} catch (IllegalAccessException e) {
throw new Error(e);
} catch (InvocationTargetException e) {
throw new Error(e);
}
}
static ByteBuffer newDirectBuffer(long address, int capacity) {
ObjectUtil.checkPositiveOrZero(capacity, "capacity");

View File

@ -0,0 +1,73 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project 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 io.netty.microbench.internal;
import io.netty.microbench.util.AbstractMicrobenchmark;
import io.netty.util.internal.PlatformDependent;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
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.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.util.concurrent.TimeUnit;
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(2)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class UnitializedArrayBenchmark extends AbstractMicrobenchmark {
@Param({ "1", "10", "100", "1000", "10000", "100000" })
private int size;
@Setup(Level.Trial)
public void setupTrial() {
if (PlatformDependent.javaVersion() < 9) {
throw new IllegalStateException("Needs Java9");
}
if (!PlatformDependent.hasUnsafe()) {
throw new IllegalStateException("Needs Unsafe");
}
}
@Override
protected String[] jvmArgs() {
// Ensure we minimize the GC overhead for this benchmark and also open up required package.
// See also https://shipilev.net/jvm-anatomy-park/7-initialization-costs/
return new String[] { "-XX:+UseParallelOldGC", "-Xmx8g", "-Xms8g",
"-Xmn6g", "--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED" };
}
@Benchmark
public byte[] allocateInitializedByteArray() {
return new byte[size];
}
@Benchmark
public byte[] allocateUninitializedByteArray() {
return PlatformDependent.allocateUninitializedArray(size);
}
}