美文网首页程序猿之路
关于Unsafe函数操作内存的效率问题

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

作者: 三斤牛肉 | 来源:发表于2017-05-19 19:59 被阅读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方式写入堆外内存比系统自带快上很多,但是读取效率相差无几。

相关文章

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

    我们知道Netty中按操作内存方式可以分为Unsafe/safe(当然没有这么命名),例如UnpooledUnsa...

  • go之unsafe

    unsafe包 unsafe包配合uintptr可以绕过go的安全检查,对内存进行操作 unsafe包中提供3个函...

  • Golang unsafe包使用

    unsafe包提供了访问底层内存的方法。是用unsafe函数可以提高访问对象的速度。通常用于对大数组的遍历。 un...

  • Unsafe

    1.Unsafe综述 作用: Unsafe是个后门类,封装了一些类似指针的操作,提供了一些可以直接操控内存和线程的...

  • 【Java源码计划】Unsafe<rt.jar_sun.m

    Unsafe 就像这个类名一样,这个类本身就不是很安全,Unsafe类使Java拥有了像C语言的指针一样操作内存空...

  • Redis字符串的性能优势三

    降低空间分配次数提升内存使用效率 字符串的追加操作会涉及到内存分配问题,然而内存分配问题会牵扯内存划分算法以及系统...

  • Unsafe类详解

    Unsafe类是进行底层操作的方法集合,可以直接操作内存,进行一些非常规操作,所以说是"不安全"的操作,但是因为直...

  • inline.内联函数

    引入内联函数是为了解决函数调用效率的问题由于函数之间的调用,会从一个内存地址调到另外一个内存地址,当函数调用完毕之...

  • 内联函数

    解决函数调用效率的问题:函数之间调用,是内存地址之间的调用,当函数调用完毕之后还会返回原来函数执行的地址。函数调用...

  • Unsafe

    关于Unsafe 如何获取Unsafe实例 Unsafe.getUnsafe() @CallerSensit...

网友评论

    本文标题:关于Unsafe函数操作内存的效率问题

    本文链接:https://www.haomeiwen.com/subject/mituxxtx.html