Reduce size of Expr from 80 to 64 bytes (#9900)

## Summary

This PR reduces the size of `Expr` from 80 to 64 bytes, by reducing the
sizes of...

- `ExprCall` from 72 to 56 bytes, by using boxed slices for `Arguments`.
- `ExprCompare` from 64 to 48 bytes, by using boxed slices for its
various vectors.

In testing, the parser gets a bit faster, and the linter benchmarks
improve quite a bit.
This commit is contained in:
Charlie Marsh 2024-02-08 18:53:13 -08:00 committed by GitHub
parent bd8123c0d8
commit 49fe1b85f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
78 changed files with 326 additions and 258 deletions

View file

@ -81,16 +81,16 @@ type FunctionArgument = (
pub(crate) fn parse_arguments(
function_arguments: Vec<FunctionArgument>,
) -> Result<ArgumentList, LexicalError> {
let mut args = vec![];
let mut keywords = vec![];
// First, run through the comments to determine the number of positional and keyword arguments.
let mut keyword_names = FxHashSet::with_capacity_and_hasher(
function_arguments.len(),
BuildHasherDefault::default(),
);
let mut double_starred = false;
for (name, value) in function_arguments {
if let Some((start, end, name)) = name {
let mut num_args = 0;
let mut num_keywords = 0;
for (name, value) in &function_arguments {
if let Some((start, _end, name)) = name {
// Check for duplicate keyword arguments in the call.
if let Some(keyword_name) = &name {
if !keyword_names.insert(keyword_name.to_string()) {
@ -98,21 +98,17 @@ pub(crate) fn parse_arguments(
LexicalErrorType::DuplicateKeywordArgumentError(
keyword_name.to_string().into_boxed_str(),
),
start,
*start,
));
}
} else {
double_starred = true;
}
keywords.push(ast::Keyword {
arg: name,
value,
range: TextRange::new(start, end),
});
num_keywords += 1;
} else {
// Positional arguments mustn't follow keyword arguments.
if !keywords.is_empty() && !is_starred(&value) {
if num_keywords > 0 && !is_starred(value) {
return Err(LexicalError::new(
LexicalErrorType::PositionalArgumentError,
value.start(),
@ -126,9 +122,26 @@ pub(crate) fn parse_arguments(
));
}
num_args += 1;
}
}
// Second, push the arguments into vectors of exact capacity. This avoids a vector resize later
// on when these vectors are boxed into slices.
let mut args = Vec::with_capacity(num_args);
let mut keywords = Vec::with_capacity(num_keywords);
for (name, value) in function_arguments {
if let Some((start, end, name)) = name {
keywords.push(ast::Keyword {
arg: name,
value,
range: TextRange::new(start, end),
});
} else {
args.push(value);
}
}
Ok(ArgumentList { args, keywords })
}

View file

@ -569,8 +569,7 @@ mod tests {
#[cfg(target_pointer_width = "64")]
#[test]
fn size_assertions() {
// 80 with Rustc >= 1.76, 88 with Rustc < 1.76
assert!(matches!(std::mem::size_of::<ParenthesizedExpr>(), 80 | 88));
assert_eq!(std::mem::size_of::<ParenthesizedExpr>(), 72);
}
#[test]

View file

@ -1406,8 +1406,18 @@ NotTest<Goal>: crate::parser::ParenthesizedExpr = {
Comparison<Goal>: crate::parser::ParenthesizedExpr = {
<location:@L> <left:Expression<"all">> <comparisons:(CompOp Expression<"all">)+> <end_location:@R> => {
let (ops, comparators) = comparisons.into_iter().map(|(op, comparator)| (op, ast::Expr::from(comparator))).unzip();
ast::ExprCompare { left: Box::new(left.into()), ops, comparators, range: (location..end_location).into() }.into()
let mut ops = Vec::with_capacity(comparisons.len());
let mut comparators = Vec::with_capacity(comparisons.len());
for (op, comparator) in comparisons {
ops.push(op);
comparators.push(comparator.into());
}
ast::ExprCompare {
left: Box::new(left.into()),
ops: ops.into_boxed_slice(),
comparators: comparators.into_boxed_slice(),
range: (location..end_location).into(),
}.into()
},
Expression<Goal>,
};
@ -1880,8 +1890,8 @@ Arguments: ast::Arguments = {
<location:@L> "(" <e: Comma<FunctionArgument>> ")" <end_location:@R> =>? {
let ArgumentList { args, keywords } = parse_arguments(e)?;
Ok(ast::Arguments {
args,
keywords,
args: args.into_boxed_slice(),
keywords: keywords.into_boxed_slice(),
range: (location..end_location).into()
})
}

View file

@ -1,5 +1,5 @@
// auto-generated: "lalrpop 0.20.0"
// sha3: fd05d84d3b654796ff740a7f905ec0ae8915f43f952428717735481947ab55e1
// sha3: 02c60b5c591440061dda68775005d87a203b5448c205120bda1566a62fc2147c
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
use ruff_python_ast::{self as ast, Int, IpyEscapeKind};
use crate::{
@ -36771,8 +36771,8 @@ fn __action241<
{
let ArgumentList { args, keywords } = parse_arguments(e)?;
Ok(ast::Arguments {
args,
keywords,
args: args.into_boxed_slice(),
keywords: keywords.into_boxed_slice(),
range: (location..end_location).into()
})
}
@ -40651,8 +40651,18 @@ fn __action515<
) -> crate::parser::ParenthesizedExpr
{
{
let (ops, comparators) = comparisons.into_iter().map(|(op, comparator)| (op, ast::Expr::from(comparator))).unzip();
ast::ExprCompare { left: Box::new(left.into()), ops, comparators, range: (location..end_location).into() }.into()
let mut ops = Vec::with_capacity(comparisons.len());
let mut comparators = Vec::with_capacity(comparisons.len());
for (op, comparator) in comparisons {
ops.push(op);
comparators.push(comparator.into());
}
ast::ExprCompare {
left: Box::new(left.into()),
ops: ops.into_boxed_slice(),
comparators: comparators.into_boxed_slice(),
range: (location..end_location).into(),
}.into()
}
}
@ -40816,8 +40826,18 @@ fn __action526<
) -> crate::parser::ParenthesizedExpr
{
{
let (ops, comparators) = comparisons.into_iter().map(|(op, comparator)| (op, ast::Expr::from(comparator))).unzip();
ast::ExprCompare { left: Box::new(left.into()), ops, comparators, range: (location..end_location).into() }.into()
let mut ops = Vec::with_capacity(comparisons.len());
let mut comparators = Vec::with_capacity(comparisons.len());
for (op, comparator) in comparisons {
ops.push(op);
comparators.push(comparator.into());
}
ast::ExprCompare {
left: Box::new(left.into()),
ops: ops.into_boxed_slice(),
comparators: comparators.into_boxed_slice(),
range: (location..end_location).into(),
}.into()
}
}