Могу ли я избежать клонирования строк, держа вместо этого ссылку? - PullRequest
0 голосов
/ 01 октября 2018

У меня есть структура данных, в которой я предоставляю обертку вокруг буфера чтения для автоматической обработки повторяющихся операторов в считывании.

Это делается путем сохранения внутреннего состояния количества оставшихся повторов иПовторяющаяся строка.

use std::fs::File;
use std::path::Path;
use std::io::BufReader;
use std::io::prelude::*;
use std::io::Error;
use std::num::NonZeroU32;
use std::mem;

pub struct Reader {
    handle: BufReader<File>,
    repeat_state: Option<(NonZeroU32, String)>,
}

impl Reader {
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Reader, Error> {
        let file = File::open(path)?;
        let handle = BufReader::new(file);

        Ok(Reader {
            handle,
            repeat_state: None,
        })
    }

    /// get next line, respecting repeat instructions
    pub fn next_line(&mut self) -> Option<String> {
        if self.repeat_state.is_some() {
            let (repeats_left, last_line) = mem::replace(&mut self.repeat_state, None).unwrap();

            self.repeat_state = NonZeroU32::new(repeats_left.get() - 1)
                .map(|repeats_left| (repeats_left, last_line.clone()));

            Some(last_line)
        } else {
            let mut line = String::new();
            if self.handle.read_line(&mut line).is_err() || line.is_empty() {
                return None
            }

            if line.starts_with("repeat ") {
                let repeats: Option<u32> = line.chars().skip(7)
                    .take_while(|c| c.is_numeric())
                    .collect::<String>().parse().ok();

                self.repeat_state = repeats
                    .and_then(|repeats| NonZeroU32::new(repeats - 1))
                    .map(|repeats_left| (repeats_left, line.clone()))
            }

            Some(line)
        }
    }
}

#[test]
fn test_next_line() {
    let source = "
line one
repeat 2    line two and line three
line four
repeat 11   lines 5-15
line 16
line 17
last line (18)
    ".trim();
    let mut input = File::create("file.txt").unwrap();
    write!(input, "{}", source);


    let mut read = Reader::new("file.txt").unwrap();
    assert_eq!(
        read.next_line(),
        Some("line one\n".to_string())
    );
    assert_eq!(
        read.next_line(),
        Some("repeat 2    line two and line three\n".to_string())
    );
    assert_eq!(
        read.next_line(),
        Some("repeat 2    line two and line three\n".to_string())
    );
    assert_eq!(
        read.next_line(),
        Some("line four\n".to_string())
    );

    for _ in 5..=15 {
        assert_eq!(
            read.next_line(),
            Some("repeat 11   lines 5-15\n".to_string())
        );
    }

    assert_eq!(
        read.next_line(),
        Some("line 16\n".to_string())
    );
    assert_eq!(
        read.next_line(),
        Some("line 17\n".to_string())
    );
    assert_eq!(
        read.next_line(),
        Some("last line (18)".to_string())
    );
}

Детская площадка

Проблема заключается в том, что мне приходится каждый раз клонировать удерживаемое повторяющееся значение, чтобы удерживать его и возвращатьЭто.Я хочу избежать этих дорогостоящих клонов, возвращая (и, возможно, сохраняя) &str.Я перепробовал несколько вещей, но не смог заставить его работать:

  • Сохранение String, возвращение &str: ошибки "не живут достаточно долго"
  • Хранение &str, возвращение &str: те же ошибки времени жизни
  • Cow<&str>
  • Box<&str>

Эти клоны являются узким местом моей программы намомент, согласно профилировщику выборки на основе времени CodeXL после сборки в режиме выпуска с информацией отладки.Теперь моя программа довольно быстрая, но мне интересно, есть ли способ их избежать.

1 Ответ

0 голосов
/ 02 октября 2018

Вы можете избежать клонирования строк, поместив их в Rc и клонировав их вместо этого.Клонирование Rc обходится дешево, поскольку состоит из увеличения счетчика:

pub struct Reader {
    handle: BufReader<File>,
    repeat_state: Option<(NonZeroU32, Rc<String>)>,
}

impl Reader {
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Reader, Error> {
        let file = File::open(path)?;
        let handle = BufReader::new(file);

        Ok(Reader {
            handle,
            repeat_state: None,
        })
    }

    /// get next line, respecting repeat instructions
    pub fn next_line(&mut self) -> Option<Rc<String>> {
        if self.repeat_state.is_some() {
            let (repeats_left, last_line) = mem::replace(&mut self.repeat_state, None).unwrap();

            self.repeat_state = NonZeroU32::new(repeats_left.get() - 1)
                .map(|repeats_left| (repeats_left, last_line.clone()));

            Some(last_line)
        } else {
            let mut line = Rc::new (String::new());
            if self.handle.read_line(Rc::make_mut (&mut line)).is_err() || line.is_empty() {
                return None
            }

            if line.starts_with("repeat ") {
                let repeats: Option<u32> = line.chars().skip(7)
                    .take_while(|c| c.is_numeric())
                    .collect::<String>().parse().ok();

                self.repeat_state = repeats
                    .and_then(|repeats| NonZeroU32::new(repeats - 1))
                    .map(|repeats_left| (repeats_left, line.clone()))
            }

            Some(line)
        }
    }
}

детская площадка

Обратите внимание, что Rc не может использоваться несколькими потоками.Если вы хотите разделить строки между потоками, вы можете использовать Arc вместо этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...