-
Notifications
You must be signed in to change notification settings - Fork 473
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
prefetcher: enable prefetcher for assets and apps #4352
Conversation
Codecov Report
@@ Coverage Diff @@
## master #4352 +/- ##
==========================================
+ Coverage 54.11% 54.18% +0.06%
==========================================
Files 401 401
Lines 51549 51583 +34
==========================================
+ Hits 27898 27952 +54
+ Misses 21311 21282 -29
- Partials 2340 2349 +9
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
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 don't know. I'm sure it's better than it was, but I can't follow everything. The whole prefetcher is quite something.
base.assets[ledgercore.AccountAsset{Address: *lr.Address, Asset: basics.AssetIndex(lr.CreatableIndex)}] = cachedAssetHolding{value: *lr.Resource.AssetHolding, exists: true} | ||
} else { | ||
base.assets[ledgercore.AccountAsset{Address: *lr.Address, Asset: basics.AssetIndex(lr.CreatableIndex)}] = cachedAssetHolding{exists: false} | ||
if txgroup.Err == nil { |
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.
Can this be flattened/simplified?
if !ok {
break
}
if txGroup.Err != nil {
// log it
} else {
// for loop
}
ledger/internal/eval.go
Outdated
base.creators[creatable{cindex: lr.CreatableIndex, ctype: lr.CreatableType}] = | ||
foundAddress{exists: false} |
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 would find all of these lines much more readable if creatable
literals did not use field names in the literals. I don't know how much others agree, but the repetitive noise words makes it very hard for me to see the things that matter.
And the structs that have exists: false
could just use {}
It's not a big deal, maybe others find this more explicit and readable.
I'd probably even make tiny functions to construct the objects that have exists: true
so that you only see the object that's being put inside.. It would get inlined.
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 agree it will require less LoC but explicit stuff all in one place imo looks better. This is probably the reason it was written this way
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 was trying to figure out what you were commenting on, but realized I toggled on ignoring whitespace, so none of these lines appear as changed to me ...
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.
Chipping in on the readability.
- some of these lines are 200+ characters long, I actually had to go full screen on a 34' ultrawide for a side-by-side comparison.
- I find the left hand side of the assignments a lot less readable than the right hand side.
Consider the following modification:
// somewhere earlier in the code..
var asset = ledgercore.AccountAsset{Address: *lr.Address, Asset: basics.AssetIndex(lr.CreatableIndex)}
// ..the code we are looking at
if lr.Resource.AssetHolding != nil {
base.assets[asset] = cachedAssetHolding{value: *lr.Resource.AssetHolding, exists: true}
} else {
base.assets[asset] = cachedAssetHolding{exists: false}
}
The rationale to move the pk construction to its own variable is that it's the same pk.
My eyes kept going up/down , left/scroll/right/more-scroll and forth between the lines to see if the pks being constructed had any difference.
The first line can be mode a bit more readable too, but honestly this is something a code formatter should handle automatically than deal with it line by line during PRs.
var asset = ledgercore.AccountAsset{
Address: *lr.Address,
Asset: basics.AssetIndex(lr.CreatableIndex)
}
As for the explicit struct construction on the right hand side, I also personally prefer the explicit construction.
A good syntax color scheme that separates types, fields, methods, consts goes a long way there to tell whats going on at a glance.
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 think it's too bad that Go was an early adopter of "there is one true formatting style" (right down to using tabs vs spaces) but did not extend that to include any hard advice on line length.
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.
fixed to improve readability
ledger/internal/eval.go
Outdated
if lr.Resource.AssetHolding != nil { | ||
base.assets[ledgercore.AccountAsset{Address: *lr.Address, Asset: basics.AssetIndex(lr.CreatableIndex)}] = cachedAssetHolding{value: *lr.Resource.AssetHolding, exists: true} | ||
} else { | ||
base.assets[ledgercore.AccountAsset{Address: *lr.Address, Asset: basics.AssetIndex(lr.CreatableIndex)}] = cachedAssetHolding{exists: false} | ||
} |
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.
For example, this entire thing could be one concise line
if lr.Resource.AssetHolding != nil { | |
base.assets[ledgercore.AccountAsset{Address: *lr.Address, Asset: basics.AssetIndex(lr.CreatableIndex)}] = cachedAssetHolding{value: *lr.Resource.AssetHolding, exists: true} | |
} else { | |
base.assets[ledgercore.AccountAsset{Address: *lr.Address, Asset: basics.AssetIndex(lr.CreatableIndex)}] = cachedAssetHolding{exists: false} | |
} | |
base.assets[ledgercore.AccountAsset{*lr.Address, basics.AssetIndex(lr.CreatableIndex)}] = newCAH(lr.Resource.AssetHolding) |
with the help of newCAH
that makes a cachedAssetHolding with exists = true or false based on whether the pointer argument it gets is nil.
// do not preload Txn.ForeignApps, Txn.ForeignAssets, Txn.Accounts | ||
// since they might be non-used arbitrary values |
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.
They could be, but it seems like we should try to load them anyway, and fail gracefully.
There's even a case to be made for load the cross product - every holding for each account/asset pair, and local state / app pair. But that seems like a lot. I don't know.
I have this funny idea that we should execute the transaction with a special ledger that responds only from cache, and fakes answers to things that aren't in cache, but records what was requested. And we use that to decide what to prefetch.
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.
Yes, I agree, there should be a cache factory in ledger for Round X that is populated assembly time by data from X-1, and used in validation.
I think @cce added this into future improvements list for lowering round time
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.
agreed, why not return to prefetching foreign arrays like we did before?
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.
this might be abused, so I'd prefer not to.
f8bd960
to
b78b539
Compare
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.
LGTM
switch stxn.Txn.Type { | ||
case protocol.PaymentTx: | ||
loadAccountsAddAccountTask(&stxn.Txn.Receiver, task, accountTasks, queue) | ||
loadAccountsAddAccountTask(&stxn.Txn.CloseRemainderTo, task, accountTasks, queue) | ||
case protocol.AssetConfigTx: | ||
loadAccountsAddResourceTask(nil, basics.CreatableIndex(stxn.Txn.ConfigAsset), basics.AssetCreatable, task, resourceTasks, queue) |
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.
This takes a cache miss on initial acfg creation when Txn.ConfigAsset == 0. We could know that id based on block header txn count and the position within the payset. Also probably not a big deal, pretty rare operation, rare cache miss.
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.
There are two different things here worth thinking about, I'm not sure which one you mean. First, in an acfg with id=0, we should not try to prefetch the 0 ASA, since we know it doesn't exist. It looks like we handle that case in loadAccountsAddResource
.
And then there's the question of whether block validation is slowed down because we didn't load the thing that will eventually be created. I don't think so. At evaluation time, acfg create should not be looking up the newly created id. (This brings up a silly optimization we should do, though it presumably does not happen in "good" txns - short-circuit the db lookup for creatables that can't exist because the id is too high.)
Also, need to be careful now about predicting the id based on position in payset. Inner txns count, so id is not just the block start id + payset index.
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.
This takes a cache miss on initial acfg creation when Txn.ConfigAsset == 0. We could know that id based on block header txn count and the position within the payset.
since we know it doesn't exist. It looks like we handle that case in loadAccountsAddResource.
I guess we want to load creator's record since it will touched in apply. Maybe as a separate PR
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.
LGTM
ledger/internal/eval.go
Outdated
base.creators[creatable{cindex: lr.CreatableIndex, ctype: lr.CreatableType}] = | ||
foundAddress{exists: false} |
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.
Chipping in on the readability.
- some of these lines are 200+ characters long, I actually had to go full screen on a 34' ultrawide for a side-by-side comparison.
- I find the left hand side of the assignments a lot less readable than the right hand side.
Consider the following modification:
// somewhere earlier in the code..
var asset = ledgercore.AccountAsset{Address: *lr.Address, Asset: basics.AssetIndex(lr.CreatableIndex)}
// ..the code we are looking at
if lr.Resource.AssetHolding != nil {
base.assets[asset] = cachedAssetHolding{value: *lr.Resource.AssetHolding, exists: true}
} else {
base.assets[asset] = cachedAssetHolding{exists: false}
}
The rationale to move the pk construction to its own variable is that it's the same pk.
My eyes kept going up/down , left/scroll/right/more-scroll and forth between the lines to see if the pks being constructed had any difference.
The first line can be mode a bit more readable too, but honestly this is something a code formatter should handle automatically than deal with it line by line during PRs.
var asset = ledgercore.AccountAsset{
Address: *lr.Address,
Asset: basics.AssetIndex(lr.CreatableIndex)
}
As for the explicit struct construction on the right hand side, I also personally prefer the explicit construction.
A good syntax color scheme that separates types, fields, methods, consts goes a long way there to tell whats going on at a glance.
b78b539
to
76b71c9
Compare
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.
Also prevented prefetching sender/receiver for zero transfers.
switch stxn.Txn.Type { | ||
case protocol.PaymentTx: | ||
loadAccountsAddAccountTask(&stxn.Txn.Receiver, task, accountTasks, queue) | ||
loadAccountsAddAccountTask(&stxn.Txn.CloseRemainderTo, task, accountTasks, queue) | ||
case protocol.AssetConfigTx: | ||
loadAccountsAddResourceTask(nil, basics.CreatableIndex(stxn.Txn.ConfigAsset), basics.AssetCreatable, task, resourceTasks, queue) |
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.
This takes a cache miss on initial acfg creation when Txn.ConfigAsset == 0. We could know that id based on block header txn count and the position within the payset.
since we know it doesn't exist. It looks like we handle that case in loadAccountsAddResource.
I guess we want to load creator's record since it will touched in apply. Maybe as a separate PR
ledger/internal/eval.go
Outdated
base.creators[creatable{cindex: lr.CreatableIndex, ctype: lr.CreatableType}] = | ||
foundAddress{exists: false} |
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.
fixed to improve readability
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.
also added a missed TestEvaluatorPrefetcherAlignmentAssetTransfer test
ops, broke some asset receiver prefetching, fixing |
Fixed one unit test and adjusted AssetReceiver prefetching condition. |
} | ||
if !stxn.Txn.AssetReceiver.IsZero() { | ||
if stxn.Txn.AssetAmount != 0 || (stxn.Txn.AssetReceiver == stxn.Txn.Sender) { | ||
// if not zero transfer and not not opt in |
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.
Should this comment be updated? It seems to have the opt in here as well.
// if not zero transfer and not not opt in | |
// if not zero transfer or is opt in |
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.
the condition is
if stxn.Txn.AssetAmount != 0
or (stxn.Txn.AssetAmount == 0 || (stxn.Txn.AssetReceiver == stxn.Txn.Sender))
i.e. not zero amount or optin. That is what the comment says
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.
LGTM
4106912
to
6bdb584
Compare
Thank you for review! I'll merge it right after the boxes go through to prevent possible conflicts. |
Summary
Enable prefetcher for asset and app transactions except the foreign fields.
Test Plan
apply
logic.