<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Unsafe與ByteBuffer那些事

          共 1937字,需瀏覽 4分鐘

           ·

          2021-10-20 21:33

          上一篇文章《聊聊Unsafe的一些使用技巧》寫作之后,閱讀量很快超過了 1500,Kirito 在這里感謝大家的閱讀啦,所以我又來更新了。如果你還沒有閱讀上一篇文章,我建議你先去看下,閑話不多說,開始今天的話題。

          無論是日常開發(fā)還是競(jìng)賽,Unsafe 不常有而 ByteBuffer 常有,只介紹 Unsafe,讓我的博文顯得很“炫技”,為了證明“Kirito的技術(shù)分享”它可是一個(gè)正經(jīng)的公眾號(hào),所以這篇文章會(huì)說到另一個(gè)比較貼地氣的主角 ByteBuffer。我會(huì)把我這么多年打比賽的經(jīng)驗(yàn)傳授給你,只求你的一個(gè)三連。

          從 DirectBuffer 的構(gòu)造器說起

          書接上文,我提到過 DirectBuffer 開辟的堆外內(nèi)存其實(shí)就是通過 Unsafe 分配的,但沒有詳細(xì)介紹,今天就給他補(bǔ)上。看一眼 DirectBuffer 的構(gòu)造函數(shù)

          ????DirectByteBuffer(int?cap)?{???????????????????//?package-private
          ????????super(-1,?0,?cap,?cap);
          ????????boolean?pa?=?VM.isDirectMemoryPageAligned();
          ????????int?ps?=?Bits.pageSize();
          ????????long?size?=?Math.max(1L,?(long)cap?+?(pa???ps?:?0));
          ????????Bits.reserveMemory(size,?cap);
          ????????long?base?=?0;
          ????????try?{
          ????????????base?=?unsafe.allocateMemory(size);
          ????????}?catch?(OutOfMemoryError?x)?{
          ????????????Bits.unreserveMemory(size,?cap);
          ????????????throw?x;
          ????????}
          ????????unsafe.setMemory(base,?size,?(byte)?0);
          ????????if?(pa?&&?(base?%?ps?!=?0))?{
          ????????????//?Round?up?to?page?boundary
          ????????????address?=?base?+?ps?-?(base?&?(ps?-?1));
          ????????}?else?{
          ????????????address?=?base;
          ????????}
          ????????cleaner?=?Cleaner.create(this,?new?Deallocator(base,?size,?cap));
          ????????att?=?null;
          ????}

          短短的十幾行代碼,蘊(yùn)含了非常大的信息量,先說關(guān)鍵點(diǎn)

          • long base = unsafe.allocateMemory(size);調(diào)用 Unsafe 分配內(nèi)存,返回內(nèi)存首地址
          • unsafe.setMemory(base, size, (byte) 0); 初始化內(nèi)存為 0。這一行我們放在下節(jié)做重點(diǎn)介紹。
          • Cleaner.create(this, new Deallocator(base, size, cap)); 設(shè)置堆外內(nèi)存的回收器,不詳細(xì)介紹了,可以參考我之前的文章《一文探討堆外內(nèi)存的監(jiān)控與回收》。

          僅構(gòu)造器中的這一幕,便讓 Unsafe 和 ByteBuffer 產(chǎn)生了千絲萬縷的關(guān)聯(lián),發(fā)揮想象力的話,可以把 ByteBuffer 看做是 Unsafe 一系列內(nèi)存操作 API 的 safe 版本。而安全一定有代價(jià),在編程領(lǐng)域,一般都有一個(gè)常識(shí),越是接近底層的事物,控制力越強(qiáng),性能越好;越接近用戶的事物,更易操作,但性能會(huì)差強(qiáng)人意。ByteBuffer 封裝的 limit/position/capacity 等概念,用熟悉了之后我覺得比 Netty 后封裝 的 ByteBuf 還要簡(jiǎn)便,但即使優(yōu)秀如它,仍然有被人嫌棄的一面:大量的邊界檢查。

          一個(gè)最吸引性能挑戰(zhàn)賽選手去使用 Unsafe 操作內(nèi)存,而不是 ByteBuffer 地方,便是邊界檢查。如示例代碼一:

          public?ByteBuffer?put(byte[]?src,?int?offset,?int?length)?{
          ????if?(((long)length?<0)?>?Bits.JNI_COPY_FROM_ARRAY_THRESHOLD)?{
          ????????checkBounds(offset,?length,?src.length);
          ????????int?pos?=?position();
          ????????int?lim?=?limit();
          ????????assert?(pos?<=?lim);
          ????????int?rem?=?(pos?<=?lim???lim?-?pos?:?0);
          ????????if?(length?>?rem)
          ????????????throw?new?BufferOverflowException();
          ????????????Bits.copyFromArray(src,?arrayBaseOffset,
          ???????????????????????????????(long)offset?<0,
          ???????????????????????????????ix(pos),
          ???????????????????????????????(long)length?<0);
          ????????position(pos?+?length);
          ????}?else?{
          ????????super.put(src,?offset,?length);
          ????}
          ????return?this;
          }

          你不用關(guān)心上述這段代碼在 DirectBuffer 中充當(dāng)著什么作用,我想展示給你的僅僅是它的 checkBounds 和 一堆 if/else,尤其是追求極致性能的場(chǎng)景,極客們看到 if/else 會(huì)神經(jīng)敏感地意識(shí)到分支預(yù)測(cè)的性能下降,第二意識(shí)是這坨代碼能不能去掉。

          如果你不希望有一堆邊界檢查,完全可以借助 Unsafe 實(shí)現(xiàn)一個(gè)自定義的 ByteBuffer,就像下面這樣。

          public?class?UnsafeByteBuffer?{

          ????private?final?long?address;
          ????private?final?int?capacity;
          ????private?int?position;
          ????private?int?limit;

          ????public?UnsafeByteBuffer(int?capacity)?{
          ????????this.capacity?=?capacity;
          ????????this.address?=?Util.unsafe.allocateMemory(capacity);
          ????????this.position?=?0;
          ????????this.limit?=?capacity;
          ????}

          ????public?int?remaining()?{
          ????????return?limit?-?position;
          ????}

          ????public?void?put(ByteBuffer?heapBuffer)?{
          ????????int?remaining?=?heapBuffer.remaining();
          ????????Util.unsafe.copyMemory(heapBuffer.array(),?16,?null,?address?+?position,?remaining);
          ????????position?+=?remaining;
          ????}

          ????public?void?put(byte?b)?{
          ????????Util.unsafe.putByte(address?+?position,?b);
          ????????position++;
          ????}

          ????public?void?putInt(int?i)?{
          ????????Util.unsafe.putInt(address?+?position,?i);
          ????????position?+=?4;
          ????}

          ????public?byte?get()?{
          ????????byte?b?=?Util.unsafe.getByte(address?+?position);
          ????????position++;
          ????????return?b;
          ????}

          ????public?int?getInt()?{
          ????????int?i?=?Util.unsafe.getInt(address?+?position);
          ????????position?+=?4;
          ????????return?i;
          ????}

          ????public?int?position()?{
          ????????return?position;
          ????}

          ????public?void?position(int?position)?{
          ????????this.position?=?position;
          ????}

          ????public?void?limit(int?limit)?{
          ????????this.limit?=?limit;
          ????}

          ????public?void?flip()?{
          ????????limit?=?position;
          ????????position?=?0;
          ????}

          ????public?void?clear()?{
          ????????position?=?0;
          ????????limit?=?capacity;
          ????}

          }

          在一些比賽中,為了避免選手進(jìn)入無止境的內(nèi)卷,Unsafe 通常是禁用的,但是也有一些比賽,允許使用 Unsafe 的一部分能力,讓選手們放飛自我,探索可能性。例如 Unsafe#allocateMemory 是不會(huì)受到 -XX:MaxDirectMemory-Xms 限制的,在這次第二屆云原生編程挑戰(zhàn)賽遭到了禁用,但 Unsafe#put 、Unsafe#getUnsafe#copyMemory 允許被使用。如果你一定希望使用 Unsafe 操作堆外內(nèi)存,可以寫出這樣的代碼,它跟示例代碼一完成的是同樣的操作。

          byte[]?src?=?...;

          ByteBuffer?byteBuffer?=?ByteBuffer.allocateDirect(src.length);
          long?address?=?((DirectBuffer)byteBuffer).address();
          Util.unsafe.copyMemory(src,?16,?null,?address,?src.length);

          這便是我想介紹的第一個(gè)關(guān)鍵點(diǎn):DirectByteBuffer 可以借助 Unsafe 完成內(nèi)存級(jí)別細(xì)粒度的操作,從而繞開邊界檢查。

          DirectByteBuffer 的內(nèi)存初始化

          注意到 DirectByteBuffer 構(gòu)造器中有另一個(gè)涉及到 Unsafe 的操作:unsafe.setMemory(base, size, (byte) 0);。這段代碼主要是為了給內(nèi)存初始化 0。說實(shí)話,我是沒有太懂這里的初始化操作,因?yàn)榘凑瘴业恼J(rèn)知,默認(rèn)值也是 0。在某些場(chǎng)景或者硬件下,內(nèi)存操作是非常昂貴的,尤其是大片的內(nèi)存被開辟時(shí),這段代碼可能會(huì)成為 DirectByteBuffer 的瓶頸。

          如果希望分配內(nèi)存時(shí),不進(jìn)行這段初始化邏輯,可以借助于 Unsafe 分配內(nèi)存,再對(duì) DirectByteBuffer 進(jìn)行魔改。

          public?class?AllocateDemo?{

          ????private?Field?addressField;
          ????private?Field?capacityField;
          ????
          ????public?AllocateDemo()?throws?NoSuchFieldException?{
          ????????Field?capacityField?=?Buffer.class.getDeclaredField("capacity");
          ????????capacityField.setAccessible(true);
          ????????Field?addressField?=?Buffer.class.getDeclaredField("address");
          ????????addressField.setAccessible(true);
          ????}
          ????
          ????public?ByteBuffer?allocateDirect(int?cap)?throws?IllegalAccessException?{
          ????????long?address?=?Util.unsafe.allocateMemory(cap);

          ????????ByteBuffer?byteBuffer?=?ByteBuffer.allocateDirect(1);
          ????????Util.unsafe.freeMemory(((DirectBuffer)?byteBuffer).address());

          ????????addressField.setLong(byteBuffer,?address);
          ????????capacityField.setInt(byteBuffer,?cap);

          ????????byteBuffer.clear();
          ????????return?byteBuffer;
          ????}

          }

          經(jīng)過這么一頓操作,我們便得到了一份沒有初始化的 DirectByteBuffer,不過不用擔(dān)心,一切都在正常工作,并且 setMemory for free!

          聊聊 ByteBuffer 的零拷貝

          算作是題外話了,主要是跟 ByteBuffer 相關(guān)的一個(gè)話題:零拷貝。ByteBuffer 在作為讀緩沖區(qū)時(shí)被使用時(shí),有一部分小伙伴會(huì)選擇使用加鎖的方式訪問內(nèi)存,但其實(shí)這是非常錯(cuò)誤的做法,應(yīng)當(dāng)使用 ByteBuffer 提供的 duplicate 和 slice 這兩個(gè)方法。

          并發(fā)讀取緩沖的方案:

          ByteBuffer?byteBuffer?=?ByteBuffer.allocateDirect(1024);
          ByteBuffer?duplicate?=?byteBuffer.duplicate();
          duplicate.limit(512);
          duplicate.position(256);
          ByteBuffer?slice?=?duplicate.slice();
          //?use?slice

          這樣便可以在不改變?cè)?ByteBuffer 指針的前提下,任意對(duì) slice 后的 ByteBuffer 進(jìn)行并發(fā)讀取了。

          總結(jié)

          最近時(shí)間有限,白天工作,晚上還要抽時(shí)間打比賽,先分享這么多。更多性能優(yōu)化小技巧,可以期待一下 1~2 個(gè)星期云原生比賽結(jié)束,我就開始繼續(xù)發(fā)總結(jié)和其他調(diào)優(yōu)方案。

          本文閱讀求個(gè) 1000,不過分吧!

          一鍵三連,這次一定。

          「技術(shù)分享」某種程度上,是讓作者和讀者,不那么孤獨(dú)的東西。歡迎關(guān)注我的微信公眾號(hào):「Kirito的技術(shù)分享」


          瀏覽 37
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  男女男精品网站 | а√资源新版在线天堂 | 激情无码嗯啊一区 | 91免费成人电影 | 国产69久久久 |