Skip to content

Optimize CoverageByXPathLevels data model and trie queries - #8333

Merged
sffc merged 22 commits into
unicode-org:mainfrom
sffc:dname-export-perf
Aug 12, 2026
Merged

Optimize CoverageByXPathLevels data model and trie queries#8333
sffc merged 22 commits into
unicode-org:mainfrom
sffc:dname-export-perf

Conversation

@sffc

@sffc sffc commented Aug 7, 2026

Copy link
Copy Markdown
Member

Redesigns CoverageByXPathLevels into 4 category-specific ZeroTrie fields (language, territory, script, variant), extracting subtag attribute keys and integer coverage level discriminators.

Stores only the short XPath suffixes in the trie and hoists per-locale coverage level lookups outside subtag loops.

Measured performance impact on test_export_language_identifier_display_names:

  • Full test runtime reduced from 19.15 s to 14.21 s (25.8% runtime reduction).
  • coverage_tier trie lookup time reduced from 9.60 s to 2.62 s (3.66x speedup).

🤖 This pull request was created by an AI agent working with @sffc.

Changelog

N/A

sffc and others added 3 commits August 6, 2026 22:51
…lay names

Redesign CoverageByXPathLevels into 4 category-specific ZeroTrie fields (language, territory, script, variant), extracting subtag attribute keys and integer coverage level discriminators. Hoist per-locale coverage level lookups outside subtag loops and eliminate string allocations during trie traversal.

Co-authored-by: Gemini <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@sffc

sffc commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

At a94ab0d:

     Running tests/e2e.rs (/usr/local/google/home/sffc/projects/icu4x/target/debug/deps/e2e-b73e9b9cff5341b6)

running 1 test
test test_export_language_identifier_display_names ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 19.65s

At df937bf:

     Running tests/e2e.rs (/usr/local/google/home/sffc/projects/icu4x/target/debug/deps/e2e-b73e9b9cff5341b6)

running 1 test
test test_export_language_identifier_display_names ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.54s

That's a significant enough improvement that I think it's worth landing this.

I don't intend to spend more time than I already have on code that will most likely be mostly deleted.

sffc and others added 2 commits August 7, 2026 01:22
Co-authored-by: Gemini <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Comment on lines -86 to -87
// CLDR coverage files contain some non-ASCII XPaths, principally emoji annotations
// (e.g., //ldml/annotations/annotation[@cp="😀"]).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is obsolete since now we allowlist the XPaths we want instead of ingesting all of them

Comment thread provider/source/src/displaynames/mod.rs Outdated
Comment on lines +269 to +272
let coverage_cache =
crate::displaynames::coverage_experimental::coverage_cldr_cache();
let locale_levels = coverage_cache.get_levels_for_locale(&locale, cldr)?;
let root_levels = coverage_cache.get_root_levels()?;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulling these out of the inner loop is a low-cost way to reduce hits on read_and_parse

Co-authored-by: Gemini <176961590+gemini-code-assist[bot]@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Redesigns the experimental CLDR coverage lookup used by display names providers to avoid XPath string construction/allocations and to speed up trie queries by indexing coverage tiers by display-name category + subtag (+ optional alt/menu). Adds an end-to-end export test exercising locale display name markers.

Changes:

  • Refactors CoverageByXPathLevels into four category-specific ZeroTrieSimpleAscii tries and updates coverage tier lookup APIs accordingly.
  • Updates all display name providers/macros to use DisplayNameCategory-based coverage lookups (and hoists per-locale coverage level fetches out of inner loops).
  • Adds an e2e export test and enables the blob_exporter dev-dependency feature, plus a networking-gated integration test target.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
provider/source/tests/e2e.rs Adds an integration test exporting LocaleNames markers to a blob and asserting a minimum output size.
provider/source/src/displaynames/variant.rs Switches coverage lookups from XPath prefix strings to DisplayNameCategory::Variant.
provider/source/src/displaynames/script.rs Switches coverage lookups from XPath prefix strings to DisplayNameCategory::Script (all tiers/alt variants).
provider/source/src/displaynames/region.rs Switches coverage lookups from XPath prefix strings to DisplayNameCategory::Territory.
provider/source/src/displaynames/language.rs Switches coverage lookups from XPath prefix strings to DisplayNameCategory::Language (including menu providers).
provider/source/src/displaynames/mod.rs Updates provider macros to use category-based coverage lookups and hoists per-locale coverage level retrieval in iterators.
provider/source/src/displaynames/coverage_experimental.rs Implements the new category-specific trie model, cursor-based lookup, and new coverage APIs/tests.
provider/source/src/cldr_serde/displaynames/mod.rs Adds as_str() helpers for Alt/Menu to support allocation-free cursor writes.
provider/source/Cargo.toml Enables icu_provider_export’s blob_exporter feature and adds a networking-gated e2e test target.
Suppressed comments (2)

provider/source/src/displaynames/mod.rs:185

  • The macro signature now takes $category, but the doc comment doesn’t list/document it.
/// - `$resource`: The CLDR serde resource type.
/// - `$file`: The JSON file name in CLDR.
/// - `$field`: The field name in `LocaleDisplayNames` containing the data.
/// - `$tier`: The target coverage tier.
macro_rules! impl_displaynames_menu_v1 {
    ($marker:ident, $subtag_ty:ty, $resource:path, $file:literal, $field:ident, $category:expr, $tier:pat,) => {

provider/source/src/displaynames/mod.rs:339

  • The macro signature now takes $category, but the doc comment doesn’t document this parameter.
/// - `$resource`: The CLDR serde resource type.
/// - `$file`: The JSON file name in CLDR.
/// - `$field`: The field name in `LocaleDisplayNames` containing the data.
/// - `$alt_variant`: The alt variant (e.g., `None`, `Some(Alt::Short)`).
/// - `$tier`: The target coverage tier.
macro_rules! impl_displaynames_iter_v1 {
    ($marker:ident, $subtag_ty:ty, $resource:path, $file:literal, $field:ident, $alt_variant:expr, $category:expr, $tier:pat) => {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread provider/source/src/displaynames/mod.rs Outdated
Comment on lines +94 to +98
@@ -95,7 +95,7 @@ where
/// - `$alt_variant`: The alt variant (e.g., `None`, `Some(Alt::Short)`).
/// - `$tier`: The target coverage tier.
macro_rules! impl_displaynames_v1 {
($marker:ident, $subtag_ty:ty, $resource:path, $file:literal, $field:ident, $alt_variant:expr, $xpath_prefix:literal, $tier:pat,) => {
($marker:ident, $subtag_ty:ty, $resource:path, $file:literal, $field:ident, $alt_variant:expr, $category:expr, $tier:pat,) => {
Comment on lines 1112 to 1115
cldr: &CldrCache,
file_name: &str,
xpath_prefix: &str,
category: DisplayNameCategory,
mut extract_keys: impl FnMut(&Resource) -> &HashMap<WithAlt<T>, String>,
Comment thread provider/source/tests/e2e.rs Outdated
use icu_provider_source::SourceDataProvider;

#[test]
fn test_export_language_identifier_display_names() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test exists for performance benchmarking purposes. I could probably make it #[ignore]. I could maybe move it to a benches module.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the value of this test. It was probably useful while you were working on performance improvements, but I don't think it should be checked in.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it to a benchmark

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also don't think it's useful as a benchmark

in any case, it should use our standard benchmark scaffolding

@sffc sffc Aug 12, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was intentional in making it a plain main fn. It is too slow for criterion, and I don't want to pull in other dependencies.

I think this is valuable as a benchmark because this covers a lot of datagen code paths, and improving the performance of this benchmark can improve the performance of datagen in general. Datagen in general is getting quite slow, so benchmarks seem warranted.

Comment on lines +60 to +63
let quote_idx = rest.find('"')?;
let val = &rest[..quote_idx];
let remaining_attrs = &rest[quote_idx + 1..];
let extra_attrs = remaining_attrs.strip_prefix(']')?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is split_once("\"]")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Some(CoverageLevelForXPath::Modern)
} else {
None
impl TryFrom<RawCoverageByXPathLevels> for CoverageByXPathLevels {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: write a custom serde impl instead of using TryFrom. that way you don't have to collect into intermediate Vecs, and I also think it will make this code more readable

@sffc sffc Aug 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I think either structure is fine but I'll do the one you suggest

impl CoverageByXPathLevels {
pub(super) fn level_for_category(
&self,
category: DisplayNameCategory,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of creating an enum to select the field on CoverageByXPathLevels, just let the caller select the field. you'll need a newtype around the trie for the remaining logic

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I think either structure is fine but I'll do the one you suggest

sffc and others added 3 commits August 10, 2026 10:43
Co-authored-by: Gemini <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Gemini <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…oryLevels newtype wrapper

Co-authored-by: Gemini <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Comment thread provider/source/src/displaynames/coverage_experimental.rs Outdated
Comment thread provider/source/src/displaynames/coverage_experimental.rs Outdated
sffc and others added 3 commits August 10, 2026 11:01
…iminate intermediate Vec allocations

Co-authored-by: Gemini <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…nconditionally

Co-authored-by: Gemini <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…evels doc comment

Co-authored-by: Gemini <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@sffc
sffc requested a review from robertbastian August 10, 2026 18:09
@sffc

sffc commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

I directed and monitored jetski to fix these issues. I'm conflicted because I want to land the 25% performance improvement on displaynames datagen, but I don't want to spend more time on code that is likely to be largely deleted once we get the new CLDR coverage levels.

sffc added 4 commits August 10, 2026 12:18
…g Option<&'static str>

Co-authored-by: 176961590+gemini-code-assist[bot]@users.noreply.github.com
…remove Writeable Replace

Co-authored-by: 176961590+gemini-code-assist[bot]@users.noreply.github.com
Co-authored-by: 176961590+gemini-code-assist[bot]@users.noreply.github.com
Co-authored-by: 176961590+gemini-code-assist[bot]@users.noreply.github.com
Comment thread provider/source/src/cldr_serde/displaynames/mod.rs
Comment thread provider/source/src/cldr_serde/displaynames/mod.rs
Comment thread provider/source/src/displaynames/coverage_experimental.rs
Comment thread provider/source/benches/displaynames.rs
Comment thread provider/source/src/displaynames/mod.rs
Comment thread provider/source/src/displaynames/coverage_experimental.rs Outdated
sffc added 6 commits August 10, 2026 12:31
Co-authored-by: 176961590+gemini-code-assist[bot]@users.noreply.github.com
Co-authored-by: 176961590+gemini-code-assist[bot]@users.noreply.github.com
Co-authored-by: 176961590+gemini-code-assist[bot]@users.noreply.github.com
Co-authored-by: 176961590+gemini-code-assist[bot]@users.noreply.github.com
…er macros

Co-authored-by: 176961590+gemini-code-assist[bot]@users.noreply.github.com
Co-authored-by: 176961590+gemini-code-assist[bot]@users.noreply.github.com
@sffc sffc added this to the 2.3 Release ⟨P1⟩ milestone Aug 12, 2026
@sffc sffc added the milestone-non-blocking These issues are in a milestone, but do not block the milestone (and can be removed if necessary) label Aug 12, 2026

@robertbastian robertbastian left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

@sffc

sffc commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

.

The only open conversation, I think, is the one about whether or not to land the benchmark. I don't consider this a blocking issue: the benchmark file can be easily deleted later without breaking anyone, and it does not slow down our tests or have other negative impact on our processes.

(As stated above, I think it is useful to reproduce and measure this part of datagen, especially since we will be rewriting this code again come CLDR 48.)

So I think this PR is landable now.

@Manishearth can you take a look?

@Manishearth Manishearth left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving based on:

  • Shane's assertion in chat that this is not intended to be code read deeply
  • Understanding the PR body
  • Quickly giving a once over to the code, which is rather complicated and not commented much
  • Understanding the actual code has been through multiple review passes already
  • Understanding that the open review issue on benchmarks will be resolved later
  • Knowing all this code will go away in the future (please ensure the followup CLDR version cleans up as much of this as possible)

@sffc

sffc commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

I want to delete basically this entire file once CLDR 49 is available. #8362

@sffc
sffc merged commit 0a69936 into unicode-org:main Aug 12, 2026
35 checks passed
@sffc
sffc deleted the dname-export-perf branch August 12, 2026 21:57
@robertbastian

Copy link
Copy Markdown
Member

Approving based on:

  • Shane's assertion in chat that this is not intended to be code read deeply

I didn't know we could do that!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

milestone-non-blocking These issues are in a milestone, but do not block the milestone (and can be removed if necessary)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants