我们知道Netty中按操作内存方式可以分为Unsafe/safe(当然没有这么命名),例如UnpooledUnsafeHeapByteBuf/UnpooledHeapByteBuf,
UnpooledUnsafeDirectByteBuf(因为DirectBuffer一定是通过unsafe方式操作的,所以没有反例)。
一般我们认为通过Unsafe类操作内存的效率更高,因为它直接操作了需要修改的内存地址。
那么具体效率如何呢,我们通过一个demo演示下。
首先需要一个Unsafe工具类
public static class UnsafeUtil {
private static final Unsafe UNSAFE;
private static long ADDRESS_FIELD_OFFSET;
static
{
try
{
//由于Unsafe是个单列,所以需要通过反射方式获取到
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe)field.get(null);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
static {
final Field addressField;
try
{
addressField = Buffer.class.getDeclaredField("address");
addressField.setAccessible(true);
ADDRESS_FIELD_OFFSET = UNSAFE.objectFieldOffset(addressField);
}
catch (Exception e)
{
e.printStackTrace();
}
}
private static final long BYTE_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
//这里抄了部分Netty的PlatformDependent0函数
static void putByte(long address, byte value) {
UNSAFE.putByte(address, value);
}
static byte getByte(long address) {
return UNSAFE.getByte(address);
}
static void putByte(byte[] data, int index, byte value) {
UNSAFE.putByte(data, BYTE_ARRAY_BASE_OFFSET + index, value);
}
static byte getByte(byte[] data, int index) {
return UNSAFE.getByte(data, BYTE_ARRAY_BASE_OFFSET + index);
}
static long getAddress(Object object) {
return UNSAFE.getLong(object, ADDRESS_FIELD_OFFSET);
}
}
然后需要一个测试类
public class ByteBufferTest
{
public static final int REPETITIONS = 1 * 1000 * 1000;
private static final byte[] bs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=!@#$%^&*()".getBytes();
private static final byte[] bytes = new byte[1024*32] ;
static {
for (int i = 0 ; i<bytes.length;i++){
bytes[i] = bs[i%bs.length];
}
}
public static void main(String[] args) throws InterruptedException
{
for (int i =0;i<5;i++) {//普通内存读写
long startTime = System.nanoTime();
byte[] memory = new byte[bytes.length];
for (int j =0;j<REPETITIONS;j++) {
for (int k = 0;k<bytes.length;k++) {
HeapUtil.setByte(memory, k, bytes[k]);
}
}
long spendWrite = System.nanoTime()-startTime;
System.gc();
Thread.sleep(2000);
startTime = System.nanoTime();
byte[] readTemp = new byte[bytes.length];
for (int j =0;j<REPETITIONS;j++) {
for (int k = 0;k<bytes.length;k++) {
readTemp[k] = HeapUtil.getByte(memory, k);
}
}
long spendRead = System.nanoTime()-startTime;
System.out.println("普通数组 write:"+spendWrite/REPETITIONS+" | read :"+spendRead/REPETITIONS);
}
for (int i =0;i<5;i++) {//Unsafe内存拷贝
long startTime = System.nanoTime();
byte[] memory = new byte[bytes.length];
for (int j =0;j<REPETITIONS;j++) {
for (int k = 0;k<bytes.length;k++) {
UnsafeUtil.putByte(memory, k, bytes[k]);
}
}
long spendWrite = System.nanoTime()-startTime;
System.gc();
Thread.sleep(2000);
startTime = System.nanoTime();
byte[] readTemp = new byte[bytes.length];
for (int j =0;j<REPETITIONS;j++) {
for (int k = 0;k<bytes.length;k++) {
readTemp[k] = UnsafeUtil.getByte(memory, k);
}
}
long spendRead = System.nanoTime()-startTime;
System.out.println("UNSAFE数组 write:"+spendWrite/REPETITIONS+" | read :"+spendRead/REPETITIONS);
}
for (int i =0;i<5;i++) {//HeapByteBuffer内存读写
long startTime = System.nanoTime();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024*32);
for (int j =0;j<REPETITIONS;j++) {
byteBuffer.clear();
for (int k = 0;k<bytes.length;k++) {
byteBuffer.put(bytes[k]);
}
}
long spendWrite = System.nanoTime()-startTime;
System.gc();
Thread.sleep(2000);
startTime = System.nanoTime();
byte[] readTemp = new byte[bytes.length];
byteBuffer.flip();
for (int j =0;j<REPETITIONS;j++) {
for (int k = 0;k<bytes.length;k++) {
readTemp[k] = byteBuffer.get(k);
}
}
long spendRead = System.nanoTime()-startTime;
System.out.println("普通堆内 write:"+spendWrite/REPETITIONS+" | read:"+spendRead/REPETITIONS);
}
for (int i =0;i<5;i++) {//普通内存写
long startTime = System.nanoTime();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*32);
for (int j =0;j<REPETITIONS;j++) {
byteBuffer.clear();
for (int k = 0;k<bytes.length;k++) {
byteBuffer.put(bytes[k]);
}
}
long spendWrite = System.nanoTime()-startTime;
System.gc();
Thread.sleep(2000);
startTime = System.nanoTime();
byte[] readTemp = new byte[bytes.length];
byteBuffer.flip();
for (int j =0;j<REPETITIONS;j++) {
for (int k = 0;k<bytes.length;k++) {
readTemp[k] = byteBuffer.get(k);
}
}
long spendRead = System.nanoTime()-startTime;
System.out.println("普通堆外 write:"+spendWrite/REPETITIONS+" | read:"+spendRead/REPETITIONS);
}
for (int i =0;i<5;i++) {//Unsafe内存拷贝
long startTime = System.nanoTime();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*32);
long address = UnsafeUtil.getAddress(byteBuffer);
for (int j =0;j<REPETITIONS;j++) {
for (int k = 0;k<bytes.length;k++) {
UnsafeUtil.putByte(address+k, bytes[k]);
}
}
long spendWrite = System.nanoTime()-startTime;
System.gc();
Thread.sleep(2000);
startTime = System.nanoTime();
byte[] readTemp = new byte[bytes.length];
for (int j =0;j<REPETITIONS;j++) {
for (int k = 0;k<bytes.length;k++) {
readTemp[k] = UnsafeUtil.getByte(address+k);
}
}
long spendRead = System.nanoTime()-startTime;
System.out.println("UNSAFE堆外 write:"+spendWrite/REPETITIONS+" | read :"+spendRead/REPETITIONS);
}
}
}
为了时实验明显,建立一个32k大小的bytes,随机写入字符,循环1000000次取平均值。
这里一共尝试了5种内存读写方式
1,直接操作byte数组
2,通过Unsafe函数操作byte数组
3,通过直接操作HeapByteBuffer
4,通过直接操作DirectByteBuffer
5,通过Unsafe方式操作DirectByteBuffer的地址
结果:
普通数组 write:2055 | read :2438
普通数组 write:2036 | read :2061
普通数组 write:2979 | read :2512
普通数组 write:2990 | read :2651
普通数组 write:3011 | read :2720
UNSAFE数组 write:3101 | read :2751
UNSAFE数组 write:2733 | read :2776
UNSAFE数组 write:2812 | read :2956
UNSAFE数组 write:3236 | read :3609
UNSAFE数组 write:2790 | read :3357
普通堆内 write:58587 | read:14177
普通堆内 write:58628 | read:15285
普通堆内 write:53766 | read:13724
普通堆内 write:58956 | read:16128
普通堆内 write:58384 | read:13832
普通堆外 write:30746 | read:16753
普通堆外 write:33815 | read:16771
普通堆外 write:33764 | read:18816
普通堆外 write:35443 | read:17189
普通堆外 write:36486 | read:17488
UNSAFE堆外 write:13225 | read :17433
UNSAFE堆外 write:13465 | read :17082
UNSAFE堆外 write:13663 | read :16786
UNSAFE堆外 write:13340 | read :16672
UNSAFE堆外 write:13616 | read :17167
可以看到通过直接操作数组和Unsafe方式操作数组差别不大,个人理解都是直接操作了JVM堆中的内存
通过ByteBuffer则效率降低很多,
通过Unsafe方式写入堆外内存比系统自带快上很多,但是读取效率相差无几。