New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace _PyUnicode_CompareWithId with _PyUnicode_EqualToASCIIId #65648
Comments
_PyUnicode_CompareWithId is used exclusively for equality comparisons (after all, identifiers aren't really sortable in a meaningful way; they're isolated values, not a continuum). But because _PyUnicode_CompareWithId maintains the general comparison behavior, not just ==/!=, it serves little purpose; while it checks the return of _PyUnicode_FromId, none of its callers check for failure anyway, so every use could just as well have been: PyUnicode_Compare(left, _PyUnicode_FromId(right)); I've attached a patch that replaces _PyUnicode_CompareWithId with _PyUnicode_CompareWithIdEqual, that:
I've replaced all the uses of the old function I could find, and all unit tests pass. I don't expect to see any meaningful speed ups as a result of the change (the most commonly traversed code that would benefit appears to be the code that creates new classes, and the code that creates reprs for objects), but the goal here is not immediate speed ups, but enabling future speed ups. I am looking into writing a PyDict_GetItem fastpath for looking up identifiers (that would remove the need to perform memory comparisons when the dictionary, as in keyword argument passing, is usually composed of interned keys), possibly in combination with making an identifier based version of PyArg_ParseTupleAndKeywords; with ArgumentClinic, it might become practical to swap in a new argument parser without having to manually change thousands of lines of code, and one of the simplest ways to improve speed would be to remove the overhead of constantly constructing, hashing, and comparing the same keyword strings every time a C function is called. Adding haypo as nosy since he created the original function in bpo-19512. |
Is there someone else who should be looking at this? Having a fast path for identifier comparisons makes sense (and the concept of ordering between essentially unique identifiers makes no sense). It's not part of the public API (limited or not) so I don't think compatibility concerns apply, so it seems like this should be a simple change... |
Note that PyUnicode_CompareWithASCII should be quite fast in most cases (it uses memcmp() on UCS1 strings). |
That said, I think it's quite a good idea. |
This issue is not just about readability or performance. It is about correctness, since none of callers check for failure of _PyUnicode_CompareWithId. I just came to the same problem from other side (replacing PyUnicode_CompareWithASCII with PyUnicode_EqualToASCII or _PyUnicode_CompareWithId). Josh's idea in general LGTM, but there are few details:
If we don't want to rewrite all the uses of _PyUnicode_CompareWithId by adding the code for checking return value for error, we should make new function never failing. _PyUnicode_CompareWithId can fail if the first argument is not valid string or if there is no memory for allocating a Unicode object for identifier. I think it is better to return false value in these circumstances. If the first argument is not string, it isn't equal to an identifier. If it is an invalid string, it can't be equal too. If there is no memory for allocating a Unicode object for identifier, we should fallback to comparing the raw content (like in PyUnicode_CompareWithASCII).
#define _PyUnicode_FROM_ID(id) ((id)->object ? (id)->object : _PyUnicode_FromId(id)) Or rename _PyUnicode_FromId to _PyUnicode_FromIdInternal and _PyUnicode_FROM_ID to _PyUnicode_FromId? This would save us from rewriting a lot of code that uses _PyUnicode_FromId.
|
+1. I think this name is more clear. Serhiy's idea on the implementation sounds good. As for _PyUnicode_FROM_ID, I think it's better for another issue. |
There are yet few subtle details.
|
_PyUnicode_FromId could fail due to memoryerror or bad encoded data. They should be treated differently like PyUnicode_READY. Could we treat it like PyDict_GetItem? If memoryerror happens it's highly possible other parts will fail too. |
_PyUnicode_FromId would not fail due to bad encoded data if use the Latin1 encoding. Seems encoded data always is ASCII. We could use PyErr_WriteUnraisable() for output a warning if it is not. |
Please don't modify _PyUnicode_FromId(), I prefer to keep it as it is, decode from UTF-8. |
Then we should add handling of three special cases: PyUnicode_READY() fails, _PyUnicode_FromId() fails and both fail due to memory error. This means that should be implemented character-by-character encoding of UCS1, UCS2, UCS4 or wchar_t (with possible surrogate pairs) to UTF-8 and comparing with UTF-8 encoded data. |
Callers should be fixed to handle errors. |
Since currently _PyUnicode_CompareWithId is used to compare a unicode with ascii identifiers for all cases, how about introduce a more specific function like _PyUnicode_EqualToASCIIId for this case? We can preserve _PyUnicode_CompareWithId for more general purpose usage. It's much easier to write a error free _PyUnicode_EqualToASCIIId. |
This would make the code of callers more complex for small benefit. And perhaps we will add more callers (if replace PyUnicode_CompareWithASCII with new function).
Great idea Xiang! |
_PyUnicode_EqualToASCIIString() added in bpo-28701 would help in the patch for this issue. |
_PyUnicode_CompareWithId is a private function. We can remove it if it has |
It doesn't. But once there is _PyUnicode_EqualToASCIIId, it's can be rarely used. The new patch implements a version of _PyUnicode_EqualToASCIIId. |
LGTM. Except that _PyUnicode_EqualToASCIIString() could be used for simplicity.
I would left it in maintained releases and removed it in 3.7 (or 3.6?). |
New changeset faf04a995031 by Serhiy Storchaka in branch '3.5': New changeset ff3dacc98b3a by Serhiy Storchaka in branch '3.6': New changeset 765013f71bc4 by Serhiy Storchaka in branch 'default': |
New changeset b995a6950139 by Serhiy Storchaka in branch '3.6': New changeset 9b053d3c74dc by Serhiy Storchaka in branch 'default': |
Thank you Josh and Xiang for your contribution. |
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:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: