Skip to content

improve performance of ContentCachingRequestWrapper [SPR-15762] #20317

@spring-projects-issues

Description

@spring-projects-issues

Zilong Song opened SPR-15762 and commented

The ContentCachingInputStream class has only implemented the int read() method defined in the InputStream class, and inherit all other methods from the super class. Among them, one is the int read(byte[], int, int) method which is often used for reading a batch of bytes.

The default implementation defined in the InputStream only do a repeated call of the int read() method. However, many subclasses of the InputStream provide some more efficient implementations for the int read(byte[], int, int) method, instead of using the default implementation (it is encouraged by the JDK). The following is part of the java doc for the int read(byte[], int, int) method.

The read(b, off, len) method for class InputStream simply calls the method read() repeatedly. If the first such call results in an IOException, that exception is returned from the call to the int read(b, off, len) method. If any subsequent call to read() results in a IOException, the exception is caught and treated as if it were end of file; the bytes read up to that point are stored into b and the number of bytes read before the exception occurred is returned. The default implementation of this method blocks until the requested amount of input data len has been read, end of file is detected, or an exception is thrown. Subclasses are encouraged to provide a more efficient implementation of this method.

Performance of reading a few bytes from an InputStream with a loop call of the int read() method and a call with the int read(byte[], int, int) method differs a lot. The latter method is much faster than the former method. My code and result for this comparison are listed bellow.

byte[] bytes = new byte[100_000]; // 100k
// test reading an input stream with loop method
ByteArrayInputStream in1 = new ByteArrayInputStream(bytes);
long start = System.nanoTime();
for (int i = 0; i < 100_000; i++) {
    in1.read();
}
System.out.println("loop calling cost: " + (System.nanoTime() - start) + "ns");

// test reading the same input stream with batch method
ByteArrayInputStream in2 = new ByteArrayInputStream(bytes);
byte[] buffer = new byte[1000];
int c = 0;
start = System.nanoTime();
while (c < 100_000) {
    c += in2.read(buffer);
}
System.out.println("batch calling cost: " + (System.nanoTime() - start) + "ns");

Finally, I got the following result on my machine.

loop calling cost: 9158452ns
batch calling cost: 85389ns

Affects: 4.3.9

Referenced from: pull request #1474, and commits d00f6f0, 4d0800f

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions