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

Aggregate cost_model into cost_tracker #18374

Merged

Conversation

tao-stones
Copy link
Contributor

Problem

Previously banking_stage has both cost_model and cost_tracker, there are times banking_stage needs to acquire lock on both objects in order to calculated transaction cost from cost_model and update cost_tracker. This could potentially lead to deadlock if future code is not careful on the order of lock acquiring.

Furthermore, banking_stage does not have to access cost_model directly, it only needs to access cost_tracker which in turn can aggregate cost_model. Making this change also paves the way to move cost tracking to its own service module, therefore offload it from banking main thread to its own thread.

Summary of Changes

  • aggregate cost_model into cost_tracker, decouple it form banking_stage
  • removed un-used functions
  • CostModel::calculate_cost(&tx) returns &TransactionCost to avoid unnecessary heap allocation.

Fixes #

…age to prevent accidental deadlock. * Simplified code, removed unused functions
@codecov
Copy link

codecov bot commented Jul 2, 2021

Codecov Report

Merging #18374 (a804280) into master (89a3e4f) will decrease coverage by 0.0%.
The diff coverage is 95.9%.

@@            Coverage Diff            @@
##           master   #18374     +/-   ##
=========================================
- Coverage    82.3%    82.3%   -0.1%     
=========================================
  Files         435      435             
  Lines      121424   121356     -68     
=========================================
- Hits       100020    99927     -93     
- Misses      21404    21429     +25     

@tao-stones tao-stones marked this pull request as ready for review July 2, 2021 04:24
@tao-stones tao-stones requested review from t-nelson and carllin July 2, 2021 04:25
t-nelson
t-nelson previously approved these changes Jul 3, 2021
Copy link
Contributor

@t-nelson t-nelson left a comment

Choose a reason for hiding this comment

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

r+. Just a lock resolution question then this is ready to go by my eye. I like the green/red ratio 😉

Comment on lines +47 to +48
let mut cost_model = self.cost_model.write().unwrap();
let tx_cost = cost_model.calculate_cost(transaction);
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like we don't need to hold the lock for the duration of this method?

let tx_cost = self
    .cost_model
    .write()
    .unwrap()
    .calculate_cost(transaction);

