1

Тема: Найшвидший спосіб завантажування текстових файлів

Завантажую текстові файли ось таким чином:

pub type MapOfIndexes = IndexMap<String, (Rc<usize>, Rc<usize>)>;
pub type DictTuple = (Rc<String>, Rc<String>, Rc<String>, Rc<String>, MapOfIndexes);

pub fn download_dictionaries(settings: &gio::Settings) ->
    Result<HashMap<String, DictTuple>> {
    // Завантаження лінгвістичних словників
    let start_function = Instant::now();
    let default_dict_paths: String = settings.string("current-dict-path").to_string();
    let mut map_of_dicts: HashMap<String, DictTuple> = Default::default();
    if default_dict_paths != ""{
        for path in default_dict_paths.split('\n'){
            if !path.is_empty(){
                let file_paths = get_mvr_paths(path)
                    .with_context(|| format!("Can't get file paths: {}", path))?;
                for file_path in file_paths{
                    let file_path_string = file_path.clone().display().to_string();
                    let mut lines = read_lines(file_path)
                        .with_context(|| format!("Can't read file path {}: ", file_path_string))?;

                    let mut dict_name = "".to_string();
                    let mut lang_pair = "".to_string();
                    let mut dict_descr = "".to_string();
                    let mut regex_key = "".to_string();
                    let mut dict_body = "".to_string();
                    let mut take_count = 0;
                    for line in lines.by_ref().flatten().take(4){
                        match take_count{
                        0 => dict_name = line,
                        1 => lang_pair = line,
                        2 => dict_descr = line,
                        3 => regex_key = line,
                        _ => unreachable!(),
                        };
                        take_count += 1;
                    }
                        // println!("Regex key: {}", regex_key.to_string());
                        // println!("Dict name: {}", dict_name);
                        // println!("Lang pair: {}", lang_pair);
                        // println!("Dict descr: {}", dict_descr);

                    for line in lines.flatten(){
                        dict_body += format!("{line}\n").as_str();
                    }
                    let dict_indexes: MapOfIndexes =
                        create_dict_indexes(&dict_body, &regex_key)?;
                    // println!("{}", dict_body);
                    map_of_dicts.insert(dict_name, (
                        Rc::new(lang_pair), Rc::new(dict_descr),
                        Rc::new(regex_key), Rc::new(dict_body), dict_indexes)
                    );
                }
            }
        }
    }
    let duration = start_function.elapsed();
    println!("Dictionaries downloading time: {:#?}", duration);
    Ok(map_of_dicts)
}

fn get_mvr_paths(dir: &str) -> Result<Vec<PathBuf>> {
    let paths = read_dir(dir)?
        // Filter out all those directory entries which couldn't be read
        .filter_map(|res| res.ok())
        // Map the directory entries to paths
        .map(|dir_entry| dir_entry.path())
        // Filter out all paths with extensions other than `mvr`
        .filter_map(|path| {
            if path.extension().map_or(false, |ext| ext == "mvr"){
                Some(path)
            } else {
                None
            }
        })
        .collect::<Vec<_>>();
    Ok(paths)
}

fn read_lines<P>(filename: P) -> Result<io::Lines<io::BufReader<File>>>
    where P: AsRef<Path>, {
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}

fn create_dict_indexes(dict_body: &String, regex_key: &String) ->
    Result<MapOfIndexes>{
    let mut words_indexes: MapOfIndexes = Default::default();
    let re = Regex::new(regex_key).with_context(|| format!("Failed to create regex key {}", regex_key))?;
    let words = re.find_iter(&dict_body);
    for word in words{
        let word_str = word.clone()?.as_str().to_string();
        let word_start = word.clone()?.start().try_into()?;
        let word_end = word.clone()?.end().try_into()?;
        words_indexes.insert(word_str, (Rc::new(word_start), Rc::new(word_end)));
        // println!("{} {} {}", word.unwrap().as_str(), word_start, word_end);
    }

    /*for (key, value) in &words_indexes{
        let (start, end) = words_indexes.get(key.as_str()).unwrap();
        let (start, end) = value;
        println!("{} {} {}", key.to_string(), start, end);
    }*/
    /*for index in 0..words_indexes.len(){
        let (word, (word_start, word_end)) =
            words_indexes.get_index(index).unwrap();
        println!("{}, {}, {}", word, word_start, word_end);
    }*/
    Ok(words_indexes)
}

