Skip to content

Commit 841c72d

Browse files
committed
Favor SmallVec over Box in pluralrules
This will help avoid heap allocations as most lists are only a few items long. The only boxed slice that was retained was the SampleRanges, as this can be of an indeterminate length.
1 parent 26b1be1 commit 841c72d

File tree

5 files changed

+53
-42
lines changed

5 files changed

+53
-42
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/plurals/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ icu_provider = { version = "0.3", path = "../../provider/core", features = ["mac
3737
icu_locid = { version = "0.3", path = "../locid" }
3838
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"], optional = true }
3939
displaydoc = { version = "0.2.3", default-features = false }
40+
smallvec = "1.6"
4041

4142
[dev-dependencies]
4243
criterion = "0.3"

components/plurals/src/rules/ast.rs

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,29 @@
1111
//! ```
1212
//! use icu::plurals::rules::parse_condition;
1313
//! use icu::plurals::rules::ast::*;
14+
//! use smallvec::{SmallVec,smallvec};
1415
//!
1516
//! let input = "i = 1";
1617
//!
1718
//! let ast = parse_condition(input.as_bytes())
1819
//! .expect("Parsing failed.");
1920
//!
20-
//! assert_eq!(ast, Condition(Box::new([
21-
//! AndCondition(Box::new([
21+
//! assert_eq!(ast, Condition(smallvec![
22+
//! AndCondition(smallvec![
2223
//! Relation {
2324
//! expression: Expression {
2425
//! operand: Operand::I,
2526
//! modulus: None,
2627
//! },
2728
//! operator: Operator::Eq,
28-
//! range_list: RangeList(Box::new([
29+
//! range_list: RangeList(smallvec![
2930
//! RangeListItem::Value(
3031
//! Value(1)
3132
//! )
32-
//! ]))
33+
//! ])
3334
//! }
34-
//! ]))
35-
//! ])));
35+
//! ])
36+
//! ]));
3637
//! ```
3738
//!
3839
//! [`PluralCategory`]: crate::PluralCategory
@@ -41,6 +42,7 @@
4142
use alloc::boxed::Box;
4243
use alloc::string::String;
4344
use core::ops::RangeInclusive;
45+
use smallvec::SmallVec;
4446

4547
/// A complete AST representation of a plural rule.
4648
/// Comprises a vector of [`AndConditions`] and optionally a set of [`Samples`].
@@ -98,25 +100,26 @@ pub struct Rule {
98100
/// ```
99101
/// use icu::plurals::rules::ast::*;
100102
/// use icu::plurals::rules::parse_condition;
103+
/// use smallvec::{SmallVec,smallvec};
101104
///
102-
/// let condition = Condition(Box::new([
103-
/// AndCondition(Box::new([Relation {
105+
/// let condition = Condition(smallvec![
106+
/// AndCondition(smallvec![Relation {
104107
/// expression: Expression {
105108
/// operand: Operand::I,
106109
/// modulus: None,
107110
/// },
108111
/// operator: Operator::Eq,
109-
/// range_list: RangeList(Box::new([RangeListItem::Value(Value(5))])),
110-
/// }])),
111-
/// AndCondition(Box::new([Relation {
112+
/// range_list: RangeList(smallvec![RangeListItem::Value(Value(5))]),
113+
/// }]),
114+
/// AndCondition(smallvec![Relation {
112115
/// expression: Expression {
113116
/// operand: Operand::V,
114117
/// modulus: None,
115118
/// },
116119
/// operator: Operator::Eq,
117-
/// range_list: RangeList(Box::new([RangeListItem::Value(Value(2))])),
118-
/// }])),
119-
/// ]));
120+
/// range_list: RangeList(smallvec![RangeListItem::Value(Value(2))]),
121+
/// }]),
122+
/// ]);
120123
///
121124
/// assert_eq!(
122125
/// condition,
@@ -127,7 +130,7 @@ pub struct Rule {
127130
///
128131
/// [`AndConditions`]: AndCondition
129132
#[derive(Debug, Clone, PartialEq)]
130-
pub struct Condition(pub Box<[AndCondition]>);
133+
pub struct Condition(pub SmallVec<[AndCondition; 2]>);
131134

132135
/// An incomplete AST representation of a plural rule. Comprises a vector of [`Relations`].
133136
///
@@ -143,32 +146,33 @@ pub struct Condition(pub Box<[AndCondition]>);
143146
/// Can be represented by the AST:
144147
///
145148
/// ```
146-
/// use icu::plurals::rules::ast::*;
149+
/// use icu_plurals::rules::ast::*;
150+
/// use smallvec::{SmallVec,smallvec};
147151
///
148-
/// AndCondition(Box::new([
152+
/// AndCondition(smallvec![
149153
/// Relation {
150154
/// expression: Expression {
151155
/// operand: Operand::I,
152156
/// modulus: None,
153157
/// },
154158
/// operator: Operator::Eq,
155-
/// range_list: RangeList(Box::new([RangeListItem::Value(Value(5))])),
159+
/// range_list: RangeList(smallvec![RangeListItem::Value(Value(5))]),
156160
/// },
157161
/// Relation {
158162
/// expression: Expression {
159163
/// operand: Operand::V,
160164
/// modulus: None,
161165
/// },
162166
/// operator: Operator::NotEq,
163-
/// range_list: RangeList(Box::new([RangeListItem::Value(Value(2))])),
167+
/// range_list: RangeList(smallvec![RangeListItem::Value(Value(2))]),
164168
/// },
165-
/// ]));
169+
/// ]);
166170
///
167171
/// ```
168172
///
169173
/// [`Relations`]: Relation
170174
#[derive(Debug, Clone, PartialEq)]
171-
pub struct AndCondition(pub Box<[Relation]>);
175+
pub struct AndCondition(pub SmallVec<[Relation; 2]>);
172176

173177
/// An incomplete AST representation of a plural rule. Comprises an [`Expression`], an [`Operator`], and a [`RangeList`].
174178
///
@@ -185,14 +189,15 @@ pub struct AndCondition(pub Box<[Relation]>);
185189
///
186190
/// ```
187191
/// use icu::plurals::rules::ast::*;
192+
/// use smallvec::{SmallVec,smallvec};
188193
///
189194
/// Relation {
190195
/// expression: Expression {
191196
/// operand: Operand::I,
192197
/// modulus: None,
193198
/// },
194199
/// operator: Operator::Eq,
195-
/// range_list: RangeList(Box::new([RangeListItem::Value(Value(3))])),
200+
/// range_list: RangeList(smallvec![RangeListItem::Value(Value(3))]),
196201
/// };
197202
///
198203
/// ```
@@ -304,17 +309,18 @@ pub enum Operand {
304309
///
305310
/// ```
306311
/// use icu::plurals::rules::ast::*;
312+
/// use smallvec::{SmallVec,smallvec};
307313
///
308-
/// RangeList(Box::new([
314+
/// RangeList(smallvec![
309315
/// RangeListItem::Value(Value(5)),
310316
/// RangeListItem::Value(Value(7)),
311317
/// RangeListItem::Value(Value(9)),
312-
/// ]));
318+
/// ]);
313319
/// ```
314320
///
315321
/// [`RangeListItems`]: RangeListItem
316322
#[derive(Debug, Clone, PartialEq)]
317-
pub struct RangeList(pub Box<[RangeListItem]>);
323+
pub struct RangeList(pub SmallVec<[RangeListItem; 2]>);
318324

319325
/// An enum of items that appear in a [`RangeList`]: `Range` or a `Value`.
320326
///
@@ -408,6 +414,8 @@ pub struct Samples {
408414
///
409415
/// ```
410416
/// use icu::plurals::rules::ast::*;
417+
/// use smallvec::{SmallVec,smallvec};
418+
///
411419
/// SampleList {
412420
/// sample_ranges: Box::new([
413421
/// SampleRange {

components/plurals/src/rules/mod.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,39 +57,40 @@
5757
//! ```
5858
//! use icu::plurals::rules::parse_condition;
5959
//! use icu::plurals::rules::ast::*;
60+
//! use smallvec::{SmallVec,smallvec};
6061
//!
6162
//! let input = "i = 1 and v = 0 @integer 1";
6263
//!
6364
//! let ast = parse_condition(input.as_bytes())
6465
//! .expect("Parsing failed.");
65-
//! assert_eq!(ast, Condition(Box::new([
66-
//! AndCondition(Box::new([
66+
//! assert_eq!(ast, Condition(smallvec![
67+
//! AndCondition(smallvec![
6768
//! Relation {
6869
//! expression: Expression {
6970
//! operand: Operand::I,
7071
//! modulus: None,
7172
//! },
7273
//! operator: Operator::Eq,
73-
//! range_list: RangeList(Box::new([
74+
//! range_list: RangeList(smallvec![
7475
//! RangeListItem::Value(
7576
//! Value(1)
7677
//! )
77-
//! ]))
78+
//! ])
7879
//! },
7980
//! Relation {
8081
//! expression: Expression {
8182
//! operand: Operand::V,
8283
//! modulus: None,
8384
//! },
8485
//! operator: Operator::Eq,
85-
//! range_list: RangeList(Box::new([
86+
//! range_list: RangeList(smallvec![
8687
//! RangeListItem::Value(
8788
//! Value(0)
8889
//! )
89-
//! ]))
90-
//! },
91-
//! ])),
92-
//! ])));
90+
//! ])
91+
//! }
92+
//! ]),
93+
//! ]));
9394
//! ```
9495
//!
9596
//! Finally, we can pass this [`AST`] (in fact, just the [`Condition`] node),

components/plurals/src/rules/parser.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ use super::lexer::{Lexer, Token};
77
use alloc::string::String;
88
use alloc::string::ToString;
99
use alloc::vec;
10-
use alloc::vec::Vec;
1110
use core::iter::Peekable;
1211
use displaydoc::Display;
12+
use smallvec::smallvec;
1313

1414
#[derive(Display, Debug, PartialEq, Eq)]
1515
#[allow(clippy::enum_variant_names)]
@@ -118,12 +118,12 @@ impl<'p> Parser<'p> {
118118
}
119119

120120
fn get_condition(&mut self) -> Result<ast::Condition, ParserError> {
121-
let mut result = vec![];
121+
let mut result = smallvec![];
122122

123123
if let Some(cond) = self.get_and_condition()? {
124124
result.push(cond);
125125
} else {
126-
return Ok(ast::Condition(result.into_boxed_slice()));
126+
return Ok(ast::Condition(result));
127127
}
128128

129129
while self.take_if(Token::Or) {
@@ -134,12 +134,12 @@ impl<'p> Parser<'p> {
134134
}
135135
}
136136
// If lexer is not done, error?
137-
Ok(ast::Condition(result.into_boxed_slice()))
137+
Ok(ast::Condition(result))
138138
}
139139

140140
fn get_and_condition(&mut self) -> Result<Option<ast::AndCondition>, ParserError> {
141141
if let Some(relation) = self.get_relation()? {
142-
let mut rel = vec![relation];
142+
let mut rel = smallvec![relation];
143143

144144
while self.take_if(Token::And) {
145145
if let Some(relation) = self.get_relation()? {
@@ -148,7 +148,7 @@ impl<'p> Parser<'p> {
148148
return Err(ParserError::ExpectedRelation);
149149
}
150150
}
151-
Ok(Some(ast::AndCondition(rel.into_boxed_slice())))
151+
Ok(Some(ast::AndCondition(rel)))
152152
} else {
153153
Ok(None)
154154
}
@@ -188,14 +188,14 @@ impl<'p> Parser<'p> {
188188
}
189189

190190
fn get_range_list(&mut self) -> Result<ast::RangeList, ParserError> {
191-
let mut range_list = Vec::with_capacity(1);
191+
let mut range_list = smallvec![];
192192
loop {
193193
range_list.push(self.get_range_list_item()?);
194194
if !self.take_if(Token::Comma) {
195195
break;
196196
}
197197
}
198-
Ok(ast::RangeList(range_list.into_boxed_slice()))
198+
Ok(ast::RangeList(range_list))
199199
}
200200

201201
fn take_if(&mut self, token: Token) -> bool {

0 commit comments

Comments
 (0)