mirror of
https://github.com/nushell/nushell.git
synced 2025-03-29 09:06:52 +01:00
# 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
162 lines
4.9 KiB
Rust
162 lines
4.9 KiB
Rust
use nu_protocol::{ShellError, Span, Value};
|
|
use std::cmp::Ordering;
|
|
|
|
pub enum Reduce {
|
|
Summation,
|
|
Product,
|
|
Minimum,
|
|
Maximum,
|
|
}
|
|
|
|
pub type ReducerFunction =
|
|
Box<dyn Fn(Value, Vec<Value>, Span, Span) -> Result<Value, ShellError> + Send + Sync + 'static>;
|
|
|
|
pub fn reducer_for(command: Reduce) -> ReducerFunction {
|
|
match command {
|
|
Reduce::Summation => Box::new(|_, values, span, head| sum(values, span, head)),
|
|
Reduce::Product => Box::new(|_, values, span, head| product(values, span, head)),
|
|
Reduce::Minimum => Box::new(|_, values, span, head| min(values, span, head)),
|
|
Reduce::Maximum => Box::new(|_, values, span, head| max(values, span, head)),
|
|
}
|
|
}
|
|
|
|
pub fn max(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
|
|
let mut biggest = data
|
|
.first()
|
|
.ok_or_else(|| {
|
|
ShellError::UnsupportedInput(
|
|
"Empty input".to_string(),
|
|
"value originates from here".into(),
|
|
head,
|
|
span,
|
|
)
|
|
})?
|
|
.clone();
|
|
|
|
for value in &data {
|
|
if let Some(result) = value.partial_cmp(&biggest) {
|
|
if result == Ordering::Greater {
|
|
biggest = value.clone();
|
|
}
|
|
} else {
|
|
return Err(ShellError::OperatorMismatch {
|
|
op_span: head,
|
|
lhs_ty: biggest.get_type(),
|
|
lhs_span: biggest.span()?,
|
|
rhs_ty: value.get_type(),
|
|
rhs_span: value.span()?,
|
|
});
|
|
}
|
|
}
|
|
Ok(biggest)
|
|
}
|
|
|
|
pub fn min(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
|
|
let mut smallest = data
|
|
.first()
|
|
.ok_or_else(|| {
|
|
ShellError::UnsupportedInput(
|
|
"Empty input".to_string(),
|
|
"value originates from here".into(),
|
|
head,
|
|
span,
|
|
)
|
|
})?
|
|
.clone();
|
|
|
|
for value in &data {
|
|
if let Some(result) = value.partial_cmp(&smallest) {
|
|
if result == Ordering::Less {
|
|
smallest = value.clone();
|
|
}
|
|
} else {
|
|
return Err(ShellError::OperatorMismatch {
|
|
op_span: head,
|
|
lhs_ty: smallest.get_type(),
|
|
lhs_span: smallest.span()?,
|
|
rhs_ty: value.get_type(),
|
|
rhs_span: value.span()?,
|
|
});
|
|
}
|
|
}
|
|
Ok(smallest)
|
|
}
|
|
|
|
pub fn sum(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
|
|
let initial_value = data.get(0);
|
|
|
|
let mut acc = match initial_value {
|
|
Some(Value::Filesize { span, .. }) => Ok(Value::Filesize {
|
|
val: 0,
|
|
span: *span,
|
|
}),
|
|
Some(Value::Duration { span, .. }) => Ok(Value::Duration {
|
|
val: 0,
|
|
span: *span,
|
|
}),
|
|
Some(Value::Int { span, .. }) | Some(Value::Float { span, .. }) => Ok(Value::int(0, *span)),
|
|
None => Err(ShellError::UnsupportedInput(
|
|
"Empty input".to_string(),
|
|
"value originates from here".into(),
|
|
head,
|
|
span,
|
|
)),
|
|
_ => Ok(Value::nothing(head)),
|
|
}?;
|
|
|
|
for value in &data {
|
|
match value {
|
|
Value::Int { .. }
|
|
| Value::Float { .. }
|
|
| Value::Filesize { .. }
|
|
| Value::Duration { .. } => {
|
|
acc = acc.add(head, value, head)?;
|
|
}
|
|
Value::Error { error } => return Err(*error.clone()),
|
|
other => {
|
|
return Err(ShellError::UnsupportedInput(
|
|
"Attempted to compute the sum of a value that cannot be summed".to_string(),
|
|
"value originates from here".into(),
|
|
head,
|
|
other.expect_span(),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
Ok(acc)
|
|
}
|
|
|
|
pub fn product(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
|
|
let initial_value = data.get(0);
|
|
|
|
let mut acc = match initial_value {
|
|
Some(Value::Int { span, .. }) | Some(Value::Float { span, .. }) => Ok(Value::int(1, *span)),
|
|
None => Err(ShellError::UnsupportedInput(
|
|
"Empty input".to_string(),
|
|
"value originates from here".into(),
|
|
head,
|
|
span,
|
|
)),
|
|
_ => Ok(Value::nothing(head)),
|
|
}?;
|
|
|
|
for value in &data {
|
|
match value {
|
|
Value::Int { .. } | Value::Float { .. } => {
|
|
acc = acc.mul(head, value, head)?;
|
|
}
|
|
Value::Error { error } => return Err(*error.clone()),
|
|
other => {
|
|
return Err(ShellError::UnsupportedInput(
|
|
"Attempted to compute the product of a value that cannot be multiplied"
|
|
.to_string(),
|
|
"value originates from here".into(),
|
|
head,
|
|
other.expect_span(),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
Ok(acc)
|
|
}
|