From 7e9ca5d2cd74b121267a9a980cb5e0b00573568b Mon Sep 17 00:00:00 2001 From: Anexen Date: Sat, 27 Jan 2024 00:27:22 +0100 Subject: [PATCH 1/8] faster pow --- src/core/periodic.rs | 11 +++++++---- src/core/scheduled/xirr.rs | 12 ++++++------ src/core/utils.rs | 18 +++++++++++++++++- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/core/periodic.rs b/src/core/periodic.rs index acd23b8..56b7259 100644 --- a/src/core/periodic.rs +++ b/src/core/periodic.rs @@ -427,7 +427,11 @@ pub fn npv(rate: f64, values: &[f64], start_from_zero: Option) -> f64 { } fn npv_deriv(rate: f64, values: &[f64]) -> f64 { - values.iter().enumerate().map(|(i, v)| -(i as f64) * v / (rate + 1.0).powf(i as f64 + 1.)).sum() + values + .iter() + .enumerate() + .map(|(i, v)| -(i as f64) * v * utils::fast_pow(rate + 1.0, -(i as f64 + 1.0))) + .sum() } pub fn irr(values: &[f64], guess: Option) -> Result { @@ -442,7 +446,6 @@ pub fn irr(values: &[f64], guess: Option) -> Result g, @@ -454,13 +457,13 @@ pub fn irr(values: &[f64], guess: Option) -> Result) -> Vec // \sum_{i=1}^n \frac{P_i}{(1 + rate)^{(d_i - d_0)/365}} fn xnpv_result(payments: &[f64], deltas: &[f64], rate: f64) -> f64 { - payments.iter().zip(deltas).map(|(p, &e)| p * (1.0 + rate).powf(-e)).sum() + payments.iter().zip(deltas).map(|(p, &e)| p * fast_pow(1.0 + rate, -e)).sum() } // XNPV first derivative @@ -94,7 +94,7 @@ fn xnpv_result(payments: &[f64], deltas: &[f64], rate: f64) -> f64 { // simplify in order to reuse cached deltas (d_i - d_0)/365 // \sum_{i=1}^n \frac{P_i * -(d_i - d_0) / 365}{(1 + rate)^{((d_i - d_0)/365 + 1)}} fn xnpv_result_deriv(payments: &[f64], deltas: &[f64], rate: f64) -> f64 { - payments.iter().zip(deltas).map(|(p, e)| p * -e * (1.0 + rate).powf(-e - 1.0)).sum() + payments.iter().zip(deltas).map(|(p, e)| p * -e * fast_pow(1.0 + rate, -e - 1.0)).sum() } #[cfg(test)] diff --git a/src/core/utils.rs b/src/core/utils.rs index cc0a117..850dae1 100644 --- a/src/core/utils.rs +++ b/src/core/utils.rs @@ -1,3 +1,8 @@ +pub(crate) fn fast_pow(a: f64, b: f64) -> f64 { + // works only if a is positive + (a.log2() * b).exp2() +} + pub(crate) fn scale(values: &[f64], factor: f64) -> Vec { values.iter().map(|v| v * factor).collect() } @@ -12,7 +17,11 @@ pub(crate) fn pairwise_mul(a: &[f64], b: &[f64]) -> Vec { pub(crate) fn series_signum(a: &[f64]) -> f64 { // returns -1. if any item is negative, otherwise +1. - if a.iter().any(|x| x.is_sign_negative()) { -1. } else { 1. } + if a.iter().any(|x| x.is_sign_negative()) { + -1. + } else { + 1. + } } pub(crate) fn sum_negatives_positives(values: &[f64]) -> (f64, f64) { @@ -24,3 +33,10 @@ pub(crate) fn sum_negatives_positives(values: &[f64]) -> (f64, f64) { } }) } + +pub(crate) fn is_a_good_rate(rate: f64, f: F) -> bool +where + F: Fn(f64) -> f64, +{ + rate.is_finite() && f(rate).abs() < 1e-3 +} From b7abef67a030b5b47edb16243182734baddd9ac6 Mon Sep 17 00:00:00 2001 From: Anexen Date: Sat, 27 Jan 2024 16:56:13 +0100 Subject: [PATCH 2/8] bump version to 0.10.2 --- CHANGELOG.md | 4 ++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbe7e81..2bc7d0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [0.10.2] - 2024-01-27 + +- (X)IRR Performance improvements + ## [0.10.1] - 2023-12-08 - XIRR improvements ([#49](https://github.com/Anexen/pyxirr/pull/49)) diff --git a/Cargo.lock b/Cargo.lock index 77b2ebf..e848a18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,7 +366,7 @@ dependencies = [ [[package]] name = "pyxirr" -version = "0.10.1" +version = "0.10.2" dependencies = [ "assert_approx_eq", "ndarray", diff --git a/Cargo.toml b/Cargo.toml index d9c9039..8abfa82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyxirr" -version = "0.10.1" +version = "0.10.2" authors = ["Anexen"] edition = "2021" description = "Rust-powered collection of financial functions for Python." From 1672a3ad4aa2a82f338966fc122731cbf4ae2c1a Mon Sep 17 00:00:00 2001 From: Anexen Date: Sat, 27 Jan 2024 22:14:37 +0100 Subject: [PATCH 3/8] update bench gh action --- .github/workflows/bench.yaml | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/.github/workflows/bench.yaml b/.github/workflows/bench.yaml index 9b79ad7..c512e0a 100644 --- a/.github/workflows/bench.yaml +++ b/.github/workflows/bench.yaml @@ -11,34 +11,28 @@ on: required: true default: true +permissions: + contents: write + deployments: write + jobs: benchmark: name: Run Rust benchmark runs-on: ubuntu-latest if: ${{ startsWith(github.ref, 'refs/tags/') || github.event.inputs.build }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: 3.9 - - uses: actions-rs/toolchain@v1 + python-version: 3.11 + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: nightly - profile: minimal - override: true - - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - ~/.cache/pip - target - key: ubuntu-latest-cargo-${{ hashFiles('**/Cargo.lock') }} - run: pip install -r bench-requirements.txt - name: Run benchmark run: cargo +nightly bench --bench comparison | tee output.txt - name: Store benchmark result - uses: rhysd/github-action-benchmark@v1 + uses: benchmark-action/github-action-benchmark@v1 with: name: Rust Benchmark tool: 'cargo' @@ -47,4 +41,4 @@ jobs: benchmark-data-dir-path: docs/bench output-file-path: output.txt auto-push: true - github-token: ${{ secrets.PERSONAL_GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} From 75ffa313261a7edef3f859833d96f7bd7a252606 Mon Sep 17 00:00:00 2001 From: Anexen Date: Sat, 27 Jan 2024 22:18:50 +0100 Subject: [PATCH 4/8] remove unused code --- tests/common/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index b1a59ae..a61f90f 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,5 +1,4 @@ mod cases; mod helpers; -pub use cases::*; pub use helpers::*; From 1d10fd80f3dded066736ba47451d5f8957ad31f6 Mon Sep 17 00:00:00 2001 From: Anexen Date: Sat, 27 Jan 2024 22:21:41 +0100 Subject: [PATCH 5/8] Revert "remove unused code" This reverts commit 75ffa313261a7edef3f859833d96f7bd7a252606. --- tests/common/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index a61f90f..b1a59ae 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,4 +1,5 @@ mod cases; mod helpers; +pub use cases::*; pub use helpers::*; From ccb6234f73fcb93c417ff8a862d2fdbf50257d9b Mon Sep 17 00:00:00 2001 From: Anexen Date: Sat, 27 Jan 2024 22:23:48 +0100 Subject: [PATCH 6/8] fix unused import --- tests/common/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index b1a59ae..1b56954 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,5 +1,6 @@ mod cases; mod helpers; +#[allow(unused_imports)] pub use cases::*; pub use helpers::*; From 24bbd6b30808898f33e8ec392ee73cb84ddf3347 Mon Sep 17 00:00:00 2001 From: github-action-benchmark Date: Sat, 27 Jan 2024 21:25:45 +0000 Subject: [PATCH 7/8] add Rust Benchmark (cargo) benchmark result for ccb6234f73fcb93c417ff8a862d2fdbf50257d9b --- docs/bench/data.js | 78 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/docs/bench/data.js b/docs/bench/data.js index 741b7fb..2d149ca 100644 --- a/docs/bench/data.js +++ b/docs/bench/data.js @@ -1,5 +1,5 @@ window.BENCHMARK_DATA = { - "lastUpdate": 1701990793475, + "lastUpdate": 1706390745809, "repoUrl": "https://github.com/Anexen/pyxirr", "entries": { "Rust Benchmark": [ @@ -3420,6 +3420,82 @@ window.BENCHMARK_DATA = { "unit": "ns/iter" } ] + }, + { + "commit": { + "author": { + "name": "Anexen", + "username": "Anexen", + "email": "avolk93@gmail.com" + }, + "committer": { + "name": "Anexen", + "username": "Anexen", + "email": "avolk93@gmail.com" + }, + "id": "ccb6234f73fcb93c417ff8a862d2fdbf50257d9b", + "message": "fix unused import", + "timestamp": "2024-01-27T21:23:48Z", + "url": "https://github.com/Anexen/pyxirr/commit/ccb6234f73fcb93c417ff8a862d2fdbf50257d9b" + }, + "date": 1706390745412, + "tool": "cargo", + "benches": [ + { + "name": "bench_python_100", + "value": 8149216, + "range": "± 55209", + "unit": "ns/iter" + }, + { + "name": "bench_python_1000", + "value": 20161064, + "range": "± 218730", + "unit": "ns/iter" + }, + { + "name": "bench_python_500", + "value": 26305873, + "range": "± 584738", + "unit": "ns/iter" + }, + { + "name": "bench_rust_100", + "value": 11250, + "range": "± 101", + "unit": "ns/iter" + }, + { + "name": "bench_rust_1000", + "value": 96669, + "range": "± 1193", + "unit": "ns/iter" + }, + { + "name": "bench_rust_500", + "value": 56507, + "range": "± 403", + "unit": "ns/iter" + }, + { + "name": "bench_scipy_100", + "value": 701667, + "range": "± 13445", + "unit": "ns/iter" + }, + { + "name": "bench_scipy_1000", + "value": 4190035, + "range": "± 112721", + "unit": "ns/iter" + }, + { + "name": "bench_scipy_500", + "value": 2855297, + "range": "± 81117", + "unit": "ns/iter" + } + ] } ] } From f65399b57d8e3b08a520ea2c45d60fb18c439969 Mon Sep 17 00:00:00 2001 From: Anexen Date: Sun, 28 Jan 2024 16:08:54 +0100 Subject: [PATCH 8/8] update GH actions --- .github/workflows/ci.yaml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7a4a948..ea4bed2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,10 +16,10 @@ jobs: test: runs-on: "ubuntu-latest" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dev-requirements run: pip install -r dev-requirements.txt - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: | ~/.cargo/registry @@ -54,12 +54,12 @@ jobs: arch: aarch64 - platform: windows-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python.constraints || matrix.python.version }} # allow-prereleases: ${{ matrix.python.version == "3.12" }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: | ~/.cargo/registry @@ -71,7 +71,7 @@ jobs: - name: Build Wheels - Linux if: matrix.os.platform == 'ubuntu-latest' - uses: messense/maturin-action@v1 + uses: PyO3/maturin-action@v1 with: maturin-version: v1.3.0 manylinux: auto @@ -79,21 +79,21 @@ jobs: - name: Build Wheels - MacOS [aarch64] if: ${{ matrix.os.platform == 'macos-latest' && matrix.os.arch == 'aarch64' }} - uses: messense/maturin-action@v1 + uses: PyO3/maturin-action@v1 with: maturin-version: v1.3.0 args: -i python --release --target aarch64-apple-darwin --strip - name: Build Wheels - MacOS [x86_64] if: ${{ matrix.os.platform == 'macos-latest' && matrix.os.arch != 'aarch64' }} - uses: messense/maturin-action@v1 + uses: PyO3/maturin-action@v1 with: maturin-version: v1.3.0 args: -i python --release --target universal2-apple-darwin --strip - name: Build Wheels - Windows if: matrix.os.platform == 'windows-latest' - uses: messense/maturin-action@v1 + uses: PyO3/maturin-action@v1 with: maturin-version: v1.3.0 args: -i python --release --strip @@ -125,9 +125,9 @@ jobs: abi: 'cp312-cp312' target: [aarch64, armv7, s390x, ppc64le, ppc64] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build Wheels - uses: messense/maturin-action@v1 + uses: PyO3/maturin-action@v1 env: PYO3_CROSS_LIB_DIR: /opt/python/${{ matrix.python.abi }}/lib with: @@ -136,7 +136,7 @@ jobs: manylinux: auto args: -i python3.9 --release --strip - name: Upload wheels - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: wheels path: target/wheels @@ -153,16 +153,16 @@ jobs: - aarch64-unknown-linux-musl - armv7-unknown-linux-musleabihf steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build Wheels - musl - uses: messense/maturin-action@v1 + uses: PyO3/maturin-action@v1 with: maturin-version: v1.3.0 target: ${{ matrix.target }} manylinux: musllinux_1_2 args: --release --strip -i 3.7 3.8 3.9 3.10 3.11 3.12 - name: Upload wheels - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: wheels path: target/wheels @@ -172,7 +172,7 @@ jobs: runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: wheels