add_transaction_cost() below looks like it can take a similar treatment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, this is a bit tricky because cost_model has this signature for calculate_cost:

    pub fn calculate_cost(&mut self, transaction: &Transaction) -> &TransactionCost {

It reuses a preallocated TransactionCost, and returns calculation result as a ref to it. The motivation is calculate_cost is called for every transaction, therefore it is in hot path. It saves noticeable overhead by avoiding allocating TransactionCost at heap for each call. But this requires the lock to live as long as tx_cost. Otherwise Rust complains:

error[E0716]: temporary value dropped while borrowed
  --> core/src/cost_tracker.rs:47:23
   |
47 |         let tx_cost = self.cost_model.write().unwrap().calculate_cost(transaction);
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                            - temporary value is freed at the end of this statement
   |                       |
   |                       creates a temporary which is freed while still in use
48 |         self.would_fit(
49 |             &tx_cost.writable_accounts,
   |             -------------------------- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

I compared the execution timing between two approaches, reuse heap object and holding lock a bit longer had much better performance.
I am glad you pointed it out, maybe I over complicated it, and there is a better way to avoid many head alloc.

core/src/banking_stage.rs Outdated Show resolved Hide resolved
core/src/cost_model.rs Outdated Show resolved Hide resolved
core/src/cost_tracker.rs Outdated Show resolved Hide resolved
carllin
carllin previously approved these changes Jul 5, 2021
@tao-stones tao-stones added the automerge Merge this Pull Request automatically once CI passes label Jul 6, 2021
@mergify mergify bot dismissed stale reviews from t-nelson and carllin July 6, 2021 14:01

Pull request has been modified.

@mergify mergify bot merged commit 0e039b4 into solana-labs:master Jul 6, 2021
mergify bot pushed a commit that referenced this pull request Jul 15, 2021
* * aggregate cost_model into cost_tracker, decouple it from banking_stage to prevent accidental deadlock. * Simplified code, removed unused functions

* review fixes

(cherry picked from commit 0e039b4)

# Conflicts:
#	banking-bench/src/main.rs
#	core/benches/banking_stage.rs
#	core/src/banking_stage.rs
#	core/src/cost_model.rs
#	core/src/cost_tracker.rs
#	core/src/tpu.rs
#	ledger-tool/src/main.rs
@tao-stones tao-stones removed the v1.7 label Jul 15, 2021
tao-stones added a commit to tao-stones/solana that referenced this pull request Jul 16, 2021
* * aggregate cost_model into cost_tracker, decouple it from banking_stage to prevent accidental deadlock. * Simplified code, removed unused functions

* review fixes
tao-stones added a commit to tao-stones/solana that referenced this pull request Jul 16, 2021
* * aggregate cost_model into cost_tracker, decouple it from banking_stage to prevent accidental deadlock. * Simplified code, removed unused functions

* review fixes
tao-stones added a commit to tao-stones/solana that referenced this pull request Jul 17, 2021
* * aggregate cost_model into cost_tracker, decouple it from banking_stage to prevent accidental deadlock. * Simplified code, removed unused functions

* review fixes
tao-stones added a commit to tao-stones/solana that referenced this pull request Sep 24, 2021
* * aggregate cost_model into cost_tracker, decouple it from banking_stage to prevent accidental deadlock. * Simplified code, removed unused functions

* review fixes
tao-stones added a commit to tao-stones/solana that referenced this pull request Sep 29, 2021
* * aggregate cost_model into cost_tracker, decouple it from banking_stage to prevent accidental deadlock. * Simplified code, removed unused functions

* review fixes
tao-stones added a commit to tao-stones/solana that referenced this pull request Sep 30, 2021
* * aggregate cost_model into cost_tracker, decouple it from banking_stage to prevent accidental deadlock. * Simplified code, removed unused functions

* review fixes
tao-stones added a commit to tao-stones/solana that referenced this pull request Oct 6, 2021
* * aggregate cost_model into cost_tracker, decouple it from banking_stage to prevent accidental deadlock. * Simplified code, removed unused functions

* review fixes
tao-stones added a commit that referenced this pull request Oct 6, 2021
* Cost Model to limit transactions which are not parallelizeable (#16694)

* * Add following to banking_stage:
  1. CostModel as immutable ref shared between threads, to provide estimated cost for transactions.
  2. CostTracker which is shared between threads, tracks transaction costs for each block.

* replace hard coded program ID with id() calls

* Add Account Access Cost as part of TransactionCost. Account Access cost are weighted differently between read and write, signed and non-signed.

* Establish instruction_execution_cost_table, add function to update or insert instruction cost, unit tested. It is read-only for now; it allows Replay to insert realtime instruction execution costs to the table.

* add test for cost_tracker atomically try_add operation, serves as safety guard for future changes

* check cost against local copy of cost_tracker, return transactions that would exceed limit as unprocessed transaction to be buffered; only apply bank processed transactions cost to tracker;

* bencher to new banking_stage with max cost limit to allow cost model being hit consistently during bench iterations

* replay stage feed back program cost (#17731)

* replay stage feeds back realtime per-program execution cost to cost model;

* program cost execution table is initialized into empty table, no longer populated with hardcoded numbers;

* changed cost unit to microsecond, using value collected from mainnet;

* add ExecuteCostTable with fixed capacity for security concern, when its limit is reached, programs with old age AND less occurrence will be pushed out to make room for new programs.

* investigate system performance test degradation  (#17919)

* Add stats and counter around cost model ops, mainly:
- calculate transaction cost
- check transaction can fit in a block
- update block cost tracker after transactions are added to block
- replay_stage to update/insert execution cost to table

* Change mutex on cost_tracker to RwLock

* removed cloning cost_tracker for local use, as the metrics show clone is very expensive.

* acquire and hold locks for block of TXs, instead of acquire and release per transaction;

* remove redundant would_fit check from cost_tracker update execution path

* refactor cost checking with less frequent lock acquiring

* avoid many Transaction_cost heap allocation when calculate cost, which
is in the hot path - executed per transaction.

* create hashmap with new_capacity to reduce runtime heap realloc.

* code review changes: categorize stats, replace explicit drop calls, concisely initiate to default

* address potential deadlock by acquiring locks one at time

* Persist cost table to blockstore (#18123)

* Add `ProgramCosts` Column Family to blockstore, implement LedgerColumn; add `delete_cf` to Rocks
* Add ProgramCosts to compaction excluding list alone side with TransactionStatusIndex in one place: `excludes_from_compaction()`

* Write cost table to blockstore after `replay_stage` replayed active banks; add stats to measure persist time
* Deletes program from `ProgramCosts` in blockstore when they are removed from cost_table in memory
* Only try to persist to blockstore when cost_table is changed.
* Restore cost table during validator startup

* Offload `cost_model` related operations from replay main thread to dedicated service thread, add channel to send execute_timings between these threads;
* Move `cost_update_service` to its own module; replay_stage is now decoupled from cost_model.

* log warning when channel send fails (#18391)

* Aggregate cost_model into cost_tracker (#18374)

* * aggregate cost_model into cost_tracker, decouple it from banking_stage to prevent accidental deadlock. * Simplified code, removed unused functions

* review fixes

* update ledger tool to restore cost table from blockstore (#18489)

* update ledger tool to restore cost model from blockstore when compute-slot-cost

* Move initialize_cost_table into cost_model, so the function can be tested and shared between validator and ledger-tool

* refactor and simplify a test

* manually fix merge conflicts

* Per-program id timings (#17554)

* more manual fixing

* solve a merge conflict

* featurize cost model

* more merge fix

* cost model uses compute_unit to replace microsecond as cost unit
(#18934)

* Reject blocks for costs above the max block cost (#18994)

* Update block max cost limit to fix performance regession (#19276)

* replace function with const var for better readability (#19285)

* Add few more metrics data points (#19624)

* periodically report sigverify_stage stats (#19674)

* manual merge

* cost model nits (#18528)

* Accumulate consumed units (#18714)

* tx wide compute budget (#18631)

* more manual merge

* ignore zerorize drop security

* - update const cost values with data collected by #19627
- update cost calculation to closely proposed fee schedule #16984

* add transaction cost histogram metrics (#20350)

* rebase to 1.7.15

* add tx count and thread id to stats (#20451)
each stat reports and resets when slot changes

* remove cost_model feature_set

* ignore vote transactions from cost model

Co-authored-by: sakridge <[email protected]>
Co-authored-by: Jeff Biseda <[email protected]>
Co-authored-by: Jack May <[email protected]>
t-nelson pushed a commit that referenced this pull request Oct 6, 2021
* Cost Model to limit transactions which are not parallelizeable (#16694)

* * Add following to banking_stage:
  1. CostModel as immutable ref shared between threads, to provide estimated cost for transactions.
  2. CostTracker which is shared between threads, tracks transaction costs for each block.

* replace hard coded program ID with id() calls

* Add Account Access Cost as part of TransactionCost. Account Access cost are weighted differently between read and write, signed and non-signed.

* Establish instruction_execution_cost_table, add function to update or insert instruction cost, unit tested. It is read-only for now; it allows Replay to insert realtime instruction execution costs to the table.

* add test for cost_tracker atomically try_add operation, serves as safety guard for future changes

* check cost against local copy of cost_tracker, return transactions that would exceed limit as unprocessed transaction to be buffered; only apply bank processed transactions cost to tracker;

* bencher to new banking_stage with max cost limit to allow cost model being hit consistently during bench iterations

* replay stage feed back program cost (#17731)

* replay stage feeds back realtime per-program execution cost to cost model;

* program cost execution table is initialized into empty table, no longer populated with hardcoded numbers;

* changed cost unit to microsecond, using value collected from mainnet;

* add ExecuteCostTable with fixed capacity for security concern, when its limit is reached, programs with old age AND less occurrence will be pushed out to make room for new programs.

* investigate system performance test degradation  (#17919)

* Add stats and counter around cost model ops, mainly:
- calculate transaction cost
- check transaction can fit in a block
- update block cost tracker after transactions are added to block
- replay_stage to update/insert execution cost to table

* Change mutex on cost_tracker to RwLock

* removed cloning cost_tracker for local use, as the metrics show clone is very expensive.

* acquire and hold locks for block of TXs, instead of acquire and release per transaction;

* remove redundant would_fit check from cost_tracker update execution path

* refactor cost checking with less frequent lock acquiring

* avoid many Transaction_cost heap allocation when calculate cost, which
is in the hot path - executed per transaction.

* create hashmap with new_capacity to reduce runtime heap realloc.

* code review changes: categorize stats, replace explicit drop calls, concisely initiate to default

* address potential deadlock by acquiring locks one at time

* Persist cost table to blockstore (#18123)

* Add `ProgramCosts` Column Family to blockstore, implement LedgerColumn; add `delete_cf` to Rocks
* Add ProgramCosts to compaction excluding list alone side with TransactionStatusIndex in one place: `excludes_from_compaction()`

* Write cost table to blockstore after `replay_stage` replayed active banks; add stats to measure persist time
* Deletes program from `ProgramCosts` in blockstore when they are removed from cost_table in memory
* Only try to persist to blockstore when cost_table is changed.
* Restore cost table during validator startup

* Offload `cost_model` related operations from replay main thread to dedicated service thread, add channel to send execute_timings between these threads;
* Move `cost_update_service` to its own module; replay_stage is now decoupled from cost_model.

* log warning when channel send fails (#18391)

* Aggregate cost_model into cost_tracker (#18374)

* * aggregate cost_model into cost_tracker, decouple it from banking_stage to prevent accidental deadlock. * Simplified code, removed unused functions

* review fixes

* update ledger tool to restore cost table from blockstore (#18489)

* update ledger tool to restore cost model from blockstore when compute-slot-cost

* Move initialize_cost_table into cost_model, so the function can be tested and shared between validator and ledger-tool

* refactor and simplify a test

* manually fix merge conflicts

* Per-program id timings (#17554)

* more manual fixing

* solve a merge conflict

* featurize cost model

* more merge fix

* cost model uses compute_unit to replace microsecond as cost unit
(#18934)

* Reject blocks for costs above the max block cost (#18994)

* Update block max cost limit to fix performance regession (#19276)

* replace function with const var for better readability (#19285)

* Add few more metrics data points (#19624)

* periodically report sigverify_stage stats (#19674)

* manual merge

* cost model nits (#18528)

* Accumulate consumed units (#18714)

* tx wide compute budget (#18631)

* more manual merge

* ignore zerorize drop security

* - update const cost values with data collected by #19627
- update cost calculation to closely proposed fee schedule #16984

* add transaction cost histogram metrics (#20350)

* rebase to 1.7.15

* add tx count and thread id to stats (#20451)
each stat reports and resets when slot changes

* remove cost_model feature_set

* ignore vote transactions from cost model

Co-authored-by: sakridge <[email protected]>
Co-authored-by: Jeff Biseda <[email protected]>
Co-authored-by: Jack May <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge Merge this Pull Request automatically once CI passes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants