-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Anchor Events Are Extremely Compute Intensive When Used Without Maximal Compiler Optimizations #863
Comments
Have you measured the difference in compute units between JSON vs base64(borsh)? |
Mango said Anchor events reduced their compute units for logging from 30k units to 4k. What kind of compute unit usage are you seeing? |
Ah perhaps I should clarify, regardless of the serialization method being used, any sort of serialization will probably end up more or less costing the same amount of compute units. I'm not super familiar with proc macros, but I think a proc macro which generates a formatted message that can be used would be cheaper.
From a quick look through the two different branches which use traditional ExamplesHere's an extremely quick PoC i put together, as per the program logs anchor events consume more than twice the amount of compute units compared to just logging a JSON string with fromatting:
Heres the same PoC but with more data being logged, and there is still approximately twice the compute units being used:
Applying the following compile optimizations drastically reduced the discrepancy, however this doesn't really solve the issue because it means assuming everyone will deploy their anchor programs with these optimizations added. lto = "fat"
codegen-units = 1
[profile.release.build-override]
opt-level = 3
incremental = false
codegen-units = 1 gives the following compute units usage:
|
Can you clarify the original issue in this context and the suggested fix? Programs don't need to use the emit! macro if they find it too costly, and if there's a way to make it more efficient, we should do it. |
Sure i'll update the original issue.
That's personally what we've done with our programs for SolFarm, but that only really puts a bandaid on the issue, and only truly works as a solution if your program never CPI's to other programs, or just never CPI's to other programs that use the |
Based on your program logs you're roughly saying that emitting the
serialized data is 2k units whereas msg! logging a json string (pre
formatted) is only 1k units.
It definitely sounds like a good argument for just printing preformatted
json strings (aka don't let the msg! function call do the json formatting
as mango used to do). Json strings are more human readable too, though it
doesn't give as much guarantee that the data will follow the IDL. Maybe a
solution could be that anchor formats its event structs into a json string
and emits that instead of a serialized struct.
Though honestly, 1k units vs. 2k units might seem large percentage wise but
in absolute terms it's more insignificant. When you use msg! by itself how
much lower can it go below 1k units and is this something anchor can even
fix?
Also relevant questions. Are you using a lot of these calls? Would it be
possible to consolidate into less calls?
…On Mon, Oct 11, 2021, 12:14 AM bonedaddy ***@***.***> wrote:
Can you clarify the original issue in this context and the suggested fix?
Sure i'll update the original issue.
Programs don't need to use the emit! macro if they find it too costly, and
if there's a way to make it more efficient, we should do it.
That's personally what we've done with our programs for SolFarm, but that
only really puts a bandaid on the issue, and only truly works as a solution
if your program never CPI's to other programs, or just never CPI's to other
programs that use the emit! macro.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#863 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADAHMGFGDEI4DY7NKAWSYITUGIM6LANCNFSM5FWAGXLQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
@bonedaddy can you measure instructions count with this PR? #870 |
To clarify this is with compiler optimizations applied, which isn't always going to be the case. Without compiler optimizations its 4k not 2k
Right it's negligible in this context where the amount of data being emitted is tiny, and there's nothing else going on. But once you start interacting with other programs via cpi, emitting multiple events, and getting into complex DeFi protocols it's not negligible at all. Up until recently the program's we've integrated with have emitted a small number of events that the compute units used by events hasn't been problematic. However when Saber migrated their farms to Quarry which emits a number of events, the compute units consumed by events started becoming problematic. When harvesting rewards from Quarry, 3 events are emitted: 1:
2:
3
These logs include roughly 2x the amount of characters as the log from my PoC. For arguments sake, lets say this implies a 2x increase in the amount of compute units. Quarry doesn't include the compiler optimizations so that means 1 event consumes 8k compute units, for a total of 24k compute units consumed across all three events, for a total consumption of 12% of the available compute units. If Quarry were to be built with the compiler optimizations, it would be half the cost so 6% of the available compute units. If the events were logged as JSON, it would amount to around 3% of the compute units. In the case of Saber's Quarry farms the token you get from harvesting is an IOU which needs to be redeemed by another program, which also emits an event of roughly equal size. Using the hypothetical estimations so far, the act of harvesting + redeeming rewards without optimizations consumes a total of 32k compute units , or 16% of the available compute units. Hypothetical estimations aside, the amount of events emitted from the harvest and redeem steps was enough that we had to adjust our vaults to do this in two separate transactions. Even if Quarry was deployed with compiler optimizations, we would be right on the edge of compute unit consumption, and would likely still have to do this in two steps. Events emitted as JSON would allow this to happen in one transaction. Solana DeFi is just getting started, and with Anchor becoming increasingly popular, its reasonable to assume that events are going to be used more and more. If DeFi on Solana is going to behave as it does in other ecosystems, this means many different protocols interacting with each other. So while on a small scale (ie a program being used by a webui, or in this PoC) i agree the cost savings dont matter that much. Once you start considering the broader picture, a Solana ecosystem with hundreds of dapps interacting with each other, it does matter. At present I believe compute units are instruction wide, the easiest work around is to simply include multiple instructions when you create a transaction. However consiering there is a change coming to Solana that will apply compute units budget transaction wide, this is only a short term solution.
Apologies if I wasn't clear, we don't actually use events with SolFarm. The issue comes from needing to interact with other programs that use events. |
Yup, I'm actually about to sign off for the night so I'll do this in the morning. |
Does compute cost actually increase linearly with the amount of text being printed? This isn't something I've heard of before.
No ones arguing against you on these points, just trying to accurately gauge what the problem actually looks like in order to have a strong starting point for figuring out potential solutions. Events and good logging are super important for the whole ecosystem! |
I'm not sure to honest, just used that as a guesstimate. However even if it doesn't scale linearly and its only a 25% increase, a 15% increase, or just a 10% increase, the compute units consumed from events becomes problematic the more complex your program gets. Even without complex programs, emitting one too many events is enough to cause problems (ie: the harvest + redemption example i mentioned previously).
Events are very important indeed, but the compute units currently consumed by events will not scale to more complex protocols that involve interacting with many other protocols. It's entirely possible to work around this now, but once transaction wide compute limits are implemented, it will be a blocker. |
@fanatid i got the following error:
|
@bonedaddy not sure why version with |
Unfortunately that made compute units consumption worse 😭 Compiling with optimizations:
|
Interesting. Thanks that checked! |
A new syscall API was added recently, which hopefully addresses some of these concerns. See solana-labs/solana#19764. |
Interesting, that looks very promising. |
What mango is doing to make this more efficient for memory usage: https://github.com/blockworks-foundation/mango-v3/pull/134/files |
@bonedaddy would you mind benchmarking again with the master branch? master now uses the |
made some benchmarks for anchor repo: https://github.com/paul-schaaf/emit-bench (master has the custom profile, summary:
we can add the custom profile to our cli templates so that every anchor project going forward uses it. |
Overview
With the default compiler optimizations, logging the same information through
emit!
is approximately 2x more expensive in compute units, than it is to directly usemsg!
.When using maximal compiler optimizations as follows, the cost increase is drastically reduced, but is still more expensive.
While this can solve compute unit usage issues within your own programs, it still leaves programs at risk of reaching compute unit limits when making CPI to third-party programs that emit events, without these compiler optimizations.
As it stands this is more of an edge-case issue, however if the current event system is kept in place I think once more programs are deployed onto Solana, and there is more interconnectivity between programs, this will probably start to become a real issue.
Proposed Solution - Avoid Borsh Serialization And Base64 Encoding Completely
Personally speaking I think the best option is to remove serialization and encoding of events, and use proc macros to format the event into a JSON string which is what gets logged. This has the added bonus of being human readable on block explorers.
Alternative Solutions
anchor init
add the aforementioned compiler optimizations to the default workspace file.Optimize The Process Of Emitting Events
Not exactly sure what this would look like, but there's probably something that can be done. At a quick glance, changing this function to preallocate the vector entirely would help, but im not sure if this is possible.
Add Compiler Optimizations To Default Workspace File Generated By Anchor
The text was updated successfully, but these errors were encountered: