我有一个客户机-服务器应用程序,其中服务器向客户机发送一些二进制数据,客户机必须根据自定义二进制格式从该字节流反序列化对象。数据通过HTTPS连接发送,客户端使用
HttpsURLConnection.getInputStream()
阅读它。
我实施了
DataDeserializer
这需要
InputStream
并完全反序列化。它的工作方式是执行多个
inputStream.read(buffer)
使用小缓冲区(通常小于100字节)调用。在实现更好的总体性能的过程中,我在这里也尝试了不同的实现。有一项改变确实显著提高了这门课的成绩(我用的是
ByteBuffer
现在要读取基元类型而不是手动进行字节移位),但是结合网络流,不会出现任何差异。有关详细信息,请参阅下面的部分。
我问题的快速总结
从网络流进行反序列化花费的时间太长,即使我证明了网络和反序列化程序本身的速度很快。我可以尝试一些常见的表演技巧吗?我已经用
BufferedInputStream
.另外,我尝试了一些成功的双重缓冲(见下面的代码)。欢迎使用任何能提高性能的解决方案。
性能测试场景
在我的测试场景中,服务器和客户机位于同一台机器上,服务器发送大约174MB的数据。代码片段可以在本文末尾找到。这里看到的所有数字都是5次测试运行的平均值。
首先我想知道,那有多快
输入流
的
HttpsURLConnection
可以读取。包装成
缓冲输入流
将整个数据写入
ByteArrayOutputStream
.
1个
然后我测试了反序列化程序的性能,将174MB作为
ByteArrayInputStream
.在我改进反序列化程序的实现之前,它花了38.151秒,改进后只花了23.466秒。
二
所以我想这就是它了…但没有。
我真正想做的,
以某种方式
,正在通过
connection.getInputStream()
到反序列化程序。奇怪的是:在反序列化程序改进之前,反序列化花费了61.413秒,改进之后是60.100秒!
三
怎么会这样?尽管反序列化程序显著改进,但这里几乎没有改进。此外,与改进无关,我惊讶地发现,这比单独的性能总和(60.100>26.250+23.466)所花费的时间更长。为什么?别误会我,我没想到这是最好的解决办法,但我也没想到会那么糟糕。
所以,有三件事要注意:
-
总体速度受网络限制,至少需要26.250秒。可能有一些HTTP设置我可以调整,或者我可以进一步优化服务器,但目前这可能不是我应该关注的。
-
我的反序列化程序实现很可能仍然不完美,但它本身比网络更快,所以我认为没有必要进一步改进它。
-
基于1。和2.我想应该是
以某种方式
可以以组合方式完成整个工作(从网络读取+反序列化),所需时间不应超过26.250秒。欢迎就如何实现这一点提出任何建议。
我在寻找某种双缓冲区,允许两个线程从中读取并并行写入。
标准Java中有类似的东西吗?最好是继承自
输入流
允许并行写入?如果有相似的东西,但不是继承自
输入流
,我可以改变我的
数据反序列化程序
也可以从中消费。
因为我没有找到这样的
DoubleBufferInputStream
,我自己实现了。
代码很长,可能不完美,我不想打扰您为我做代码检查。它有两个16KB的缓冲区。使用它,我可以将整体性能提高到39.885秒。
4
这比60.100秒要好得多,但比26.250秒差得多,选择不同的缓冲区大小变化不大。所以,我希望有人能引导我实现一些好的双缓冲区。
测试代码
1(26.250秒)
InputStream inputStream = new BufferedInputStream(connection.getInputStream());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[16 * 1024];
int count = 0;
long start = System.nanoTime();
while ((count = inputStream.read(buffer)) >= 0) {
outputStream .write(buffer, 0, count);
}
long end = System.nanoTime();
2(23.466秒)
InputStream inputStream = new ByteArrayInputStream(entire174MBbuffer);
DataDeserializer deserializer = new DataDeserializer(inputStream);
long start = System.nanoTime();
deserializer.Deserialize();
long end = System.nanoTime();
3(60.100秒)
InputStream inputStream = new BufferedInputStream(connection.getInputStream());
DataDeserializer deserializer = new DataDeserializer(inputStream);
long start = System.nanoTime();
deserializer.Deserialize();
long end = System.nanoTime();
4(39.885秒)
MyDoubleBufferInputStream doubleBufferInputStream = new MyDoubleBufferInputStream();
new Thread(new Runnable() {
@Override
public void run() {
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream())) {
byte[] buffer = new byte[16 * 1024];
int count = 0;
while ((count = inputStream.read(buffer)) >= 0) {
doubleBufferInputStream.write(buffer, 0, count);
}
} catch (IOException e) {
} finally {
doubleBufferInputStream.closeWriting(); // read() may return -1 now
}
}
}).start();
DataDeserializer deserializer = new DataDeserializer(doubleBufferInputStream);
long start = System.nanoTime();
deserializer.deserialize();
long end = System.nanoTime();
更新
根据要求,这里是我的反序列化程序的核心。我认为最重要的方法是
prepareForRead()
它执行流的实际读取。
class DataDeserializer {
private InputStream _stream;
private ByteBuffer _buffer;
public DataDeserializer(InputStream stream) {
_stream = stream;
_buffer = ByteBuffer.allocate(256 * 1024);
_buffer.order(ByteOrder.LITTLE_ENDIAN);
_buffer.flip();
}
private int readInt() throws IOException {
prepareForRead(4);
return _buffer.getInt();
}
private long readLong() throws IOException {
prepareForRead(8);
return _buffer.getLong();
}
private CustomObject readCustomObject() throws IOException {
prepareForRead(/*size of CustomObject*/);
int customMember1 = _buffer.getInt();
long customMember2 = _buffer.getLong();
// ...
return new CustomObject(customMember1, customMember2, ...);
}
// several other built-in and custom object read methods
private void prepareForRead(int count) throws IOException {
while (_buffer.remaining() < count) {
if (_buffer.capacity() - _buffer.limit() < count) {
_buffer.compact();
_buffer.flip();
}
int read = _stream.read(_buffer.array(), _buffer.limit(), _buffer.capacity() - _buffer.limit());
if (read < 0)
throw new EOFException("Unexpected end of stream.");
_buffer.limit(_buffer.limit() + read);
}
}
public HugeCustomObject Deserialize() throws IOException {
while (...) {
// call several of the above methods
}
return new HugeCustomObject(/* deserialized members */);
}
}
更新2
我稍微修改了一下代码片段1,以便更准确地了解时间花在哪里:
InputStream inputStream = new BufferedInputStream(connection.getInputStream());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[16 * 1024];
long read = 0;
long write = 0;
while (true) {
long t1 = System.nanoTime();
int count = istream.read(buffer);
long t2 = System.nanoTime();
read += t2 - t1;
if (count < 0)
break;
t1 = System.nanoTime();
ostream.write(buffer, 0, count);
t2 = System.nanoTime();
write += t2 - t1;
}
System.out.println(read + " " + write);
这告诉我,从网络流中读取需要25.756秒,而写入到
按earrayOutputstream
只需要0.817秒,这是有意义的,因为这两个数字几乎完美地加起来就是先前测量的26.250秒(加上一些额外的测量开销)。
以同样的方式,我修改了代码片段4:
MyDoubleBufferInputStream doubleBufferInputStream = new MyDoubleBufferInputStream();
new Thread(new Runnable() {
@Override
public void run() {
try (InputStream inputStream = new BufferedInputStream(httpChannelOutputStream.getConnection().getInputStream(), 256 * 1024)) {
byte[] buffer = new byte[16 * 1024];
long read = 0;
long write = 0;
while (true) {
long t1 = System.nanoTime();
int count = inputStream.read(buffer);
long t2 = System.nanoTime();
read += t2 - t1;
if (count < 0)
break;
t1 = System.nanoTime();
doubleBufferInputStream.write(buffer, 0, count);
t2 = System.nanoTime();
write += t2 - t1;
}
System.out.println(read + " " + write);
} catch (IOException e) {
} finally {
doubleBufferInputStream.closeWriting();
}
}
}).start();
DataDeserializer deserializer = new DataDeserializer(doubleBufferInputStream);
deserializer.deserialize();
现在我希望测量的读取时间与前面的示例中的完全相同。但是,相反,
read
变量的值为39.294s(
这怎么可能??与前一个25.756s的示例中测量的代码完全相同!
)
*
当写入我的双缓冲区时,只需要0.096秒。同样,这些数字几乎完美地加起来就是代码段4的测量时间。
另外,我使用Java VisualVM对这个代码进行了剖析。这告诉我这条线花了40秒
run()
方法和这40秒中的100%是CPU时间。另一方面,它在反序列化程序内部也花费40秒,但这里只有26秒是CPU时间,14秒是等待时间。这与从网络读取到
ByteBufferOutputStream
.所以我想我必须改进我的双缓冲器的“缓冲器切换算法”。
*)对这种奇怪的观察有什么解释吗?我只能想象这种测量方法是非常不准确的。但是,最新测量的读写时间与原始测量值完全相同,因此不能
那个
不准确的。。。有人能帮我解释一下吗?
我在档案中找不到这些读写性能…我将尝试找到一些允许我观察这两个方法的分析结果的设置。