@@ -495,19 +495,80 @@ get_decomp_record(PyObject *self, Py_UCS4 code,
495495#define NCount (VCount*TCount)
496496#define SCount (LCount*NCount)
497497
498+ /* Small combining runs are usually cheaper with insertion sort. */
499+ #define CANONICAL_ORDERING_COUNTING_SORT_THRESHOLD 20
500+
501+ static void
502+ canonical_ordering_sort_insertion (int kind , void * data ,
503+ Py_ssize_t start , Py_ssize_t end )
504+ {
505+ for (Py_ssize_t i = start + 1 ; i < end ; i ++ ) {
506+ Py_UCS4 code = PyUnicode_READ (kind , data , i );
507+ unsigned char combining = _getrecord_ex (code )-> combining ;
508+ Py_ssize_t j = i ;
509+
510+ while (j > start ) {
511+ Py_UCS4 previous = PyUnicode_READ (kind , data , j - 1 );
512+ if (_getrecord_ex (previous )-> combining <= combining ) {
513+ break ;
514+ }
515+ PyUnicode_WRITE (kind , data , j , previous );
516+ j -- ;
517+ }
518+ if (j != i ) {
519+ PyUnicode_WRITE (kind , data , j , code );
520+ }
521+ }
522+ }
523+
524+ static void
525+ canonical_ordering_sort_counting (int kind , void * data ,
526+ Py_ssize_t start , Py_ssize_t end ,
527+ Py_UCS4 * sortbuf )
528+ {
529+ Py_ssize_t counts [256 ] = {0 };
530+ Py_ssize_t run_length = end - start ;
531+ Py_ssize_t total = 0 ;
532+
533+ for (Py_ssize_t i = start ; i < end ; i ++ ) {
534+ Py_UCS4 code = PyUnicode_READ (kind , data , i );
535+ unsigned char combining = _getrecord_ex (code )-> combining ;
536+ counts [combining ]++ ;
537+ }
538+
539+ for (size_t i = 0 ; i < Py_ARRAY_LENGTH (counts ); i ++ ) {
540+ Py_ssize_t count = counts [i ];
541+ counts [i ] = total ;
542+ total += count ;
543+ }
544+
545+ /* Reuse counts[] as the next output slot for each CCC. */
546+ for (Py_ssize_t i = start ; i < end ; i ++ ) {
547+ Py_UCS4 code = PyUnicode_READ (kind , data , i );
548+ unsigned char combining = _getrecord_ex (code )-> combining ;
549+ sortbuf [counts [combining ]++ ] = code ;
550+ }
551+ for (Py_ssize_t i = 0 ; i < run_length ; i ++ ) {
552+ PyUnicode_WRITE (kind , data , start + i , sortbuf [i ]);
553+ }
554+ }
555+
498556static PyObject *
499557nfd_nfkd (PyObject * self , PyObject * input , int k )
500558{
501559 PyObject * result ;
502560 Py_UCS4 * output ;
503561 Py_ssize_t i , o , osize ;
504- int kind ;
505- const void * data ;
562+ int input_kind , result_kind ;
563+ const void * input_data ;
564+ void * result_data ;
506565 /* Longest decomposition in Unicode 3.2: U+FDFA */
507566 Py_UCS4 stack [20 ];
508567 Py_ssize_t space , isize ;
509568 int index , prefix , count , stackptr ;
510569 unsigned char prev , cur ;
570+ Py_UCS4 * sortbuf = NULL ;
571+ Py_ssize_t sortbuflen = 0 ;
511572
512573 stackptr = 0 ;
513574 isize = PyUnicode_GET_LENGTH (input );
@@ -527,11 +588,11 @@ nfd_nfkd(PyObject *self, PyObject *input, int k)
527588 return NULL ;
528589 }
529590 i = o = 0 ;
530- kind = PyUnicode_KIND (input );
531- data = PyUnicode_DATA (input );
591+ input_kind = PyUnicode_KIND (input );
592+ input_data = PyUnicode_DATA (input );
532593
533594 while (i < isize ) {
534- stack [stackptr ++ ] = PyUnicode_READ (kind , data , i ++ );
595+ stack [stackptr ++ ] = PyUnicode_READ (input_kind , input_data , i ++ );
535596 while (stackptr ) {
536597 Py_UCS4 code = stack [-- stackptr ];
537598 /* Hangul Decomposition adds three characters in
@@ -596,35 +657,66 @@ nfd_nfkd(PyObject *self, PyObject *input, int k)
596657 PyMem_Free (output );
597658 if (!result )
598659 return NULL ;
660+
599661 /* result is guaranteed to be ready, as it is compact. */
600- kind = PyUnicode_KIND (result );
601- data = PyUnicode_DATA (result );
662+ result_kind = PyUnicode_KIND (result );
663+ result_data = PyUnicode_DATA (result );
602664
603- /* Sort canonically. */
665+ /* Sort each consecutive combining-character run canonically. */
604666 i = 0 ;
605- prev = _getrecord_ex (PyUnicode_READ (kind , data , i ))-> combining ;
606- for (i ++ ; i < PyUnicode_GET_LENGTH (result ); i ++ ) {
607- cur = _getrecord_ex (PyUnicode_READ (kind , data , i ))-> combining ;
608- if (prev == 0 || cur == 0 || prev <= cur ) {
609- prev = cur ;
667+ while (i < o ) {
668+ Py_ssize_t run_length , run_start ;
669+ int needs_sort = 0 ;
670+
671+ Py_UCS4 ch = PyUnicode_READ (result_kind , result_data , i );
672+ prev = _getrecord_ex (ch )-> combining ;
673+ if (prev == 0 ) {
674+ i ++ ;
610675 continue ;
611676 }
612- /* Non-canonical order. Need to switch *i with previous. */
613- o = i - 1 ;
614- while (1 ) {
615- Py_UCS4 tmp = PyUnicode_READ (kind , data , o + 1 );
616- PyUnicode_WRITE (kind , data , o + 1 ,
617- PyUnicode_READ (kind , data , o ));
618- PyUnicode_WRITE (kind , data , o , tmp );
619- o -- ;
620- if (o < 0 )
621- break ;
622- prev = _getrecord_ex (PyUnicode_READ (kind , data , o ))-> combining ;
623- if (prev == 0 || prev <= cur )
677+
678+ run_start = i ++ ;
679+ while (i < o ) {
680+ Py_UCS4 ch = PyUnicode_READ (result_kind , result_data , i );
681+ cur = _getrecord_ex (ch )-> combining ;
682+ if (cur == 0 ) {
624683 break ;
684+ }
685+ if (prev > cur ) {
686+ needs_sort = 1 ;
687+ }
688+ prev = cur ;
689+ i ++ ;
690+ }
691+ if (!needs_sort ) {
692+ continue ;
693+ }
694+
695+ run_length = i - run_start ;
696+ if (run_length < CANONICAL_ORDERING_COUNTING_SORT_THRESHOLD ) {
697+ canonical_ordering_sort_insertion (result_kind , result_data ,
698+ run_start , i );
699+ continue ;
625700 }
626- prev = _getrecord_ex (PyUnicode_READ (kind , data , i ))-> combining ;
701+
702+ if (run_length > sortbuflen ) {
703+ Py_UCS4 * new_sortbuf = PyMem_Resize (sortbuf ,
704+ Py_UCS4 ,
705+ run_length );
706+ if (new_sortbuf == NULL ) {
707+ PyErr_NoMemory ();
708+ PyMem_Free (sortbuf );
709+ Py_DECREF (result );
710+ return NULL ;
711+ }
712+ sortbuf = new_sortbuf ;
713+ sortbuflen = run_length ;
714+ }
715+
716+ canonical_ordering_sort_counting (result_kind , result_data ,
717+ run_start , i , sortbuf );
627718 }
719+ PyMem_Free (sortbuf );
628720 return result ;
629721}
630722
0 commit comments