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

Adding additional logging when overflow occurs in calculate_stake_rew… #3783

Merged
merged 4 commits into from
Dec 4, 2024

Conversation

roryharr
Copy link

Problem

Currently unwrap is used during calculate_stake_rewards, this lead to a lack of logging when an overflow occurs.

It might be better to take an action other than panic!, but it matches the current code and it's not clear to me what other action should be taken.

Summary of Changes

Additional logs during overflow calculations during calculate_stake_rewards
No change of functionality, validated through new unit tests.

Fixes #

Copy link

mergify bot commented Nov 25, 2024

The Firedancer team maintains a line-for-line reimplementation of the
native programs, and until native programs are moved to BPF, those
implementations must exactly match their Agave counterparts.
If this PR represents a change to a native program implementation (not
tests), please include a reviewer from the Firedancer team. And please
keep refactors to a minimum.

Copy link

@joncinque joncinque left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution! If you want logging with unwrap(), rather than adding all of the match statements, it's more idiomatic to use expect(&format!("My formatted message with {variable}")): https://doc.rust-lang.org/std/option/enum.Option.html#method.expect

How about making that change?

programs/stake/src/rewards.rs Outdated Show resolved Hide resolved
programs/stake/src/rewards.rs Outdated Show resolved Hide resolved
@roryharr
Copy link
Author

Thanks for your contribution! If you want logging with unwrap(), rather than adding all of the match statements, it's more idiomatic to use expect(&format!("My formatted message with {variable}")): https://doc.rust-lang.org/std/option/enum.Option.html#method.expect

How about making that change?

Thanks. Rewriting it in this way does make me think that bigints should be used for the temporary product, but that would be a functional change and have a minor performance impact. I'm not familiar enough with the hot path to know whether that is a concern and the trade off is worth making.

For the final division, fitting within a u64 makes sense as an expected outcome.

programs/stake/src/rewards.rs Outdated Show resolved Hide resolved
Comment on lines 186 to 198
let rewards = points
.checked_mul(u128::from(point_value.rewards))
.unwrap()
.checked_div(point_value.points)
.unwrap();
.expect(&format!(
"Rewards should fit within u128, inputs were {} and {}",
points, point_value.rewards
));

let rewards = u64::try_from(rewards).unwrap();
// The unwrap is safe, as points_value.points is guaranteed to be non zero above.
// Expect is verifying that the result fits in u64
let rewards = u64::try_from(rewards.checked_div(point_value.points).unwrap()).expect(&format!(
"Rewards should fit within u64, inputs were {}, {} and {}",
points, point_value.rewards, point_value.points
));

Choose a reason for hiding this comment

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

Sorry for only doing this now, but I ran a bench on this PR, and just adding the string formatting has a surprising negative performance impact, causing this function to go from ~5.28ns to ~7ns.

Since rewards payouts are a huge bottleneck, I'm hesitant to do anything that will negatively impact performance. I also tried changing to unwrap_or_else to panic lazily, but it made no difference. If we use static strings, however, the performance is exactly the same. What do you think about using static strings instead? That would be just "Rewards should fit within u128" and "Rewards should fit within u64"

Copy link
Author

Choose a reason for hiding this comment

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

Interesting! I guess it can discard the values otherwise/re-use the register space. I wonder if it's a single value causing the perf differentiation (eg points).

Let me see if i can get setup on my side to measure and get a no perf delta improvement that still provides some useful data.

Choose a reason for hiding this comment

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

Yeah for sure! Here's my bench -- it requires making a few extra structs public to make it work, but I hope it provides a good start:

#![feature(test)]

extern crate test;
use {
    solana_stake_program::{points::{PointValue, null_tracer}, rewards::calculate_stake_rewards, stake_state::new_stake},
    solana_vote_program::vote_state::VoteState,
    solana_sdk::{
        pubkey::Pubkey,
        stake_history::StakeHistory,
    },
    test::Bencher,
};


#[bench]
fn calculate_rewards_tests(bencher: &mut Bencher) {
    let stake = 1_000_000_000;
    let rewards = 20_000_000_000;
    let credits = 30_000_000_000;

    let mut vote_state = VoteState::default();

    let stake = new_stake(stake, &Pubkey::default(), &vote_state, 100_000_000_000);

    vote_state.increment_credits(0, credits);

    bencher.iter(|| {
        calculate_stake_rewards(
            0,
            &stake,
            &PointValue {
                rewards: rewards,
                points: 1,
            },
            &vote_state,
            &StakeHistory::default(),
            null_tracer(),
            None,
        );
    });
}

Copy link
Author

@roryharr roryharr Dec 3, 2024

Choose a reason for hiding this comment

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

Yup, I got it to work locally. Seeing similar results that means there is no way to log useful values without effecting performance.

One interesting/very surprising result:

    let rewards = points
        .checked_mul(u128::from(point_value.rewards))
        .expect(&format!(
            "Rewards should fit within u128, inputs were {}", 3 as u32
        ));

test rewards::tests::bench_add_two ... bench: 38.25 ns/iter (+/- 0.54)

    let rewards = points
        .checked_mul(u128::from(point_value.rewards))
        .expect(&format!(
            "Rewards should fit within u128, inputs were {}", 3
        ));

test rewards::tests::bench_add_two ... bench: 22.27 ns/iter (+/- 0.67)

Even printing constants as u32 is causing a performance degradation.

Copy link
Author

@roryharr roryharr Dec 3, 2024

Choose a reason for hiding this comment

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

On further testing, even having the expects statement is causing a performance delta.

I will close this out.

Choose a reason for hiding this comment

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

On my testing, when I did expect("static string") it recovered the original performance, which makes sense since there's no extra work required. If you wanted to do that, I'd be down!

Copy link
Author

@roryharr roryharr Dec 3, 2024

Choose a reason for hiding this comment

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

I tested that and still saw significant performance delta.
Base code:
test rewards::tests::bench_calculate_stake_rewards ... bench: 8.78 ns/iter (+/- 0.32)

With expects using static string:
test rewards::tests::bench_calculate_stake_rewards ... bench: 23.16 ns/iter (+/- 0.43)

Edit: It was because the format call was still present, despite no formatting being used. Removing the formatting call I get the same results as with unwrap.

@roryharr roryharr closed this Dec 3, 2024
@roryharr roryharr reopened this Dec 3, 2024
@roryharr roryharr force-pushed the log_stake_rewards_overflows branch 2 times, most recently from d2b089e to 62875ff Compare December 3, 2024 17:26
Copy link

@joncinque joncinque left a comment

Choose a reason for hiding this comment

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

One last little thing to make this clearer, then we can get this in! Thanks for your patience.

programs/stake/src/rewards.rs Outdated Show resolved Hide resolved
@joncinque joncinque added the CI Pull Request is ready to enter CI label Dec 3, 2024
@anza-team anza-team removed the CI Pull Request is ready to enter CI label Dec 3, 2024
Copy link

@joncinque joncinque left a comment

Choose a reason for hiding this comment

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

Also, be sure to run ./cargo nightly fmt --all from the top of the repo for formatting and ./scripts/cargo-clippy-nightly.sh to make sure it'll pass CI. I think there's just the one issue, but this way you'll be sure before pushing

0,
&stake,
&PointValue {
rewards: rewards,

Choose a reason for hiding this comment

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

clippy is also complaining about this line in CI https://buildkite.com/anza/agave/builds/15002#01938ee1-02e8-4df2-8494-112c107b5803:

Suggested change
rewards: rewards,
rewards,

@joncinque joncinque added the CI Pull Request is ready to enter CI label Dec 4, 2024
@anza-team anza-team removed the CI Pull Request is ready to enter CI label Dec 4, 2024
Copy link

@joncinque joncinque left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution!

@joncinque joncinque added the automerge automerge Merge this Pull Request automatically once CI passes label Dec 4, 2024
@mergify mergify bot merged commit f294b99 into anza-xyz:master Dec 4, 2024
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge automerge Merge this Pull Request automatically once CI passes community need:merge-assist
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants