Fix X86 MulDecomposer performance - #8081
Conversation
32964c4 to
be653e9
Compare
|
@vijaysun-omr please review/test/merge. once that is done, i will backport it to 0.57 repo. |
|
I will request a review from @0xdaryl on this, even as I do the testing based on looking at the (relatively safe) changes |
|
Jenkins build all |
| ; | ||
| } | ||
|
|
||
| if (j > 3) { // counting from 0, this means there are more than 3 decomposed operations |
There was a problem hiding this comment.
Maybe this "3" should be named via an macro constant, so it appears to be less "magic".
Having the ability to change this value via an env var to experiment would also be handy
|
@0xdaryl there might be some historical background re X86 codegen doing multiply-by-4 through add twice. in general, add twice is slower than a single shift-left-by-2. please provide the historical reason of doing that and whether it is still relevant, before i make these "corrections". |
aa55963 to
4c3fd1f
Compare
4c3fd1f to
dba4610
Compare
|
I measured this on multiplication by 52 which is what I saw in one of our benchmarks. While performance was much better than the original sequence, it was still slower by 1-3% it looked like, vs the performance with the env var set to disable all this logic and just just an x86 |
|
Two possible approaches going forward:
|
This code really needs to be looked at through a lens from 2003, when it was first tuned and contributed and then largely left alone for the past 20 years. In the Pentium 4 days, immediate shifts were more expensive than adds. I looked back at some historical documentation and found that SHL by a constant had a latency of 4-5 cycles, but a single ADD register is only a 0.5-1 .5 cycles (therefore two adds are cheaper than a single constant shift). This might explain some of the tuning decisions here. I think a handful of people may have poked at bits of this code over the years, but a comprehensive review of the tuning decisions for modern x86 processors wasn't done and seems warranted. |
|
I suggest a staged approach where
is done first. @zl-wang could you please make this change and ready this PR for delivery ? |
|
sure ... let me get onto it today or tomorrow |
20fba2d to
c62e70e
Compare
|
I used the C program below and gcc -O3 to verify our mul decomposition strategy: (compare what is generated for each mul operation) a few observations:
|
|
Thanks, the current implementation does quite well in my runs on some relevant benchmarks. I have asked for further confirmation using the state of this PR as it stands. If that also checks out, I feel it should be ready for a final review from @0xdaryl |
|
Jenkins build all |
|
@0xdaryl is this ready for merging ? Please go ahead when you are ready |
0xdaryl
left a comment
There was a problem hiding this comment.
Other than the comment removal, I think this is fine.
| // _cg); | ||
| // } | ||
| // } else | ||
| if (shiftAmount != 0) { |
There was a problem hiding this comment.
Can you remove the commentary and the commented-out code? As mentioned in an earlier PR comment, this was a point-in-time decision based on the technology at the time that people have tinkered with over the years for various hardware evolutions.
I think it is fair to remove it and just use your approach by default.
We ran into performance issues when a multiply(by a constant) operation is decomposed into 5 simple operations. On nearly all modern X86 implementations (in the last decade at least), integer multiply instruction has a latency of 3 cycles. Assuming lea/add/sub/shl simple operations having a single cycle latency (all true from web queries), you can see why the performance degraded when it is decomposed (usually the sequence has back to back register dependency). All decomposition solutions where no performance benefit is expected are deleted. In a few solutions, the sequences were made better (shorter). Also, general Add sequence for shifting is disabled. Adding a better testing condition (missed in the original code to begin with). The condition to use the solutions available is tightened with considerations of eventual performance benefit. Comment and disabled code are removed.
c62e70e to
bce5c2a
Compare
|
Jenkins build xlinux,win,osx |
We ran into performance issues when a multiply(by a constant) operation is decomposed into 5 simple operations. On nearly all modern X86 implementations (in the last decade at least), integer multiply instruction has a latency of 3 cycles. Assuming lea/add/sub/shl simple operations having a single cycle latency, you can see why the performance degraded when it is decomposed (usually the sequence has back to back register dependency). Here is a quick fix by conditioning the decomposition on the number of simple operations (although preferably this should be done according to processor specifics).
Also, fix two locations where two ADD(s) are most likely inferior to a single shift.