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

prefetcher: enable prefetcher for assets and apps #4352

Merged
merged 8 commits into from
Oct 31, 2022

Conversation

algorandskiy
Copy link
Contributor

Summary

Enable prefetcher for asset and app transactions except the foreign fields.

Test Plan

  1. Modified all prefetcher error tests to ensure all transaction groups returned by the prefetecher even in case errors.
  2. Checked prefetcher_alignment_test that validates prefetcher's behavior againsy apply logic.

@codecov
Copy link

codecov bot commented Aug 4, 2022

Codecov Report

Merging #4352 (6bdb584) into master (7e8ca90) will increase coverage by 0.06%.
The diff coverage is 67.64%.

@@            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     
Impacted Files Coverage Δ
ledger/internal/eval.go 67.15% <8.57%> (-0.69%) ⬇️
ledger/internal/prefetcher/prefetcher.go 95.51% <98.50%> (+27.32%) ⬆️
ledger/roundlru.go 90.56% <0.00%> (-5.67%) ⬇️
network/wsPeer.go 66.03% <0.00%> (-4.86%) ⬇️
ledger/blockqueue.go 85.63% <0.00%> (-2.88%) ⬇️
crypto/merkletrie/trie.go 66.42% <0.00%> (-2.19%) ⬇️
crypto/merkletrie/node.go 91.62% <0.00%> (-1.87%) ⬇️
catchup/peerSelector.go 98.95% <0.00%> (-1.05%) ⬇️
catchup/service.go 68.14% <0.00%> (-0.50%) ⬇️
network/wsNetwork.go 64.82% <0.00%> (ø)
... and 3 more

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

jannotti
jannotti previously approved these changes Aug 4, 2022
Copy link
Contributor

@jannotti jannotti left a 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 {
Copy link
Contributor

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
}

Comment on lines 1598 to 1565
base.creators[creatable{cindex: lr.CreatableIndex, ctype: lr.CreatableType}] =
foundAddress{exists: false}
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

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 ...

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed to improve readability

Comment on lines 1603 to 1578
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}
}
Copy link
Contributor

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

Suggested change
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.

ledger/internal/prefetcher/prefetcher.go Show resolved Hide resolved
ledger/internal/prefetcher/prefetcher.go Show resolved Hide resolved
ledger/internal/prefetcher/prefetcher.go Show resolved Hide resolved
Comment on lines +337 to +341
// do not preload Txn.ForeignApps, Txn.ForeignAssets, Txn.Accounts
// since they might be non-used arbitrary values
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

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?

Copy link
Contributor Author

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.

ledger/internal/prefetcher/prefetcher.go Show resolved Hide resolved
ledger/internal/eval.go Outdated Show resolved Hide resolved
Copy link
Contributor

@brianolson brianolson left a 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)
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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

icorderi
icorderi previously approved these changes Sep 15, 2022
Copy link
Contributor

@icorderi icorderi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines 1598 to 1565
base.creators[creatable{cindex: lr.CreatableIndex, ctype: lr.CreatableType}] =
foundAddress{exists: false}
Copy link
Contributor

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.

Copy link
Contributor Author

@algorandskiy algorandskiy left a 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)
Copy link
Contributor Author

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

Comment on lines 1598 to 1565
base.creators[creatable{cindex: lr.CreatableIndex, ctype: lr.CreatableType}] =
foundAddress{exists: false}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed to improve readability

Copy link
Contributor Author

@algorandskiy algorandskiy left a 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

icorderi
icorderi previously approved these changes Sep 15, 2022
@algorandskiy
Copy link
Contributor Author

ops, broke some asset receiver prefetching, fixing

@algorandskiy
Copy link
Contributor Author

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
Copy link
Contributor

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.

Suggested change
// if not zero transfer and not not opt in
// if not zero transfer or is opt in

Copy link
Contributor Author

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

algonautshant
algonautshant previously approved these changes Sep 20, 2022
Copy link
Contributor

@algonautshant algonautshant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@algorandskiy
Copy link
Contributor Author

Thank you for review! I'll merge it right after the boxes go through to prevent possible conflicts.

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

Successfully merging this pull request may close these issues.

6 participants