-
Notifications
You must be signed in to change notification settings - Fork 126
Description
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
}