Skip to content

Commit fffc85d

Browse files
authored
refactor(attrs): apply #[inline]/#[cold]+#[inline(never)] (#339) (#375)
* refactor(attrs): apply #[inline] / #[cold]+#[inline(never)] per rules (#339) - `#[inline]` on hot-path public entry points and small accessors in `strategies/*`, `pnl/*`, `greeks/utils`, `pricing/{payoff,utils}`, `risk/span`, `volatility/utils`, etc. following the audit in `rules/global_rules.md` §Compiler Attributes. - `#[inline(never)]` on large / rarely-taken constructors such as `CustomStrategy::new` and other multi-arg builders that the optimiser has no business inlining. - `#[cold] #[inline(never)]` on every error constructor across `error/*` (`DecimalError::{overflow, arithmetic_error, conversion_error, out_of_bounds, invalid_precision, invalid_value}`, `PricingError::{method_error, simulation_error, invalid_engine, other, unsupported_option_type, non_finite}`, and the equivalents in `greeks`, `volatility`, `simulation`, `strategies`, `options`, `position`, `probability`, `chains`, `curves`, `surfaces`, `trade`) so panic / error paths don't pollute the inlining budget of the happy path. `#[inline(always)]` intentionally _not_ added here: the rules require a Criterion benchmark demonstrating the win first. That audit is tracked by the benchmark work on numeric kernels and will land in a follow-up PR. Closes #339 * refactor(attrs): #[inline] on AdjustmentConfig builder chain
1 parent cc50366 commit fffc85d

50 files changed

