Skip to content

Commit

Permalink
perf: use memory efficient search of str prefix
Browse files Browse the repository at this point in the history
  • Loading branch information
funbiscuit committed Jan 21, 2024
1 parent 847b39c commit d97c7b9
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 18 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


[Demo](examples/arduino/README.md) of CLI running on Arduino Nano.
Memory usage: 15KiB of ROM and 0.5KiB of static RAM. Most of static RAM is used by help strings.
Memory usage: 14KiB of ROM and 0.5KiB of static RAM. Most of static RAM is used by help strings.

![Arduino Demo](examples/arduino/demo.gif)

Expand Down
13 changes: 4 additions & 9 deletions embedded-cli/src/autocomplete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl<'a> Autocompletion<'a> {
}

/// Whether autocompletion is partial
/// and futher input is required
/// and further input is required
pub fn is_partial(&self) -> bool {
self.partial
}
Expand All @@ -71,17 +71,12 @@ impl<'a> Autocompletion<'a> {
// compare new autocompletion to existing and keep
// only common prefix
let len = match self.autocompleted() {
Some(current) => autocompletion
.char_indices()
.zip(current.char_indices())
.find(|((_, a1), (_, a2))| a1 != a2)
.map(|((pos, _), _)| pos)
.unwrap_or(current.len().min(autocompletion.len())),
Some(current) => utils::common_prefix_len(autocompletion, current),
None => autocompletion.len(),
};

if len > self.buffer.len() {
// if buffer is full with this autocompletion, there is not much sence in doing it
// if buffer is full with this autocompletion, there is not much sense in doing it
// since user will not be able to type anything else
// so just do nothing with it
} else {
Expand All @@ -100,7 +95,7 @@ mod tests {
use crate::autocomplete::Autocompletion;

#[test]
fn nomerge() {
fn no_merge() {
let mut input = [0; 64];

let autocompletion = Autocompletion::new(&mut input);
Expand Down
42 changes: 42 additions & 0 deletions embedded-cli/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,27 @@ pub fn char_count(text: &str) -> usize {
count
}

/// Returns length (in bytes) of longest common prefix
pub fn common_prefix_len(left: &str, right: &str) -> usize {
let mut accum1 = Utf8Accum::default();

let mut pos = 0;
let mut byte_counter = 0;

for (&b1, &b2) in left.as_bytes().iter().zip(right.as_bytes().iter()) {
if b1 != b2 {
break;
}
let c1 = accum1.push_byte(b1);
byte_counter += 1;
if c1.is_some() {
pos = byte_counter;
}
}

pos
}

/// Function to rotate `buf` by `mid` elements
///
/// Not using `core::slice::rotate_left` since it
Expand Down Expand Up @@ -130,4 +151,25 @@ mod tests {
fn char_count(#[case] text: &str) {
assert_eq!(utils::char_count(text), text.chars().count())
}

#[rstest]
#[case("abcdef", "abcdef")]
#[case("abcdef", "abc")]
#[case("abcdef", "abc ghf")]
#[case("abcdef", "")]
#[case("", "")]
#[case("абв 佐佗佟𑿁", "абв 佐佗佟𑿁")]
#[case("абв 佐佗佟𑿁𑿆𑿌", "абв 佐佗佟𑿁")]
#[case("абв 佐佗佟𑿁 𑿆𑿌", "абв 佐佗𑿁佟")]
fn common_prefix(#[case] left: &str, #[case] right: &str) {
let expected = left
.char_indices()
.zip(right.char_indices())
.find(|((_, a1), (_, a2))| a1 != a2)
.map(|((pos, _), _)| pos)
.unwrap_or(right.len().min(left.len()));

assert_eq!(utils::common_prefix_len(left, right), expected);
assert_eq!(utils::common_prefix_len(right, left), expected);
}
}
16 changes: 8 additions & 8 deletions examples/arduino/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This example shows how to build cli with Arduino Nano.
Another Arduino can also be used, but you will have to tweak configs.
Example uses ~15KiB of ROM and ~0.5KiB of static RAM.
Example uses ~14KiB of ROM and ~0.5KiB of static RAM.
Most of RAM is taken by derived implementations for help and autocomplete
that don't use progmem. In future this should be fixed.

Expand Down Expand Up @@ -33,13 +33,13 @@ cargo bloat --release
Example output:
```
File .text Size Crate Name
1.6% 54.5% 8.1KiB arduino_cli arduino_cli::try_run
0.2% 6.7% 1016B embedded_cli embedded_cli::autocomplete::Autocompletion::merge_autocompletion
0.1% 3.6% 538B embedded_cli embedded_cli::token::Tokens::new
0.1% 3.5% 530B embedded_cli embedded_cli::help::HelpRequest::from_tokens
0.1% 3.1% 472B embedded_cli embedded_cli::token::Tokens::remove
0.8% 25.4% 3.8KiB And 34 smaller methods. Use -n N to show more.
3.0% 100.0% 14.8KiB .text section size, the file size is 498.4KiB
1.6% 56.9% 8.1KiB arduino_cli arduino_cli::try_run
0.1% 3.7% 538B embedded_cli embedded_cli::token::Tokens::new
0.1% 3.6% 530B embedded_cli embedded_cli::help::HelpRequest::from_tokens
0.1% 3.2% 472B embedded_cli embedded_cli::token::Tokens::remove
0.1% 3.2% 468B embedded_cli embedded_cli::cli::Cli<W,E,CommandBuffer,HistoryBuffer>::navigate_history
0.7% 26.0% 3.7KiB And 34 smaller methods. Use -n N to show more.
2.9% 100.0% 14.2KiB .text section size, the file size is 493.3KiB
```

To find total static memory usage:
Expand Down

0 comments on commit d97c7b9

Please sign in to comment.