-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Address key protobuf performance issues #1054
Comments
@xfxyjwf FYI |
Yeah, can we have all the above please (smile). Over in the project I work on (apache hbase):
Thanks. |
If we give you 0-copy could the side-car move into the protobuf as a simple binary string? If so then I think we can pretty easily meet your needs |
It could. We on 2.5 pb currently. It'd require an incompatible upgrade? Thanks Louis. |
What are your compatability gaps with the proto3 runtime ? (awaits answer in fear) |
What the gap is a TODO. |
The gap is annoying to deal with at best (I tried to make that switch at some point within Arista and we gave up after going deep enough in that rabbit hole and then deciding we had yaks in a more urgent need of a shave). |
Would a shaded proto3 runtime help ? If you recall where yak shaving was On Sun, Oct 11, 2015 at 10:34 PM, Benoit Sigoure [email protected]
|
"Would a shaded proto3 runtime help ?" We could shade, yeah. Would our 2.5 clients be able to talk to a 3.0 server? Any movement here or something to try? Our new stumbling block is CodedOutputStream. We have a DirectByteBuffer whose data is offheap and we want it to stay offheap. COS does not support ByteBuffer in 2.5. In 2.6 it does but insists on copying data from the offheap DBB on heap. We are considering forking PB. |
Forking, ouch. What sorts of arguments does the open-source community need to put forward to convince the protobuf-java maintainers that this issue needs to be tackled upstream? |
@saintstack work is underway to support zero-copy for Adding zero-copy support for Agree with @tsuna ... yeah, let's not fork if we can avoid it :) |
That doesn't look bad @nmittler Would it be proto3 only? |
@saintstack I don't believe so. |
Yes. UnsafeByteOperations will be in the next release, and is currently available in master already. |
UnsafeByteOperations is great. At least one issue that Stack above describes is that we want our RPC buffers to be off-heap DBBs. The data that is in the sidecar can be decoded by us nicely, but the RPC call header, etc are in PB so we have to decode that as well using CodedInputStream. In this case, the CIS backed by NIOByteString still allocates a byte[] on-heap and does the whole-copy: @nmittler are you saying that CIS will be changed to not do this extra-copy? If so, that will give us what we want. |
What @enis said. It'd be a CIS that can read from the DBB w/o bringing it on heap (and does not do a byte-at-a-time reading out of the backing DBB but reads longs and words at a time when it can). Need same in COS. I can try it out np. |
@enis ... Nothing right now is planned for PRs are always welcome if you don't want to wait :) |
Great. I think we (in HBase community) can come up with the PR for CIS over BB / BS. |
Thanks.. Ya we need COS also to work with DBB with zero copy. Happy to work on CIS part. |
@enis @anoopsjohn sgtm. I'm hoping to have to COS work sorted out soon, which might help provide some direction for CIS ... we'll see :) |
@nmittler Thanks for the inputs/discussions. We can work on COS based on the direction of COS. |
Not targeting a release, but actively being worked on. |
FYI, I've begun work for giving CIS full direct ByteBuffer support. Stay tuned. |
Thanks Nathan :) |
Thanks Nathan.. |
Hey @anoopsjohn, would you mind just creating a PR for protobuf and then cc'ing me? I'd be happy to take a look and see if we can absorb those changes for a future protobuf release. Thanks! |
Do you know what status of providing a zero-copy read of the message payload in gRPC Java and C libraries? I have a use case (sending Apache Arrow streams with gRPC) where I would use a proto message like: message ArrowMessagePB {
required bytes serialized_data;
} I would like to avoid extra memory copies on the server side and on the client side. If this is already supported, or if there is a recommended workaround, I'm very interested. I was also talking with @lukecwik about this recently. As an aside, in the gRPC docs it says (http://www.grpc.io/docs/guides/concepts.html)
Along the lines of this question (zero-copy sends and receives -- e.g. zeromq has pretty good support for this http://zeromq.org/blog:zero-copy) I haven't been able to find more detail about what skipping the protobuf serde step would involve. cc @leifwalsh |
Related: Apache Kudu built the notion of a zero copy "sidecar" into its RPC subsystem https://github.com/apache/kudu/blob/master/src/kudu/rpc/rpc_sidecar.h#L29 |
@wesm If you don't want the Protobuf overhead, you can use InputStream Directly and skip the copy. It's more complicated, but there is an example of how to send the raw ByteBufs around here: grpc-java/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java Line 495 in 89bc2cd
|
I've finally made some progress on this: Still need to test more but we should look for a way to incorporate some of the behavior of these two reflection classes [2] directly into GRPC (somehow... some initial thoughts in the classes). |
Is there any progress on that ? I'm trying to replace a custom Netty protocol by gRPC but I fear the lack of zero-copy and object pooling will kill performance. |
@cbornet, I'd say right now, yes, absolutely, it is possible to make something faster using Netty directly. But that's not something everyone can do, and it requires a lot of energy to create and maintain, and there is the question of how much benefit you will gain. What percentage of performance are you willing to lose for ease of use and maintenance? .1%, 1%, 10%, 50%? If you just want raw bytes it isn't so bad, but if you want any serialization format that can easily evolve over time it gets more complicated. A major issue with this work is there is no lifetime for protobuf messages, but that's almost universal with serialization implementations in Java. If you have a message format you like in your custom approach, we can probably adapt gRPC to be efficient with it. So if you are happy working with FileRegion in general is harder, because we will need to cut it into frames. We'd also lose the benefits of it when using TLS, and we're not too interested to go out of our way to optimize only plaintext. So if we had Kernel TLS support, it might be a more interesting discussion. For sending proto, I'll note that For receiving proto, a lifetime must be established to make it useful, as gRPC has the data in Netty ByteBufs which have lifetimes. And nobody has good ideas as to how to accomplish that. The only idea I have is to have a mode that will require the message to be consumed synchronously within the |
@ejona86 thanks for the detailed answer. In my gRPC adapter, I had to put the payload in a protobuf ByteString and as I understand it even if I do: // send being a protobuf message received in an onNext()
ByteString headersAndPayload = send.getHeadersAndPayload();
ByteBuffer buffer = headersAndPayload.asReadOnlyByteBuffer();
ByteBuf payload = Unpooled.wrappedBuffer(buffer); there's still a copy from Netty to ByteString, is that correct ? For serialization, I did ByteBuf metadataAndPayload = entry.getDataBuffer();
ByteString payload = UnsafeByteOperations.unsafeWrap(metadataAndPayload.nioBuffer());
// ... code to call observer's onNext with message and payload
entry.release(); // will release the metadataAndPayload ByteBuf Does it provide 0-copy ? Am I doing something wrong with the lifetimes ? |
No, that approach is fine. The
Yes. The problem is
If gRPC optimized reading ByteBuffers from the protobuf (which I believe is feasible), then that would be zero-copy. But the lifetime is wrong! It isn't safe to call So you should see a pattern: ByteBuf allows zero-copy with pooling. ByteBuffer can only do zero-copy without pooling. Explicit object lifetimes are essential for pooling. Object lifetimes are "viral" in that anything holding an object with a lifetime must have a lifetime. Protobuf messages have no lifetime, thus you either have to copy out of a pooled object or figure out a side-band method for communicating the lifetime. |
Maybe this could be proposed as an expert mode where the application would have to release the ByteBuf itself ? A bit like how it's done in Netty's ChannelInboundHandlerAdapter. Or you do the release in gRPC but the application has the possibility to retain/release the ByteBuf on its side.
Indeed ! Would it be possible then to register a callback or listener to get notified when gRPC has handled the message and it's safe to release the resources ? |
I spoke some with @cbornet on Gitter about using pooled buffers when sending. Specifically how the
|
Big +1 for onNext(Message, Runnable onRelease). |
With my API proposal, I think some people may think this is "solved" now. There's plenty of details though. I'm dumping some of my thoughts for details.
I realized we sort of already have such a callback: the InputStream.close() after the message is serialized. However, that isn't directly useful, because gRPC may serialize multiple times for retry. It also doesn't help us with Protobuf specifically because we need to go "around" the protobuf API (although for something like Pulsar that isn't a problem). It could maybe be used for receiving instead of sending, or maybe we could change how retries are implemented. It may be fair to mirror what we did for compression: add a method to change the current "message releaser", and let you change that each message if you'd like. It would not be thread-safe, and the "current" "releaser" when onNext() is called will be the releaser called when the message is no longer used.
While that works, I think it does expose a flaw in the approach: Pulsar-like-formats should "just work" without the need to set the releaser. Which would imply it maybe should be part of the Marshaller. I don't know if we make this part of the Marshaller as well or exclusively, along with a shim to allow it for Protobuf. For client-side I can see how we could have an interceptor shim that would wrap the Marshaller and you'd call In general, I'm also seeing trouble with interceptors. Some interceptors may "drop messages on the floor." Those would be incompatible with this approach, but there are probably few enough of those interceptors you could probably "fix them." We'll need to think of how "natural" it will be for them to implement proper semantics. The unfortunate part is it would be hard to discover interceptors that were leaking messages. Internally, we will need NoopClientStream, RetriableStream, and okhttp, netty, and inprocess streams to support this. Note that RetriableStream will need to do coordination between the sub-streams it creates. It may make sense for this to be explicit (like Since this call can happen after the RPC is closed, we can't use the normal executor for this, except maybe if we had a fallback executor. We generally don't want to run callbacks from our network thread, since it is trivial for applications to accidentally block within them, but maybe in this case we make an exception. Unclear. |
This is a naive question, but would the approach of adding a different mode to protobuf that generates class that support lifecycle be a feasible way of handling this? A quick sketch of what I mean:
could generate something like:
|
Any change along these lines is highly likely to be an API breaking change. The problem is that even if only one message had this support, all messages must transitively gain the support as well. If a message contains that "one special message," then it needs to have a lifetime as well. Since protobuf requires that adding a field to a message is backward-compatible, that means that all messages are potential containers for a lifetime-constrained message and would need be have their own lifecycles ("just in case"). There are some approaches to try to reduce the impact of such a change, like by requiring each message to explicitly enable the behavior via an option. So you could only have fields of lifecycle-based messages in a lifecycle-based message. But that would produce cross-language compatibility problems. It would also cause trouble with generic And remember that ByteBuffer in Java does not have a lifetime. ByteBuf in Netty does, but Protobuf can't easily depend on Netty. So you'd need to provide a Factory for creating instances and a function to release instances. |
@ejona86 thanks for the detailed response.
Agreed. I think this would have to be an all or nothing compilation mode, plus potentially additional options that were mentioned. i think the question is would it be possible to decouple this sufficiently and reduce code duplication from the API change? Or is the callback approach outlined above preferable? From gRPC core, it seems like it would need to understand an API which can transfer raw memory buffers to a deserializer?
I don't understand this. I thought we were specifically discussing Java (or at least that was what I had cached in my head)? What other languages would this impact? Other JVM based languages?
This likely depends on the flow of ownership but I agree it is a concern. Are there specific APIs related to gRPC that you are concerned with?
Since we don't seem close a solution, it seems like JEP-370 might be a way to make this generic across future JVMs? |
I don't think all-or-nothing is on the table, as that is basically a new separate protobuf library. You'd need to allow both styles to exist in the same binary (to avoid diamond dependency problems) and so it'd have to be in a new package. You couldn't mix generated code.
If someone modified a proto and wasn't aware of the Java-isms, they could add a field in a non-lifecycle'd message to a lifecycle'd message. That would build fine in all non-Java languages. But now Java is broken and can't communicate with other languages.
It's a general concern. But gRPC would definitely be impacted. If you use an older gRPC with a newer protobuf (which is fully allowed) then
It'll be helpful... in a decade? Maybe 5 years if we are lucky? Protobuf and gRPC still support Java 7 and it is really hard to tell when it is "acceptable" to drop support for older versions. There are some ways to allow it to be used on newer JVMs while remaining compatible with older JVMs, but you pay a complexity cost for them. Even with JEP-370 you'll probably still want a factory so you can use a pool of buffers, so using ByteBuffer instead is probably not that big of a difference. Also, JEP-370 just has a single owner of memory; we frequently see the need for full reference counting. There's probably ways to hide that... blah, blah. It still won't be easy. Easier, but not easy. |
@njhill in #7330 (comment) said:
ParseTakesOwnership marker would be easy to implement. Although if it is unsupported (say, an interceptor has mucked with the marshaller) there would be no indication. Something along that direction could be nice. Yes, outbound ownership transfer has to be handled separately. And yes, today a message may be marshalled multiple times which means there is no way to know when memory can be released other than via the GC. I'll note there might be difficulty as well if the message is never marshalled. |
This is something of a coverall issue and should be factored out into separate issues for each individual piece of work. Listed here are a number of areas for improving the interaction between protobuf and GRPC to improve overall performance and reduce memory pressure on the system.
In rough order of priority....
@nmittler @ejona86 - Feel free to pile on / cc folks
The text was updated successfully, but these errors were encountered: