Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Conversation

@gui1117
Copy link
Contributor

@gui1117 gui1117 commented Jul 19, 2021

This reduce the size of runtime call to be less than 90.
It also improve its usage in frame-utility.

@gui1117 gui1117 added A3-in_progress Pull request is in progress. No review needed at this stage. B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. labels Jul 19, 2021
@gui1117 gui1117 added A0-please_review Pull request needs code review. D9-needsaudit 👮 PR contains changes to fund-managing logic that should be properly reviewed and externally audited A3-in_progress Pull request is in progress. No review needed at this stage. and removed A3-in_progress Pull request is in progress. No review needed at this stage. A0-please_review Pull request needs code review. labels Jul 19, 2021
@gui1117 gui1117 added A0-please_review Pull request needs code review. A3-in_progress Pull request is in progress. No review needed at this stage. and removed A3-in_progress Pull request is in progress. No review needed at this stage. A7-needspolkadotpr A0-please_review Pull request needs code review. labels Jul 19, 2021
@gui1117
Copy link
Contributor Author

gui1117 commented Jul 19, 2021

thinking again maybe we should introduce something similar to pallet::compact but to box the given arg when generating the call: #[pallet::box]. But I'm not sure it worth complexifying the pallet declaration.

impl<T: Config> Pallet<T> {
/// The limit on the number of batched calls.
fn batched_calls_limit() -> u32 {
let allocator_limit = 33554432;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let allocator_limit = 33554432;
let allocator_limit = 33554432;

feels this number should come from somewhere else?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

indeed but we can't currently link to it because it is in the client.
Maybe the proper solution is to have a configuratble limit: type MaxBatched: Get<u32>, or move the allocator limit to the primitives

Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe better to set a hardcoded limit of 10_000 and then assert this in a test

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 think it is safe to merge like this because the allocator limit should not decrease, that said I opened an issue #9392 and a PR #9393
once merge I'll update this code as well.

origin: OriginFor<T>,
source: <T::Lookup as StaticLookup>::Source,
dest: <T::Lookup as StaticLookup>::Source,
source: Box<<T::Lookup as StaticLookup>::Source>,
Copy link
Contributor

Choose a reason for hiding this comment

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

curious: what is the static size of this? a bit surprised that it is part of the offending ones.

Copy link
Contributor Author

@gui1117 gui1117 Jul 20, 2021

Choose a reason for hiding this comment

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

it is 40 bytes if I remember correctly, note that an account id is already 32 bytes

Copy link
Contributor

@kianenigma kianenigma left a comment

Choose a reason for hiding this comment

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

Didn't check all, but looks mostly good to me.

Would be good if we add some guidelines around this. Maybe you can compile a pallet with PRINT_CALL_SIZE=1 or similar, and it would report the call size.

This is something that needs to be communicated to devhub IMO.

Comment on lines +364 to +366
code: Box<Vec<u8>>,
data: Box<Vec<u8>>,
salt: Box<Vec<u8>>,
Copy link
Member

@athei athei Jul 20, 2021

Choose a reason for hiding this comment

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

Why do we box Vec? It already allocates its elements on the heap. There are more places where this happened. I just mark this one as example.

Copy link
Contributor Author

@gui1117 gui1117 Jul 20, 2021

Choose a reason for hiding this comment

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

I was surprised too but a vec is 24 bytes https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=076c59fc288d9fe6bb17e1c7c92e35a7

so this extrinsic variant fields are: 16 + 8 + 24 * 3 added to the variant indices: 1 for pallet variant, 1 for call variant. It is 98 bytes

Copy link
Member

Choose a reason for hiding this comment

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

Ohh. I expected it to be much smaller. Then go ahead.

Copy link
Contributor

@kianenigma kianenigma Jul 20, 2021

Choose a reason for hiding this comment

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

@gui1117
Copy link
Contributor Author

gui1117 commented Jul 20, 2021

maybe better than this we can just box each pallets call.

pub enum RuntimeCall {
    PalletCall(Box<PalletCall>),
...
}

So that if some pallet call have big size it is not shared with all other pallet calls

@athei
Copy link
Member

athei commented Jul 20, 2021

maybe better than this we can just box each pallets call.

pub enum RuntimeCall {
    PalletCall(Box<PalletCall>),
...
}

So that if some pallet call have big size it is not shared with all other pallet calls

I was about to make exactly this proposition but was unsure if it is possible with those macros. It makes the code really ugly to have Boxes everywhere. Would love to see it implemented in this way transparently for the pallets. Also this would be way less allocation pressure, right?

@kianenigma
Copy link
Contributor

kianenigma commented Jul 20, 2021

But the downside of that is that even for small data that are more efficiently managed on the stack, you force them to be on the heap. I recall that in another PR benchmarked showed 100% performance hit (twice larger weight) in boxing stuff.

I (unfortunately) think that manual boxing is better to begin with, and over time we improve it if we learn that a certain pattern is best.

and again I want to emphasize that some tool to print the size of call enum variants is very important.

@athei
Copy link
Member

athei commented Jul 20, 2021

But the downside of that is that even for small data that are more efficiently managed on the stack, you force them to be on the heap. I recall that in another PR benchmarked showed 100% performance hit (twice larger weight) in boxing stuff.

You mean for dispatchables which have small parameters? Yeah that is an obvious drawback.

But maybe we can just make this more granular? Instead of boxing per pallet or per argument, boxing per dispatchable (you will opt-in with an attribute) might be the sweet spot?

enum RuntimeCall {
    CoolPallet(CoolPallet),
}

enum CoolPallet {
    BigDispatchable(Box<(BigArg, EvenBiggerArg)>),
    SmallDispatchable(u32),
}

mod cool_pallet {
    #[weight(1000), box_args]
    fn big_dispatchable(big_arg: BigArg, even_bigger_arg: EvenBiggerArg) {}

    #[weight(1000)]
    fn small_dispatchable(small_arg: u32) {}
}

assert_eq!(Asset::<Test>::get(0).unwrap().approvals, 1);
assert_eq!(Balances::reserved_balance(&1), 1);
assert_ok!(Assets::transfer_approved(Origin::signed(2), 0, 1, 3, 40));
assert_ok!(Assets::transfer_approved(Origin::signed(2), 0, Box::new(1), Box::new(3), 40));
Copy link
Member

Choose a reason for hiding this comment

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

this is a bit sad.

@bkchr
Copy link
Member

bkchr commented Jul 20, 2021

maybe better than this we can just box each pallets call.

pub enum RuntimeCall {
    PalletCall(Box<PalletCall>),
...
}

So that if some pallet call have big size it is not shared with all other pallet calls

Yeah, I also thought that we should do this as a last step, but before maybe fix the individual pallet calls.

@xlc
Copy link
Contributor

xlc commented Jul 20, 2021

I kinda getting why this is needed but is there some proper analysis and explanation of why this is necessary and what should parachain do with their pallets?

@gui1117
Copy link
Contributor Author

gui1117 commented Jul 20, 2021

I agree with what @athei propose: opt-in boxing of the whole call variant.
EDIT: second thought I feel like we add complexity for little benefit. Maybe reducing call to 200 bytes is ok and enough

@athei
Copy link
Member

athei commented Jul 20, 2021

I agree with what @athei propose: opt-in boxing of the whole call variant.
EDIT: second thought I feel like we add complexity for little benefit. Maybe reducing call to 200 bytes is ok and enough

Complexity of the macro you mean? But these are one time costs. Burdening the pallet authors with boxing everything will be payed by more people and more often.

@kianenigma
Copy link
Contributor

kianenigma commented Jul 20, 2021

reading everything again, I see two ways to go:

  1. Box each parameter individually. I honestly think adding macro syntax for this (e.g. (#[box] arg: Arg) instead of arg: Box<Arg>) is superfluous. If you want to box one argument, just do it manually, it is not that many extra characters and it makes the code easier to understand, with less macro magic. And you ONLY do this when you know that the size needs to be small. So this is not a cost for all users @athei, disagreeing with what you said:

Burdening the pallet authors with boxing everything will be payed by more people and more often.

  1. Box the whole call of one pallet. I see this as a viable addition to the pallet macro. If you define
#[box_call]
#[pallet] 
mod pallet {
     // stuff
}

your entire pallet call is boxed when being amalgamated into the upper runtime call.

I think a combination of both is fine. If a pallet mostly have large calls, we can box it all. For other pallet, we manually box one or few arguments of individual dispatchables.

@gui1117
Copy link
Contributor Author

gui1117 commented Jul 20, 2021

@ascjones had a usecase that requires to put all call variant into its own struct, so that generating type_info is easier.

If we accept this direction then we can add an opt-in attribute #[pallet::box_variant] for calls which allow to box an individual variant.

@gui1117
Copy link
Contributor Author

gui1117 commented Jul 20, 2021

sorry for the back and forth but Andrew doesn't need to wrap in struct anymore. I think we can go for boxing the pallet calls in the runtime outer call. (We should probably also avoid more than 200 bytes pallet calls, there are a clippy lint for this, but it is less important).

@gui1117 gui1117 closed this Jul 20, 2021
@gui1117
Copy link
Contributor Author

gui1117 commented Jul 20, 2021

This is the follow-up #9398

@gui1117 gui1117 deleted the gui-improve-call-usage branch July 20, 2021 15:37
@louismerlin louismerlin removed the D9-needsaudit 👮 PR contains changes to fund-managing logic that should be properly reviewed and externally audited label Oct 13, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

A3-in_progress Pull request is in progress. No review needed at this stage. B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants