程序猿之路

关于Unsafe函数操作内存的效率问题

2017-05-19  本文已影响193人  三斤牛肉

我们知道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方式写入堆外内存比系统自带快上很多,但是读取效率相差无几。

上一篇下一篇

猜你喜欢

热点阅读