Expand description

the big-O-test crate

big-o-test GitHub Actions big-o-test on crates.io big-o-test on docs.rs

The big-O-test crate dynamically analyzes algorithms for space and time resource consumption, allowing tests to enforce a maximum complexity – detecting, as soon as possible, eventual performance regressions.

Browse the Docs.

It is able to operate both on regular and iterator algorithms – the later being useful to test CRUD operations.

Reports are issued using the Big O Notation (hence the name) and it works by measuring how the algorithm’s CPU times & RAM space requirements grow in relation to the amount of data or number of elements that it is applied on.

By using this crate on tests, you are enforcing – through real measurements – how your program should behave in regard to resource consumption – allowing you to foresee, when in production, the resource requirements and, eventually, helping in the process of optimization, as you are free to do changes that are sure to cause a test failure when regressions in space or time complexities are introduced. It is, as such, meant to work as a development tool, alongside with tests & benchmarks.

A distinction is made between regular, non-iterator Algorithms and Iterator Algorithms. The latter encompasses algorithms that operate on a single element per call; they may fit into the following categories:

  • those that alter the amount of data they operate on – such as inserts, deletes, extractions and loads (EtL)
  • those that operate on a constant data set – such as queries and data transformations (eTl)

A special method is provided to test CRUD operations, as in the example bellow:

CRUD test example

crud_example.png

The optional measurement output issued by this test:

Vec Insert & Remove (worst case) with ParkingLot CRUD Algorithm Complexity Analysis:
  First Pass (create: 8090µs/+64.42KiB, read: 15254µs/+432.00b, update: 13948µs/+432.00b); Second Pass (create: 22440µs/+64.42KiB, read: 15232µs/+432.00b, update: 13839µs/+432.00b):

'Create' set resizing algorithm measurements:
pass          Δt              Δs            Σn            t⁻
1)        8090µs       +64.42KiB         16384         0.494µs
2)       22440µs       +64.42KiB         32768         1.370µs
--> Algorithm  Time Analysis: O(n)
--> Algorithm Space Analysis: O(1) (allocated: 128.20KiB; auxiliary used space: 656.00b)


'Read' constant set algorithm measurements:
pass          Δt              Δs            Σn            ⊆r            t⁻
1)       15254µs        +432.00b         16384        163840         0.093µs
2)       15232µs        +432.00b         32768        163840         0.093µs
--> Algorithm  Time Analysis: O(1)
--> Algorithm Space Analysis: O(1) (allocated: 208.00b; auxiliary used space: 656.00b)


'Update' constant set algorithm measurements:
pass          Δt              Δs            Σn            ⊆r            t⁻
1)       13948µs        +432.00b         16384        163840         0.085µs
2)       13839µs        +432.00b         32768        163840         0.084µs
--> Algorithm  Time Analysis: O(1)
--> Algorithm Space Analysis: O(1) (allocated: 208.00b; auxiliary used space: 656.00b)


Delete Passes (2nd: 23365µs/+432.00b; 1st: 7744µs/+432.00b) r=262144:
'Delete' set resizing algorithm measurements:
pass          Δt              Δs            Σn            t⁻
1)        7744µs        +432.00b         16384         0.473µs
2)       23365µs        +432.00b         32768         1.426µs
--> Algorithm  Time Analysis: O(n)
--> Algorithm Space Analysis: O(1) (allocated: 208.00b; auxiliary used space: 656.00b)

Regular algorithm example

A regular, non-iterator algorithm is run only once for each pass – in the example bellow, this algorithm is vec::sort():

regular_algo_example.png

The optional measurement output issued by this test:

Running 'Quicksort a reversed vec' algorithm:
  Resetting: 3406857µs/+768.00MiB; Pass 1: 658484µs/76.29MiB; Pass 2: 1315255µs/152.59MiB

'Quicksort a reversed vec' regular-algorithm measurements:
pass          Δt              Δs             n            s⁻           t⁻
1)      658484µs        76.29MiB      40000000            2b         0.016µs
2)     1315255µs       152.59MiB      80000000            2b         0.016µs
--> Algorithm  Time Analysis: O(n)
--> Algorithm Space Analysis: O(n) (allocated: 0.00b; auxiliary used space: 228.88MiB)

Usage in projects

Add this to your Cargo.toml:

[dev-dependencies]
ctor = "0.1"
big-o-test = "0.2"

Then create an Integration Test, setting it up to execute tests linearly – see tests/big_o_tests.rs for an example on how this may be easily achieved.

Disabling the Rust’s default Parallel Test Runner is crucial for accurately measuring time & memory – nonetheless, special care was taken to avoid flaky tests: an automatic retrying mechanism kicks in when the time complexity analysis doesn’t match the maximum accepted value.

Note

To measure the space resource requirements, this crate sets a custom Global Allocator capable of gathering allocation metrics. It only affects tests, but still imposes a non-negligible overhead – each allocation / de-allocation updates a dozen atomic counters.

Re-exports

pub use configs::ALLOC;
pub use configs::OUTPUT;
pub use runners::standard::test_algorithm;
pub use runners::standard::test_constant_set_iterator_algorithm;
pub use runners::standard::test_set_resizing_iterator_algorithm;
pub use runners::crud::test_crud_algorithms;

Modules

Contains conditional compilation definitions attending to:

Exports time & space algorithm complexity analysis functions, as well as the needed types to operate on them. See:

Global allocator (wrapper around the System’s default allocator) capable of gathering allocation/de-allocation/re-allocation metrics and (min, max) memory usage between two (or more) points in time.

Contains executors of the algorithms, gathering metrics to pass to crate::low_level_analysis in order to have their complexity measured

Macros

experimental/rudimentary assertion macro to let an ‘observed_complexity’ better than ‘expected_complexity’ to pass, in the hope to reduce false-negative test failures

Structs

prebuilt TimeUnit constants

Enums

Possible time & space complexity analysis results, in big-O notation. Results are for a single operation – remember a pass have several operations, so the time for the analysis should have ‘* 2 * p’ added – ‘p’ being the size for each one of the 2 passes required for the analysis.