gpt4 book ai didi

java - 如何提高从HttpsURLConnection.getInputStream()反序列化对象的性能?

转载 作者:行者123 更新时间:2023-12-02 11:05:15 34 4
gpt4 key购买 nike

我有一个客户端服务器应用程序,其中服务器向客户端发送一些二进制数据,并且客户端必须根据自定义二进制格式从该字节流中反序列化对象。数据通过HTTPS连接发送,并且客户端使用HttpsURLConnection.getInputStream()读取数据。

我实现了一个DataDeserializer,它使用一个InputStream并将其完全反序列化。它的工作方式是使用较小的缓冲区(通常少于100个字节)执行多个inputStream.read(buffer)调用。为了获得更好的整体性能,我还在这里尝试了不同的实现。一项更改确实显着提高了此类的性能(我现在使用ByteBuffer来读取基本类型,而不是通过字节移位手动进行操作),但是与网络流结合使用时,不会出现差异。有关更多详细信息,请参见以下部分。

我的问题的快速摘要

即使我证明网络和解串器本身都很快,但从网络流进行反序列化仍然花费了很长时间。我可以尝试一些常见的性能技巧吗?我已经用BufferedInputStream包装了网络流。另外,我尝试了双缓冲并取得了一些成功(请参见下面的代码)。任何获得更好性能的解决方案都是值得欢迎的。



性能测试方案

在我的测试方案中,服务器和客户端位于同一台计算机上,服务器发送约174 MB的数据。可以在本文结尾处找到代码片段。您在此处看到的所有数字均为5次测试运行的平均值。

首先,我想知道InputStream中的HttpsURLConnection可以读取的速度。包裹到BufferedInputStream中后,花费了26.250s的时间将全部数据写入ByteArrayOutputStream .1

然后,我测试了解串器的性能,将其全部174 MB作为ByteArrayInputStream传递了。在我改进解串器的实现之前,它花费了38.151s。改进后只花了23.466s.2
我想就是这样,但是没有。

我实际上想要做的是将connection.getInputStream()传递给解串器。奇怪的是:在反序列化器改进之前,反序列化花费了61.413s,而在改进之后是60.100s!3

怎么会这样尽管解串器明显改善,但这里几乎没有任何改善。另外,与该改进无关,我感到惊讶的是,这花费的时间比单独的性能总结所花费的时间更长(60.100> 26.250 + 23.466)。为什么?不要误会我的意思,我没想到这会是最好的解决方案,但是我也没想到它会那么糟糕。

因此,需要注意三件事:


整体速度受网络约束,至少需要26.250s。也许有一些我可以调整的http设置,或者我可以进一步优化服务器,但是就目前而言,这可能不是我应该关注的重点。
我的解串器实现很可能仍不完美,但单靠它比网络快,因此我认为没有必要进一步改进它。
基于1.和2。我假设应该以某种方式可以组合起来完成整个工作(从网络中读取+反序列化),而该过程不会超过26.250s。欢迎提供有关如何实现此目标的任何建议。


我正在寻找某种双缓冲,允许两个线程从中读取并并行写入。
在标准Java中是否有类似的东西?最好是从InputStream继承的某些类允许并行写入?如果有类似的东西,但不是从InputStream继承的,我也许也可以更改我的DataDeserializer以从该对象消费。

由于我没有找到任何这样的DoubleBufferInputStream,因此我自己实现了它。
该代码很长,可能并不完美,我不想打扰您为我做代码审查。它具有两个16kB缓冲区。使用它,我能够将整体性能提高到39.885s.4
那比60.100s好得多,但比26.250s还差很多。选择不同的缓冲区大小并没有太大变化。因此,我希望有人可以引导我实现一些好的双缓冲区实现。



测试代码

1(26.250s)

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.100s)

InputStream inputStream = new BufferedInputStream(connection.getInputStream());
DataDeserializer deserializer = new DataDeserializer(inputStream);

long start = System.nanoTime();
deserializer.Deserialize();
long end = System.nanoTime();


4(39.885 s)

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.756s,而写入 ByteArrayOutputStream则仅需要0.817s。这是有道理的,因为这两个数字几乎完美地合计为先前测得的26.250s(加上一些额外的测量开销)。

我以同样的方式修改了代码片段#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.096s。同样,这些数字几乎可以完美地概括为代码段4的测量时间。
另外,我使用Java VisualVM剖析了相同的代码。这告诉我,40s花费在该线程的 run()方法中,而这40s中的100%是CPU时间。另一方面,它在解串器内部也要花费40秒,但是这里CPU时间只有26秒,而等待时间只有14秒。这与从网络读取到 ByteBufferOutputStream的时间完全匹配。因此,我想我必须改进双缓冲区的“缓冲区切换算法”。

*)这个奇怪的发现有什么解释吗?我只能想象这种测量方法非常不准确。但是,最新测量值的读取和写入时间可以完美地与原始测量值相加,因此,它不可能是不准确的。有人可以对此进行说明吗?
我无法在事件探查器中找到这些读写性能...我将尝试找到一些设置,使我能够观察这两种方法的分析结果。

最佳答案

显然,我的“错误”是使用32位JVM(精确的是jre1.8.0_172)。
在64位版本的JVM和tadaaa上运行完全相同的代码片段...在那儿运行起来非常快捷而且很有意义。

特别是,请参见这些新数字以获取相应的代码段:


片段1:4.667秒(对比26.250秒)
摘要2:11.568秒(相对于23.466秒)
片段3:17.185秒(相比60.100秒)
片段4:12.336秒(相对于39.885秒)


因此,显然,给Does Java 64 bit perform better than the 32-bit version?的答案不再是真的。或者,此特定的32位JRE版本中存在严重的错误。我还没有测试其他任何人。

如您所见,#4仅比#2慢一点,这完全符合我最初的假设


  基于1.和2.我假设应该以某种方式
  以合并的方式完成整个工作(从网络中读取+
  反序列化),最多不超过26.250s。


同样,我的问题的更新2中描述的分析方法的非常奇怪的结果也不再发生。我还没有在64位中重复执行每个测试,但是现在我所做的所有分析结果都是合理的,即无论哪个代码段,相同的代码都花费相同的时间。因此,也许这确实是一个错误,或者有人有合理的解释吗?

关于java - 如何提高从HttpsURLConnection.getInputStream()反序列化对象的性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51020591/

34 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com