Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-add pooled sliced buffers? #4333

Closed
ninja- opened this issue Oct 8, 2015 · 9 comments
Closed

Re-add pooled sliced buffers? #4333

ninja- opened this issue Oct 8, 2015 · 9 comments
Assignees
Milestone

Comments

@ninja-
Copy link

ninja- commented Oct 8, 2015

77ff24b has deleted the sliced pooled buffer.
@Scottmitch could you re-evaluate this decision. I really liked using slices here and there but I got rid of them to reduce garbage creation after this was reverted.

I am aiming to get rid of copy() etc. again and have another strategy for rewriting packets that would need to expand the buffer.

So instead of operating on a copy

Original (a copy from stream):
-> AAA BBB CCC
rewrite into
-> AAA DDDD CCC

Aiming to do something like this if only the slices are to be recycled again:

Original:
-> AAA BBB CCC

Send:

  1. send slice1 AAA
  2. send DDDD
  3. send slice2 containing CCC
@ninja-
Copy link
Author

ninja- commented Oct 8, 2015

not to mention the composite buffers :D but I could live without them
(I can just create my own recyclable wrapper and unwrap it at encoder level if I ever need to make a composite out of something)

Oops, closed the issue.

is it really that complicated to properly release them in case the original buffer is an input stream and the slice is made at decoder, fired through the pipeline and later slice.release()d by handler?
I am also going to take care that the slice would never be .retain()ed so it wouldn't block discardSomeReadBytes(). Well, as long as it's fully written to downstream I guess.

I think I can see your point @Scottmitch so how about having two refcnt counters at slices so one of them would just increase the original refcnt and the second one would be dedicated to deciding when to recycle the slice? I hope I am not complicating too much. We could create a common class called DerivedByteBuf we could make DerivedByteBuf take care of maintaing these two refcnts.

@ninja- ninja- closed this as completed Oct 8, 2015
@ninja- ninja- reopened this Oct 8, 2015
@Scottmitch
Copy link
Member

@ninja- - I considered fixing the pooling code for derived buffers but it boiled down to having feedback from the PooledByteBuf to all its derived buffers when the reference count went to 0. This would require keeping some sort of collection in PooledByteBuf that must be thread safe. I did not go through this exercise because it seemed like overhead of maintaining this thread safe collection may out weight the benefits of pooling in some/most cases.

That lead me to take the "easier" path which was to pull it out because it was not functioning as originally intended.

Can you explain why copy() is preferred over slice() due to the objects returned by slice() not being pooled?

@ninja-
Copy link
Author

ninja- commented Oct 9, 2015

Well whether replacing most copy()s with slice() is a premature optimization is an another topic. But what I mean by copy is, well, I forgot to mention that I am not using directly copy().

    ByteBuf dst = ctx.alloc().directBuffer( length );
    in.readBytes( dst );

here's the "copy" as copy() would also use unpooled buffers or something like that.

So using slices avoids memory copies at frame decoder level but would come at a cost of trashing the memory with garbage as there is no pooling. For each single packet read, garbage. And I worked quite hard to pool everything so that the number of garbage it creates during I/O is an exact 0.

IMO that's the behaviour we could use:

  1. creating a new derived buffer increases refCnt on the original
  2. the derived one maintains its own refCnt starting on 1 and calling retain() on derived would not touch the refCnt on the original
  3. if release() is called on derived, the "own refCnt" is decreased. if "own refCnt" is zero, decrease the "original's refCnt" by one. also recycle
  4. the user needs to make sure he calls slice.release() so that the original would also be released at some point. but this makes me wonder if that would break buf.slice().copy() since it would start leaking the original buffer...?

But I think that could break things...we need to think about it again knowing that the slices are usually made out of the input stream.
I wouldn't go the slice collection route for the same reasons you mentioned.

@Scottmitch
Copy link
Member

@ninja- - If you can make it work without API changes or change in expected behavior then that would be good :) Also be sure to consider the case where there are slice objects of other slice objects.

@ninja-
Copy link
Author

ninja- commented Oct 9, 2015

@Scottmitch what do you think about the proposed model? the part when I mention that it's hard not to break buf.slice().copy()? Actually the slices now aren't increasing the original refCnt...hmmm :/
I can't even see how the current model is supposed to work. I mean that when I call channel.write(slice) I expect the original buffer to be released if all slices has been written...but clearly this couldn't be the case looking at the code. Am I missing some part of code in derived buffer that actually touches the original refCnt as I can't see any?

@ninja-
Copy link
Author

