diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1cade0b..f970aa7f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: needs: build_sdist strategy: matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest] + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm, macos-latest] steps: - name: Checkout diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4b6a553c..e044b746 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,6 +50,10 @@ jobs: - ubuntu-24.04-arm - macos-latest - windows-latest + - windows-11-arm + exclude: + - os: windows-11-arm + py: "3.8" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # was: actions/checkout@v6 - name: Setup python for test ${{ matrix.py }} diff --git a/src/ngram_search.h b/src/ngram_search.h index c00f782a..6e97eda2 100644 --- a/src/ngram_search.h +++ b/src/ngram_search.h @@ -231,13 +231,13 @@ struct ngram_search_s { * linked lists of CHANs, one list per word, and each CHAN in this * set is allocated only on demand and freed if inactive. */ - root_chan_t *root_chan; /**< Roots of search tree. */ - int32 n_root_chan_alloc; /**< Number of root_chan allocated */ - int32 n_root_chan; /**< Number of valid root_chan */ - int32 n_nonroot_chan; /**< Number of valid non-root channels */ - int32 max_nonroot_chan; /**< Maximum possible number of non-root channels */ - root_chan_t *rhmm_1ph; /**< Root HMMs for single-phone words */ - + root_chan_t *root_chan; /**< Roots of search tree. */ + int32 n_root_chan_alloc; /**< Number of root_chan allocated */ + int32 n_root_chan; /**< Number of valid root_chan */ + int32 n_nonroot_chan; /**< Number of valid non-root channels */ + int32 max_nonroot_chan; /**< Maximum possible number of non-root channels */ + root_chan_t *rhmm_1ph; /**< Root HMMs for single-phone words */ + root_chan_t ***root_lookup; /**< Root channel lookup table */ /** * Channels associated with a given word (only used for right * contexts, single-phone words in fwdtree search, and word HMMs diff --git a/src/ngram_search_fwdtree.c b/src/ngram_search_fwdtree.c index 586cf05d..67a93f70 100644 --- a/src/ngram_search_fwdtree.c +++ b/src/ngram_search_fwdtree.c @@ -79,6 +79,10 @@ init_search_tree(ngram_search_t *ngs) ndiph = 0; ngs->n_1ph_words = 0; n_ci = bin_mdef_n_ciphone(ps_search_acmod(ngs)->mdef); + /* Allocate and zero-initialize the root channel lookup table indexed by + * (ciphone, ci2phone) for constant-time root channel lookup. + */ + ngs->root_lookup = (root_chan_t ***)ckd_calloc_2d(n_ci, n_ci, sizeof(root_chan_t *)); /* Allocate a bitvector with flags for each possible diphone. */ dimap = bitvec_alloc(n_ci * n_ci); for (w = 0; w < n_words; w++) { @@ -175,7 +179,7 @@ create_search_channels(ngram_search_t *ngs) { chan_t *hmm; root_chan_t *rhmm; - int32 w, i, j, p, ph, tmatid; + int32 w, j, p, ph, tmatid; int32 n_words; dict_t *dict = ps_search_dict(ngs); dict2pid_t *d2p = ps_search_dict2pid(ngs); @@ -210,12 +214,14 @@ create_search_channels(ngram_search_t *ngs) * allocate one if not found. */ ciphone = dict_first_phone(dict, w); ci2phone = dict_second_phone(dict, w); - for (i = 0; i < ngs->n_root_chan; ++i) { - if (ngs->root_chan[i].ciphone == ciphone - && ngs->root_chan[i].ci2phone == ci2phone) - break; - } - if (i == ngs->n_root_chan) { + + /* Lookup an existing root channel for the first two CI phones. + * This replaces the previous O(n_root_chan) linear search with + * an O(1) table lookup. + */ + rhmm = ngs->root_lookup[ciphone][ci2phone]; + + if (rhmm == NULL) { rhmm = &(ngs->root_chan[ngs->n_root_chan]); rhmm->hmm.tmatid = bin_mdef_pid2tmatid(ps_search_acmod(ngs)->mdef, ciphone); /* Begin with CI phone? Not sure this makes a difference... */ @@ -223,10 +229,10 @@ create_search_channels(ngram_search_t *ngs) bin_mdef_pid2ssid(ps_search_acmod(ngs)->mdef, ciphone); rhmm->ciphone = ciphone; rhmm->ci2phone = ci2phone; + /* Cache the newly created root channel for constant-time lookups. */ + ngs->root_lookup[ciphone][ci2phone] = rhmm; ngs->n_root_chan++; } - else - rhmm = &(ngs->root_chan[i]); E_DEBUG("word %s rhmm %d\n", dict_wordstr(dict, w), rhmm - ngs->root_chan); /* Now, rhmm = root channel for w. Go on to remaining phones */