-
Notifications
You must be signed in to change notification settings - Fork 401
ln: reduce size_of Event from 1680 B -> 576 B #3723
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
base: main
Are you sure you want to change the base?
Conversation
Reduces `mem::size_of::<Event>()` from 1680 B -> 1072 B
Reduces `mem::size_of::<Event>()` from 1072 B -> 576 B
I've assigned @wpaulino as a reviewer! |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3723 +/- ##
==========================================
- Coverage 89.10% 89.09% -0.02%
==========================================
Files 156 156
Lines 123431 123431
Branches 123431 123431
==========================================
- Hits 109985 109967 -18
- Misses 10760 10774 +14
- Partials 2686 2690 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, I'm not sure if its preferable to trade the size against an increased risk of heap fragmentation? Did you consider that, and how did you decide a smaller Event
type was worth it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m not sure I understand the reason for this change. Did you encounter any issues, or is it just for size reduction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see how the large size can be a constraint for environments with smaller stack sizes/limited memory. Events are tracked in LDK within a Vec, so the memory of each one already lives on the heap. The issue comes from moving each event into the event handler, so the 1680B will be put on the stack every time regardless of the event variant size. Perhaps we should modify the event handler to take events by reference instead?
Sure, not denying it can become an issue in certain circumstances.
The issue is not that they live on the heap, the issue of heap fragmentation is that you have a bazillion tiny allocations that make huge parts of memory unusable after some time, as it becomes harder and harder to find 'fitting gaps' for larger allocations.
I guess although would be a pity to give up on move semantics? FWIW, it might be worth exploring if the event queue could be an |
Yeah I definitely don't think we should
Not sure how else we can address this otherwise. FWIW, we don't have move semantics on wire messages even though we could, possibly for the same reason as we'd want to avoid them here.
That extra allocation is not ideal, but it would also go away if we gave the |
Right, there is slight heap fragmentation in 1.5 / 25 variants in exchange for reduced heap+stack usage, cache misses, etc for all variants. I'm not super familiar with the bench setup in LDK, but I tried running
Within the noise threshold, but not nothing. An OnionMessenger process_events bench would probably make this more apparent. Ultimately the most performant approach IMO would be to just hand a batch of events to the consumer instead of the current "event-by-event" handling. That would complicate the error handling, but would let us persist batches of events that we need to handle asynchronously. |
Sorry, to add on to this, we currently have some layers of async handlers for each async fn event_handler.get_ldk_handler_future(event) // 14624 B (!)
async fn event_handler.handle_inline(event) // 12928 B
async fn event_handler.handle_event(id, event) // 11136 B
async fn event_handler.persist_and_spawn_handler(event) // 1872 B
async fn event::handle_payment_claimable(id, {..}) // ...
... And each layer gets blown up by + size_of Event. So reducing size_of Event would have a multiplicative effect in reducing the size of our futures. |
Did you adjust the sample size to something significant? Otherwise executing a single bench run is far from represenative either way.
Hmm, well, I think we started discussing this in https://github.com/orgs/lightningdevkit/discussions/2381 / #2491, where we established why it's not trivial to "just enable" concurrent event handling for certain variants at least. But I agree that would be the proper/longer term fix for the issue at hand.
Without more context I'm not quite understanding what these layers do or why you chose to go that way. But it seems the issue you are trying to solve by this PR is partially self-inflicted by your architecture, IIUC? Just out of curiousity, would you like the alternative solution proposed above, i.e., giving |
FWIW I'm incredibly skeptical that changing a few boxes would result in a change that is more than 100 nanoseconds across the entire send pipeline, which is definitely unmeasureable in the As for whether to reduce the size of
If it were just this, I'd say go for it! This event is relatively rare (as it only happens on a timer when we have stuff pending on chain), so the impact is capped.
But this case is a bit trickier. #3730 may reduce allocations in One thing we could do here is take #3730 a few steps further and have similar pre-allocated variable-length things for some of the stuff in BOLT 12 structs - eg. pre-allocate the blinded paths, pre-allocate the issuer, description, and payer note, and pre-allocate in |
It looks like the
Event
enum has gotten pretty large @ 1680 B, which forces even small variants likeEvent::OnionMessagePeerConnected { peer_node_id: PublicKey }
to waste a bunch of memory. It also blows up the size of our handler Future(s), since we moveEvent
s into them.Fortunately we can clean up some of the low-hanging fruit pretty easily. Here's two diffs that reduce
size_of::<Event>()
from 1680 B -> 576 B.InvoiceContents
inBolt12Invoice
andStaticInvoice
. This shrinksEvent
from 1680 B -> 1072 BInvoiceContents
is private, so this shouldn't be a breaking changeAnchorDescriptor
inBumpTransactionEvent
. This shrinksEvent
from 1072 B -> 576 B.AnchorDescriptor
is technically public and boxing it is a semver breaking change, but the struct's pretty deep in there... Guess I'll leave that to your discretion.We could go even further to 320 B by boxing
PaymentPurpose
, but that feels like a much more invasive / semver breaking change.