ninja- commented Oct 10, 2015

@Scottmitch @normanmaurer ok I see now how it worked. I have an idea how to re-add it, but under another function like rslice() while slice() would stay without ref-counting.
rslice() would increase the parent refcnt by default and decrease when deallocated.
I am struggling to make a class structure that will make both a sliced and reference-counted buffer. any help?

@ninja-
Copy link
Author

ninja- commented Oct 10, 2015

ok I think I got it...the class structure is quite ugly but it works. basically the derived buffer is refCnted by default and UnpooledSlicedByteBuf contains erasure for refCnting that forwards to parent. PooledSlicedByteBuf extends directly over AbstractSlicedByteBuf

I implemented this with ~ 1000 lines of diff, will PR to review... under rslice() as well as rduplicate() also readRefSlice

@ninja-
Copy link
Author

ninja- commented Oct 11, 2015

ninja- pushed a commit to ninja-/netty that referenced this issue Oct 11, 2015
ninja- pushed a commit to ninja-/netty that referenced this issue Oct 12, 2015
normanmaurer added a commit that referenced this issue Oct 26, 2015
Motivation:

Pooling ByteBufs for slice / duplicate operations can miminize object creation a lot. We should try to pool these if possible.

Modifications:

- Add new overloaded operations to ByteBuf that can be used to obtain a retained slice or duplicate. These can be pooled internally as optimization.
- Use optimized version in codecs.

Result:

Less object creation when using PooledByteBufAllocator.
merlimat added a commit to merlimat/netty that referenced this issue Oct 30, 2015
Motivation:

Pooling ByteBufs for slice / duplicate operations can miminize object creation a lot. We should try to pool these if possible.

Modifications:

Add new overloaded operations to ByteBuf that can be used to obtain a retained slice or duplicate. These can be pooled internally as optimization.
Use optimized version in codecs.
Result:

Less object creation when using PooledByteBufAllocator.
trustin added a commit that referenced this issue Apr 13, 2016
Related: #4333 #4421

Motivation:

slice(), duplicate() and readSlice() currently create a non-recyclable
derived buffer instance. Under heavy load, an application that creates a
lot of derived buffers can put the garbage collector under pressure.

Modifications:

- Deprecate the old derived buffer implementations
- Add the new recyclable derived buffer implementations, which has its
  own reference count value
  - When a derived buffer is created, its internal reference count is 0.
  - When retain() is called on a derived buffer, the internal reference
    count becomes a positive value and the original buffer's retain() is
    called.
  - When release() is called on a derived buffer, the internal reference
    count is decreased first, and then the original buffer's release()
    is called when the internal reference count is 0.
- Add ByteBufUtil.duplicate/slice() so that a user can easily implement
  ByteBuf.duplicate/slice()
- Add missing overrides in some ByteBuf impls
- Fix incorrect range checks in SlicedByteBuf
- Miscellaneous:
  - Merge Duplicated/SlicedAbstractByteBuf.unwrap0() into unwrap() using
    covariant return type

Result:

- Derived buffers are now recycled when retained and released, although
  they are not recycled if a user called release() against the original
  buffer.

    buf.slice().retain().release(); // recycled
    buf.slice().retain(); buf.release(); // not recycled

- Correct range checks in SlicedByteBuf
trustin added a commit that referenced this issue Apr 13, 2016
Related: #4333 #4421

Motivation:

slice(), duplicate() and readSlice() currently create a non-recyclable
derived buffer instance. Under heavy load, an application that creates a
lot of derived buffers can put the garbage collector under pressure.

Modifications:

- Deprecate the old derived buffer implementations
- Add the new recyclable derived buffer implementations, which has its
  own reference count value
  - When a derived buffer is created, its internal reference count is 0.
  - When retain() is called on a derived buffer, the internal reference
    count becomes a positive value and the original buffer's retain() is
    called.
  - When release() is called on a derived buffer, the internal reference
    count is decreased first, and then the original buffer's release()
    is called when the internal reference count is 0.
- Add ByteBufUtil.duplicate/slice() so that a user can easily implement
  ByteBuf.duplicate/slice()
- Add missing overrides in some ByteBuf impls
- Fix incorrect range checks in SlicedByteBuf
- Miscellaneous:
  - Merge Duplicated/SlicedAbstractByteBuf.unwrap0() into unwrap() using
    covariant return type

Result:

- Derived buffers are now recycled when retained and released, although
  they are not recycled if a user called release() against the original
  buffer.

    buf.slice().retain().release(); // recycled
    buf.slice().retain(); buf.release(); // not recycled

- Correct range checks in SlicedByteBuf
trustin added a commit that referenced this issue Apr 14, 2016
Related: #4333 #4421 #5128

Motivation:

slice(), duplicate() and readSlice() currently create a non-recyclable
derived buffer instance. Under heavy load, an application that creates a
lot of derived buffers can put the garbage collector under pressure.

Modifications:

- Add the new recyclable derived buffer implementations, which has its
  own reference count value
- Add ByteBufUtil.duplicateRetained/sliceRetained() so that a user can
  easily implement ByteBuf.duplicateRetained/sliceRetained()
- Use duplicateRetained() and sliceRetained() wherever possible

Result:

Derived buffers are now recycled when created via sliceRetained() and
duplicateRetained()
trustin added a commit that referenced this issue Apr 14, 2016
Related: #4333 #4421 #5128

Motivation:

slice(), duplicate() and readSlice() currently create a non-recyclable
derived buffer instance. Under heavy load, an application that creates a
lot of derived buffers can put the garbage collector under pressure.

Modifications:

- Add ByteBuf.rslice/rduplicate/readRSlice() which creates a
  non-recyclable derived buffer
- Add the new recyclable derived buffer implementations, which has its
  own reference count value
- Add ByteBufUtil.rduplicate/rslice() so that a user can
  easily implement ByteBuf.rduplicate/rslice()
- Add ByteBufHolder.rduplicate()
- Add a new protected method DefaultByteBufHolder.copy(ByteBuf) so that
  a user can implement copy/duplicate/rduplicate() easily
- Use rduplicate() and rslice() wherever possible

Result:

Derived buffers are now recycled when created via rslice() and
rduplicate()
trustin added a commit that referenced this issue Apr 16, 2016
Related: #4333 #4421 #5128

Motivation:

slice(), duplicate() and readSlice() currently create a non-recyclable
derived buffer instance. Under heavy load, an application that creates a
lot of derived buffers can put the garbage collector under pressure.

Modifications:

- Add the following methods which creates a non-recyclable derived buffer
  - retainedSlice()
  - retainedDuplicate()
  - readRetainedSlice()
- Add the new recyclable derived buffer implementations, which has its
  own reference count value
- Add ByteBufUtil.retainedSlice() and retainedDuplicate() so that a user
  can implement the new ByteBuf methods easily
- Add ByteBufHolder.retainedDuplicate()
- Add ByteBufHolder.replace(ByteBuf) so that..
  - a user can replace the content of the holder in a consistent way
  - copy/duplicate/retainedDuplicate() can delegate the holder
    construction to replace(ByteBuf)
- Use retainedDuplicate() and retainedSlice() wherever possible
- Miscellaneous:
  - Rename DuplicateByteBufTest to DuplicatedByteBufTest (missing 'D')

Result:

Derived buffers are now recycled when created via retainedSlice() and
retainedDuplicate()
trustin added a commit that referenced this issue Apr 17, 2016
Related: #4333 #4421 #5128

Motivation:

slice(), duplicate() and readSlice() currently create a non-recyclable
derived buffer instance. Under heavy load, an application that creates a
lot of derived buffers can put the garbage collector under pressure.

Modifications:

- Add the following methods which creates a non-recyclable derived buffer
  - retainedSlice()
  - retainedDuplicate()
  - readRetainedSlice()
- Add the new recyclable derived buffer implementations, which has its
  own reference count value
- Add ByteBufUtil.retainedSlice() and retainedDuplicate() so that a user
  can implement the new ByteBuf methods easily
- Add ByteBufHolder.retainedDuplicate()
- Add ByteBufHolder.replace(ByteBuf) so that..
  - a user can replace the content of the holder in a consistent way
  - copy/duplicate/retainedDuplicate() can delegate the holder
    construction to replace(ByteBuf)
- Use retainedDuplicate() and retainedSlice() wherever possible
- Miscellaneous:
  - Rename DuplicateByteBufTest to DuplicatedByteBufTest (missing 'D')

Result:

Derived buffers are now recycled when created via retainedSlice() and
retainedDuplicate()
trustin added a commit that referenced this issue May 6, 2016
Related: #4333 #4421 #5128

Motivation:

slice(), duplicate() and readSlice() currently create a non-recyclable
derived buffer instance. Under heavy load, an application that creates a
lot of derived buffers can put the garbage collector under pressure.

Modifications:

