Pewarta Proxy

📋 Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

⚙️ Pengaturan Proxy Target

Skip to content

encoding package's normalize_encoding() function is too slow #55531

Closed

Description

@malemburg
BPO 11322
Nosy @malemburg, @gpshead, @jcea, @abalkin, @vstinner, @ezio-melotti, @serhiy-storchaka
Files
  • encoding_normalize_optimize.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2011-02-25.15:55:31.555>
    labels = ['3.7', 'expert-unicode', 'performance']
    title = "encoding package's normalize_encoding() function is too\tslow"
    updated_at = <Date 2022-01-24.23:38:06.218>
    user = 'https://github.com/malemburg'

    bugs.python.org fields:

    activity = <Date 2022-01-24.23:38:06.218>
    actor = 'gregory.p.smith'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Unicode']
    creation = <Date 2011-02-25.15:55:31.555>
    creator = 'lemburg'
    dependencies = []
    files = ['45909']
    hgrepos = []
    issue_num = 11322
    keywords = ['patch']
    message_count = 10.0
    messages = ['129386', '129389', '129460', '129463', '165517', '220630', '220633', '283266', '283271', '283333']
    nosy_count = 8.0
    nosy_names = ['lemburg', 'gregory.p.smith', 'jcea', 'belopolsky', 'vstinner', 'ezio.melotti', 'sdaoden', 'serhiy.storchaka']
    pr_nums = []
    priority = 'normal'
    resolution = None
    stage = None
    status = 'open'
    superseder = None
    type = 'performance'
    url = 'https://bugs.python.org/issue11322'
    versions = ['Python 3.7']

    Linked PRs

    Activity

    1. malemburg commented on Feb 25, 2011

      @malemburg
      MemberAuthor

      I don't know who changed the encoding's package normalize_encoding() function (wasn't me), but it's a really slow implementation.

      The original version used the .translate() method which is a lot faster and can be adapted to work with the Unicode variant of the .translate() method just as well.

      _norm_encoding_map = ('                                              . '
                            '0123456789       ABCDEFGHIJKLMNOPQRSTUVWXYZ     '
                            ' abcdefghijklmnopqrstuvwxyz                     '
                            '                                                '
                            '                                                '
                            '                ')
      
      def normalize_encoding(encoding):
      
          """ Normalize an encoding name.
      
              Normalization works as follows: all non-alphanumeric
              characters except the dot used for Python package names are
              collapsed and replaced with a single underscore, e.g. '  -;#'
              becomes '_'. Leading and trailing underscores are removed.
      
              Note that encoding names should be ASCII only; if they do use
              non-ASCII characters, these must be Latin-1 compatible.
      
          """
          # Make sure we have an 8-bit string, because .translate() works
          # differently for Unicode strings.
          if hasattr(__builtin__, "unicode") and isinstance(encoding, unicode):
              # Note that .encode('latin-1') does *not* use the codec
              # registry, so this call doesn't recurse. (See unicodeobject.c
              # PyUnicode_AsEncodedString() for details)
              encoding = encoding.encode('latin-1')
          return '_'.join(encoding.translate(_norm_encoding_map).split())
    2. abalkin commented on Feb 25, 2011

      @abalkin
      Member

      I don't think the normalize_encoding() function was the culprit for bpo-11303 because I measured timings with timeit which averages multiple runs while normalize_encoding() is called only the one time per encoding spelling due to caching.

    3. vstinner commented on Feb 25, 2011

      @vstinner
      Member

      We should first implement the same algorithm of the 3 normalization functions and add tests for them (at least for the function in normalization):

      • normalize_encoding() in encodings: it doesn't convert to lowercase and keep non-ASCII letters
      • normalize_encoding() in unicodeobject.c
      • normalizestring() in codecs.c

      normalize_encoding() in encodings is more laxist than the two other functions: it normalizes " utf 8 " to 'utf_8'. But it doesn't convert to lowercase and keeps non-ASCII letters: "UTF-8é" is normalized "UTF_8é".

      I don't know if the normalization functions have to be more or less strict, but I think that they should all give the same result.

    4. malemburg commented on Feb 25, 2011

      @malemburg
      MemberAuthor

      STINNER Victor wrote:

      STINNER Victor <victor.stinner@haypocalc.com> added the comment:

      We should first implement the same algorithm of the 3 normalization functions and add tests for them (at least for the function in normalization):

      • normalize_encoding() in encodings: it doesn't convert to lowercase and keep non-ASCII letters
      • normalize_encoding() in unicodeobject.c
      • normalizestring() in codecs.c

      normalize_encoding() in encodings is more laxist than the two other functions: it normalizes " utf 8 " to 'utf_8'. But it doesn't convert to lowercase and keeps non-ASCII letters: "UTF-8é" is normalized "UTF_8é".

      I don't know if the normalization functions have to be more or less strict, but I think that they should all give the same result.

      Please see this message for an explanation of why we have those
      three functions, why they are different and what their application
      space is:

      http://bugs.python.org/issue5902#msg129257

      This ticket is just about the encoding package's codec search
      function, not the other two, and I don't want to change
      semantics, just its performance.

    5. changed the title [-]encoding package's normalize_encoding() function is too slow[/-] [+]encoding package's normalize_encoding() function is too slow[/+] on Feb 25, 2011
    6. serhiy-storchaka commented on Jul 15, 2012

      @serhiy-storchaka
      Member

      I don't know who changed the encoding's package normalize_encoding() function (wasn't me), but it's a really slow implementation.

      See changeset 54ef645d08e4 (Git commit ad5b9de).

    7. BreamoreBoy commented on Jun 15, 2014

      BreamoreBoymannequin
      Mannequin

      What's the status of this issue, as we've lived with this really slow implementation for well over three years?

    8. malemburg commented on Jun 15, 2014

      @malemburg
      MemberAuthor

      On 15.06.2014 15:02, Mark Lawrence wrote:

      What's the status of this issue, as we've lived with this really slow implementation for well over three years?

      I guess it just needs someone to write a patch.

      Note that encoding lookups are cached, so the slowness only
      becomes an issue if you lookup lots of different encodings.

    9. malemburg commented on Dec 15, 2016

      @malemburg
      MemberAuthor

      Thanks for the patch.

      Victor has implemented the function in C, AFAIK, so an even better approach would be to expose that function at the Python level and use it in the encodings package.

    10. vstinner commented on Dec 15, 2016

      @vstinner
      Member

      It seems like encodings.normalize_encoding() currently has no unit test! Before modifying it, I would prefer to see a few unit tests:

      • " utf 8 "
      • "UtF 8"
      • "utf8\xE9"
      • etc.

      Since we are talking about an optimmization, I would like to see a benchmark result before/after. I also would like to test Marc-Andre's idea of exposing the C function _Py_normalize_encoding().

      _Py_normalize_encoding() works on a byte string encoded to Latin1. To implement encodings.normalize_encoding(), we might rewrite the function to work on Py_UCS4 character, or have a fast version on char*, and a more generic version for UCS2 and UCS4?

    11. vstinner commented on Dec 15, 2016

      @vstinner
      Member

      Oh, while reading Mercurial history, I found a note that I wrote:

      "It's not exactly the same than encodings.normalize_encoding(): the C function also converts to lowercase."

      IHMO it's fine to modify encodings.normalize_encoding() to also convert to lower-case.

    12. transferred this issue fromon Apr 10, 2022
    13. 12 remaining items

    14. StanFromIreland commented on Jul 22, 2025

      @StanFromIreland
      Member

      It is only called once per encoding.

      Note: It is then called for all aliases.

    15. serhiy-storchaka commented on Jul 22, 2025

      @serhiy-storchaka
      Member

      It is called once for every encoding name. How many different encoding names do you use in a program? Current normalize_encoding('utf-8') takes 0.5 microseconds on my computer. You need to look up millions different encoding names to make it burdensome, and you will have memory issue (due to the growing cache) before this.

    16. malemburg commented on Jul 27, 2025

      @malemburg
      MemberAuthor

      What is the problem if normalize_encoding() is slow? It is only called once per encoding. After that, the codec is cached. It does not affect performance of real programs.

      Yes, it is slow (and it was much faster before until someone changed the function) and yes, if the encoding is found, the information is cached by the codec registry.

      But that's not the point. We should not have multiple different variants of encoding name normalization in the stdlib.

      It is better to stick with the C implementation _Py_normalize_encoding() as the single source or truth.

    17. malemburg commented on Jul 27, 2025

      @malemburg
      MemberAuthor

      I've also had a look at the flow of things:

      The search functions in the encodings package are called by the codec lookup function (in Python/codecs.c) with an already normalized encoding name.

      The extra normalization happening in the encodings package is not really needed, AFAICT.

      I think we can remove the call from the search function and perhaps only leave the normalize_encoding() function as an alias for the new C function. We should then document that the search function in the encodings package needs to be called with a normalized encoding name.

      I doubt this would cause much breakage and it does make lookups a tad faster for applications that have to deal with lots of different encodings (e.g. mail processors or data import functions).

    18. serhiy-storchaka commented on Jul 28, 2025

      @serhiy-storchaka
      Member

      I think there were good reasons to have different normalizations because they have different purpose.

      Initially codecs.lookup() only converted the encoding name to lower case (ASCII-only letters). It was pretty safe, because encoding names are case-insensitive. In 9e896b3 is started also replacing spaces with hyphens. I do not know reasons for this, perhaps because of "US ASCII"?

      Then the name is passed to search functions. encodings.search_function() is one of the implementations of a search function. It needs to convert the encoding name to the module name, it does it by calling encodings.normalize_encoding(). Any filesystem specific characters (/, \, :, non-ASCII and control characters, etc) should be replaced. Safe bet is to leave only ASCII letters, digits, underscore and a dot (which is a separator between module name components). This normalization is only needed for that particular implementation, which reads encodings from Python modules. Other implementations, like iconv, does not need it (see #88886).

      _Py_normalize_encoding() was used to normalize few builtin encoding names ("utf-8", "iso-8859-1", etc) to fasten the check. In general, it should combine two above transformations (convertion to lower case and replacing some separators), because it bypasses them, but this is not necessary. In worst case you will use a slow path for unusual spelling of builtin encoding name.

      Transformation in codecs.lookup() should be fast, and it should not remove meaningful parts from the name. I am not even sure that any transformation is needed here. _Py_normalize_encoding() should be super fast, but it only need to work for builtin encoding names and their common variations. So "ISO_8859-1" should be accepted as well as "ISO-8859-1", but accepting "ISO/8859:1" is an undesirable side effect. encodings.search_function() needs to convert encoding name to module name. It should normalize any common separators, but I think that it is better to return None for non-ASCII names or control characters.

    19. malemburg commented on Jul 28, 2025

      @malemburg
      MemberAuthor

      There are two parts to all this:

      • The codec registry together with its lookup function
      • The encoding package search function

      The codec lookup function used to do only partial normalization, to make sure that search functions don't get to see weird encoding names. The encoding package search function additionally had to make sure that the encoding names are converted to valid Python module names (and additionally uses an alias table helping with this).

      Other search functions are free to apply different normalizations.

      More recently, the encoding package search function was converted to an internal C function by Victor: _Py_normalize_encoding(). This function is used in the codec lookup function for normalization, replacing the previous version. The change hasn't caused any issues as far as I am aware.

      The reason I am pushing for having just a single normalization function in the stdlib is to simplify the setup and have a central place to define what encoding normalization means. By exposing the _Py_normalize_encoding() function as a Python callable C function, we can achieve this.

      And going one step further, we can even remove the normalization call in the encoding package's search function to the normalization function, since it the encoding name will already have been normalized in the same way by the calling codec lookup function. It doesn't make sense to call the same normalization function twice every time you want to find a codec in the encodings package.

      Note that this is a new feature and not something we can or should backport.

    20. serhiy-storchaka commented on Jul 28, 2025

      @serhiy-storchaka
      Member

      The change hasn't caused any issues as far as I am aware.

      It caused. See #88886.

      I am inclined to the opposite opinion -- remove normalization in the codec lookup function, leave this to the search function. So the third-party search function should see weird encoding names and handle them as they prefer. "foo-bar" and "foo+bar" can be different encodings, or only one of them is correct and we need to know the original name.

      Also, non-ASCII characters and control codes should not be silently ignored. They should either cause immediate error or simply be passed to the search functions as is. If they not found matching encoding, we will get a standard error. There are also encodings with official non-ASCII name (e.g. https://en.wikipedia.org/wiki/KOI-7).

    21. malemburg commented on Jul 28, 2025

      @malemburg
      MemberAuthor

      You do have a point there, but this is starting to turn into a partial redesign of the way codec lookups were handled in recent Python versions.

      The alternative approach would be to:

      • not normalize encoding names in the codecs lookup function and pass them on to the search functions as-is
      • replace the encodings package normalization function with the new C function
      • update the documentation accordingly

      The internal C function can continue to be used in short cuts to the resp. codec C APIs as is done now.

      This should then also resolve the python-iconv package error, I guess.

    22. malemburg commented on Jul 29, 2025

      @malemburg
      MemberAuthor

      Ok, so once #137167 has landed, we should then expose the internal _Py_normalize_encoding() function in Python and use it in the encodings package's normalize_encoding() function.

    23. StanFromIreland commented on Jul 29, 2025

      @StanFromIreland
      Member

      Ok I will update the PR once it is merged.

    24. added a commit that references this issue on Oct 30, 2025
    25. added a commit that references this issue on Dec 6, 2025
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Labels

    extension-modulesC modules in the Modules dirperformancePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directorytopic-unicodetype-featureA feature request or enhancement

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions