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:
parent
fb113dce3a
commit
e482d933f7
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user