GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-156695: Improve accuracy of complex div and pow by hpkfft · Pull Request #156968 · python/cpython Conversation
|
| Benchmark | main | patch |
|---|---|---|
| cdiv(z, w) | 13.1 ns | 11.1 ns: 1.19x faster |
| cpow(z, -5) | 26.3 ns | 27.0 ns: 1.03x slower |
| cpow(z, -4) | 25.9 ns | 26.6 ns: 1.03x slower |
| cpow(z, -3) | 25.5 ns | 25.1 ns: 1.01x faster |
| cpow(z, -1) | 26.5 ns | 24.7 ns: 1.07x faster |
| cpow(z, 1) | 22.5 ns | 21.3 ns: 1.06x faster |
| cpow(z, 2) | 22.8 ns | 23.0 ns: 1.01x slower |
| cpow(z, 3) | 23.3 ns | 23.5 ns: 1.01x slower |
| cpow(z, 4) | 24.5 ns | 24.8 ns: 1.01x slower |
| cpow(z, 5) | 24.4 ns | 25.5 ns: 1.04x slower |
| Geometric mean | (ref) | 1.02x faster |
Benchmark hidden because not significant (1): cpow(z, -2)
The internal function c_square doesn't seem helpful. Probably, it should just be deleted.
Consider special values (inf, nan) and add tests.
Also, some current tests are designed to verify the current implementation and need to be updated accordingly.
If a core developer is interested in working with me with the intention of then merging this, please let me know.
It's been fun, but I don't want to invest more time otherwise.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
This has merge conflict. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Few comments.
This catch-all pr seems unmanageable for me so far.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
|
||
| Py_complex | ||
| _Py_c_sum(Py_complex a, Py_complex b) | ||
| _Py_c_sum(Py_complex a, Py_complex b) // public API (soft deprecated) |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This just makes diff larger.
It's apparent for a reader, that this function is public. Then reader can take look on the header file and find this comment:
/* Operations on complex numbers (soft deprecated since Python 3.15). */ PyAPI_FUNC(Py_complex) _Py_c_sum(Py_complex, Py_complex); PyAPI_FUNC(Py_complex) _Py_c_diff(Py_complex, Py_complex); PyAPI_FUNC(Py_complex) _Py_c_neg(Py_complex); PyAPI_FUNC(Py_complex) _Py_c_prod(Py_complex, Py_complex); PyAPI_FUNC(Py_complex) _Py_c_quot(Py_complex, Py_complex); PyAPI_FUNC(Py_complex) _Py_c_pow(Py_complex, Py_complex); PyAPI_FUNC(double) _Py_c_abs(Py_complex);
IMO, this should be reverted.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I disagree it's apparent for a reader. Without the comment _Py_c_sum() and _Py_cr_sum() look the same. But the former is public, and the latter is private. It's confusing to me, at least.
I want to avoid setting errno. Look at complex_div(), which now calls c_quot(a, b) to avoid calling _Py_c_quot(a, b) because that sets errno. But complex_div() does call _Py_rc_quot(), which is private, so it doesn't set errno.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I disagree it's apparent for a reader.
I don't see any excuse for a reader, who can't take look on header files, where those functions are declared.
I want to avoid setting errno.
I don't think this is a bad, per se. #156145 is not for this. That issue suggests not rely on errno set by libm's functions. Like #156887 does, for example.
You suggest to invent essentially a new private API layer. What are arguments for?
BTW, if we will do this - there be a plenty questions. Pass in/out arguments by reference? Return error status? Rely on floating-point exceptions instead? One possible new place for using low-level private API is bytecode specialization (as we do for floats/ints). I would expect much larger speedup from this.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is a bad, per se.
On d.p.o, Mark called it evil, and I agree with him. I respect your right to have a different opinion.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
On d.p.o, Mark called it evil, and I agree with him.
I suspect someone misinterpret his words. Relevant quote is:
float_pow in particular seems unnecessarily evil in that it’s not just reading that global state, but also writing to it (code ref) to force overflow exceptions, so even on boxes whose libm doesn’t touch errno (e.g., macOS), correct operation still relies on writes and reads of errno.
Detailed explanation of the issue in the d.p.o thread is here.
I don't think this fits errno usage pattern e.g. for most of cmath's functions, like atanh(). Here we explicitly set errno, regardless on behavior of libm's functions.
Evil is some particular way of working with errno...
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| static inline bool | ||
| _Py_c_isnan(Py_complex a) | ||
| { | ||
| return isnan(a.real) || isnan(a.imag); | ||
| } | ||
|
|
||
| static inline bool | ||
| _Py_c_isinf(Py_complex a) | ||
| { | ||
| return isinf(a.real) || isinf(a.imag); | ||
| } | ||
|
|
||
| static inline bool | ||
| _Py_c_isfinite(Py_complex a) | ||
| { | ||
| return isfinite(a.real) && isfinite(a.imag); | ||
| } | ||
|
|
||
| static inline bool | ||
| _Py_c_iszero(Py_complex a) | ||
| { | ||
| return a.real == 0.0 && a.imag == 0.0; | ||
| } |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
That, probably, can go to a separate pr. I can do this, if core devs are interested.
This pr touches many things and it's good to make it in some more manageable pieces.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Yes, I would like it if you put this in a separate PR. Then I'll merge with main, and my diff will be smaller!
Also, it's your idea and your work. (I thought it had been merged already, and I was using it locally, so I didn't want to make the effort to revert it locally before pushing this PR.)
Honestly, I've lost track of all the issues and PRs that affect complexobject.c....
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| r.real = r.imag = Py_NAN; | ||
| } | ||
|
|
||
| #else // USE_EFFICIENCT_SCALING_DIV_ALGORITHM |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
That's it. I would prefer, if you reduce pr just to deal with underflows in the division. I.e. to fix referenced issue.
BTW, in CPython we assume IEEE doubles (at least as binary format), I think you can use this algorithm unconditionally.
CC @Aniketsy, perhaps you can try this approach in your pr.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| int64_t ar, ai, br, bi; // Assumes IEEE 754 binary64. | ||
| memcpy(&ar, &a.real, 8); | ||
| memcpy(&ai, &a.imag, 8); | ||
| memcpy(&br, &b.real, 8); | ||
| memcpy(&bi, &b.imag, 8); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
You can use instead a union type, as in article.
BTW, what are reason for modification of the cdiv() algorithm? I would prefer original version, to simplify review. Unless there is a huge performance boost.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
The motivation for this cdiv() algorithm is better accuracy. It does not make sense to me to leave a large worst-case error for cdiv() while changing cpow() to be more accurate. Complex division is more fundamental and more widely used than complex power. By improving the accuracy of cdiv(), one gets improved accuracy for cpow(z, -1) without any extra work.
The performance improvement (if I may make a guess) is from eliminating errno. Though, the new cdiv() algorithm has two floating-point divisions instead of three, so that may help performance a little.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
The motivation for this cdiv() algorithm is better accuracy.
I understand.
My question was: why you aren't using original algorithm, presented in quoted article?
The performance improvement (if I may make a guess) is from eliminating errno.
Another good reason to leave errno stuff for a different pr.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| int saved_errno = errno; | ||
| r = c_abs(z, &e); | ||
| // Sets errno = ERANGE on overflow, else errno is unchanged. | ||
| errno = (e) ? e : saved_errno; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
As observed by Serhiy, this change uncovers an issue in cmath regarding underflow:
>>> cmath.polar(complex(1e300, 1e-320))
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
cmath.polar(complex(1e300, 1e-320))
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
OverflowError: math range error
This can be fixed in cmath_polar_impl() in cmathmodule.c. This is a note to myself before changing this PR from a draft.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
Spurious ZeroDivisionError for complex powers when the phase is infinite
There was an error while loading. Please reload this page.