Але завантаження одного словника займає цілих 12 секунд,
у той час як в С++(Qt) за 10 секунд завантажується 18 словників.
Чи є спосіб пришвидшити завантаження текстових файлів?

2

Re: Найшвидший спосіб завантажування текстових файлів

flamegraph-ом дивилися? У clippy зауважень немає?

Кілька зауважень до коду (не по швидкості):
- для повернення ітераторів і інших складних типів існує синтаксис-> impl Trait. Зокрема, можете одразу flatten робити без зміни типу, що повертається.
- якщо у вас є цикл, у якому ви робите щось окреме для кожного індексу циклу, то вам не потрібен цикл. Просто читайте з ітератора за допомогою next.
- далі,

let word_str = word.clone()?.as_str().to_string();
let word_start = word.clone()?.start().try_into()?;
let word_end = word.clone()?.end().try_into()?;

word тут, як я розумію, Match. Нащо ви його клонуєте, та ще й три рази? Ну, припустимо, вам дійсно його треба клонувати (хоч я й не певен) - то що ж заважає зробити

let word = word.clone()?;

і далі працювати вже з цим?
- ось тут:

                     for line in lines.flatten(){
                        dict_body += format!("{line}\n").as_str();
                    }

[String] += format!(...) - це антипатерн, ви створюєте string лише для того, щоб його знищити (здається, clippy про це попереджає).

dict_body.push(line);
dict_body.push_char('\n');

працює значно швидше, і в clippy був якийсь спеціально оптимізований фокус на кшталт

dict_body = format!("{dict_body}{line}\n");

Ви скаржитеся, що C++ працює швидше. У C++ ви теж використовуєте аналоги IndexMap та Regex? А там у скільки потоків файли читаються?

Подякували: leofun011

3

Re: Найшвидший спосіб завантажування текстових файлів

koala написав:

flamegraph-ом дивилися? У clippy зауважень немає?

Кілька зауважень до коду (не по швидкості):
- для повернення ітераторів і інших складних типів існує синтаксис-> impl Trait. Зокрема, можете одразу flatten робити без зміни типу, що повертається.
- якщо у вас є цикл, у якому ви робите щось окреме для кожного індексу циклу, то вам не потрібен цикл. Просто читайте з ітератора за допомогою next.
- далі,

let word_str = word.clone()?.as_str().to_string();
let word_start = word.clone()?.start().try_into()?;
let word_end = word.clone()?.end().try_into()?;

word тут, як я розумію, Match. Нащо ви його клонуєте, та ще й три рази? Ну, припустимо, вам дійсно його треба клонувати (хоч я й не певен) - то що ж заважає зробити

let word = word.clone()?;

і далі працювати вже з цим?
- ось тут:

                     for line in lines.flatten(){
                        dict_body += format!("{line}\n").as_str();
                    }

[String] += format!(...) - це антипатерн, ви створюєте string лише для того, щоб його знищити (здається, clippy про це попереджає).

dict_body.push(line);
dict_body.push_char('\n');

працює значно швидше, і в clippy був якийсь спеціально оптимізований фокус на кшталт

dict_body = format!("{dict_body}{line}\n");

Ви скаржитеся, що C++ працює швидше. У C++ ви теж використовуєте аналоги IndexMap та Regex? А там у скільки потоків файли читаються?

Якщо без клона:

