conjure-cp / conjure-oxide

Mozilla Public License 2.0
9 stars 16 forks source link

[RFC] Performance testing using Conjure and Conjure-Oxide #428

Open EEJDempster opened 2 weeks ago

EEJDempster commented 2 weeks ago

This is some basic documentation outlining the rough plan of how performance testing is being approached as part of the project. This will help me keep track of where along this goal I am, and generally just mean that work I'm doing doesn't just exist in a vacuum of my own notes. Most of these notes have come following meetings with Ty and Oz.

Please feel free to comment anything I might have missed or any ways this could be improved.

Overview

Details

The basis of the implementation of this is founded from get_solutions_from_conjure, which Nik implemented a few weeks ago. Rather than grabbing the stats JSON file generated from the test run (as will be required in my implementation), it instead selects the solutions, as shown in a (not complete) snippet of the method below:

#[allow(clippy::unwrap_used)]
pub fn get_solutions_from_conjure(
    essence_file: &str,
) -> Result<Vec<HashMap<Name, Literal>>, EssenceParseError> {
    // this is ran in parallel, and we have no guarantee by rust that invocations to this function
    // don't share the same tmp dir.
    let mut rng = rand::thread_rng();
    let rand: i8 = rng.gen();

    let mut tmp_dir = std::env::temp_dir();
    tmp_dir.push(Path::new(&rand.to_string()));

    let mut cmd = std::process::Command::new("conjure");
    let output = cmd
        .arg("solve")
        .arg("--output-format=json")
        .arg("--solutions-in-one-file")
        .arg("--number-of-solutions=all")
        .arg("--copy-solutions=no")
        .arg("-o")
        .arg(&tmp_dir)
        .arg(essence_file)
        .output()
        .map_err(|e| EssenceParseError::ConjureSolveError(e.to_string()))?;

    if !output.status.success() {
        return Err(EssenceParseError::ConjureSolveError(format!(
            "conjure solve exited with failure: {}",
            String::from_utf8(output.stderr).unwrap()
        )));
    }

    let solutions_files: Vec<_> = glob(&format!("{}/*.solutions.json", tmp_dir.display()))
        .unwrap()
        .collect();

Currently, this implementation takes in an essence file and runs conjure solve --output-format=json --solutions-in-one-file --number-of-solutions=all --copy-solutions=no -o [temporary directory] [input essence_file]. This command calls the conjure solution on a specific essence file and outputs all of the results of this into a temporary directory. The arguments just ensure that the output is returned in the same format as conjure-oxide (for example conjure-oxide returns all solutions, so the arguments must specify that conjure returns all arguments to ensure correctness). The current implementation can be either used as inspiration or directly modified to return the stats files for comparison.

Basic Steps

  1. Modify/use Nik's get_solutions_from_conjure to get stats files from conjure.
    • A struct will likely be used when reading the JSON to only gather the very specific intended results
    • If the initial method is modified rather than a new one created, then it is likely that the return type will be a tuple, such as (demonstrative code, not actually checked for functionality) Result<(Vec<HashMap<Name, Constant>>,Optional STATSFILE), EssenceParseError>
  2. Modify integrated_test and integrated_test_inner to make use of this modification to get_solutions_from_conjure
    • Possibly implement a specific test library to performance test - this could be done by giving a different extension, as seen in .disabled, or by creating a new directory of performance tests.
  3. Use these modified implementations to
    • Gather conjure stats for a specific essence problem
    • Gather conjure-oxide stats for the exact same essence problem
    • Compare these two for the given values (rewriter time, solver time, nodes, total time) and flag any major disparity (emphasis on flagging when nodes is different, specifically when it is higher in conjure-oxide).

There is some degree to which running problems multiple times to generate a more accurate solver and rewrite time relationship becomes inefficient when the problems become sufficiently complex, hence the interest in nodes. I am not sure to what degree the conjure-oxide implementation is currently capable of solving these more complex problems at present, so I'm not sure if this specific concern is relevant as it stands.

Any comments on this would be welcome! If I've missed any major parts of this explanation, please let me know :). A draft PR will be made soon to start the implementation of this.