| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
You microbenchmark is only testing <. What happens if you have a mix of comparisons? |
Sorry, something went wrong.
| PyObject *left = SECOND(); | ||
| DEOPT_IF(!PyUnicode_CheckExact(left), COMPARE_OP); | ||
| DEOPT_IF(!PyUnicode_CheckExact(right), COMPARE_OP); | ||
| DEOPT_IF(!PyUnicode_IS_READY(left), COMPARE_OP); |
There was a problem hiding this comment.
Is this necessary? If left == right then they are equal regardless of whether the string is ready or not.
Sorry, something went wrong.
There was a problem hiding this comment.
I expect that almost all strings are ready. I suppose the alternative is
DEOPT_IF(!PyUnicode_CheckExact(left), COMPARE_OP);
DEOPT_IF(!PyUnicode_CheckExact(right), COMPARE_OP);
int res = 1;
if (left != right) {
DEOPT_IF(!PyUnicode_IS_READY(left), COMPARE_OP);
DEOPT_IF(!PyUnicode_IS_READY(left), COMPARE_OP);
res = comare_the_strings_assuming_theyre_ready(left, right);
}
is that what you mean?
Sorry, something went wrong.
There was a problem hiding this comment.
Yes. Assuming that the identity shortcut is worthwhile, then it makes sense to shortcut as much work as possible.
You are also missing out of the quick inequality test using lengths len(a) != len(b) implies that a != b.
Maybe wrap unicode_compare_eq in an equality function that readies the strings (I'm surprised that no such thing exists) and use that?
int res = 1;
if (left != right) {
res = _PyUnicode_Equal(left, right);
}
Sorry, something went wrong.
|
There are a lot of branches in the int and float specializations, which is not good for performance. Because neither comparison can fail (ints have a total ordering, and floats have a total ordering plus a special not-equal-if-either-is-a-nan result) we can avoid the branching by masking the result of a comparison with a code for the operator. Also see my comment on the issue about avoiding the need to push and pop objects. |
Sorry, something went wrong.
|
Here's one idea for ints: cmp = ... // same as sign = (cmp > 0) - (cmp < 0) int sign = (int)((cmp>>63) - ((-cmp)>>63)); int flags = 1 << (sign + 1); int res = flags & mask; // mask: 0b001 is negative, 0b010 is zero, 0b100 is positive, etc. next_instruction() if (res) JUMP_BY(oparg); Then the mask can be chosen depending on whether the following instruction is pop_jump_if_true/false. There would still be the branch depending on whether Py_SIZE(left) - Py_SIZE(right) == 0 and a loop over digits and a branch to check for Py_SIZE(left) < 0, but at least it would save the switch over oparg, the push/pop/incref/decref, and one DISPATCH(). Maybe the float equivalent would deopt if either arg is NaN and then do int sign = (dleft > dright) - (dleft < dright). |
Sorry, something went wrong.
|
These three combined opcodes have slightly worse hit percentages on some benchmarks. |
Sorry, something went wrong.
|
Newer Pyperformance numbers (Note that my setup is not very stable): Slower (20):
Faster (17):
Benchmark hidden because not significant (18): chaos, crypto_pyaes, dulwich_log, json_dumps, json_loads, logging_format, raytrace, regex_compile, regex_v8, richards, sympy_expand, sympy_integrate, sympy_sum, sympy_str, xml_etree_parse, xml_etree_iterparse, xml_etree_generate, xml_etree_process Geometric mean: 1.00x slower |
Sorry, something went wrong.
|
Newest microbenchmarks: pretty good, even including more comparisons from pyperf import Runner
runner = Runner()
runner.timeit(
"float_loop",
setup="arr = [float(x) for x in range(5_000_000)]",
stmt="for x in arr:\n"
" if x > 1e6 or x == 1e6 or x < 0.0:\n"
" break\n",
)
runner.timeit(
"int_loop",
setup="arr = list(range(5_000_000))",
stmt="for i in arr:\n"
" if i > 6_000_000 or i == 6_000_000 or i < 0:\n"
" break\n",
)
runner.timeit(
"str_loop",
setup="arr = ['Py'] * 5_000_000",
stmt="for word in arr:\n"
" if word == 'Python' or word != 'Py':\n"
" break\n",
)
"""
float_loop: Mean +- std dev: [micro_main] 41.5 ms +- 0.4 ms -> [micro_combined3] 23.7 ms +- 0.8 ms: 1.75x faster
int_loop: Mean +- std dev: [micro_main] 202 ms +- 2 ms -> [micro_combined3] 133 ms +- 1 ms: 1.52x faster
str_loop: Mean +- std dev: [micro_main] 140 ms +- 7 ms -> [micro_combined3] 88.1 ms +- 3.9 ms: 1.59x faster
Geometric mean: 1.62x faster
""" |
Sorry, something went wrong.
|
Slower (19):
Faster (20):
Benchmark hidden because not significant (16): chaos, float, logging_format, pickle, pidigits, python_startup_no_site, regex_compile, regex_dna, richards, sympy_integrate, telco, tornado_http, unpickle, xml_etree_parse, xml_etree_generate, xml_etree_process Geometric mean: 1.00x slower |
Sorry, something went wrong.
|
My results are less noisy and show a ~1% speedup. Given that some of the largest improvements are from notoriously noisy benchmarks, 1% may be overstating the speedup but it does seem to be real. |
Sorry, something went wrong.
| double dleft = PyFloat_AS_DOUBLE(left); | ||
| double dright = PyFloat_AS_DOUBLE(right); | ||
| int sign = (dleft > dright) - (dleft < dright); | ||
| DEOPT_IF(isnan(dleft), COMPARE_OP); |
There was a problem hiding this comment.
Potentially we could use the fact that nans are not equal to everything including themselves to add a fourth bit to the mask. Probably leave that for another PR as the maths starts getting a bit convoluted.
Sorry, something went wrong.
| goto success; | ||
| } | ||
| } | ||
| SPECIALIZATION_FAIL(COMPARE_OP, SPEC_FAIL_OTHER); |
There was a problem hiding this comment.
I'd like more information here.
Is this a builtin class, or a Python class?
Does it override the comparison, or (in the case of ==/!==) does it rely on the default identity comparison?
Doesn't need to be in this PR, though.
Sorry, something went wrong.
|
Looks good in general. The approach seems sound and gives a speedup. There a couple of things that need to be tightened up, and a couple of possible enhancements (for future PRs). |
Sorry, something went wrong.
|
👍 |
Sorry, something went wrong.
⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️Hi! The buildbot AMD64 Arch Linux Asan Debug 3.x has failed when building commit 03768c4. What do you need to do:
You can take a look at the buildbot page here: https://buildbot.python.org/all/#builders/585/builds/782 Summary of the results of the build (if available): Click to see traceback logsremote: Enumerating objects: 21, done.
remote: Counting objects: 5% (1/20)
remote: Counting objects: 10% (2/20)
remote: Counting objects: 15% (3/20)
remote: Counting objects: 20% (4/20)
remote: Counting objects: 25% (5/20)
remote: Counting objects: 30% (6/20)
remote: Counting objects: 35% (7/20)
remote: Counting objects: 40% (8/20)
remote: Counting objects: 45% (9/20)
remote: Counting objects: 50% (10/20)
remote: Counting objects: 55% (11/20)
remote: Counting objects: 60% (12/20)
remote: Counting objects: 65% (13/20)
remote: Counting objects: 70% (14/20)
remote: Counting objects: 75% (15/20)
remote: Counting objects: 80% (16/20)
remote: Counting objects: 85% (17/20)
remote: Counting objects: 90% (18/20)
remote: Counting objects: 95% (19/20)
remote: Counting objects: 100% (20/20)
remote: Counting objects: 100% (20/20), done.
remote: Compressing objects: 10% (1/10)
remote: Compressing objects: 20% (2/10)
remote: Compressing objects: 30% (3/10)
remote: Compressing objects: 40% (4/10)
remote: Compressing objects: 50% (5/10)
remote: Compressing objects: 60% (6/10)
remote: Compressing objects: 70% (7/10)
remote: Compressing objects: 80% (8/10)
remote: Compressing objects: 90% (9/10)
remote: Compressing objects: 100% (10/10)
remote: Compressing objects: 100% (10/10), done.
remote: Total 21 (delta 10), reused 11 (delta 10), pack-reused 1
From https://github.com/python/cpython
* branch main -> FETCH_HEAD
Note: switching to '03768c4d139df46212a091ed931aad03bec18b57'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 03768c4d13 [bpo-45885](https://bugs.python.org/issue45885): Specialize COMPARE_OP (GH-29734)
Switched to and reset branch 'main'
renaming build/scripts-3.11/pydoc3 to build/scripts-3.11/pydoc3.11
renaming build/scripts-3.11/idle3 to build/scripts-3.11/idle3.11
renaming build/scripts-3.11/2to3 to build/scripts-3.11/2to3-3.11
renaming build/scripts-3.11/pydoc3 to build/scripts-3.11/pydoc3.11
renaming build/scripts-3.11/idle3 to build/scripts-3.11/idle3.11
renaming build/scripts-3.11/2to3 to build/scripts-3.11/2to3-3.11
renaming build/scripts-3.11/pydoc3 to build/scripts-3.11/pydoc3.11
renaming build/scripts-3.11/idle3 to build/scripts-3.11/idle3.11
renaming build/scripts-3.11/2to3 to build/scripts-3.11/2to3-3.11
make: *** [Makefile:1746: buildbottest] Terminated
Cannot open file '/buildbot/buildarea/3.x.pablogsal-arch-x86_64.asan_debug/build/test-results.xml' for upload |
Sorry, something went wrong.
|
@sweeneyde |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
https://bugs.python.org/issue45885