Lines changed: 228 additions & 12 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/backtesting/results.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ impl SimulationStatsResult {
196196
/// - P&L statistics (total, average, max, min)
197197
/// - Holding period information
198198
/// - Exit reason distribution
199+
#[inline(never)]
199200
pub fn print_summary(&self) {
200201
use prettytable::{Cell, Row, Table, color, format};
201202
use rust_decimal_macros::dec;
@@ -351,6 +352,7 @@ impl SimulationStatsResult {
351352
/// - Final P&L
352353
/// - Holding period
353354
/// - Exit reason
355+
#[inline(never)]
354356
pub fn print_individual_results(&self) {
355357
use prettytable::{Cell, Row, Table, color, format};
356358
use rust_decimal_macros::dec;

src/chains/chain.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ impl OptionChain {
370370
/// - `expiration_date` is missing from price params
371371
/// - Failed to get days from expiration date
372372
/// - Failed to get date string from expiration date
373+
#[inline(never)]
373374
pub fn build_chain(params: &OptionChainBuildParams) -> Result<Self, ChainError> {
374375
let underlying_price = params
375376
.price_params
@@ -1090,6 +1091,7 @@ impl OptionChain {
10901091
/// Returns [`ChainError::FileError`] wrapping a `FileErrorKind::IOError`
10911092
/// when the file cannot be created or written, or
10921093
/// `FileErrorKind::ParseError` when `csv` serialization fails.
1094+
#[inline(never)]
10931095
pub fn save_to_csv(&self, file_path: &str) -> Result<(), ChainError> {
10941096
let full_path = format!("{}/{}.csv", file_path, self.get_title());
10951097
let mut wtr = WriterBuilder::new().from_path(full_path)?;
@@ -1170,6 +1172,7 @@ impl OptionChain {
11701172
/// Returns [`ChainError::FileError`] wrapping a `FileErrorKind::IOError`
11711173
/// when the file cannot be created or written, or
11721174
/// `FileErrorKind::ParseError` when `serde_json` serialization fails.
1175+
#[inline(never)]
11731176
pub fn save_to_json(&self, file_path: &str) -> Result<(), ChainError> {
11741177
let full_path = format!("{}/{}.json", file_path, self.get_title());
11751178
let file = File::create(full_path)?;
@@ -1223,6 +1226,7 @@ impl OptionChain {
12231226
/// `FileErrorKind::ParseError` when the CSV records cannot be parsed.
12241227
/// Invalid option data (bad strike, volatility or price) surfaces as
12251228
/// [`ChainError::OptionDataError`].
1229+
#[inline(never)]
12261230
pub fn load_from_csv(file_path: &str) -> Result<Self, ChainError> {
12271231
let mut rdr = csv::Reader::from_path(file_path)?;
12281232
let mut options = BTreeSet::new();
@@ -1313,6 +1317,7 @@ impl OptionChain {
13131317
/// Returns [`ChainError::FileError`] wrapping `FileErrorKind::IOError`
13141318
/// when the file cannot be opened, or `FileErrorKind::ParseError`
13151319
/// when `serde_json` deserialization fails.
1320+
#[inline(never)]
13161321
pub fn load_from_json(file_path: &str) -> Result<Self, ChainError> {
13171322
let file = File::open(file_path)?;
13181323
let mut option_chain: OptionChain = serde_json::from_reader(file)?;
@@ -1586,6 +1591,7 @@ impl OptionChain {
15861591
/// # Returns
15871592
///
15881593
/// An iterator where each item is a reference to an `OptionData`.
1594+
#[inline]
15891595
pub fn iter(&self) -> impl Iterator<Item = &OptionData> {
15901596
self.get_single_iter()
15911597
}
@@ -1601,6 +1607,7 @@ impl OptionChain {
16011607
/// Since the `options` field is stored as a `BTreeSet`, the elements are ordered
16021608
/// in ascending order based on the sorting rules of `BTreeSet` (typically defined by `Ord` implementation).
16031609
///
1610+
#[inline]
16041611
pub fn get_single_iter(&self) -> impl Iterator<Item = &OptionData> {
16051612
self.options.iter().filter(|option| option.validate())
16061613
}
@@ -2648,6 +2655,7 @@ impl OptionChain {
26482655
/// # Returns
26492656
///
26502657
/// A `String` representing the expiration date of the option chain.
2658+
#[inline]
26512659
#[must_use]
26522660
pub fn get_expiration_date(&self) -> String {
26532661
self.expiration_date.clone()
@@ -2657,6 +2665,7 @@ impl OptionChain {
26572665
///
26582666
/// # Returns
26592667
/// * `Option<ExpirationDate>` - The expiration date if it can be parsed, or `None` if parsing fails.
2668+
#[inline]
26602669
#[must_use]
26612670
pub fn get_expiration(&self) -> Option<ExpirationDate> {
26622671
ExpirationDate::from_string(&self.expiration_date).ok()
@@ -3181,6 +3190,7 @@ impl OptionChain {
31813190
/// This method prints the option chain directly to stdout using prettytable's
31823191
/// `printstd()` method, which properly displays colors in the terminal.
31833192
/// Use this method instead of `info!("{}", chain)` to see colored headers.
3193+
#[inline(never)]
31843194
pub fn show(&self) {
31853195
// Print header information
31863196
let mut header = Table::new();

src/chains/optiondata.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ impl OptionData {
246246
///
247247
/// # Note
248248
/// The `Positive` type is assumed to enforce non-negative values for correctness.
249+
#[inline]
249250
#[must_use]
250251
pub fn get_call_spread(&self) -> Option<Positive> {
251252
match (self.call_bid, self.call_ask) {
@@ -281,6 +282,7 @@ impl OptionData {
281282
///
282283
/// # Errors
283284
/// This function does not return an error. It simply returns `None` if the calculation is not feasible.
285+
#[inline]
284286
#[must_use]
285287
pub fn get_call_spread_per(&self) -> Option<Positive> {
286288
match (self.call_bid, self.call_ask) {
@@ -310,6 +312,7 @@ impl OptionData {
310312
/// to convert to a numeric type for calculation purposes.
311313
/// The spread is always represented as a positive value.
312314
///
315+
#[inline]
313316
#[must_use]
314317
pub fn get_put_spread(&self) -> Option<Positive> {
315318
match (self.put_bid, self.put_ask) {
@@ -337,6 +340,7 @@ impl OptionData {
337340
///
338341
/// # Note
339342
/// This function returns `None` if there are missing values for either `put_bid` or `put_ask`.
343+
#[inline]
340344
#[must_use]
341345
pub fn get_put_spread_per(&self) -> Option<Positive> {
342346
match (self.put_bid, self.put_ask) {
@@ -364,6 +368,7 @@ impl OptionData {
364368
///
365369
/// Ensure that the `Positive` type enforces constraints to prevent invalid values
366370
/// such as negative volatility.
371+
#[inline]
367372
#[must_use]
368373
pub fn get_volatility(&self) -> Positive {
369374
self.implied_volatility
@@ -373,6 +378,7 @@ impl OptionData {
373378
///
374379
/// # Arguments
375380
/// * `volatility` - A positive decimal value representing the implied volatility.
381+
#[inline]
376382
pub fn set_volatility(&mut self, volatility: &Positive) {
377383
self.implied_volatility = *volatility;
378384
}
@@ -449,6 +455,7 @@ impl OptionData {
449455
/// a valid positive number.
450456
///
451457
/// [`Positive`]: struct.Positive.html
458+
#[inline]
452459
#[must_use]
453460
pub fn strike(&self) -> Positive {
454461
self.strike_price
@@ -464,6 +471,7 @@ impl OptionData {
464471
/// # Returns
465472
///
466473
/// `true` if all required call option data is present, `false` otherwise.
474+
#[inline]
467475
pub(crate) fn valid_call(&self) -> bool {
468476
self.strike_price > Positive::ZERO && self.call_bid.is_some() && self.call_ask.is_some()
469477
}
@@ -478,6 +486,7 @@ impl OptionData {
478486
/// # Returns
479487
///
480488
/// `true` if all required put option data is present, `false` otherwise.
489+
#[inline]
481490
pub(crate) fn valid_put(&self) -> bool {
482491
self.strike_price > Positive::ZERO && self.put_bid.is_some() && self.put_ask.is_some()
483492
}
@@ -490,6 +499,7 @@ impl OptionData {
490499
/// # Returns
491500
///
492501
/// The call option's ask price as a `Positive` value, or `None` if the price is unavailable.
502+
#[inline]
493503
#[must_use]
494504
pub fn get_call_buy_price(&self) -> Option<Positive> {
495505
self.call_ask
@@ -503,6 +513,7 @@ impl OptionData {
503513
/// # Returns
504514
///
505515
/// The call option's bid price as a `Positive` value, or `None` if the price is unavailable.
516+
#[inline]
506517
#[must_use]
507518
pub fn get_call_sell_price(&self) -> Option<Positive> {
508519
self.call_bid
@@ -516,6 +527,7 @@ impl OptionData {
516527
/// # Returns
517528
///
518529
/// The put option's ask price as a `Positive` value, or `None` if the price is unavailable.
530+
#[inline]
519531
#[must_use]
520532
pub fn get_put_buy_price(&self) -> Option<Positive> {
521533
self.put_ask
@@ -529,6 +541,7 @@ impl OptionData {
529541
/// # Returns
530542
///
531543
/// The put option's bid price as a `Positive` value, or `None` if the price is unavailable.
544+
#[inline]
532545
#[must_use]
533546
pub fn get_put_sell_price(&self) -> Option<Positive> {
534547
self.put_bid
@@ -540,6 +553,7 @@ impl OptionData {
540553
/// fields of the `OptionData` struct are `None`, indicating missing price information.
541554
/// It returns `false` if all four fields have valid price data.
542555
///
556+
#[inline]
543557
#[must_use]
544558
pub fn some_price_is_none(&self) -> bool {
545559
self.call_bid.is_none()
@@ -1070,6 +1084,7 @@ impl OptionData {
10701084
/// A tuple containing:
10711085
/// * First element: The call option mid-price (bid+ask)/2, or `None` if not available
10721086
/// * Second element: The put option mid-price (bid+ask)/2, or `None` if not available
1087+
#[inline]
10731088
#[must_use]
10741089
pub fn get_mid_prices(&self) -> (Option<Positive>, Option<Positive>) {
10751090
(self.call_middle, self.put_middle)
@@ -1101,6 +1116,7 @@ impl OptionData {
11011116
/// * Second element: `Option<Decimal>` - The delta value for the put option. May be `None` if
11021117
/// the delta value is not available or could not be calculated.
11031118
///
1119+
#[inline]
11041120
#[must_use]
11051121
pub fn current_deltas(&self) -> (Option<Decimal>, Option<Decimal>) {
11061122
(self.delta_call, self.delta_put)
@@ -1117,6 +1133,7 @@ impl OptionData {
11171133
/// * `Option<Decimal>` - The current gamma value wrapped in `Some` if it exists,
11181134
/// or `None` if the gamma value is not set.
11191135
///
1136+
#[inline]
11201137
#[must_use]
11211138
pub fn current_gamma(&self) -> Option<Decimal> {
11221139
self.gamma

src/error/chains.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,8 @@ impl ChainError {
517517
///
518518
/// A `ChainError` containing the strike validation error details
519519
#[must_use]
520+
#[cold]
521+
#[inline(never)]
520522
pub fn invalid_strike(strike: f64, reason: &str) -> Self {
521523
ChainError::OptionDataError(OptionDataErrorKind::InvalidStrike {
522524
strike,
@@ -538,6 +540,8 @@ impl ChainError {
538540
///
539541
/// A `ChainError` containing the volatility validation error details
540542
#[must_use]
543+
#[cold]
544+
#[inline(never)]
541545
pub fn invalid_volatility(volatility: Option<f64>, reason: &str) -> Self {
542546
ChainError::OptionDataError(OptionDataErrorKind::InvalidVolatility {
543547
volatility,
@@ -560,6 +564,8 @@ impl ChainError {
560564
///
561565
/// A `ChainError` containing the price validation error details
562566
#[must_use]
567+
#[cold]
568+
#[inline(never)]
563569
pub fn invalid_prices(bid: Option<f64>, ask: Option<f64>, reason: &str) -> Self {
564570
ChainError::OptionDataError(OptionDataErrorKind::InvalidPrices {
565571
bid,
@@ -583,6 +589,8 @@ impl ChainError {
583589
///
584590
/// A `ChainError` containing the strategy legs validation error details
585591
#[must_use]
592+
#[cold]
593+
#[inline(never)]
586594
pub fn invalid_legs(expected: usize, found: usize, reason: &str) -> Self {
587595
ChainError::StrategyError(StrategyErrorKind::InvalidLegs {
588596
expected,
@@ -605,6 +613,8 @@ impl ChainError {
605613
///
606614
/// A `ChainError` containing the parameter validation error details
607615
#[must_use]
616+
#[cold]
617+
#[inline(never)]
608618
pub fn invalid_parameters(parameter: &str, reason: &str) -> Self {
609619
ChainError::ChainBuildError(ChainBuildErrorKind::InvalidParameters {
610620
parameter: parameter.to_string(),
@@ -625,6 +635,8 @@ impl ChainError {
625635
///
626636
/// A `ChainError` containing the parameter validation error details
627637
#[must_use]
638+
#[cold]
639+
#[inline(never)]
628640
pub fn invalid_price_calculation(reason: &str) -> Self {
629641
ChainError::OptionDataError(OptionDataErrorKind::PriceCalculationError(
630642
reason.to_string(),

src/error/curves.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ impl CurveError {
190190
/// - For example, attempting an unsupported computation method on a specific curve type.
191191
///
192192
#[must_use]
193+
#[cold]
194+
#[inline(never)]
193195
pub fn operation_not_supported(operation: &str, reason: &str) -> Self {
194196
CurveError::OperationError(OperationErrorKind::NotSupported {
195197
operation: operation.to_string(),
@@ -209,6 +211,8 @@ impl CurveError {
209211
/// - For example, providing malformed or missing parameters for interpolation or curve construction.
210212
///
211213
#[must_use]
214+
#[cold]
215+
#[inline(never)]
212216
pub fn invalid_parameters(operation: &str, reason: &str) -> Self {
213217
CurveError::OperationError(OperationErrorKind::InvalidParameters {
214218
operation: operation.to_string(),

src/error/decimal.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ impl DecimalError {
229229
///
230230
/// A new `DecimalError::InvalidValue` instance
231231
#[must_use]
232+
#[cold]
233+
#[inline(never)]
232234
pub fn invalid_value(value: f64, reason: &str) -> Self {
233235
DecimalError::InvalidValue {
234236
value,
@@ -249,6 +251,8 @@ impl DecimalError {
249251
/// # Returns
250252
///
251253
/// A new `DecimalError::ArithmeticError` instance
254+
#[cold]
255+
#[inline(never)]
252256
#[must_use]
253257
pub fn arithmetic_error(operation: &str, reason: &str) -> Self {
254258
DecimalError::ArithmeticError {
@@ -271,6 +275,8 @@ impl DecimalError {
271275
/// # Returns
272276
///
273277
/// A new `DecimalError::ConversionError` instance
278+
#[cold]
279+
#[inline(never)]
274280
#[must_use]
275281
pub fn conversion_error(from_type: &str, to_type: &str, reason: &str) -> Self {
276282
DecimalError::ConversionError {
@@ -293,6 +299,8 @@ impl DecimalError {
293299
/// # Returns
294300
///
295301
/// A new `DecimalError::OutOfBounds` instance
302+
#[cold]
303+
#[inline(never)]
296304
#[must_use]
297305
pub fn out_of_bounds(value: f64, min: f64, max: f64) -> Self {
298306
DecimalError::OutOfBounds { value, min, max }
@@ -311,6 +319,8 @@ impl DecimalError {
311319
/// # Returns
312320
///
313321
/// A new `DecimalError::InvalidPrecision` instance
322+
#[cold]
323+
#[inline(never)]
314324
#[must_use]
315325
pub fn invalid_precision(precision: i32, reason: &str) -> Self {
316326
DecimalError::InvalidPrecision {

src/error/greeks.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,8 @@ impl GreeksError {
495495
/// # Returns
496496
/// A `GreeksError::InputError` with `InvalidVolatility` kind
497497
#[must_use]
498+
#[cold]
499+
#[inline(never)]
498500
pub fn invalid_volatility(value: f64, reason: &str) -> Self {
499501
GreeksError::InputError(InputErrorKind::InvalidVolatility {
500502
value,
@@ -514,6 +516,8 @@ impl GreeksError {
514516
/// # Returns
515517
/// A `GreeksError::InputError` with `InvalidTime` kind
516518
#[must_use]
519+
#[cold]
520+
#[inline(never)]
517521
pub fn invalid_time(value: Positive, reason: &str) -> Self {
518522
GreeksError::InputError(InputErrorKind::InvalidTime {
519523
value,
@@ -533,6 +537,8 @@ impl GreeksError {
533537
/// # Returns
534538
/// A `GreeksError::CalculationError` with `DeltaError` kind
535539
#[must_use]
540+
#[cold]
541+
#[inline(never)]
536542
pub fn delta_error(reason: &str) -> Self {
537543
GreeksError::CalculationError(CalculationErrorKind::DeltaError {
538544
reason: reason.to_string(),
@@ -545,9 +551,9 @@ impl GreeksError {
545551
/// Intended to be used at `f64` → `Decimal` boundaries inside
546552
/// Greeks kernels, as a thin constructor paired with an
547553
/// `if !value.is_finite() { .. }` guard.
548-
#[must_use]
549-
#[inline]
550554
#[cold]
555+
#[inline(never)]
556+
#[must_use]
551557
pub fn non_finite(context: &'static str, value: f64) -> Self {
552558
GreeksError::NonFinite { context, value }
553559
}

0 commit comments

Comments
 (0)