Repository navigation
Improve performance in jitcdde_template.c - #67
Conversation
If I am not mistaken, this is not a beneficial change. The Horner scheme is best if you have the coefficients of the polynomial in its canonical form. But that’s not what we have here. Instead we have states and derivative at certain points, from which you first need to compute the coefficients of the polynomial, which itself takes more time than evaluating the polynomial itself. The reason why this evaluation of the polynomial is so unusual is that it was the best I could find for this case. The best way to compare performances here is to look at the total number of additions (including subtractions) and multiplications and here my code beats yours. For example for getting the state, I have seven additions and multiplications each, while you have nine additions and eight multiplications. I am not super sure whether multiplications by 2 are somewhat cheaper, so you might redeem a bit of performance here, but I don’t see it being enough. Now, where you might save some runtime in evaluation is if you store the coefficients of the polynomial for a pair of anchors that will be re-used later. However, I would estimate that it won’t make up for the runtime lost by administrating this storage, let alone the pains to implement something like this.
I haven’t tried to understand the benchmarks in detail yet and haven’t checked all the changes, but I expect this to be a purely beneficial change. At worst, it doesn’t do anything. I guess that I didn’t implement it like this from the start, because I hoped the compiler would take care of this, as anchors are rather lightweight structs. Seems like I was wrong about that. |
Yeah, agreed, I'll remove that part. I haven't managed to measure any impact that didn't look like noise there.
IIUC, the speedup from this should only show up for larger systems due to the typedef struct anchor
{
double time;
double state[{{n}}];
double diff[{{n}}];
struct anchor * next;
struct anchor * previous;
...
} anchor;I've very briefly tried it with the |
e456578 to
d0d11ef
Compare
This has two performance improvements:
-O3does it anyway).I've tested the changes on the
examples/kuramoto_network.pyexample (using hyperfine to do full timing) and using this benchmark (to time separate parts). The results can be seen below.This was run on my machine (AMD Ryzen AI 9 HX 370) and Python 3.14 using GCC and SymEngine 0.14.0. The huge amount of time is spent in the compilation step, but not sure we can do much there (between symengine and gcc doing work). The improvement I was hoping for is in the
runpart, which seems pretty nice 😁What do you think? Also checked that there are no new leaks reported by valgrind.
Before
After
I'm not sure why the
hyperfinerun was so much faster after, since it really should be dominated by the compilation part.Wilson-Cowan
I've also tested this on my own code (a similarly sized Wilson-Cowan system with fewer delays) and it seems to improve the situation quite a lot. When running a Lyapunov exponent calculation, I'm seeing
This is a bit dramatic and I'm not quite sure why it improves it so much, but it seems repeatable with the changes on this branch. I can make an smaller script for it, if you think it's useful.