rust 优化函数操作字符串(堆内存分配)

okxuctiv  于 2023-10-20  发布在  其他
关注(0)|答案(2)|浏览(136)

我正在做一个练习,我应该只使用标准包。关键是获取字符串向量的所有单词,并仅输出首字母。[“一二”,“三”] -> [“O。T.",“T."]我让它用一个“天真的显式”代码工作:

pub fn initials_explicit_heavy(texts: Vec<&str>) -> Vec<String> {
    let mut texts_initials: Vec<String> = vec![];
    for one_text in texts {
        let mut one_text_initials: String = "".to_string();
        for word in one_text.split_whitespace() {
            let first: char = word.chars().next().unwrap();
            let first_and_dot: String = first.to_string() + ". ";
            one_text_initials += &first_and_dot;
        }
        texts_initials.push(one_text_initials.trim_end().to_string());
        // texts_initials.push(one_text_initials);
    }
    texts_initials
}

但是这个函数分配了太多的堆内存。51次而不是38次。我不确定问题在哪里。我应该使用迭代器而不是for循环吗?避免所有的to_string()转换?还是用我忽略的魔法任何提示都是受欢迎的。
--编辑--
抱歉,我忘了说函数类型sugnature被冻结了。

(texts: Vec<&str>) -> Vec<String>
z9smfwbn

z9smfwbn1#

下面是代码的改进版本,它尽可能接近您的版本:

pub fn initials_explicit_heavy(texts: &[&str]) -> Vec<String> {
    let mut texts_initials: Vec<String> = vec![];
    for one_text in texts {
        let mut one_text_initials: String = "".to_string();
        for word in one_text.split_whitespace() {
            let first: char = word.chars().next().unwrap();
            write!(one_text_initials, "{}. ", first).ok();
        }
        one_text_initials.pop();
        texts_initials.push(one_text_initials);
    }
    texts_initials
}

改进:
1.接受切片作为参数。不需要消耗矢量。
1.使用write!()宏写入分配的字符串。这将保存first.to_string()的所有中间分配。
1.只需弹出最后一个字符来处理尾随空格。如果字符串为空,则此操作将不执行任何操作,并保存one_text_initials.trim_end().to_string()所需的分配
您可以通过预先计算one_text_initials所需的字节数并立即分配具有正确大小的结果向量来进一步提高重新分配的数量:

pub fn initials_explicit_heavy(texts: &[&str]) -> Vec<String> {
    let mut texts_initials: Vec<String> = Vec::with_capacity(texts.len());
    for one_text in texts {
        let initials_len = one_text
            .split_whitespace()
            .map(|word| word.chars().next().unwrap().len_utf8() + 2)
            .sum::<usize>();
        let mut one_text_initials = String::with_capacity(initials_len);
        for word in one_text.split_whitespace() {
            let first: char = word.chars().next().unwrap();
            write!(one_text_initials, "{}. ", first).ok();
        }
        one_text_initials.pop();
        texts_initials.push(one_text_initials);
    }
    texts_initials
}

这段代码使用了构建返回值所需的最小数量的分配,因此该方面无法进一步改进。您仍然可以通过定义一个返回首字母的迭代器来减少代码重复。
最后,这里是一个以我个人风格实现的函数版本,输入来自注解(而不是试图接近您的代码):

pub fn initials_string_vec(strings: &[&str]) -> Vec<String> {
    strings.iter().copied().map(initials_string).collect()
}

pub fn initials_string(s: &str) -> String {
    let len = initials(s).map(|c| c.len_utf8() + 2).sum::<usize>();
    let mut result = String::with_capacity(len);
    result.extend(initials(s).flat_map(|c| [c, '.', ' ']));
    result.pop();
    result
}

pub fn initials(s: &str) -> impl Iterator<Item = char> + '_ {
    s.split_whitespace().filter_map(|word| word.chars().next())
}

(我通常根本不会费心去实现initials_string_vec(),因为它所做的只是将现有函数Map到切片上。

axr492tv

axr492tv2#

你不一定需要使用迭代器。事实上,在你的任务中使用迭代器是很不方便的。之所以有这么多的分配,是因为你经常这样做,基本上每次你试图把一些东西转换成字符串(例如。对于每个文本,你做"".to_string()one_text_initials.trim_end().to_string(),对于每个单词,你做first.to_string()one_text_initials += &first_and_dot;,这很可能需要重新分配以容纳更多的数据。
这里有一个可能的修改,先做一些分配,然后每个文本只分配一个(在texts_initials.push(one_text_initials.iter().collect())行上):
Playground

pub fn initials_explicit_lighter(texts: Vec<&str>) -> Vec<String> {
    let mut texts_initials: Vec<String> = vec![];
    let mut one_text_initials: Vec<char> = vec![]; 
    for one_text in texts {
        one_text_initials.clear();
        let mut words = one_text.split_whitespace();
        
        // handle first word without space
        if let Some(word) = words.next() {
            let first: char = word.chars().next().unwrap();
            one_text_initials.push(first);
            one_text_initials.push('.');
        }
        
        // the rest of the words with space
        for word in words {
            let first: char = word.chars().next().unwrap();
            one_text_initials.push(' ');
            one_text_initials.push(first);
            one_text_initials.push('.');
        }
        texts_initials.push(one_text_initials.iter().collect());
    }
    
    texts_initials
}

注意,one_text_initials也会为每个字增长,但由于one_text_initials是在循环外部预分配的,因此每次使用one_text_initials.clear()时,只有向量的大小被重置为0,而分配的内存不会被释放,因此对于下一个字,重新分配内存的可能性较小

相关问题