On Mon, 27 Mar 2023 15:24:05 GMT, Brian Burkhalter <b...@openjdk.org> wrote:

>> I skimmed through the latest version but there are still several issues.  
>> Can you try the patch below instead? This will ensure that read, skip, 
>> reset, etc. check buf as before. It also ensures that read1 does the same 
>> threshold check as it has always done - your patch changes that in a subtle 
>> way and I think we have to be careful with behavior changes to this really 
>> old API.
>> 
>> To Eirik's comment about benchmarks. This change is about memory footprint 
>> and lazily creating the internal buffer. A good example is the BIS on stdin 
>> which has an 8k buffer that is only needed if something reads from 
>> System.in. There may be a few more like this. The other case is a large read 
>> where BIS will delegate to the wrapped stream when it doesn't have any bytes 
>> buffered. It would be useful to get some data on cases where it helps (the 
>> BIS wrapping BIS only works when the enclosing BIS has the same or larger 
>> buffer than the enclosed BIS).
>> 
>> 
>> --- a/src/java.base/share/classes/java/io/BufferedInputStream.java
>> +++ b/src/java.base/share/classes/java/io/BufferedInputStream.java
>> @@ -57,6 +57,8 @@ public class BufferedInputStream extends FilterInputStream 
>> {
>>  
>>      private static final int DEFAULT_BUFFER_SIZE = 8192;
>>  
>> +    private static final byte[] EMPTY = new byte[0];
>> +
>>      /**
>>       * As this class is used early during bootstrap, it's motivated to use
>>       * Unsafe.compareAndSetObject instead of AtomicReferenceFieldUpdater
>> @@ -70,6 +72,9 @@ public class BufferedInputStream extends FilterInputStream 
>> {
>>      // initialized to null when BufferedInputStream is sub-classed
>>      private final InternalLock lock;
>>  
>> +    // initial buffer size (DEFAULT_BUFFER_SIZE or size specified to 
>> constructor)
>> +    private final int initialSize;
>> +
>>      /**
>>       * The internal buffer array where the data is stored. When necessary,
>>       * it may be replaced by another array of
>> @@ -166,16 +171,42 @@ public class BufferedInputStream extends 
>> FilterInputStream {
>>      }
>>  
>>      /**
>> -     * Check to make sure that buffer has not been nulled out due to
>> -     * close; if not return it;
>> +     * Returns the internal buffer, optionally allocating it if empty.
>> +     * @param allocateIfEmpty true to allocate if empty
>> +     * @throws IOException if the stream is closed (buf is null)
>>       */
>> -    private byte[] getBufIfOpen() throws IOException {
>> +    private byte[] getBufIfOpen(boolean allocateIfEmpty) throws IOException 
>> {
>>          byte[] buffer = buf;
>> -        if (buffer == null)
>> +        if (allocateIfEmpty && buffer == EMPTY) {
>> +            buffer = new byte[initialSize];
>> +            if (!U.compareAndSetReference(this, BUF_OFFSET, EMPTY, buffer)) 
>> {
>> +                // re-read buf
>> +                buffer = buf;
>> +            }
>> +        }
>> +        if (buffer == null) {
>>              throw new IOException("Stream closed");
>> +        }
>>          return buffer;
>>      }
>>  
>> +    /**
>> +     * Returns the internal buffer, allocating it if empty.
>> +     * @throws IOException if the stream is closed (buf is null)
>> +     */
>> +    private byte[] getBufIfOpen() throws IOException {
>> +        return getBufIfOpen(true);
>> +    }
>> +
>> +    /**
>> +     * Throws IOException if the stream is closed (buf is null).
>> +     */
>> +    private void ensureOpen() throws IOException {
>> +        if (buf == null) {
>> +            throw new IOException("Stream closed");
>> +        }
>> +    }
>> +
>>      /**
>>       * Creates a {@code BufferedInputStream}
>>       * and saves its  argument, the input stream
>> @@ -205,13 +236,15 @@ public class BufferedInputStream extends 
>> FilterInputStream {
>>          if (size <= 0) {
>>              throw new IllegalArgumentException("Buffer size <= 0");
>>          }
>> -        buf = new byte[size];
>> -
>> -        // use monitors when BufferedInputStream is sub-classed
>> +        initialSize = size;
>>          if (getClass() == BufferedInputStream.class) {
>> +            // use internal lock and lazily create buffer when not 
>> subclassed
>>              lock = InternalLock.newLockOrNull();
>> +            buf = EMPTY;
>>          } else {
>> +            // use monitors and eagerly create buffer when subclassed
>>              lock = null;
>> +            buf = new byte[size];
>>          }
>>      }
>>  
>> @@ -307,7 +340,8 @@ public class BufferedInputStream extends 
>> FilterInputStream {
>>                 if there is no mark/reset activity, do not bother to copy the
>>                 bytes into the local buffer.  In this way buffered streams 
>> will
>>                 cascade harmlessly. */
>> -            if (len >= getBufIfOpen().length && markpos == -1) {
>> +            int size = Math.max(getBufIfOpen(false).length, initialSize);
>> +            if (len >= size && markpos == -1) {
>>                  return getInIfOpen().read(b, off, len);
>>              }
>>              fill();
>> @@ -374,7 +408,7 @@ public class BufferedInputStream extends 
>> FilterInputStream {
>>      }
>>  
>>      private int implRead(byte[] b, int off, int len) throws IOException {
>> -        getBufIfOpen(); // Check for closed stream
>> +        ensureOpen();
>>          if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
>>              throw new IndexOutOfBoundsException();
>>          } else if (len == 0) {
>> @@ -421,7 +455,7 @@ public class BufferedInputStream extends 
>> FilterInputStream {
>>      }
>>  
>>      private long implSkip(long n) throws IOException {
>> -        getBufIfOpen(); // Check for closed stream
>> +        ensureOpen();
>>          if (n <= 0) {
>>              return 0;
>>          }
>> @@ -544,7 +578,7 @@ public class BufferedInputStream extends 
>> FilterInputStream {
>>      }
>>  
>>      private void implReset() throws IOException {
>> -        getBufIfOpen(); // Cause exception if closed
>> +        ensureOpen();
>>          if (markpos < 0)
>>              throw new IOException("Resetting to invalid mark");
>>          pos = markpos;
>
>> Can you try the patch below instead? This will ensure that read, skip, 
>> reset, etc. check buf as before.
> 
> I think that the implementation provided by @AlanBateman looks good.

The BIS changes in this PR looks okay. I can't be Reviewer for that part 
because I provided the current changes. @bplb tells me that he will review.

I don't have strong views on the benchmark. The main benefit of the lazy 
allocation is, in my view, for cases where a BIS is created and either not 
used, or the first usage is long after it is created.  We see this with 
System.in but it's possible that most BIS usages involve a read soon after the 
BIS is created. The other scenario is the subtle optimization to bypass the 
internal buffer. This only works in the benchmark because readAllBytes uses a 
16k buffer.

-------------

PR Comment: https://git.openjdk.org/jdk/pull/13150#issuecomment-1495839430

Reply via email to