- Add the following methods which creates a non-recyclable derived buffer
  - retainedSlice()
  - retainedDuplicate()
  - readRetainedSlice()
- Add the new recyclable derived buffer implementations, which has its
  own reference count value
- Add ByteBufUtil.retainedSlice() and retainedDuplicate() so that a user
  can implement the new ByteBuf methods easily
- Add ByteBufHolder.retainedDuplicate()
- Add ByteBufHolder.replace(ByteBuf) so that..
  - a user can replace the content of the holder in a consistent way
  - copy/duplicate/retainedDuplicate() can delegate the holder
    construction to replace(ByteBuf)
- Use retainedDuplicate() and retainedSlice() wherever possible
- Miscellaneous:
  - Rename DuplicateByteBufTest to DuplicatedByteBufTest (missing 'D')

Result:

Derived buffers are now recycled when created via retainedSlice() and
retainedDuplicate() and derived from a pooled buffer
trustin added a commit that referenced this issue May 7, 2016
Related: #4333 #4421 #5128

Motivation:

slice(), duplicate() and readSlice() currently create a non-recyclable
derived buffer instance. Under heavy load, an application that creates a
lot of derived buffers can put the garbage collector under pressure.

Modifications:

- Add the following methods which creates a non-recyclable derived buffer
  - retainedSlice()
  - retainedDuplicate()
  - readRetainedSlice()
- Add the new recyclable derived buffer implementations, which has its
  own reference count value
- Add ByteBufHolder.retainedDuplicate()
- Add ByteBufHolder.replace(ByteBuf) so that..
  - a user can replace the content of the holder in a consistent way
  - copy/duplicate/retainedDuplicate() can delegate the holder
    construction to replace(ByteBuf)
- Use retainedDuplicate() and retainedSlice() wherever possible
- Miscellaneous:
  - Rename DuplicateByteBufTest to DuplicatedByteBufTest (missing 'D')

Result:

Derived buffers are now recycled when created via retainedSlice() and
retainedDuplicate() and derived from a pooled buffer
trustin added a commit that referenced this issue May 14, 2016
Related: #4333 #4421 #5128

Motivation:

slice(), duplicate() and readSlice() currently create a non-recyclable
derived buffer instance. Under heavy load, an application that creates a
lot of derived buffers can put the garbage collector under pressure.

Modifications:

- Add the following methods which creates a non-recyclable derived buffer
  - retainedSlice()
  - retainedDuplicate()
  - readRetainedSlice()
- Add the new recyclable derived buffer implementations, which has its
  own reference count value
- Add ByteBufHolder.retainedDuplicate()
- Add ByteBufHolder.replace(ByteBuf) so that..
  - a user can replace the content of the holder in a consistent way
  - copy/duplicate/retainedDuplicate() can delegate the holder
    construction to replace(ByteBuf)
- Use retainedDuplicate() and retainedSlice() wherever possible
- Miscellaneous:
  - Rename DuplicateByteBufTest to DuplicatedByteBufTest (missing 'D')
  - Make ReplayingDecoderByteBuf.reject() return an exception instead of
    throwing it so that its callers don't need to add dummy return
    statement

Result:

Derived buffers are now recycled when created via retainedSlice() and
retainedDuplicate() and derived from a pooled buffer
normanmaurer pushed a commit that referenced this issue May 17, 2016
Related: #4333 #4421 #5128

Motivation:

slice(), duplicate() and readSlice() currently create a non-recyclable
derived buffer instance. Under heavy load, an application that creates a
lot of derived buffers can put the garbage collector under pressure.

Modifications:

- Add the following methods which creates a non-recyclable derived buffer
  - retainedSlice()
  - retainedDuplicate()
  - readRetainedSlice()
- Add the new recyclable derived buffer implementations, which has its
  own reference count value
- Add ByteBufHolder.retainedDuplicate()
- Add ByteBufHolder.replace(ByteBuf) so that..
  - a user can replace the content of the holder in a consistent way
  - copy/duplicate/retainedDuplicate() can delegate the holder
    construction to replace(ByteBuf)
- Use retainedDuplicate() and retainedSlice() wherever possible
- Miscellaneous:
  - Rename DuplicateByteBufTest to DuplicatedByteBufTest (missing 'D')
  - Make ReplayingDecoderByteBuf.reject() return an exception instead of
    throwing it so that its callers don't need to add dummy return
    statement

Result:

Derived buffers are now recycled when created via retainedSlice() and
retainedDuplicate() and derived from a pooled buffer
@normanmaurer
Copy link
Member

Fixed by #5144

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants