ByteBuffer/IntBuffer/ShortBuffer Java 类快吗?
Posted
技术标签:
【中文标题】ByteBuffer/IntBuffer/ShortBuffer Java 类快吗?【英文标题】:Are the ByteBuffer/IntBuffer/ShortBuffer Java classes fast? 【发布时间】:2010-10-27 21:25:16 【问题描述】:我正在开发一个 android 应用程序(显然是在 Java 中),我最近更新了我的 UDP 阅读器代码。在这两个版本中,我都设置了一些缓冲区并接收 UDP 数据包:
byte[] buf = new byte[10000];
short[] soundData = new short[1000];
DatagramPacket packet = new DatagramPacket (buf, buf.length);
socket.receive (packet);
在最初的版本中,我将数据一次一个字节地组合在一起(实际上是 16 个 PCM 音频数据):
for (int i = 0; i < count; i++)
soundData[i] = (short) (((buf[k++]&0xff) << 8) + (buf[k++]&0xff));
在更新的版本中,我使用了一些我开始时不知道的很酷的 Java 工具:
bBuffer = ByteBuffer.wrap (buf);
sBuffer = bBuffer.asShortBuffer();
sBuffer.get (soundData, 0, count);
在这两种情况下,“计数”都被正确填充(我检查过)。但是,我的流式音频似乎出现了新问题——也许处理得不够快——这对我来说没有任何意义。显然,缓冲区代码正在编译成多于三个 JVM 代码语句,但是当我开始执行此操作时,似乎是一个合理的假设,即第二个版本会比第一个版本快。
显然,我并没有坚持我的代码必须使用 Java NIO 缓冲区,但至少乍一看,这样做似乎有点像“斗鱼”。
有人对快速、简单的 Java UDP 阅读器有任何建议吗?是否有普遍接受的“最佳方式”??
谢谢, R.
【问题讨论】:
NIO 并不打算比“普通”IO 更快,它只是更具可扩展性。stole the syntax
哈哈,你是认真的吗?谷歌窃取了 java 语法,就像作者强化了英语语法一样
@Mayra,图书馆本身是完全不同的。 Android 库实现!= Sun 库实现。此外,为 Android 编译的类 Java 代码不会编译为 Java 字节码,而是编译为 Dalvik。重点?这个问题最初被标记为“Java”,而实际上它应该被标记为“android”。由于 Rich 是一名 android 开发人员而不是 Java 开发人员,他至少应该知道如何正确标记他的问题 :)
@Falmarri,你的类比不成立。作者不会完全重新定义英语中所有单词的含义,然后坚持要求其他人使用正确的定义写小说,并期望替代翻译保持优雅。
作者肯定会重新定义单词的含义。谷歌并没有坚持任何事情。如果您想为 android 编写代码,请使用他们的 api。或者不,你可以用 C++ 编写。或者编译一个python解释器。它是开源的。至于您对@Mayra 的回复,[android] 标记的问题也非常合法地标记为 [java]。使用 openJDK 实现的人是否也不允许标记他们的问题 [java]?您显然必须为 Sun/Oracle 工作才能如此努力。
【参考方案1】:
如果不是将数据包读取到字节数组中(将数据从本机缓冲区复制到数组中),然后将其包装在新的 ByteBuffer 中(创建新对象)并转换为 ShortBuffer,那么您的代码会更高效(创建一个新对象)您只设置一次对象并避免复制。
您可以使用 DatagramChannel.socket() 创建您的套接字,然后像往常一样连接它并使用 socket.getChannel() 来获取 DatagramChannel 对象。该对象将允许您将数据包直接读取到现有的 ByteBuffer(您应该使用 ByteBuffer.allocateDirect 创建以实现最大效率)。然后,您只需使用一次 asShortBuffer() 即可将您的数据视图创建为短裤,并在每次重新填充 ByteBuffer 后从该 ShortBuffer 中读取。
因此代码如下所示:
DatagramSocket socket = DatagramChannel.socket();
// code to connect socket
DatagramChannel channel = socket.getChannel();
ByteBuffer buffer = ByteBuffer.allocateDirect (10000);
// you may want to invoke buffer.order(...) here to tell it what byte order to use
ShortBuffer shortBuf = buffer.asShortBuffer();
// in your receive loop:
buffer.clear();
channel.receive(buffer);
shortBuf.position(0).limit(buffer.position()/2); // may ignore a byte if odd number received
shortBuf.get(soundBuf,0,shortBuf.limit());
您应该会发现这比以前的代码效率更高,因为它避免了数据的整个副本,并且格式转换是由手动优化的代码处理的,而不是编译器生成的可能不是最佳的字节操作。如果您使用平台原生字节顺序,它会更有效率(我相信Android在所有可用的平台上都使用little-endian字节顺序,并且您上面的代码似乎是big-endian,所以这可能是不可能的为你),在这种情况下 shortBuf.get() 成为直接内存副本。
【讨论】:
很好的答案 Jules,不知道为什么这不是被接受的。【参考方案2】:一般来说,直接使用原始类型会比使用对象更有效,因为您避免了创建对象、函数调用等的一些开销。
使用实用对象除了速度之外还有其他原因:方便、安全等。
在这种特殊情况下测试差异的最佳方法是实际测量它。使用大型数据集尝试这两种方法并计时。然后,您可以决定是否值得在这种情况下获得好处。
您还可以使用 Android 的分析器来查看问题的真正所在。见TraceView。
【讨论】:
我好像引发了一场宗教战争……抱歉,感谢您的理性。 目前 NIO 的一些东西非常可怕。已经进行了改进,并将开始出现在未来的版本中。同时,“测试和测量”是一个很好的答案。【参考方案3】:我会使用 DataInputStream 来完成这个任务,它包裹着一个包裹着字节数组的 ByteArrayInputStream。封装了 readShort() 中的移位,没有 ByteBuffer 的开销。
或者,您可以通过 DatagramChannel 直接读取 DirectByteBuffer,而不是使用 DatagramPacket 和 DatagramSocket。目前你正在使用 java.net 和 java.nio 的混合。
我预计这两种方法之间不会有重大的性能差异,但我希望它们都比您的混合方法更快。
【讨论】:
在没有 ByteBuffer 开销的情况下封装了 readShort() 中的移位。 readShort 比 ByteBuffer 的相同操作有更多开销 @bestsss: 'overheads' 复数:尤其是创建开销。我指的是那些。 direct ByteBuffer 有一些分配(和释放,由于基于终结器的分配),但 ByteArrayInputStream 与 ByteBuffer.wrap 具有相同的分配成本,并且所有方法都是同步的,而且 ByteBuffer 往往会享受额外的JIT 固有的。此外,您不会为每个数据包创建直接 ByteBuffer,而是为每个套接字创建一次(或将其从池中取出)。 @bestsss:你离题太远了。如果 ByteBuffer.readShort() 比 DataInputStream.readShort() 更有效,考虑到后者的其他开销,无论它们可以与之相比,我希望看到一些实际证据。 @EJP,写你自己的测试,或者只看impl。以上是关于ByteBuffer/IntBuffer/ShortBuffer Java 类快吗?的主要内容,如果未能解决你的问题,请参考以下文章