diff --git a/Cargo.lock b/Cargo.lock index 8bc44a8..e900b7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "alsa" version = "0.11.0" @@ -99,6 +108,18 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + [[package]] name = "anyhow" version = "1.0.101" @@ -437,6 +458,12 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.55" @@ -491,6 +518,58 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "clipboard-win" version = "5.4.1" @@ -733,6 +812,61 @@ dependencies = [ "libc", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -874,6 +1008,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1918,6 +2058,26 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -1929,10 +2089,12 @@ name = "jap_learn" version = "0.1.1" dependencies = [ "chrono", + "criterion", "dirs", "hex", "iced", "rand", + "rayon", "reqwest", "rhai", "rodio", @@ -2800,6 +2962,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "openssl" version = "0.10.77" @@ -2978,6 +3146,34 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "polling" version = "3.11.0" @@ -3125,6 +3321,26 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "read-fonts" version = "0.35.0" @@ -3174,6 +3390,35 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -4123,6 +4368,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.10.0" diff --git a/Cargo.toml b/Cargo.toml index bf66a10..3dda2bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,11 @@ rodio = "0.22.2" sha2 = "0.11.0" hex = "0.4.3" tokio = { version = "1.50.0", features = ["rt", "rt-multi-thread", "macros"] } +rayon = "1.12.0" +[dev-dependencies] +criterion = "0.5" + +[[bench]] +name = "split_bench" # Имя файла в benches/ без расширения +harness = false # Отключаем стандартный тестовый раннер diff --git a/benches/split_bench.rs b/benches/split_bench.rs new file mode 100644 index 0000000..1f2d897 --- /dev/null +++ b/benches/split_bench.rs @@ -0,0 +1,43 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn bench_split(c: &mut Criterion) { + let input = "data_1"; + + c.bench_function("split_with_comma", |b| { + b.iter(|| { + // black_box запрещает компилятору оптимизировать результат + black_box(split_with_coma(black_box(input))) + }) + }); +} + +fn bench_split_new(c: &mut Criterion) { + let input = "data_1"; + + c.bench_function("split_with_comma gpt", |b| { + b.iter(|| { + // black_box запрещает компилятору оптимизировать результат + black_box(split_with_coma(black_box(input))) + }) + }); +} + + +pub fn split_with_coma(ts: &str) -> Vec { + ts.split(',') + .map(|ts| ts.to_lowercase().trim().to_string()) + .filter(|t| !t.is_empty()) + .collect::>() +} + +pub fn new_split_with_coma(ts: &str) -> Vec { + ts.split(',') + .map(|s| s.trim()) // 0 аллокаций, просто срез + .filter(|s| !s.is_empty()) // отбрасываем пустые до аллокации + .map(|s| s.to_lowercase()) // 1 аллокация на валидный токен + .collect() +} + +// Можно добавить несколько бенчмарков в группу +criterion_group!(benches, bench_split, bench_split_new); +criterion_main!(benches); \ No newline at end of file diff --git a/src/dictionary.rs b/src/dictionary.rs index 80f9c39..b9ce26f 100644 --- a/src/dictionary.rs +++ b/src/dictionary.rs @@ -15,7 +15,10 @@ use iced::widget::space::horizontal; use iced::widget::*; use iced::{Border, Color, Length, Shadow, Task}; use rand::random_range; -use std::collections::HashMap; +use rayon::iter::IndexedParallelIterator; +use rayon::iter::ParallelIterator; +use rayon::prelude::IntoParallelRefIterator; +use std::collections::{HashMap, HashSet}; use std::fs; use std::ops::Add; use std::path::PathBuf; @@ -71,11 +74,14 @@ impl NavigatedPage for DictionaryState { let mut words = vec![]; let dict = &self.state.lock().unwrap().dictionary; - for i in 0..self.include_map.len() { - if self.include_map[i] { - words.push(dict[i].clone()); - } - } + words = self + .include_map + .par_iter() + .zip(0..self.include_map.len()) + .filter(|(flag, _)| **flag) + .map(|(_, index)| dict[index].clone()) + .collect(); + return Some(Page::DictionaryQuiz(DictionaryQuizState::new( words, self.reverse, @@ -136,15 +142,14 @@ impl DictionaryState { let dict = &mut self.state.lock().unwrap().dictionary; dict[i].key = v; } - return self.launch_auto_save_offset(i) + return self.launch_auto_save_offset(i); } DictionaryMessage::SetValue(i, v) => { { let dict = &mut self.state.lock().unwrap().dictionary; dict[i].value = v } - return self.launch_auto_save_offset(i) - + return self.launch_auto_save_offset(i); } DictionaryMessage::SetTags(i, v) => { { @@ -153,7 +158,7 @@ impl DictionaryState { } self.update_tags(); - return self.launch_auto_save_offset(i) + return self.launch_auto_save_offset(i); } DictionaryMessage::WordAction(i) => { @@ -166,9 +171,9 @@ impl DictionaryState { } DictionaryMessage::Include(i, b) => self.include_map[i] = b, DictionaryMessage::IncludeTag(t, v) => { - let index : u32; + let index: u32; { - let mut state = self.state.lock().unwrap(); + let state = self.state.lock().unwrap(); index = state.word_groups[self.selected_group_index].id; } self.tag_map.insert(t, v); @@ -184,9 +189,7 @@ impl DictionaryState { self.search = s; } DictionaryMessage::SetTyping(b) => self.no_typing = b, - DictionaryMessage::SubmitWord(i) => { - self.save_word(i) - } + DictionaryMessage::SubmitWord(i) => self.save_word(i), Back => {} Test => {} DictionaryMessage::CreateGroup => { @@ -218,13 +221,12 @@ impl DictionaryState { } DictionaryMessage::SelectGroup(i) => { self.selected_group_index = i; - let index : u32; + let index: u32; { let state = self.state.lock().unwrap(); index = state.word_groups[i].id; } self.update_words_include(index) - } DeleteGroup => { if self.selected_group_index == 0 { @@ -242,10 +244,12 @@ impl DictionaryState { } ChangeDirection => { self.reverse_list = !self.reverse_list; - }, + } DictionaryMessage::TrySave(word_index) => { let now = Utc::now(); - if !self.auto_save_queue.contains_key(&word_index) { return Task::none(); } + if !self.auto_save_queue.contains_key(&word_index) { + return Task::none(); + } if self.auto_save_queue[&word_index] <= now { self.auto_save_queue.remove(&word_index); self.save_word(word_index); @@ -266,10 +270,13 @@ impl DictionaryState { } fn launch_auto_save_offset(&mut self, index: usize) -> Task { - let save_time = Utc::now().add(TimeDelta::milliseconds(2800)); + let save_time = Utc::now().add(TimeDelta::milliseconds(900)); self.auto_save_queue.insert(index, save_time); let message = RootMessage::Dictionary(DictionaryMessage::TrySave(index)); - Task::perform(async { tokio::time::sleep(Duration::from_secs(3)).await }, |_| message) + Task::perform( + async { tokio::time::sleep(Duration::from_secs(1)).await }, + |_| message, + ) } pub fn view(&self) -> iced::Element<'_, DictionaryMessage> { @@ -429,36 +436,40 @@ impl DictionaryState { } fn update_tags(&mut self) { - let mut tags_list: Vec = vec![]; + let mut tags_list: HashSet = HashSet::new(); let dict = &self.state.lock().unwrap().dictionary; - for element in dict { - tags_list.append(&mut split_with_coma(element.tags.clone())); - } + dict.iter().for_each(|element| { + split_with_coma(element.tags.as_str()) + .iter() + .for_each(|tag| { + tags_list.insert(tag.clone()); + }); + }); let current_tags = self .tag_map .keys() .map(|k| k.clone().to_string()) .collect::>(); - for current in current_tags { - if !tags_list.contains(¤t) { - self.tag_map.remove(¤t.clone()); - } - } - for found_tag in &tags_list { - if !self.tag_map.contains_key(&found_tag.clone()) { - self.tag_map.insert(found_tag.clone(), false); - } - } + current_tags + .iter() + .filter(|tag| !tags_list.contains(*tag)) + .for_each(|tag| { + self.tag_map.remove(tag); + }); + + tags_list.iter().for_each(|tag| { + self.tag_map.insert(tag.clone(), false); + }); } fn update_words_include(&mut self, group_id: u32) { let include_tags = self .tag_map .iter() - .filter(|i| *(*i).1) + .filter(|(_, value)| **value) .map(|(t, _)| t.clone()) .collect::>(); @@ -469,14 +480,11 @@ impl DictionaryState { let dict = &self.state.lock().unwrap().dictionary; - for i in 0..self.include_map.len() { - let tags = split_with_coma(dict[i].tags.clone()); - if tags.iter().all(|t| include_tags.contains(t)) && dict[i].group_id == group_id { - self.include_map[i] = true; - } else { - self.include_map[i] = false; - } - } + self.include_map = dict.par_iter() + .map(|word| (split_with_coma(word.tags.as_str()), word.group_id)) + .map(|(tags, word_group_id)| { + tags.iter().all(|t| include_tags.contains(t)) && word_group_id == group_id + }).collect(); } fn groups_panel(&self) -> iced::Element<'_, DictionaryMessage> { @@ -519,7 +527,7 @@ impl DictionaryState { } } -pub fn split_with_coma(ts: String) -> Vec { +pub fn split_with_coma(ts: &str) -> Vec { ts.split(',') .map(|ts| ts.to_lowercase().trim().to_string()) .filter(|t| !t.is_empty()) diff --git a/src/dictionary_test.rs b/src/dictionary_test.rs index cae6b05..52eeb58 100644 --- a/src/dictionary_test.rs +++ b/src/dictionary_test.rs @@ -144,7 +144,7 @@ impl DictionaryQuizState { self.score.total += 1; } if self.answer == self.correct - || split_with_coma(self.correct.clone()).contains(&self.answer) + || split_with_coma(self.correct.as_str()).contains(&self.answer) { if self.is_help == false { self.score.correct += 1; diff --git a/src/lang.rs b/src/lang.rs index 8629340..d4c7d93 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -349,11 +349,12 @@ impl CardSet { if !word_ids.contains(&stat.word_id) { delete_stat(&stat, &state_locked.connection); current_set.remove(index); + }else{ + index += 1; } - index += 1; } - let weights = current_set.iter().map(|s| (100.0 / s.calculated_score()).powf(2.0)).collect::>(); + let weights = current_set.iter().map(|s| (100.0 / s.calculated_score()).powf(2.0) * 2.0).collect::>(); let indexes = WeightedIndex::new(weights).unwrap(); Self { diff --git a/src/main.rs b/src/main.rs index be1cc6f..1880723 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,7 +63,7 @@ pub enum RootMessage { Keyboard(Event), } -enum Page { +pub enum Page { Selector(SelectorState), Quiz(QuizState), Writing(WritingState), @@ -207,14 +207,4 @@ trait NavigatedPage { pub trait KeyPressedPage { fn press(&mut self, message: &Event) -> Task; -} - -/*pub fn danger_button(x: &Theme, status: Status) -> Style{ - Style { - background: Some(Color(x.palette().danger)), - text_color: x.palette().text, - border: Border::default().rounded(2), - shadow: Default::default(), - snap: false, - } -}*/ +} \ No newline at end of file diff --git a/src/repetitions.rs b/src/repetitions.rs index 0180eaf..9615c8c 100644 --- a/src/repetitions.rs +++ b/src/repetitions.rs @@ -195,7 +195,7 @@ impl RepetitionsState { .style(move |_x: &Theme, _status| Style { background: None, text_color: if self.correct_filters[i.clone()] { - _x.palette().text + _x.palette().primary } else { _x.palette().warning }, diff --git a/src/word.rs b/src/word.rs index 13f8823..91ffcf3 100644 --- a/src/word.rs +++ b/src/word.rs @@ -41,7 +41,8 @@ impl WordState { WordMessage::Save => { let mut state = self.state.lock().unwrap(); state.dictionary[self.index] = self.word.clone(); - update_word(&mut self.word, &state.connection) + update_word(&mut self.word, &state.connection); + return Task::done(RootMessage::Word(WordMessage::Back)) } WordMessage::Delete => { let mut state = self.state.lock().unwrap();