4

java | 零拷贝

 1 year ago
source link: https://benpaodewoniu.github.io/2023/01/26/java207/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

零拷贝的体现之一,对原始 ByteBuf 进行切片分成多个 ByteBuf ,切片后的 ByteBuf 并没有发生内存复制。

还是用原始的 ByteBuf 的内存,切片后的 ByteBuf 维护独立的 readwrite 指针。

slice

207_0.svg
package com.redisc;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;


@Slf4j
public class Run {

public static void main(String[] args) throws IOException {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd'});
log(byteBuf);
// 在切片过程中,没有发生切片复制
ByteBuf byteBuf1 = byteBuf.slice(0, 5);
ByteBuf byteBuf2 = byteBuf.slice(5, 5);

log(byteBuf1);
log(byteBuf2);

byteBuf1.setByte(0, '1'); // 对 byteBuf1 修改,验证切片没有发生复制
log(byteBuf);
}

private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}
read index:0 write index:10 capacity:10
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 65 66 61 62 63 64 |abcdefabcd |
+--------+-------------------------------------------------+----------------+
read index:0 write index:5 capacity:5
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 65 |abcde |
+--------+-------------------------------------------------+----------------+
read index:0 write index:5 capacity:5
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 66 61 62 63 64 |fabcd |
+--------+-------------------------------------------------+----------------+
read index:0 write index:10 capacity:10
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 62 63 64 65 66 61 62 63 64 |1bcdefabcd |
+--------+-------------------------------------------------+----------------+

slice 的引用计数器需要使用正确使用,如下

package com.redisc;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;


@Slf4j
public class Run {

public static void main(String[] args) throws IOException {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd'});
// 在切片过程中,没有发生切片复制
ByteBuf byteBuf1 = byteBuf.slice(0, 5);
ByteBuf byteBuf2 = byteBuf.slice(5, 5);

byteBuf.release();
log(byteBuf1);
}

private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}
Exception in thread "main" io.netty.util.IllegalReferenceCountException: refCnt: 0
at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1454)
at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1383)
at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1379)
at io.netty.buffer.AbstractByteBuf.getByte(AbstractByteBuf.java:355)
at io.netty.buffer.AbstractUnpooledSlicedByteBuf.getByte(AbstractUnpooledSlicedByteBuf.java:120)
at io.netty.buffer.AbstractByteBuf.getUnsignedByte(AbstractByteBuf.java:368)
at io.netty.buffer.ByteBufUtil$HexUtil.appendPrettyHexDump(ByteBufUtil.java:1580)
at io.netty.buffer.ByteBufUtil$HexUtil.access$500(ByteBufUtil.java:1420)
at io.netty.buffer.ByteBufUtil.appendPrettyHexDump(ByteBufUtil.java:1416)
at io.netty.buffer.ByteBufUtil.appendPrettyHexDump(ByteBufUtil.java:1407)
at com.redisc.Run.log(Run.java:35)
at com.redisc.Run.main(Run.java:24)

这是因为 ByteBuf 的切片,并不是真的复制。当 byteBuf.release(); 之后,bytebuf 已经消失,所以,bytebuf1 也就没有了。想要继续使用需要用下面的代码

package com.redisc;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;


@Slf4j
public class Run {

public static void main(String[] args) throws IOException {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd'});
// 在切片过程中,没有发生切片复制
ByteBuf byteBuf1 = byteBuf.slice(0, 5);
byteBuf1.retain();
ByteBuf byteBuf2 = byteBuf.slice(5, 5);

byteBuf.release();
log(byteBuf1);
byteBuf1.release();
}

private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}

分片完,需要先执行 retain,使用完之后再执行 release

duplicate

零拷贝,相当于截取 ByteBuf 的所有内容,没有 max capacity 的限制。也是与原始的 ByteBuf 使用同一块底层内存,只不过读写指针独立。

将底层内存数据进行深度拷贝,无论读写都与原始 ByteBuf 无关。

compositeBuffer

package com.redisc;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;


@Slf4j
public class Run {

public static void main(String[] args) throws IOException {
ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf1.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd'});
ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf2.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'a', 'b', 'c', 'd'});

// ByteBuf byteBuf3 = ByteBufAllocator.DEFAULT.buffer();
// byteBuf3.writeBytes(byteBuf1).writeBytes(byteBuf2); // 这会导致真正的复制

CompositeByteBuf byteBuf4 = ByteBufAllocator.DEFAULT.compositeBuffer();
byteBuf4.addComponents(true, byteBuf1, byteBuf2); // 零拷贝
log(byteBuf4);
}

private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}
read index:0 write index:20 capacity:20
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 65 66 61 62 63 64 61 62 63 64 65 66 |abcdefabcdabcdef|
|00000010| 61 62 63 64 |abcd |
+--------+-------------------------------------------------+----------------+

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK