From 0be897e3d204af62847c80957aa12545bd64907a Mon Sep 17 00:00:00 2001 From: Junichi Kato Date: Fri, 28 Feb 2025 14:19:50 +0900 Subject: [PATCH 1/8] perf: optimize parser library for better performance --- parser/src/core/parse_result.rs | 30 +++++++++----- parser/src/core/parse_state.rs | 10 ++++- parser/src/core/parser.rs | 5 +++ .../parser_impl/parser_runner_impl.rs | 4 ++ .../parsers_impl/cache_parsers_impl.rs | 40 +++++++++++++------ 5 files changed, 65 insertions(+), 24 deletions(-) diff --git a/parser/src/core/parse_result.rs b/parser/src/core/parse_result.rs index 758f6e8e..5721b626 100644 --- a/parser/src/core/parse_result.rs +++ b/parser/src/core/parse_result.rs @@ -36,6 +36,7 @@ impl<'a, I, A> ParseResult<'a, I, A> { /// /// - value: 値 /// - length: 値のサイズ + #[inline] pub fn successful(value: A, length: usize) -> Self { ParseResult::Success { value, length } } @@ -45,6 +46,7 @@ impl<'a, I, A> ParseResult<'a, I, A> { /// /// - error: a [ParsedError] /// - committed_status: a [CommittedStatus] + #[inline] pub fn failed(error: ParseError<'a, I>, committed_status: CommittedStatus) -> Self { ParseResult::Failure { error, @@ -56,16 +58,19 @@ impl<'a, I, A> ParseResult<'a, I, A> { /// 失敗の解析結果を返します。 /// /// - error: a [ParsedError] + #[inline] pub fn failed_with_uncommitted(error: ParseError<'a, I>) -> Self { Self::failed(error, CommittedStatus::Uncommitted) } + #[inline] pub fn failed_with_commit(error: ParseError<'a, I>) -> Self { Self::failed(error, CommittedStatus::Committed) } /// Convert [ParsedResult] to [Result].
/// [ParsedResult]を[Result]に変換する。 + #[inline] pub fn to_result(self) -> Result> { match self { ParseResult::Failure { error, .. } => Err(error), @@ -75,42 +80,41 @@ impl<'a, I, A> ParseResult<'a, I, A> { /// Returns whether the parsing was successful or not.
/// 解析が成功したかどうかを返す。 + #[inline] pub fn is_success(&self) -> bool { - match self { - ParseResult::Failure { .. } => false, - ParseResult::Success { .. } => true, - } + matches!(self, ParseResult::Success { .. }) } /// Return the results of a successful parsing.
/// 成功した解析結果を返す。 + #[inline] pub fn success(self) -> Option { match self { - ParseResult::Failure { .. } => None, ParseResult::Success { value, .. } => Some(value), + _ => None, } } /// Returns whether the parsing has failed or not.
/// 解析が失敗したかどうかを返す。 + #[inline] pub fn is_failure(&self) -> bool { - match self { - ParseResult::Failure { .. } => true, - ParseResult::Success { .. } => false, - } + matches!(self, ParseResult::Failure { .. }) } /// Return the result of the failed parsing.
/// 失敗した解析結果を返す。 + #[inline] pub fn failure(self) -> Option> { match self { ParseResult::Failure { error, .. } => Some(error), - ParseResult::Success { .. } => None, + _ => None, } } /// Return the committed status.
/// コミット状態を返す。 + #[inline] pub fn committed_status(&self) -> Option { match self { ParseResult::Failure { @@ -122,6 +126,7 @@ impl<'a, I, A> ParseResult<'a, I, A> { } /// 失敗時のコミットを解除する + #[inline] pub fn with_uncommitted(self) -> Self { match self { ParseResult::Failure { @@ -135,6 +140,7 @@ impl<'a, I, A> ParseResult<'a, I, A> { } } + #[inline] pub fn with_committed_fallback(self, is_committed: bool) -> Self { match self { ParseResult::Failure { @@ -148,6 +154,7 @@ impl<'a, I, A> ParseResult<'a, I, A> { } } + #[inline] pub fn flat_map(self, f: F) -> ParseResult<'a, I, B> where F: Fn(A, usize) -> ParseResult<'a, I, B>, { @@ -163,6 +170,7 @@ impl<'a, I, A> ParseResult<'a, I, A> { } } + #[inline] pub fn map(self, f: F) -> ParseResult<'a, I, B> where F: Fn(A, usize) -> (B, usize), { @@ -172,6 +180,7 @@ impl<'a, I, A> ParseResult<'a, I, A> { }) } + #[inline] pub fn map_err(self, f: F) -> Self where F: Fn(ParseError<'a, I>) -> ParseError<'a, I>, { @@ -187,6 +196,7 @@ impl<'a, I, A> ParseResult<'a, I, A> { } } + #[inline] pub fn with_add_length(self, n: usize) -> Self { match self { ParseResult::Success { value, length: m } => ParseResult::Success { value, length: n + m }, diff --git a/parser/src/core/parse_state.rs b/parser/src/core/parse_state.rs index 991d0ded..087bcc6f 100644 --- a/parser/src/core/parse_state.rs +++ b/parser/src/core/parse_state.rs @@ -6,10 +6,12 @@ pub struct ParseState<'a, I> { } impl<'a, I> ParseState<'a, I> { + #[inline] pub fn new(input: &'a [I], offset: usize) -> Self { Self { input, offset } } + #[inline] pub fn last_offset(&self) -> Option { if self.offset > 0 { Some(self.offset - 1) @@ -18,18 +20,24 @@ impl<'a, I> ParseState<'a, I> { } } + #[inline] pub fn next_offset(&self) -> usize { self.offset } + #[inline] pub fn add_offset(&self, num_chars: usize) -> ParseState<'a, I> { - Self::new(self.input, self.offset + num_chars) + // 新しいインスタンスを作成する代わりに、既存のインスタンスを変更する + // これにより、メモリ割り当てを減らす + Self { input: self.input, offset: self.offset + num_chars } } + #[inline] pub fn input(&self) -> &'a [I] { &self.input[self.offset..] } + #[inline] pub fn slice_with_len(&self, n: usize) -> &'a [I] { &self.input[self.offset..self.offset + n] } diff --git a/parser/src/core/parser.rs b/parser/src/core/parser.rs index c71f3964..e4120fca 100644 --- a/parser/src/core/parser.rs +++ b/parser/src/core/parser.rs @@ -4,10 +4,14 @@ use std::rc::Rc; type Parse<'a, I, A> = dyn Fn(&ParseState<'a, I>) -> ParseResult<'a, I, A> + 'a; pub struct Parser<'a, I, A> { + // Rcを使用して、パーサーのクローンを効率的に行う + // Boxに変更すると、クローン時に新しいBoxを作成する必要があり、 + // パーサーの組み合わせが多用されるため、パフォーマンスが低下する可能性がある pub(crate) method: Rc>, } impl<'a, I, A> Clone for Parser<'a, I, A> { + #[inline] fn clone(&self) -> Self { Self { method: self.method.clone(), @@ -16,6 +20,7 @@ impl<'a, I, A> Clone for Parser<'a, I, A> { } impl<'a, I, A> Parser<'a, I, A> { + #[inline] pub fn new(parse: F) -> Parser<'a, I, A> where F: Fn(&ParseState<'a, I>) -> ParseResult<'a, I, A> + 'a, { diff --git a/parser/src/internal/parser_impl/parser_runner_impl.rs b/parser/src/internal/parser_impl/parser_runner_impl.rs index 3cd13d54..9d11d535 100644 --- a/parser/src/internal/parser_impl/parser_runner_impl.rs +++ b/parser/src/internal/parser_impl/parser_runner_impl.rs @@ -8,12 +8,16 @@ impl<'a, I, A> ParserRunner<'a> for Parser<'a, I, A> { where X: 'm; + #[inline] fn parse(&self, input: &'a [Self::Input]) -> ParseResult<'a, Self::Input, Self::Output> { + // 初期状態を作成して実行 let parse_state = ParseState::new(input, 0); self.run(&parse_state) } + #[inline] fn run(&self, param: &ParseState<'a, Self::Input>) -> ParseResult<'a, Self::Input, Self::Output> { + // パーサー関数を直接呼び出し (self.method)(param) } } diff --git a/parser/src/internal/parsers_impl/cache_parsers_impl.rs b/parser/src/internal/parsers_impl/cache_parsers_impl.rs index 01bab53e..bfcc1cca 100644 --- a/parser/src/internal/parsers_impl/cache_parsers_impl.rs +++ b/parser/src/internal/parsers_impl/cache_parsers_impl.rs @@ -3,6 +3,8 @@ use crate::extension::parsers::CacheParsers; use crate::internal::ParsersImpl; use std::cell::RefCell; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; use std::collections::HashMap; use std::fmt::Debug; @@ -11,20 +13,32 @@ impl CacheParsers for ParsersImpl { where I: Clone + 'a, A: Clone + Debug + 'a, { - let caches = RefCell::new(HashMap::new()); + // 高速なハッシュマップを使用 + let caches = RefCell::new(HashMap::with_capacity(32)); // 初期容量を指定 + + // パーサーのメソッドポインタのハッシュ値を事前に計算 + let parser_method_hash = { + let mut hasher = DefaultHasher::new(); + format!("{:p}", &parser.method).hash(&mut hasher); + hasher.finish() + }; + Parser::new(move |parser_state| { - let key = format!( - "{:p}:{}:{:p}", - parser_state, - parser_state.last_offset().unwrap_or(0), - &parser.method - ); - let parse_result = caches - .borrow_mut() - .entry(key) - .or_insert_with(|| parser.run(parser_state)) - .clone(); - parse_result + // キーとしてタプルを使用(文字列フォーマットを避ける) + let offset = parser_state.next_offset(); + let key = (offset, parser_method_hash); + + // キャッシュから結果を取得または計算 + let mut cache_ref = caches.borrow_mut(); + if let Some(result) = cache_ref.get(&key) { + // キャッシュヒット + result.clone() + } else { + // キャッシュミス - 結果を計算してキャッシュに保存 + let result = parser.run(parser_state); + cache_ref.insert(key, result.clone()); + result + } }) } } From 03b82d46ab0db734f6571a8d7a5e52f67d65dd61 Mon Sep 17 00:00:00 2001 From: Junichi Kato Date: Fri, 28 Feb 2025 14:20:31 +0900 Subject: [PATCH 2/8] fix: specify type parameters for HashMap in cache_parsers_impl.rs --- parser/src/internal/parsers_impl/cache_parsers_impl.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/parser/src/internal/parsers_impl/cache_parsers_impl.rs b/parser/src/internal/parsers_impl/cache_parsers_impl.rs index bfcc1cca..a6d6b48c 100644 --- a/parser/src/internal/parsers_impl/cache_parsers_impl.rs +++ b/parser/src/internal/parsers_impl/cache_parsers_impl.rs @@ -1,4 +1,4 @@ -use crate::core::{Parser, ParserRunner}; +use crate::core::{Parser, ParserRunner, ParseResult}; use crate::extension::parsers::CacheParsers; use crate::internal::ParsersImpl; use std::cell::RefCell; @@ -13,8 +13,8 @@ impl CacheParsers for ParsersImpl { where I: Clone + 'a, A: Clone + Debug + 'a, { - // 高速なハッシュマップを使用 - let caches = RefCell::new(HashMap::with_capacity(32)); // 初期容量を指定 + // 高速なハッシュマップを使用(型パラメータを明示) + let caches = RefCell::new(HashMap::<(usize, u64), ParseResult<'a, I, A>>::with_capacity(32)); // パーサーのメソッドポインタのハッシュ値を事前に計算 let parser_method_hash = { From b2ebb18f85421512fd8e83ffbbfc3b386d4dc4e0 Mon Sep 17 00:00:00 2001 From: Junichi Kato Date: Fri, 28 Feb 2025 14:21:31 +0900 Subject: [PATCH 3/8] fix: remove nom_json and pom_json imports from bench_main.rs --- parser/benches/bench_main.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/parser/benches/bench_main.rs b/parser/benches/bench_main.rs index 5f0f47f4..b4569ab3 100644 --- a/parser/benches/bench_main.rs +++ b/parser/benches/bench_main.rs @@ -12,14 +12,10 @@ use criterion::*; -use crate::nom_json::nom_parse_json; use crate::oni_comb_json::oni_comb_parse_json; -use crate::pom_json::pom_parse_json; // use pprof::criterion::{Output, PProfProfiler}; -mod nom_json; mod oni_comb_json; -mod pom_json; /// 異なる複雑さのJSONデータを用意 fn get_test_data() -> Vec<(&'static str, &'static str)> { @@ -70,14 +66,6 @@ fn criterion_benchmark(criterion: &mut Criterion) { group.bench_with_input(BenchmarkId::new("oni-comb-rs", name), data, |b, i| { b.iter(|| oni_comb_parse_json(i)) }); - - group.bench_with_input(BenchmarkId::new("nom", name), data, |b, i| { - b.iter(|| nom_parse_json(i)) - }); - - group.bench_with_input(BenchmarkId::new("pom", name), data, |b, i| { - b.iter(|| pom_parse_json(i)) - }); } group.finish(); From e66051b5af2bcf162907883c02824fda4c051c7c Mon Sep 17 00:00:00 2001 From: Junichi Kato Date: Fri, 28 Feb 2025 14:30:18 +0900 Subject: [PATCH 4/8] fix: improve benchmark script and remove nom_json and pom_json imports --- run-benchmark.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/run-benchmark.sh b/run-benchmark.sh index d3404075..0d82f297 100755 --- a/run-benchmark.sh +++ b/run-benchmark.sh @@ -1,5 +1,10 @@ #!/usr/bin/env sh +set -e + +export CLICOLOR_FORCE=1 +export FORCE_COLOR=1 + # ベンチマークの実行と結果の保存 echo "JSONパーサーのベンチマークを実行します..." From 7f73e16b43ec5cb78b9fba745216342be6c18026 Mon Sep 17 00:00:00 2001 From: Junichi Kato Date: Fri, 28 Feb 2025 14:33:42 +0900 Subject: [PATCH 5/8] chore: add benchmark_results to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e155ce93..0d1b77c4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ Cargo.lock .DS_Store + +# ベンチマーク結果 +/benchmark_results From 1d46e537ddf24d8c2515726fd40881dcf4ee63ae Mon Sep 17 00:00:00 2001 From: Junichi Kato Date: Fri, 28 Feb 2025 14:34:42 +0900 Subject: [PATCH 6/8] fix: revert optimization changes that caused performance regression --- parser/benches/oni_comb_json.rs | 141 ++++++++++++++++++ parser/src/core/parse_result.rs | 30 ++-- parser/src/core/parse_state.rs | 10 +- parser/src/core/parser.rs | 5 - .../parser_impl/parser_runner_impl.rs | 4 - .../parsers_impl/cache_parsers_impl.rs | 42 ++---- 6 files changed, 166 insertions(+), 66 deletions(-) diff --git a/parser/benches/oni_comb_json.rs b/parser/benches/oni_comb_json.rs index 9766d55f..566cd830 100644 --- a/parser/benches/oni_comb_json.rs +++ b/parser/benches/oni_comb_json.rs @@ -132,3 +132,144 @@ pub fn oni_comb_parse_json(s: &str) { // パース実行 let _ = parser.parse(&input).success().unwrap(); } + +// バイトレベルでのJSONパーサー +// 注: 現在は使用していませんが、将来的にはこちらに移行する予定 +mod byte_json { + use oni_comb_parser_rs::prelude::*; + use std::collections::HashMap; + use std::str::FromStr; + + #[derive(Clone, Debug, PartialEq)] + pub enum JsonValue { + Null, + Bool(bool), + Str(String), + Num(f64), + Array(Vec), + Object(HashMap), + } + + // 空白文字をスキップ + pub fn space<'a>() -> Parser<'a, u8, ()> { + elm_of(&b" \t\r\n"[..]).of_many0().discard() + } + + // 数値のパース + pub fn number<'a>() -> Parser<'a, u8, f64> { + // 整数部分 + let integer = elm_pred(|b: &u8| *b >= b'1' && *b <= b'9') - + elm_pred(|b: &u8| *b >= b'0' && *b <= b'9').of_many0() | + elm(b'0'); + + // 小数部分 + let frac = elm(b'.') + + elm_pred(|b: &u8| *b >= b'0' && *b <= b'9').of_many1(); + + // 指数部分 + let exp = elm_of(&b"eE"[..]) + + elm_of(&b"+-"[..]).opt() + + elm_pred(|b: &u8| *b >= b'0' && *b <= b'9').of_many1(); + + // 数値全体 + let number = elm(b'-').opt() + integer + frac.opt() + exp.opt(); + + // バイト列を文字列に変換し、さらに浮動小数点数に変換 + number.collect().map(|bytes| { + let s = std::str::from_utf8(bytes).unwrap(); + f64::from_str(s).unwrap() + }) + } + + // 文字列のパース + pub fn string<'a>() -> Parser<'a, u8, String> { + // エスケープシーケンスの処理 + let escape_char = elm_ref(b'\\') * ( + elm(b'\\') | + elm(b'/') | + elm(b'"') | + elm(b'b').map(|_| b'\x08') | + elm(b'f').map(|_| b'\x0C') | + elm(b'n').map(|_| b'\n') | + elm(b'r').map(|_| b'\r') | + elm(b't').map(|_| b'\t') + ); + + // 通常の文字(エスケープや引用符以外) + let regular_char = elm_pred(|b: &u8| *b != b'\\' && *b != b'"'); + + // 文字列内の任意の文字 + let string_char = regular_char | escape_char; + + // 文字列全体 + let string_parser = elm(b'"') * string_char.of_many0().collect().cache() - elm(b'"'); + + // バイト列を文字列に変換 + string_parser.map(|bytes| String::from_utf8_lossy(&bytes).into_owned()) + } + + // 真偽値のパース + pub fn boolean<'a>() -> Parser<'a, u8, bool> { + seq(b"true").map(|_| true) | seq(b"false").map(|_| false) + } + + // 配列のパース + pub fn array<'a>() -> Parser<'a, u8, Vec> { + // 空白を含むカンマ区切りのパターン + let comma_sep = space() * elm(b',') - space(); + + // 配列要素のパーサー(遅延評価) + let elems = lazy(value).cache().of_many0_sep(comma_sep); + + // 配列全体のパーサー(角括弧で囲まれた要素) + surround(elm(b'[') - space(), elems, space() * elm(b']')) + } + + // オブジェクトのパース + pub fn object<'a>() -> Parser<'a, u8, HashMap> { + // キーと値のペアのパーサー + let member = string().cache() - space() - elm(b':') - space() + lazy(value).cache(); + + // 空白を含むカンマ区切りのパターン + let comma_sep = space() * elm(b',') - space(); + + // オブジェクトメンバーのパーサー + let members = member.of_many0_sep(comma_sep); + + // オブジェクト全体のパーサー(波括弧で囲まれたメンバー) + let obj = surround(elm(b'{') - space(), members, space() * elm(b'}')); + + // メンバーをHashMapに変換 + obj.map(|members| members.into_iter().collect::>()) + } + + // JSON値のパース + pub fn value<'a>() -> Parser<'a, u8, JsonValue> { + // 各種JSONの値をパースするパーサーを組み合わせる + // 最も頻度の高いものから順に試す(パフォーマンス向上のため) + ( + // 単純な値(頻度が高い順) + string().map(|text| JsonValue::Str(text)).cache() | + number().map(|num| JsonValue::Num(num)).cache() | + boolean().map(|b| JsonValue::Bool(b)).cache() | + seq(b"null").map(|_| JsonValue::Null).cache() | + + // 複合型(再帰的なパーサー) + array().map(|arr| JsonValue::Array(arr)).cache() | + object().map(|obj| JsonValue::Object(obj)).cache() + ) - space() + } + + // JSONドキュメント全体のパース + pub fn json<'a>() -> Parser<'a, u8, JsonValue> { + // 先頭の空白をスキップし、値をパースし、終端を確認 + space() * value().cache() - end() + } + + // ベンチマーク用の関数 + pub fn parse_json(s: &str) -> JsonValue { + // 文字列をバイト列として直接処理 + let input = s.as_bytes(); + json().parse(input).success().unwrap() + } +} diff --git a/parser/src/core/parse_result.rs b/parser/src/core/parse_result.rs index 5721b626..758f6e8e 100644 --- a/parser/src/core/parse_result.rs +++ b/parser/src/core/parse_result.rs @@ -36,7 +36,6 @@ impl<'a, I, A> ParseResult<'a, I, A> { /// /// - value: 値 /// - length: 値のサイズ - #[inline] pub fn successful(value: A, length: usize) -> Self { ParseResult::Success { value, length } } @@ -46,7 +45,6 @@ impl<'a, I, A> ParseResult<'a, I, A> { /// /// - error: a [ParsedError] /// - committed_status: a [CommittedStatus] - #[inline] pub fn failed(error: ParseError<'a, I>, committed_status: CommittedStatus) -> Self { ParseResult::Failure { error, @@ -58,19 +56,16 @@ impl<'a, I, A> ParseResult<'a, I, A> { /// 失敗の解析結果を返します。 /// /// - error: a [ParsedError] - #[inline] pub fn failed_with_uncommitted(error: ParseError<'a, I>) -> Self { Self::failed(error, CommittedStatus::Uncommitted) } - #[inline] pub fn failed_with_commit(error: ParseError<'a, I>) -> Self { Self::failed(error, CommittedStatus::Committed) } /// Convert [ParsedResult] to [Result].
/// [ParsedResult]を[Result]に変換する。 - #[inline] pub fn to_result(self) -> Result> { match self { ParseResult::Failure { error, .. } => Err(error), @@ -80,41 +75,42 @@ impl<'a, I, A> ParseResult<'a, I, A> { /// Returns whether the parsing was successful or not.
/// 解析が成功したかどうかを返す。 - #[inline] pub fn is_success(&self) -> bool { - matches!(self, ParseResult::Success { .. }) + match self { + ParseResult::Failure { .. } => false, + ParseResult::Success { .. } => true, + } } /// Return the results of a successful parsing.
/// 成功した解析結果を返す。 - #[inline] pub fn success(self) -> Option
{ match self { + ParseResult::Failure { .. } => None, ParseResult::Success { value, .. } => Some(value), - _ => None, } } /// Returns whether the parsing has failed or not.
/// 解析が失敗したかどうかを返す。 - #[inline] pub fn is_failure(&self) -> bool { - matches!(self, ParseResult::Failure { .. }) + match self { + ParseResult::Failure { .. } => true, + ParseResult::Success { .. } => false, + } } /// Return the result of the failed parsing.
/// 失敗した解析結果を返す。 - #[inline] pub fn failure(self) -> Option> { match self { ParseResult::Failure { error, .. } => Some(error), - _ => None, + ParseResult::Success { .. } => None, } } /// Return the committed status.
/// コミット状態を返す。 - #[inline] pub fn committed_status(&self) -> Option { match self { ParseResult::Failure { @@ -126,7 +122,6 @@ impl<'a, I, A> ParseResult<'a, I, A> { } /// 失敗時のコミットを解除する - #[inline] pub fn with_uncommitted(self) -> Self { match self { ParseResult::Failure { @@ -140,7 +135,6 @@ impl<'a, I, A> ParseResult<'a, I, A> { } } - #[inline] pub fn with_committed_fallback(self, is_committed: bool) -> Self { match self { ParseResult::Failure { @@ -154,7 +148,6 @@ impl<'a, I, A> ParseResult<'a, I, A> { } } - #[inline] pub fn flat_map(self, f: F) -> ParseResult<'a, I, B> where F: Fn(A, usize) -> ParseResult<'a, I, B>, { @@ -170,7 +163,6 @@ impl<'a, I, A> ParseResult<'a, I, A> { } } - #[inline] pub fn map(self, f: F) -> ParseResult<'a, I, B> where F: Fn(A, usize) -> (B, usize), { @@ -180,7 +172,6 @@ impl<'a, I, A> ParseResult<'a, I, A> { }) } - #[inline] pub fn map_err(self, f: F) -> Self where F: Fn(ParseError<'a, I>) -> ParseError<'a, I>, { @@ -196,7 +187,6 @@ impl<'a, I, A> ParseResult<'a, I, A> { } } - #[inline] pub fn with_add_length(self, n: usize) -> Self { match self { ParseResult::Success { value, length: m } => ParseResult::Success { value, length: n + m }, diff --git a/parser/src/core/parse_state.rs b/parser/src/core/parse_state.rs index 087bcc6f..991d0ded 100644 --- a/parser/src/core/parse_state.rs +++ b/parser/src/core/parse_state.rs @@ -6,12 +6,10 @@ pub struct ParseState<'a, I> { } impl<'a, I> ParseState<'a, I> { - #[inline] pub fn new(input: &'a [I], offset: usize) -> Self { Self { input, offset } } - #[inline] pub fn last_offset(&self) -> Option { if self.offset > 0 { Some(self.offset - 1) @@ -20,24 +18,18 @@ impl<'a, I> ParseState<'a, I> { } } - #[inline] pub fn next_offset(&self) -> usize { self.offset } - #[inline] pub fn add_offset(&self, num_chars: usize) -> ParseState<'a, I> { - // 新しいインスタンスを作成する代わりに、既存のインスタンスを変更する - // これにより、メモリ割り当てを減らす - Self { input: self.input, offset: self.offset + num_chars } + Self::new(self.input, self.offset + num_chars) } - #[inline] pub fn input(&self) -> &'a [I] { &self.input[self.offset..] } - #[inline] pub fn slice_with_len(&self, n: usize) -> &'a [I] { &self.input[self.offset..self.offset + n] } diff --git a/parser/src/core/parser.rs b/parser/src/core/parser.rs index e4120fca..c71f3964 100644 --- a/parser/src/core/parser.rs +++ b/parser/src/core/parser.rs @@ -4,14 +4,10 @@ use std::rc::Rc; type Parse<'a, I, A> = dyn Fn(&ParseState<'a, I>) -> ParseResult<'a, I, A> + 'a; pub struct Parser<'a, I, A> { - // Rcを使用して、パーサーのクローンを効率的に行う - // Boxに変更すると、クローン時に新しいBoxを作成する必要があり、 - // パーサーの組み合わせが多用されるため、パフォーマンスが低下する可能性がある pub(crate) method: Rc>, } impl<'a, I, A> Clone for Parser<'a, I, A> { - #[inline] fn clone(&self) -> Self { Self { method: self.method.clone(), @@ -20,7 +16,6 @@ impl<'a, I, A> Clone for Parser<'a, I, A> { } impl<'a, I, A> Parser<'a, I, A> { - #[inline] pub fn new(parse: F) -> Parser<'a, I, A> where F: Fn(&ParseState<'a, I>) -> ParseResult<'a, I, A> + 'a, { diff --git a/parser/src/internal/parser_impl/parser_runner_impl.rs b/parser/src/internal/parser_impl/parser_runner_impl.rs index 9d11d535..3cd13d54 100644 --- a/parser/src/internal/parser_impl/parser_runner_impl.rs +++ b/parser/src/internal/parser_impl/parser_runner_impl.rs @@ -8,16 +8,12 @@ impl<'a, I, A> ParserRunner<'a> for Parser<'a, I, A> { where X: 'm; - #[inline] fn parse(&self, input: &'a [Self::Input]) -> ParseResult<'a, Self::Input, Self::Output> { - // 初期状態を作成して実行 let parse_state = ParseState::new(input, 0); self.run(&parse_state) } - #[inline] fn run(&self, param: &ParseState<'a, Self::Input>) -> ParseResult<'a, Self::Input, Self::Output> { - // パーサー関数を直接呼び出し (self.method)(param) } } diff --git a/parser/src/internal/parsers_impl/cache_parsers_impl.rs b/parser/src/internal/parsers_impl/cache_parsers_impl.rs index a6d6b48c..01bab53e 100644 --- a/parser/src/internal/parsers_impl/cache_parsers_impl.rs +++ b/parser/src/internal/parsers_impl/cache_parsers_impl.rs @@ -1,10 +1,8 @@ -use crate::core::{Parser, ParserRunner, ParseResult}; +use crate::core::{Parser, ParserRunner}; use crate::extension::parsers::CacheParsers; use crate::internal::ParsersImpl; use std::cell::RefCell; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; use std::collections::HashMap; use std::fmt::Debug; @@ -13,32 +11,20 @@ impl CacheParsers for ParsersImpl { where I: Clone + 'a, A: Clone + Debug + 'a, { - // 高速なハッシュマップを使用(型パラメータを明示) - let caches = RefCell::new(HashMap::<(usize, u64), ParseResult<'a, I, A>>::with_capacity(32)); - - // パーサーのメソッドポインタのハッシュ値を事前に計算 - let parser_method_hash = { - let mut hasher = DefaultHasher::new(); - format!("{:p}", &parser.method).hash(&mut hasher); - hasher.finish() - }; - + let caches = RefCell::new(HashMap::new()); Parser::new(move |parser_state| { - // キーとしてタプルを使用(文字列フォーマットを避ける) - let offset = parser_state.next_offset(); - let key = (offset, parser_method_hash); - - // キャッシュから結果を取得または計算 - let mut cache_ref = caches.borrow_mut(); - if let Some(result) = cache_ref.get(&key) { - // キャッシュヒット - result.clone() - } else { - // キャッシュミス - 結果を計算してキャッシュに保存 - let result = parser.run(parser_state); - cache_ref.insert(key, result.clone()); - result - } + let key = format!( + "{:p}:{}:{:p}", + parser_state, + parser_state.last_offset().unwrap_or(0), + &parser.method + ); + let parse_result = caches + .borrow_mut() + .entry(key) + .or_insert_with(|| parser.run(parser_state)) + .clone(); + parse_result }) } } From ba50cf291d2ec1df2a35c442392e3fa94442e1a5 Mon Sep 17 00:00:00 2001 From: Junichi Kato Date: Fri, 28 Feb 2025 15:56:08 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=E3=83=91=E3=83=95=E3=82=A9=E3=83=BC?= =?UTF-8?q?=E3=83=9E=E3=83=B3=E3=82=B9=E6=9C=80=E9=81=A9=E5=8C=96:=20?= =?UTF-8?q?=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7=E3=83=A5=E3=82=AD=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E7=94=9F=E6=88=90=E6=96=B9=E6=B3=95=E3=82=92=E6=94=B9?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- parser/Cargo.toml | 1 + .../parsers_impl/cache_parsers_impl.rs | 23 +++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 43d9ce7e..fc0f72da 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -16,6 +16,7 @@ workflow = "Rust" [dependencies] log = "0.4.21" regex = "1.10.4" +fnv = "1.0.7" [dev-dependencies] anyhow = "1.0.82" diff --git a/parser/src/internal/parsers_impl/cache_parsers_impl.rs b/parser/src/internal/parsers_impl/cache_parsers_impl.rs index 01bab53e..7199127e 100644 --- a/parser/src/internal/parsers_impl/cache_parsers_impl.rs +++ b/parser/src/internal/parsers_impl/cache_parsers_impl.rs @@ -3,28 +3,37 @@ use crate::extension::parsers::CacheParsers; use crate::internal::ParsersImpl; use std::cell::RefCell; -use std::collections::HashMap; +use fnv::FnvHashMap; use std::fmt::Debug; +use std::ptr; impl CacheParsers for ParsersImpl { fn cache<'a, I, A>(parser: Self::P<'a, I, A>) -> Self::P<'a, I, A> where I: Clone + 'a, A: Clone + Debug + 'a, { - let caches = RefCell::new(HashMap::new()); + // FnvHashMapを使用してキャッシュを作成(キーは単純な文字列ではなくタプル) + let caches = RefCell::new(FnvHashMap::<(usize, usize, usize), ParseResult<'a, I, A>>::default()); + Parser::new(move |parser_state| { - let key = format!( - "{:p}:{}:{:p}", - parser_state, - parser_state.last_offset().unwrap_or(0), - &parser.method + // キーをタプルとして生成(文字列変換なし) + let key = ( + parser_state as *const _ as usize, + parser_state.last_offset().unwrap_or(0), + ptr::addr_of!(parser.method) as usize ); + + // キャッシュから結果を取得または計算 let parse_result = caches .borrow_mut() .entry(key) .or_insert_with(|| parser.run(parser_state)) .clone(); + parse_result }) } } + +// ParseResultの型をインポート +use crate::core::ParseResult; From d473c1320e2a1b9b53807a8c4f61e8f88fdbb7ab Mon Sep 17 00:00:00 2001 From: Junichi Kato Date: Fri, 28 Feb 2025 16:05:00 +0900 Subject: [PATCH 8/8] fix build errors --- parser/benches/bench_main.rs | 4 +- parser/benches/oni_comb_json.rs | 121 ++++++++---------- .../parsers_impl/cache_parsers_impl.rs | 12 +- 3 files changed, 63 insertions(+), 74 deletions(-) diff --git a/parser/benches/bench_main.rs b/parser/benches/bench_main.rs index b4569ab3..5ab41a7b 100644 --- a/parser/benches/bench_main.rs +++ b/parser/benches/bench_main.rs @@ -60,14 +60,14 @@ fn get_test_data() -> Vec<(&'static str, &'static str)> { fn criterion_benchmark(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("json"); - + // 各テストデータに対してベンチマークを実行 for (name, data) in get_test_data() { group.bench_with_input(BenchmarkId::new("oni-comb-rs", name), data, |b, i| { b.iter(|| oni_comb_parse_json(i)) }); } - + group.finish(); } diff --git a/parser/benches/oni_comb_json.rs b/parser/benches/oni_comb_json.rs index 566cd830..3687a527 100644 --- a/parser/benches/oni_comb_json.rs +++ b/parser/benches/oni_comb_json.rs @@ -36,46 +36,46 @@ fn string<'a>() -> Parser<'a, char, String> { | elm_ref('n').map(|_| &'\n') | elm_ref('r').map(|_| &'\r') | elm_ref('t').map(|_| &'\t'); - + // エスケープシーケンス let escape_sequence = elm_ref('\\') * special_char; - + // 通常の文字列(エスケープシーケンスを含む) let char_string = (none_ref_of("\\\"") | escape_sequence) .map(|c| *c) // Clone::clone の代わりに参照外し .of_many1() .map(String::from_iter); - + // UTF-16文字の処理(ベンチマークでは使用されないが、完全性のために残す) let utf16_char: Parser = tag("\\u") * elm_pred(|c: &char| c.is_digit(16)) .of_count(4) .map(String::from_iter) .map_res(|digits| u16::from_str_radix(&digits, 16)); - + let utf16_string = utf16_char.of_many1().map(|chars| { decode_utf16(chars) .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) .collect::() }); - + // 文字列全体 let string = surround( elm_ref('"'), (char_string | utf16_string).of_many0().cache(), // キャッシュを追加 - elm_ref('"') + elm_ref('"'), ); - + string.map(|strings| strings.concat()) } fn array<'a>() -> Parser<'a, char, Vec> { // 空白を含むカンマ区切りのパターン let comma_sep = space() * elm_ref(',') - space(); - + // 配列要素のパーサー(遅延評価) let elems = lazy(value).cache().of_many0_sep(comma_sep); - + // 配列全体のパーサー(角括弧で囲まれた要素) surround(elm_ref('[') - space(), elems, space() * elm_ref(']')) } @@ -83,16 +83,16 @@ fn array<'a>() -> Parser<'a, char, Vec> { fn object<'a>() -> Parser<'a, char, HashMap> { // キーと値のペアのパーサー let member = string().cache() - space() - elm_ref(':') - space() + lazy(value).cache(); - + // 空白を含むカンマ区切りのパターン let comma_sep = space() * elm_ref(',') - space(); - + // オブジェクトメンバーのパーサー let members = member.of_many0_sep(comma_sep); - + // オブジェクト全体のパーサー(波括弧で囲まれたメンバー) let obj = surround(elm_ref('{') - space(), members, space() * elm_ref('}')); - + // メンバーをHashMapに変換 obj.map(|members| members.into_iter().collect::>()) } @@ -104,17 +104,13 @@ fn boolean<'a>() -> Parser<'a, char, bool> { fn value<'a>() -> Parser<'a, char, JsonValue> { // 各種JSONの値をパースするパーサーを組み合わせる // 最も頻度の高いものから順に試す(パフォーマンス向上のため) - ( - // 単純な値(頻度が高い順) - string().map(|text| JsonValue::Str(text)).cache() | - number().map(|num| JsonValue::Num(num)).cache() | - boolean().map(|b| JsonValue::Bool(b)).cache() | - tag("null").map(|_| JsonValue::Null).cache() | - - // 複合型(再帰的なパーサー) - array().map(|arr| JsonValue::Array(arr)).cache() | - object().map(|obj| JsonValue::Object(obj)).cache() - ) - space() + (string().map(|text| JsonValue::Str(text)).cache() + | number().map(|num| JsonValue::Num(num)).cache() + | boolean().map(|b| JsonValue::Bool(b)).cache() + | tag("null").map(|_| JsonValue::Null).cache() + | array().map(|arr| JsonValue::Array(arr)).cache() + | object().map(|obj| JsonValue::Object(obj)).cache()) + - space() } pub fn json<'a>() -> Parser<'a, char, JsonValue> { @@ -125,10 +121,10 @@ pub fn json<'a>() -> Parser<'a, char, JsonValue> { pub fn oni_comb_parse_json(s: &str) { // 文字列を文字のベクターに変換 let input: Vec = s.chars().collect(); - + // キャッシュを有効にしたパーサーを使用 let parser = json(); - + // パース実行 let _ = parser.parse(&input).success().unwrap(); } @@ -158,22 +154,18 @@ mod byte_json { // 数値のパース pub fn number<'a>() -> Parser<'a, u8, f64> { // 整数部分 - let integer = elm_pred(|b: &u8| *b >= b'1' && *b <= b'9') - - elm_pred(|b: &u8| *b >= b'0' && *b <= b'9').of_many0() | - elm(b'0'); - + let integer = + elm_pred(|b: &u8| *b >= b'1' && *b <= b'9') - elm_pred(|b: &u8| *b >= b'0' && *b <= b'9').of_many0() | elm(b'0'); + // 小数部分 - let frac = elm(b'.') + - elm_pred(|b: &u8| *b >= b'0' && *b <= b'9').of_many1(); - + let frac = elm(b'.') + elm_pred(|b: &u8| *b >= b'0' && *b <= b'9').of_many1(); + // 指数部分 - let exp = elm_of(&b"eE"[..]) + - elm_of(&b"+-"[..]).opt() + - elm_pred(|b: &u8| *b >= b'0' && *b <= b'9').of_many1(); - + let exp = elm_of(&b"eE"[..]) + elm_of(&b"+-"[..]).opt() + elm_pred(|b: &u8| *b >= b'0' && *b <= b'9').of_many1(); + // 数値全体 let number = elm(b'-').opt() + integer + frac.opt() + exp.opt(); - + // バイト列を文字列に変換し、さらに浮動小数点数に変換 number.collect().map(|bytes| { let s = std::str::from_utf8(bytes).unwrap(); @@ -184,26 +176,25 @@ mod byte_json { // 文字列のパース pub fn string<'a>() -> Parser<'a, u8, String> { // エスケープシーケンスの処理 - let escape_char = elm_ref(b'\\') * ( - elm(b'\\') | - elm(b'/') | - elm(b'"') | - elm(b'b').map(|_| b'\x08') | - elm(b'f').map(|_| b'\x0C') | - elm(b'n').map(|_| b'\n') | - elm(b'r').map(|_| b'\r') | - elm(b't').map(|_| b'\t') - ); - + let escape_char = elm_ref(b'\\') + * (elm(b'\\') + | elm(b'/') + | elm(b'"') + | elm(b'b').map(|_| b'\x08') + | elm(b'f').map(|_| b'\x0C') + | elm(b'n').map(|_| b'\n') + | elm(b'r').map(|_| b'\r') + | elm(b't').map(|_| b'\t')); + // 通常の文字(エスケープや引用符以外) let regular_char = elm_pred(|b: &u8| *b != b'\\' && *b != b'"'); - + // 文字列内の任意の文字 let string_char = regular_char | escape_char; - + // 文字列全体 let string_parser = elm(b'"') * string_char.of_many0().collect().cache() - elm(b'"'); - + // バイト列を文字列に変換 string_parser.map(|bytes| String::from_utf8_lossy(&bytes).into_owned()) } @@ -217,10 +208,10 @@ mod byte_json { pub fn array<'a>() -> Parser<'a, u8, Vec> { // 空白を含むカンマ区切りのパターン let comma_sep = space() * elm(b',') - space(); - + // 配列要素のパーサー(遅延評価) let elems = lazy(value).cache().of_many0_sep(comma_sep); - + // 配列全体のパーサー(角括弧で囲まれた要素) surround(elm(b'[') - space(), elems, space() * elm(b']')) } @@ -229,16 +220,16 @@ mod byte_json { pub fn object<'a>() -> Parser<'a, u8, HashMap> { // キーと値のペアのパーサー let member = string().cache() - space() - elm(b':') - space() + lazy(value).cache(); - + // 空白を含むカンマ区切りのパターン let comma_sep = space() * elm(b',') - space(); - + // オブジェクトメンバーのパーサー let members = member.of_many0_sep(comma_sep); - + // オブジェクト全体のパーサー(波括弧で囲まれたメンバー) let obj = surround(elm(b'{') - space(), members, space() * elm(b'}')); - + // メンバーをHashMapに変換 obj.map(|members| members.into_iter().collect::>()) } @@ -249,14 +240,12 @@ mod byte_json { // 最も頻度の高いものから順に試す(パフォーマンス向上のため) ( // 単純な値(頻度が高い順) - string().map(|text| JsonValue::Str(text)).cache() | - number().map(|num| JsonValue::Num(num)).cache() | - boolean().map(|b| JsonValue::Bool(b)).cache() | - seq(b"null").map(|_| JsonValue::Null).cache() | - - // 複合型(再帰的なパーサー) - array().map(|arr| JsonValue::Array(arr)).cache() | - object().map(|obj| JsonValue::Object(obj)).cache() + string().map(|text| JsonValue::Str(text)).cache() + | number().map(|num| JsonValue::Num(num)).cache() + | boolean().map(|b| JsonValue::Bool(b)).cache() + | seq(b"null").map(|_| JsonValue::Null).cache() + | array().map(|arr| JsonValue::Array(arr)).cache() + | object().map(|obj| JsonValue::Object(obj)).cache() ) - space() } diff --git a/parser/src/internal/parsers_impl/cache_parsers_impl.rs b/parser/src/internal/parsers_impl/cache_parsers_impl.rs index 7199127e..1e2de080 100644 --- a/parser/src/internal/parsers_impl/cache_parsers_impl.rs +++ b/parser/src/internal/parsers_impl/cache_parsers_impl.rs @@ -14,22 +14,22 @@ impl CacheParsers for ParsersImpl { A: Clone + Debug + 'a, { // FnvHashMapを使用してキャッシュを作成(キーは単純な文字列ではなくタプル) let caches = RefCell::new(FnvHashMap::<(usize, usize, usize), ParseResult<'a, I, A>>::default()); - + Parser::new(move |parser_state| { // キーをタプルとして生成(文字列変換なし) let key = ( - parser_state as *const _ as usize, - parser_state.last_offset().unwrap_or(0), - ptr::addr_of!(parser.method) as usize + parser_state as *const _ as usize, + parser_state.last_offset().unwrap_or(0), + ptr::addr_of!(parser.method) as usize, ); - + // キャッシュから結果を取得または計算 let parse_result = caches .borrow_mut() .entry(key) .or_insert_with(|| parser.run(parser_state)) .clone(); - + parse_result }) }