Skip to content

Performance depends in surprising ways on struct definitions #1958

@aaronbembenek-aws

Description

@aaronbembenek-aws

Subtle differences in the definitions of structs---even when data of that type is never created---excessively affects Kani's performance.

I tried this code:

enum Expr {
    A,
    B(Box<Expr>),
    C(Box<Expr>, Box<Expr>),
    D(Box<Expr>, Box<Expr>, Box<Expr>),
    E(Box<Expr>, Box<Expr>, Box<Expr>, Box<Expr>),
}

enum Result<S, T> {
    Ok(S),
    Err(T),
}

enum Err<X, Y, Z> {
    A(X),
    B(Y, Z),
}

type Err1 = Err<String, String, String>;
type Err2<'a> = Err<String, &'a str, String>;
type Err3<'a> = Err<String, String, &'a str>;
type Err4<'a> = Err<String, &'a str, &'a str>;
type Err5<'a> = Err<&'a str, String, String>;
type Err6<'a> = Err<&'a str, &'a str, String>;
type Err7<'a> = Err<&'a str, String, &'a str>;
type Err8<'a> = Err<&'a str, &'a str, &'a str>;
type Err9<'a> = Err<Expr, &'a str, String>;
type Err10<'a> = Err<Box<Expr>, &'a str, String>;

// Takes >10s
#[cfg_attr(kani, kani::proof, kani::unwind(2))]
fn slow_harness1() {
    let _: Result<Expr, Err2> = Result::Ok(Expr::A);
}

// Takes >10s
#[cfg_attr(kani, kani::proof, kani::unwind(2))]
fn slow_harness2() {
    let _: Result<Expr, Err9> = Result::Ok(Expr::A);
}

// Takes ~1s
#[cfg_attr(kani, kani::proof, kani::unwind(2))]
fn fast_harness() {
    let _: Result<Expr, Err1> = Result::Ok(Expr::A);
    //let _: Result<Expr, Err2> = Result::Ok(Expr::A);
    let _: Result<Expr, Err3> = Result::Ok(Expr::A);
    let _: Result<Expr, Err4> = Result::Ok(Expr::A);
    let _: Result<Expr, Err5> = Result::Ok(Expr::A);
    let _: Result<Expr, Err6> = Result::Ok(Expr::A);
    let _: Result<Expr, Err7> = Result::Ok(Expr::A);
    let _: Result<Expr, Err8> = Result::Ok(Expr::A);
    //let _: Result<Expr, Err9> = Result::Ok(Expr::A);
    let _: Result<Expr, Err10> = Result::Ok(Expr::A);
}

using the following command line invocations:

kani file.rs --harness slow_harness1
kani file.rs --harness slow_harness2
kani file.rs --harness fast_harness

with Kani v0.16.0 (commit #55ba7d64) and CBMC v5.71.0.

I expected to see this happen: the time it takes to verify

let _: Result<Expr, T> = Result::Ok(Expr::A);

does not depend on the instantiation of the type parameter T.

Instead, this happened: the time it takes varies greatly with what T is instantiated with.

It takes >10s if T is instantiated with Err2:

struct Err2<'a> {
    A(String),
    B(&'a str, String)
}

It takes <1s if T is instantiated with Err3:

struct Err3<'a> {
    A(String),
    B(String, &'a str)
}

Furthermore, performance also depends on surprising ways on the type Expr. For example, say we add another variant:

enum Expr {
    A,
    B(Box<Expr>),
    C(Box<Expr>, Box<Expr>),
    D(Box<Expr>, Box<Expr>, Box<Expr>),
    E(Box<Expr>, Box<Expr>, Box<Expr>, Box<Expr>),
    F(Box<Expr>, Box<Expr>, Box<Expr>, Box<Expr>, Box<Expr>), // new variant
}

Now, slow_harness1 is actually faster than before - it takes less than 1s. However, if we instead tweak the new variant by dropping one of the arguments, performance is much worse (40s):

enum Expr {
    A,
    B(Box<Expr>),
    C(Box<Expr>, Box<Expr>),
    D(Box<Expr>, Box<Expr>, Box<Expr>),
    E(Box<Expr>, Box<Expr>, Box<Expr>, Box<Expr>),
    F(Box<Expr>, Box<Expr>, Box<Expr>, Box<Expr>), // new variant
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-CBMCIssue related to an existing CBMC issue[C] Feature / EnhancementA new feature request or enhancement to an existing feature.[E] PerformanceTrack performance improvement (Time / Memory / CPU)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions