Box ShellError in Value::Error (#8375)

# Description

Our `ShellError` at the moment has a `std::mem::size_of<ShellError>` of
136 bytes (on AMD64). As a result `Value` directly storing the struct
also required 136 bytes (thanks to alignment requirements).

This change stores the `Value::Error` `ShellError` on the heap.

Pro:
- Value now needs just 80 bytes
- Should be 1 cacheline less (still at least 2 cachelines)

Con:
- More small heap allocations when dealing with `Value::Error`
  - More heap fragmentation
  - Potential for additional required memcopies

# Further code changes

Includes a small refactor of `try` due to a type mismatch in its large
match.

# User-Facing Changes

None for regular users.

Plugin authors may have to update their matches on `Value` if they use
`nu-protocol`

Needs benchmarking to see if there is a benefit in real world workloads.
**Update** small improvements in runtime for workloads with high volume
of values. Significant reduction in maximum resident set size, when many
values are held in memory.

# Tests + Formatting
This commit is contained in:
Stefan Holderbach
2023-03-12 09:57:27 +01:00
committed by GitHub
parent c26d91fb61
commit a52386e837
153 changed files with 648 additions and 520 deletions

View File

@@ -193,12 +193,12 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "int, filesize, float, string".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.expect_span(),
},
}),
},
}
}

View File

@@ -87,12 +87,12 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "integer or filesize".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.expect_span(),
},
}),
},
}
}

View File

@@ -188,13 +188,13 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "integer, float, filesize, string, date, duration, binary or bool"
.into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.expect_span(),
},
}),
},
}
}

View File

@@ -161,17 +161,19 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
},
Value::String { val, .. } => match string_to_boolean(val, span) {
Ok(val) => Value::Bool { val, span },
Err(error) => Value::Error { error },
Err(error) => Value::Error {
error: Box::new(error),
},
},
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "bool, integer, float or string".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.expect_span(),
},
}),
},
}
}

View File

@@ -209,12 +209,12 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
Value::Error { .. } => return input.clone(),
other => {
return Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string and integer".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
},
}),
};
}
};
@@ -245,21 +245,21 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
Some(eastoffset) => match_datetime!(eastoffset.timestamp_nanos(ts)),
None => Value::Error {
error: ShellError::DatetimeParseError(input.debug_value(), *span),
error: Box::new(ShellError::DatetimeParseError(input.debug_value(), *span)),
},
},
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
Some(westoffset) => match_datetime!(westoffset.timestamp_nanos(ts)),
None => Value::Error {
error: ShellError::DatetimeParseError(input.debug_value(), *span),
error: Box::new(ShellError::DatetimeParseError(input.debug_value(), *span)),
},
},
Zone::Error => Value::Error {
// This is an argument error, not an input error
error: ShellError::TypeMismatch {
error: Box::new(ShellError::TypeMismatch {
err_message: "Invalid timezone or offset".to_string(),
span: *span,
},
}),
},
},
};
@@ -273,7 +273,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
Ok(d) => Value::Date { val: d, span: head },
Err(reason) => {
Value::Error {
error: ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
error: Box::new(ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) }),
}
}
},
@@ -292,12 +292,12 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
},
}),
},
}
}
@@ -374,7 +374,7 @@ mod tests {
#[test]
fn takes_timestamp_offset_as_int() {
let date_int = Value::test_int(1614434140_000000000);
let date_int = Value::test_int(1_614_434_140_000_000_000);
let timezone_option = Some(Spanned {
item: Zone::East(8),
span: Span::test_data(),

View File

@@ -88,12 +88,12 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
match other.parse::<f64>() {
Ok(x) => Value::float(x, head),
Err(reason) => Value::Error {
error: ShellError::CantConvert {
error: Box::new(ShellError::CantConvert {
to_type: "float".to_string(),
from_type: reason.to_string(),
span: *span,
help: None,
},
}),
},
}
}
@@ -108,12 +108,12 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string, integer or bool".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
},
}),
},
}
}

View File

@@ -174,7 +174,9 @@ fn into_duration(
Box::new(move |old| action(old, &d, float_precision, head)),
);
if let Err(error) = r {
return Value::Error { error };
return Value::Error {
error: Box::new(error),
};
}
}
@@ -430,7 +432,7 @@ fn action(
}
}
}
Err(e) => Value::Error { error: e },
Err(e) => Value::Error { error: Box::new(e) },
}
} else {
input.clone()
@@ -464,34 +466,36 @@ fn action(
}
}
}
Err(e) => Value::Error { error: e },
Err(e) => Value::Error { error: Box::new(e) },
}
} else {
Value::Error {
error: ShellError::CantConvert {
error: Box::new(ShellError::CantConvert {
to_type: "string".into(),
from_type: "duration".into(),
span,
help: None,
},
}),
}
}
} else {
match string_to_duration(val, span, *value_span) {
Ok(val) => Value::Duration { val, span },
Err(error) => Value::Error { error },
Err(error) => Value::Error {
error: Box::new(error),
},
}
}
}
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string or duration".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.expect_span(),
},
}),
},
}
}

View File

@@ -110,19 +110,21 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
val,
span: value_span,
},
Err(error) => Value::Error { error },
Err(error) => Value::Error {
error: Box::new(error),
},
},
Value::Nothing { .. } => Value::Filesize {
val: 0,
span: value_span,
},
other => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string and integer".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: value_span,
},
}),
},
}
} else {

View File

@@ -189,12 +189,12 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
Ok(v) => v,
_ => {
return Value::Error {
error: ShellError::CantConvert {
error: Box::new(ShellError::CantConvert {
to_type: "float".to_string(),
from_type: "integer".to_string(),
span,
help: None,
},
}),
}
}
}
@@ -206,7 +206,9 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
if radix == 10 {
match int_from_string(val, span) {
Ok(val) => Value::Int { val, span },
Err(error) => Value::Error { error },
Err(error) => Value::Error {
error: Box::new(error),
},
}
} else {
convert_int(input, span, radix)
@@ -232,10 +234,10 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
.unwrap()
{
Value::Error {
error: ShellError::IncorrectValue {
error: Box::new(ShellError::IncorrectValue {
msg: "DateTime out of range for timestamp: 1677-09-21T00:12:43Z to 2262-04-11T23:47:16".to_string(),
span
},
}),
}
} else {
Value::Int {
@@ -269,13 +271,13 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "integer, float, filesize, date, string, binary, duration or bool"
.into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.expect_span(),
},
}),
},
}
}
@@ -292,7 +294,7 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
{
match int_from_string(val, head) {
Ok(x) => return Value::int(x, head),
Err(e) => return Value::Error { error: e },
Err(e) => return Value::Error { error: Box::new(e) },
}
} else if val.starts_with("00") {
// It's a padded string
@@ -300,12 +302,12 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
Ok(n) => return Value::int(n, head),
Err(e) => {
return Value::Error {
error: ShellError::CantConvert {
error: Box::new(ShellError::CantConvert {
to_type: "string".to_string(),
from_type: "int".to_string(),
span: head,
help: Some(e.to_string()),
},
}),
}
}
}
@@ -316,24 +318,24 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
Value::Error { .. } => return input.clone(),
other => {
return Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string and integer".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.expect_span(),
},
}),
};
}
};
match i64::from_str_radix(i.trim(), radix) {
Ok(n) => Value::int(n, head),
Err(_reason) => Value::Error {
error: ShellError::CantConvert {
error: Box::new(ShellError::CantConvert {
to_type: "string".to_string(),
from_type: "int".to_string(),
span: head,
help: None,
},
}),
},
}
}
@@ -483,9 +485,9 @@ mod test {
}
#[rstest]
#[case("2262-04-11T23:47:16+00:00", 0x7fffffff_ffffffff)]
#[case("2262-04-11T23:47:16+00:00", 0x7fff_ffff_ffff_ffff)]
#[case("1970-01-01T00:00:00+00:00", 0)]
#[case("1677-09-21T00:12:44+00:00", -0x7fffffff_ffffffff)]
#[case("1677-09-21T00:12:44+00:00", -0x7fff_ffff_ffff_ffff)]
fn datetime_to_int_values_that_work(
#[case] dt_in: DateTime<FixedOffset>,
#[case] int_expected: i64,
@@ -522,14 +524,15 @@ mod test {
},
Span::test_data(),
);
if let Value::Error {
error: ShellError::IncorrectValue { msg: e, .. },
} = actual
{
assert!(
e.contains(err_expected),
"{e:?} doesn't contain {err_expected}"
);
if let Value::Error { error } = actual {
if let ShellError::IncorrectValue { msg: e, .. } = *error {
assert!(
e.contains(err_expected),
"{e:?} doesn't contain {err_expected}"
);
} else {
panic!("Unexpected error variant {error:?}")
}
} else {
panic!("Unexpected actual value {actual:?}")
}

View File

@@ -185,12 +185,12 @@ fn into_record(
Value::Record { cols, vals, span } => Value::Record { cols, vals, span },
Value::Error { .. } => input,
other => Value::Error {
error: ShellError::OnlySupportsThisInputType {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: other.get_type().to_string(),
dst_span: call.head,
src_span: other.expect_span(),
},
}),
},
};
Ok(res.into_pipeline_data())

View File

@@ -247,28 +247,28 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
span: _,
} => Value::Error {
// Watch out for CantConvert's argument order
error: ShellError::CantConvert {
error: Box::new(ShellError::CantConvert {
to_type: "string".into(),
from_type: "record".into(),
span,
help: Some("try using the `to nuon` command".into()),
},
}),
},
Value::Binary { .. } => Value::Error {
error: ShellError::CantConvert {
error: Box::new(ShellError::CantConvert {
to_type: "string".into(),
from_type: "binary".into(),
span,
help: Some("try using the `decode` command".into()),
},
}),
},
x => Value::Error {
error: ShellError::CantConvert {
error: Box::new(ShellError::CantConvert {
to_type: String::from("string"),
from_type: x.get_type().to_string(),
span,
help: None,
},
}),
},
}
}