Török Edwin
2008-Mar-31 14:01 UTC
[LLVMdev] Introducing a branch optimization and prediction pass
Evan Cheng wrote:> On Mar 29, 2008, at 6:02 AM, Török Edwin wrote: > > >> Hi, >> >> I would like to transform unpredictable conditional branches with a >> small body into cmov instructions to reduce branch miss penalty. >> LLVM generates cmov/setcc instructions when SelectInst is used. The >> idea >> is to transform regular branches into selects, when that is possible >> and >> profitable. >> > > LLVM is already aggressively turning branches into selects as far as I > can see. > >Does this happen in Codegen? I tried some simple examples a while ago, and IIRC llvm missed some opportunities to use cmov.>> However this needs branch prediction info, and it seems LLVM doesn't >> provide that AFAICT. >> Could gcc's branch predictor be used here, or are there plans for an >> llvm-specific branch predictor? >> > > Sure, either the FE or some earlier profiling pass should provide some > branch predication info. >Ok. At least one of the above should be implemented before this optimization can have a real use. However for experimentation we could try to apply this optimization assuming each branch in unpredictable. At least we'll get an idea of what is the worst performance regression that could happen if branch prediction info is wrong ;)> > Ok. You want to speculatively execute the code and only writes back > the result if the predicate is true, right? >Yes, I speculatively execute the code. However the result is always written, because I cannot use 'cmov' to write to memory (just registers). So the solution proposed in the AMD manual, is to use cmov to select the address to write to. If predicate is false, it is written to a dummy address on the stack: cmp eax, ebx ; a < b ? cmovge edi, esi ; ptr = (a >= b) ? &dummy : &c[i] cmovl ecx, edx ; a < b ? i : i + 1 mov [edi], eax ; *ptr = a IIRC gcc-4.2 didn't generate cmov for this case. I'll write some simple examples, build with llvm, gcc-4.2, gcc-4.3, and icc, then measure speeds. Last time I did that on a simple example, and the result was 200 Mb/s vs. 1Gb/s. Then apply this optimization (either by hand or using a small prototype) and see if there are any significant improvements.> If there are any instructions with side effects then this cannot be > done. >Agreed.> >> * determine if branch is statically/dynamically predictable: >> * statically: if compiler can predict branch, don't apply this >> optimization >> * dynamically: can branch be predicted based on execution history >> (i.e. can CPU predict it?) >> > > Based on profiling data from previous runs? Or dynamic profiling in > JIT situation? >Good idea. However in absence of profiling info there should be some heuristics, I am not sure what that could be ATM.> >> * how to determine if a branch's body is small? >> if execution_cost_of(body) + >> execution_cost_of(cmov)/branch_miss_probability < branch_miss_penalty, >> or simply: execution_cost_of(body) < branch_miss_penalty >> If it is bigger, then it is faster if we take the miss penalty (and >> don't execute the body) >> But miss-penalties, and execution_costs are target specific, so use >> TargetData here (and teach it about penalties), or >> use some heuristic based on number of instruction in basic block? >> > > Sounds reasonable. But it's potentially more complicated than this for > a target like x86 that have very few registers. The speculatively > executed code will clobber registers so it has significant cost if the > result of computation is not written back. >I experimented on x86-64 a while ago, that doesn't have such a shortage of registers, and results were promising. I have some code that processes some data in a loop, but due to branch misspredictions, it can "only" work at ~200 Mb/s. Eliminating the branches could speed it up to 1Gb/s or more, since there are no other penalties in that code (L1 data cache misses are rare, the only bottleneck is branch misprediction). I'll redo my tests on x86 too (see above), and maybe we can come up with an empiric formula to use for x86. Something like associating a cost with register pressure increase?>> * if we cannot transform all instructions from the basic block into >> instr+cmov, then it is not worth applying this optimization (since >> we'll >> have a branch anyway). >> > > Sure. It might be worthwhile to extend the if-converter to do this. > The first step is to turn it on for x86 to convert blocks which are > entirely move's. >That would mean to apply this optimization on machine-basic-blocks, right? I was thinking of a generic llvm IR optimization pass, but maybe machine-basic-blocks pass is better, since we are doing something very specific for targets.>> Does Codegen guarantee to always transform Select into CMOV/SETCC (or >> equivalent), on targets that support it? >> > > Right. >Ok.>> * memory ordering/multi-thread safety: > That's something to worry about later. :-) >Ok.>> If we unconditionally read from slow_filter_table it could actually >> reduce the performance (assume slow_filter_table is huge), and might >> not >> be legal because we violate >> the short-circuit evaluation. >> > > Right, speculative loads would require much more analysis to ensure > safety. This might be something you don't want to do in step 1. >Makes sense, first implementation shouldn't break too much ;)>> (BTW, the branch predictor should say it is predictable, and we >> wouldn't >> apply the transformation at all, but we cannot rely on the branch >> predictor to make >> short-circuit eval guarantees). >> >> Thoughts? >> > > Adding branch prediction info is obviously useful even if speculation > is not performed. There are always CFG optimization passes (any > perhaps isel passes) that can make use of them. I am not sure whether > speculation can actually have a positive impact on x86. It's something > that requires some experimentation if you are interested. > > Evan >I have a prototype of this optimization (without branch prediction), and I can use that to experiment. However it only handles some very simple cases, and I should rewrite it to be more generic. Best regards, --Edwin
Evan Cheng
2008-Mar-31 18:08 UTC
[LLVMdev] Introducing a branch optimization and prediction pass
On Mar 31, 2008, at 7:01 AM, Török Edwin wrote:> Evan Cheng wrote: >> On Mar 29, 2008, at 6:02 AM, Török Edwin wrote: >> >> >>> Hi, >>> >>> I would like to transform unpredictable conditional branches with a >>> small body into cmov instructions to reduce branch miss penalty. >>> LLVM generates cmov/setcc instructions when SelectInst is used. The >>> idea >>> is to transform regular branches into selects, when that is possible >>> and >>> profitable. >>> >> >> LLVM is already aggressively turning branches into selects as far >> as I >> can see. >> >> > > Does this happen in Codegen? > I tried some simple examples a while ago, and IIRC llvm missed some > opportunities to use cmov.In optimization passes. I'd need to see an example of the missed transformation unless you are referring to using cmov to implement speculative execution, which is something llvm doesn't do right now.> > >>> However this needs branch prediction info, and it seems LLVM doesn't >>> provide that AFAICT. >>> Could gcc's branch predictor be used here, or are there plans for >>> an >>> llvm-specific branch predictor? >>> >> >> Sure, either the FE or some earlier profiling pass should provide >> some >> branch predication info. >> > > Ok. At least one of the above should be implemented before this > optimization can have a real use. > However for experimentation we could try to apply this optimization > assuming each branch in unpredictable. > At least we'll get an idea of what is the worst performance regression > that could happen if branch prediction info is wrong ;)Sounds good.> > >> >> Ok. You want to speculatively execute the code and only writes back >> the result if the predicate is true, right? >> > > Yes, I speculatively execute the code. > However the result is always written, because I cannot use 'cmov' to > write to memory (just registers). > So the solution proposed in the AMD manual, is to use cmov to select > the > address to write to. If predicate is false, it is written to a dummy > address on the stack: > > cmp eax, ebx ; a < b ? > cmovge edi, esi ; ptr = (a >= b) ? &dummy : &c[i] > cmovl ecx, edx ; a < b ? i : i + 1 > mov [edi], eax ; *ptr = aRight.> > > > IIRC gcc-4.2 didn't generate cmov for this case. > > I'll write some simple examples, build with llvm, gcc-4.2, gcc-4.3, > and > icc, then measure speeds. Last time I did that on a simple example, > and > the result was 200 Mb/s vs. 1Gb/s. > Then apply this optimization (either by hand or using a small > prototype) > and see if there are any significant improvements.Sounds good.> > >> If there are any instructions with side effects then this cannot be >> done. >> > > Agreed. > >> >>> * determine if branch is statically/dynamically predictable: >>> * statically: if compiler can predict branch, don't apply this >>> optimization >>> * dynamically: can branch be predicted based on execution history >>> (i.e. can CPU predict it?) >>> >> >> Based on profiling data from previous runs? Or dynamic profiling in >> JIT situation? >> > > Good idea. However in absence of profiling info there should be some > heuristics, I am not sure what that could be ATM.This can be done later. I think the first step should be to get the transformation piece ready.> > >> >>> * how to determine if a branch's body is small? >>> if execution_cost_of(body) + >>> execution_cost_of(cmov)/branch_miss_probability < >>> branch_miss_penalty, >>> or simply: execution_cost_of(body) < branch_miss_penalty >>> If it is bigger, then it is faster if we take the miss penalty >>> (and >>> don't execute the body) >>> But miss-penalties, and execution_costs are target specific, so >>> use >>> TargetData here (and teach it about penalties), or >>> use some heuristic based on number of instruction in basic block? >>> >> >> Sounds reasonable. But it's potentially more complicated than this >> for >> a target like x86 that have very few registers. The speculatively >> executed code will clobber registers so it has significant cost if >> the >> result of computation is not written back. >> > > I experimented on x86-64 a while ago, that doesn't have such a > shortage > of registers, and results were promising. > I have some code that processes some data in a loop, but due to branch > misspredictions, it can "only" work at ~200 Mb/s. > Eliminating the branches could speed it up to 1Gb/s or more, since > there > are no other penalties in that code (L1 data cache misses are rare, > the > only bottleneck is branch misprediction). > > I'll redo my tests on x86 too (see above), and maybe we can come up > with > an empiric formula to use for x86. > Something like associating a cost with register pressure increase?I think, at least for x86 (32 bit), we should start by targeting very restricted cases, i.e. tiny basic blocks. Then you can add heuristics to compute the register pressure, etc.> > >>> * if we cannot transform all instructions from the basic block into >>> instr+cmov, then it is not worth applying this optimization (since >>> we'll >>> have a branch anyway). >>> >> >> Sure. It might be worthwhile to extend the if-converter to do this. >> The first step is to turn it on for x86 to convert blocks which are >> entirely move's. >> > > That would mean to apply this optimization on machine-basic-blocks, > right? > I was thinking of a generic llvm IR optimization pass, but maybe > machine-basic-blocks pass is better, since we are doing something very > specific for targets.Right. I think you want to do this in codegen where you have target information. Of course, this means propagating branch prediction information through optimization passes. That can be a real pain. Once that complexity is solved, making use of the if-converter should be fairly easy. Right now, if-conversion predicate BB's which are completely predicable. You can teach it speculation by merging blocks and introduce merge blocks which are made of a number of move instructions. This should be pretty trivial for triangle sub-cfg's. However, the current if-converter is a post-register allocation pass. That might not be right for your needs (since speculation will have to introduce new temporaries). One possibility is to add the speculation pass before register allocation, but leave the mov -> cmov job to the if-converter.> > >>> Does Codegen guarantee to always transform Select into CMOV/SETCC >>> (or >>> equivalent), on targets that support it? >>> >> >> Right. >> > > Ok. > >>> * memory ordering/multi-thread safety: >> That's something to worry about later. :-) >> > > Ok. > >>> If we unconditionally read from slow_filter_table it could actually >>> reduce the performance (assume slow_filter_table is huge), and might >>> not >>> be legal because we violate >>> the short-circuit evaluation. >>> >> >> Right, speculative loads would require much more analysis to ensure >> safety. This might be something you don't want to do in step 1. >> > > Makes sense, first implementation shouldn't break too much ;) > >>> (BTW, the branch predictor should say it is predictable, and we >>> wouldn't >>> apply the transformation at all, but we cannot rely on the branch >>> predictor to make >>> short-circuit eval guarantees). >>> >>> Thoughts? >>> >> >> Adding branch prediction info is obviously useful even if speculation >> is not performed. There are always CFG optimization passes (any >> perhaps isel passes) that can make use of them. I am not sure whether >> speculation can actually have a positive impact on x86. It's >> something >> that requires some experimentation if you are interested. >> >> Evan >> > > I have a prototype of this optimization (without branch prediction), > and > I can use that to experiment. > However it only handles some very simple cases, and I should rewrite > it > to be more generic.Sounds good. Thanks. Evan> > > Best regards, > --Edwin > > > _______________________________________________ > LLVM Developers mailing list > LLVMdev at cs.uiuc.edu http://llvm.cs.uiuc.edu > http://lists.cs.uiuc.edu/mailman/listinfo/llvmdev
Török Edwin
2008-Mar-31 20:55 UTC
[LLVMdev] Introducing a branch optimization and prediction pass
Evan Cheng wrote: [snip]>> Good idea. However in absence of profiling info there should be some >> heuristics, I am not sure what that could be ATM. >> > > This can be done later. I think the first step should be to get the > transformation piece ready. >Ok.> I think, at least for x86 (32 bit), we should start by targeting very > restricted cases, i.e. tiny basic blocks. Then you can add heuristics > to compute the register pressure, etc. >Ok.>> That would mean to apply this optimization on machine-basic-blocks, >> right? >> I was thinking of a generic llvm IR optimization pass, but maybe >> machine-basic-blocks pass is better, since we are doing something very >> specific for targets. >> > > Right. I think you want to do this in codegen where you have target > information. Of course, this means propagating branch prediction > information through optimization passes. That can be a real pain. > > Once that complexity is solved, making use of the if-converter should > be fairly easy. Right now, if-conversion predicate BB's which are > completely predicable. You can teach it speculation by merging blocks > and introduce merge blocks which are made of a number of move > instructions. This should be pretty trivial for triangle sub-cfg's. > > However, the current if-converter is a post-register allocation pass. > That might not be right for your needs (since speculation will have to > introduce new temporaries). One possibility is to add the speculation > pass before register allocation, but leave the mov -> cmov job to the > if-converter. >I'll have a look at the if-converter sometime this weekend.>> I have a prototype of this optimization (without branch prediction), >> and >> I can use that to experiment. >> However it only handles some very simple cases, and I should rewrite >> it >> to be more generic. >> > > Sounds good. Thanks. > > EvanI wrote some small tests, see the attachments for a gcc 4.2, 4.3, icc, llvm, llvm+branchopt comparison. I didn't try tweaking compiler flags much. On x86-64: + ./loops_llvm CPU user time: 76661, speed: 1304.444Mb/s CPU user time: 119993, speed: 833.382Mb/s CPU user time: 173322, speed: 576.961Mb/s CPU user time: 133324, speed: 750.053Mb/s CPU user time: 676623, speed: 147.793Mb/s CPU user time: 776616, speed: 128.764Mb/s CPU user time: 739952, speed: 135.144Mb/s CPU user time: 903274, speed: 110.708Mb/s + ./loops_llvm_branchopt CPU user time: 79995, speed: 1250.078Mb/s CPU user time: 123325, speed: 810.866Mb/s CPU user time: 169989, speed: 588.273Mb/s CPU user time: 133325, speed: 750.047Mb/s CPU user time: 679956, speed: 147.068Mb/s CPU user time: 329978, speed: 303.051Mb/s CPU user time: 543298, speed: 184.061Mb/s CPU user time: 596628, speed: 167.609Mb/s On x86-32: + ./loops_llvm CPU user time: 96661, speed: 1034.543Mb/s CPU user time: 129991, speed: 769.284Mb/s CPU user time: 256650, speed: 389.636Mb/s CPU user time: 163323, speed: 612.284Mb/s CPU user time: 739951, speed: 135.144Mb/s CPU user time: 829946, speed: 120.490Mb/s CPU user time: 803281, speed: 124.489Mb/s CPU user time: 1086596, speed: 92.031Mb/s + ./loops_llvm_branchopt CPU user time: 103326, speed: 967.811Mb/s CPU user time: 129992, speed: 769.278Mb/s CPU user time: 256650, speed: 389.636Mb/s CPU user time: 166656, speed: 600.038Mb/s CPU user time: 739951, speed: 135.144Mb/s CPU user time: 593295, speed: 168.550Mb/s CPU user time: 939939, speed: 106.390Mb/s CPU user time: 1066597, speed: 93.756Mb/s It benefits x86-64 on the last 3 tests, while it benefits x86-32 only on 1 test, and hurts another one. I haven't looked at the generated assembly yet, that is the next step. However tests 4 and 5 should have same runtime, yet they differ hugely. I'll file bugs. Best regards, --Edwin -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: log_64 URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20080331/4b77e66a/attachment.ksh> -------------- next part -------------- A non-text attachment was scrubbed... Name: loops.c Type: text/x-csrc Size: 4802 bytes Desc: not available URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20080331/4b77e66a/attachment.c> -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: log_32 URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20080331/4b77e66a/attachment-0001.ksh>