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

[FRAME core] ☂ Benchmark syntax V2 follow-ups #380

Closed
13 of 27 tasks
ggwpez opened this issue Jan 12, 2023 · 23 comments
Closed
13 of 27 tasks

[FRAME core] ☂ Benchmark syntax V2 follow-ups #380

ggwpez opened this issue Jan 12, 2023 · 23 comments
Labels
I6-meta A specific issue for grouping tasks or bugs of a specific category.

Comments

@ggwpez
Copy link
Member

ggwpez commented Jan 12, 2023

After paritytech/substrate#12924 is done, some things to consider (cc @sam0x17):

@sam0x17
Copy link
Contributor

sam0x17 commented Jan 23, 2023

@ggwpez as part of this, do we also want callable benchmark functions based on the existing function defs? I was contemplating just taking the function defs, replacing the Linear components with things like x: u32, and adding a verify boolean to the end of the signature

So this:

#[benchmark]
fn my_benchmark(x: Linear<1, 100>) {
    let caller = whitelisted_caller();
    for i in 0..x {
        do_something();
    }

    #[extrinsic_call]
    _(RawOrigin::Signed(caller.clone()), something, something);

    assert_eq!(something, something);
}

would expand to something like this (in addition to all the traits and structs it already expands to):

#[benchmark]
fn my_benchmark(x: u32, verify: bool) {
    let caller = whitelisted_caller();
    for i in 0..x {
        do_something();
    }

    my_benchmark(RawOrigin::Signed(caller.clone()), something, something);

    if verify {
        assert_eq!(something, something);
    }
}

Then the functions would be acessible directly from the enclosing module like:

benchmarks::my_benchmark(33, true);

I don't know if this would work in all cases. One of the trait impls already basically does this, but I figured people are going to be confused if they realize these function defs don't actually become function defs, so this would fix that

We could also preserve doc comments on these function defs (in addition to putting them on weightinfo, etc)

@ggwpez
Copy link
Member Author

ggwpez commented Jan 23, 2023

I don't know if this would work in all cases. One of the trait impls already basically does this, but I figured people are going to be confused if they realize these function defs don't actually become function defs, so this would fix that

Not sure how useful this would be, since nobody should call these functions manually anyway. Or in which case would you want to call them directly?

We could also preserve doc comments on these function defs (in addition to putting them on weightinfo, etc)

Yea we should somehow expose the docs i think, but its rather low priority. We could also do some additional doc crate for that since the benches are private.

@sam0x17
Copy link
Contributor

sam0x17 commented Jan 24, 2023

Ok this makes sense. Will focus on the other items first then 👍🏻

@jasl
Copy link
Contributor

jasl commented Jan 25, 2023

Although

#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), something, something);

is valid syntax, but it looks very rare, and Clion warns #[extrinsic_call] has syntax error (expects '(', ';', , '[' or '}',but got '_', I shall submit a ticket for them)

parhaps use macro here is more readdable and IDE-friendly?

@sam0x17
Copy link
Contributor

sam0x17 commented Jan 25, 2023 via email

@jasl
Copy link
Contributor

jasl commented Jan 25, 2023

Is that just for the _() version?

On Wed, Jan 25, 2023 at 5:49 AM Jun Jiang @.> wrote: Although #[extrinsic_call] (RawOrigin::Signed(caller.clone()), something, something); is valid syntax, but it looks very rare, and Clion warns #[extrinsic_call] has syntax error (expects '(', ';', , '[' or '}',but got '') parhaps use macro here is more readdable and friendly for IDE? — Reply to this email directly, view it on GitHub <#13132 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAOE4LLIIE7FMHK5GIKTMK3WUEAKHANCNFSM6AAAAAATZKNEKM . You are receiving this because you were assigned.Message ID: @.>

yes, given full name will resovle the syntax error.

@sam0x17
Copy link
Contributor

sam0x17 commented Jan 27, 2023

yes, given full name will resovle the syntax error.

@ggwpez what do you think of this? I'm not particularly attached to the _() shortcut so I'd be fine with removing it, but I know you like it.

On the flipside, there are a number of places in the codebase already where things like rust-analyzer break.

@jasl
Copy link
Contributor

jasl commented Jan 27, 2023

Another issue is blank benchmark will make compiling error

#[benchmarks]
mod benchmarks {
	use super::*;

	impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test);
}

should I open an issue for this?

@ggwpez
Copy link
Member Author

ggwpez commented Jan 27, 2023

parhaps use macro here is more readdable and IDE-friendly?

On the flipside, there are a number of places in the codebase already where things like rust-analyzer break.

Yea, I dont think we should try to work around a the bugs of downstream tooling. This would only hinder our progress, but in this case the syntax is not very good to begin with.

I'm not particularly attached to the _() shortcut so I'd be fine with removing it, but I know you like it.

TBH I dont like it that much either… any better idea? Since we have the chance to throw out the garbage we should try to get the best syntax possible.

#[extrinsic_call(x, y, z)]

could also work, but maybe confusing as to what it is doing since otherwise its used as attribute macro.
We could also try self instead of _, or can we leave the _ away and just have the (args...)?
(cc @shawntabrizi in case you are reading this)

@ggwpez
Copy link
Member Author

ggwpez commented Jan 27, 2023

Another issue is blank benchmark will make compiling error

#[benchmarks]
mod benchmarks {
	use super::*;

	impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test);
}

should I open an issue for this?

What should that code do? It cannot implement tests without a benchmark. Guess we could fix that, but I dont see how it would be useful.

@sam0x17
Copy link
Contributor

sam0x17 commented Jan 27, 2023

Yeah regarding the empty benchmark thing, it will correctly issue no errors in the case that you just do:

#[benchmarks]
mod benchmarks {
}

Beyond that, of course impl_benchmark_test_suite! won't work if there are no benchmarks...

@sam0x17
Copy link
Contributor

sam0x17 commented Jan 27, 2023

@ggwpez

I really like self from the options you presented above

@bkchr
Copy link
Member

bkchr commented Jan 27, 2023

#[extrinsic_call(x, y, z)]

This is probably much longer than the actual name of the function we are benchmarking. What I want to say with this is that the _ syntax doesn't bring any advantage when we need this attribute macro.

In general I have to say that the current syntax is a little bit less intuitive than the previous syntax. Currently everything is written in one context, making it look like one big function. I think the previous approach of having different "stages" made it much more obvious. But I don't have any proposal for a better syntax at hand 🙈

@jasl
Copy link
Contributor

jasl commented Jan 27, 2023

Another issue is blank benchmark will make compiling error

#[benchmarks]
mod benchmarks {
	use super::*;

	impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test);
}

should I open an issue for this?

What should that code do? It cannot implement tests without a benchmark. Guess we could fix that, but I dont see how it would be useful.

the blank benchmarking sceanario is: say I'm working on a new pallet, its logic is WIP so I'm not start working on benchmarking, but I shall leave a skeleton at first

@jasl
Copy link
Contributor

jasl commented Feb 3, 2023

an off-topic warn: I'm test build Substrate with latest nightly rustc and got
warning: the following packages contain code that will be rejected by a future version of Rust: nalgebra v0.27.1
this dependency depends by linregress and linregress only depends by frame-benchmarking

@sam0x17
Copy link
Contributor

sam0x17 commented Feb 3, 2023 via email

@ggwpez
Copy link
Member Author

ggwpez commented Feb 3, 2023

We can just update it i guess.

@sam0x17
Copy link
Contributor

sam0x17 commented Feb 3, 2023

We can just update it i guess.

I'll make a PR

update: paritytech/substrate#13310

@bkchr
Copy link
Member

bkchr commented Feb 17, 2023

#[extrinsic_call(x, y, z)]

This is probably much longer than the actual name of the function we are benchmarking. What I want to say with this is that the _ syntax doesn't bring any advantage when we need this attribute macro.

In general I have to say that the current syntax is a little bit less intuitive than the previous syntax. Currently everything is written in one context, making it look like one big function. I think the previous approach of having different "stages" made it much more obvious. But I don't have any proposal for a better syntax at hand see_no_evil

I thought about this again and came to the following syntax in my head:

#[benchmark]
fn my_call() {
     setup! {
          let a = something();
          let origin = Alice;
     };
     // Here we can support either `call!` when the name of the benchmark is equal to the call we want to execute or we would
    // also support something like custom! {} where you are totally free to put there in whatever logic you want as part of the benchmark.
     call!(origin, a);
     // This obviously being optional.
     verify! {}
}

This would give the entire function more structure and also clear intent on what is what etc. These macros obviously wouldn't be real macros, just something for us to parse. I also thought about creating a module and having each of these steps as a function in the module. The problem is the passing of arguments from the setup to the actual call. It would be even more "magic".

But yeah this here being just some proposal, maybe someone else comes up with something better based on my idea :)

@ggwpez
Copy link
Member Author

ggwpez commented Feb 23, 2023

#[benchmark]
fn my_call() {
     setup! {
          let a = something();
          let origin = Alice;
     };
     // Here we can support either `call!` when the name of the benchmark is equal to the call we want to execute or we would
    // also support something like custom! {} where you are totally free to put there in whatever logic you want as part of the benchmark.
     call!(origin, a);
     // This obviously being optional.
     verify! {}
}

Hm… In the beginning this could be useful, but for more advanced devs it will be annoying to explicitly mention these stages.
We could make an No-OP attribute macro:

#[benchmark]
fn my_call() {
     #[setup]
     {
          // setup logic
     }

     #[block]
     {
          // custom logic; this is already possible.
     ]

     #[verify]
     {
          // optional verification code...
     }
}

These attributes would just do nothing and be optional. Not sure if that improves the situation although it does add a bit more structure for new-comers.

@bkchr
Copy link
Member

bkchr commented Feb 23, 2023

We could make an No-OP attribute macro:

Yeah they would also be no-op in my example above. I just wanted to raise, if you are happy with the current syntax, fine. I'm (who isn't that advanced in our benchmarking stuff) was quite confused by the old syntax and would be happy if we are being a little bit more expressive in the new one. I'm also not really convinced by my proposed syntax as it still doesn't "feel" right.

@jasl
Copy link
Contributor

jasl commented Feb 23, 2023

I like

#[benchmark]
fn my_call() {
     setup! {
          let a = something();
          let origin = Alice;
     };
     // Here we can support either `call!` when the name of the benchmark is equal to the call we want to execute or we would
    // also support something like custom! {} where you are totally free to put there in whatever logic you want as part of the benchmark.
     call!(origin, a);
     // This obviously being optional.
     verify! {}
}

this style because compared to attribute style, it's more compact, and _() style made CLion Rust unhappy, that's fine, but somewhat annoying...

I agree these macro should be optional, but using them could made complex benchmark more readable

@ggwpez ggwpez changed the title ☂️ Benchmark syntax V2 follow-ups [FRAME core] ☂ Benchmark syntax V2 follow-ups Apr 27, 2023
@juangirini juangirini transferred this issue from paritytech/substrate Aug 24, 2023
@the-right-joyce the-right-joyce added I6-meta A specific issue for grouping tasks or bugs of a specific category. and removed J1-meta labels Aug 25, 2023
@ggwpez
Copy link
Member Author

ggwpez commented Mar 19, 2024

The last two important issue to be feature complete have been done now. Going to close and otherwise re-open smaller issues.

@ggwpez ggwpez closed this as completed Mar 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I6-meta A specific issue for grouping tasks or bugs of a specific category.
Projects
Status: Done
Development

No branches or pull requests

5 participants