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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容