for word in words{
    |         ---- move occurs because `word` has type `Result<fancy_regex::Match<'_>, fancy_regex::Error>`, which does not implement the `Copy` trait
137 |         let word_str = word?.as_str().to_string();
138 |         let word_start = word?.start().try_into()?;
    |                          ----- `word` moved due to this method call
139 |         let word_end = word?.end().try_into()?;
    |                        ^^^^ value used here after move
    |
help: you can `clone` the value and consume it, but this might not be your desired behavior
    |
138 |         let word_start = word.clone()?.start().try_into()?;
    |                                             ++++++++

У С++ реалізоване все схожим чином. Тут або в Rust погано із завантаженням текстових файлів,
або fancy regex працює повільно. А ще не можу нормально автодоповнення слів прописати,
бо в gtk4 застарілий метод не працює, а новий буде лише в gtk5.
А інші GUI ще надто сирі.
Тому доведеться повертатися в С++, принаймні поки що.

4

Re: Найшвидший спосіб завантажування текстових файлів

Teg Miles написав:

.. доведеться повертатися в С++, принаймні поки що.

Користувачі форуму підтримують ваше рішеня (фото-видиво, *.webp)

https://i.giphy.com/xdok6Uv1O18C996fCX.webp

Подякували: koala1

5

Re: Найшвидший спосіб завантажування текстових файлів

Звертаю увагу: ви на початку не написали, що використовуєте fancy_regex, а не звичайний regex. Ви спеціально пастки для тих, хто відповідатиме, ставите?
У будь-якому разі, проблема у виклику branch (операторі ?), який приймає параметром переміщений self.

let word = word?;
let word_str = word.as_str().to_string();
let word_start = word.start();
let word_end = word.end();

має розв'язати проблему. І ще я ніяк не зрозумію, нащо вам для перетворення usize в usize потрібно робити .try_into()?, це якийсь культ карго?

Подякували: leofun011

6

Re: Найшвидший спосіб завантажування текстових файлів

koala написав:

Звертаю увагу: ви на початку не написали, що використовуєте fancy_regex, а не звичайний regex. Ви спеціально пастки для тих, хто відповідатиме, ставите?
У будь-якому разі, проблема у виклику branch (операторі ?), який приймає параметром переміщений self.

let word = word?;
let word_str = word.as_str().to_string();
let word_start = word.start();
let word_end = word.end();

має розв'язати проблему. І ще я ніяк не зрозумію, нащо вам для перетворення usize в usize потрібно робити .try_into()?, це якийсь культ карго?

Не допомогло, усе так само повільно.
Звичайний regex кастрований, там немає необхідної мені частини функцій.
Окрім того fancy_regex зроблено на основі regex.
Щодо .try_into(), я просто забув прибрати його, спочатку був не usize, а u32.

7

Re: Найшвидший спосіб завантажування текстових файлів

leofun01 написав:
Teg Miles написав:

.. доведеться повертатися в С++, принаймні поки що.

Користувачі форуму підтримують ваше рішеня (фото-видиво, *.webp)

https://i.giphy.com/xdok6Uv1O18C996fCX.webp

От вам смішно, а мені доведеться пригадувати що я там кілька місяців тому робив у Qt  :D

8

Re: Найшвидший спосіб завантажування текстових файлів

Звісно усе так само повільно, ви що, через рядок мою відповідь читали, і перший пропустили?

9

Re: Найшвидший спосіб завантажування текстових файлів

абсолютно без корисна порада, як виявилось
Teg Miles написав:

Завантажую текстові файли ось таким чином:

fn read_lines<P>(filename: P) -> Result<io::Lines<io::BufReader<File>>>
    where P: AsRef<Path>, {
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}

Але завантаження одного словника займає цілих 12 секунд,
у той час як в С++(Qt) за 10 секунд завантажується 18 словників.
Чи є спосіб пришвидшити завантаження текстових файлів?

Чекай, так в C++ ти мав рівно ту ж проблему, і рішеня там було.
Чого ж ти не взяв BufReader::with_capacity(0x4000, file); ?

upd: І це не відміняє тормозів на regex'ах.

10

Re: Найшвидший спосіб завантажування текстових файлів

Там 8 кілобайтів, цілком пристойно. Не схоже, що це критичне місце.
Там ще є побудова регекса, причому fancy_regex, у циклі, і цього, схоже, не оминути - але я не бачу цього в C++. Звичайним регексом не обійдетеся? У будь-якому разі, є результати профайлінгу?

Подякували: leofun011

11

Re: Найшвидший спосіб завантажування текстових файлів

Ось вам С++:

void FileLoader::create_dict_indexes()
{
    if(!dicts.isEmpty())
    {
        for(const auto& [lang_pair, descr, regex_key, dict_body]:
             std::as_const(dicts))
        {
            const QString& dict_name = dicts.key(
                std::make_tuple(lang_pair, descr, regex_key, dict_body));
            const QString& reg_key = *regex_key;
            const QString& full_dict = *dict_body;

            QRegularExpression re;
            re.setPattern(reg_key);
            re.setPatternOptions(QRegularExpression::MultilineOption);
            QRegularExpressionMatchIterator match_iter =
                re.globalMatch(full_dict);

            while(match_iter.hasNext())
            {
                QRegularExpressionMatch match = match_iter.next();
                const QString& word = match.captured(0);
                const int& word_start = match.capturedStart(0);
                const int& word_end = match.capturedEnd(0);

                dict_indexes[dict_name].append(std::make_tuple(
                    std::make_shared<QString>(word),
                    std::make_shared<int>(word_start),
                    std::make_shared<int>(word_end)
                ));
                const QString& clean_word_lower =
                    simplified_word(word).toLower();

                words[dict_name].insert(
                    clean_word_lower, std::make_shared<QString>(word));
            }
        }
    }
}

12 Востаннє редагувалося Teg Miles (19.05.2024 22:18:41)

Re: Найшвидший спосіб завантажування текстових файлів

koala написав:

Там 8 кілобайтів, цілком пристойно. Не схоже, що це критичне місце.
Там ще є побудова регекса, причому fancy_regex, у циклі, і цього, схоже, не оминути - але я не бачу цього в C++. Звичайним регексом не обійдетеся? У будь-якому разі, є результати профайлінгу?

Звичайний регекс в Rust не може поглянути на те, що стоїть до чи після слова, яке шукають.
Для мене це важливо при витягуванні слів-термінів зі словника.
Як краще зробити профайлінг?

13

Re: Найшвидший спосіб завантажування текстових файлів

Teg Miles написав:

Як краще зробити профайлінг?

Прочитайте нарешті перший рядок моєї першої відповіді, ну?

14 Востаннє редагувалося Teg Miles (19.05.2024 22:49:00)

Re: Найшвидший спосіб завантажування текстових файлів

koala написав:
Teg Miles написав:

Як краще зробити профайлінг?

Прочитайте нарешті перший рядок моєї першої відповіді, ну?

Завтра займуся цим.
Поглянув ще раз опис крейта fancy regex.
Там backtracking блоатить по експоненті.
Навіть може до denial service дійти справа, у них так і написано.
Тому, гадаю, справа саме в регексі.

15

Re: Найшвидший спосіб завантажування текстових файлів

От для того, щоб не гадати, і придумали профайлінг.
І ні, fancy_regex має проблеми, але лише при умисно сформованих зловмисних регексах. І якби у вас ці проблеми спливли, ви б скаржилися не лише на час, а й на пам'ять.

16

Re: Найшвидший спосіб завантажування текстових файлів

Встановив flamegraph, спробував ввести команду flamegraph [-o my_flamegraph.svg] --шлях до теки debug.
Отримав помилку error: unexpected argument '[-o' found
Це ж наче їхній приклад з Github. Чому не працює?

17

Re: Найшвидший спосіб завантажування текстових файлів

Квадратними дужками позначають необов'язкову частину. Їх не треба набирати.

Подякували: leofun011

18

Re: Найшвидший спосіб завантажування текстових файлів

Тепер видає ось таку помилку: Failed to collect 'cycles:Pu'. Не знаходить відповідного файлу чи каталогу.

19

Re: Найшвидший спосіб завантажування текстових файлів

Я знову питаю: не набридло?

Подякували: leofun011