www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Doubt Branchless code

reply matheus <matheus gmail.com> writes:
Hello,

Back in C days I used to write code with function pointer to 
avoid writing branches inside loops,

To avoid this:

for(;;){
   if(opt == 1){ func_1(); }else{func_2()};
}
Or maybe using switch if there were more options...

So I used to do:

if(opt == 1){ fp  = &func_1(); }else{ fp = &func_2()};

for(;;){
  fp();
}


I would like to know if there is a different way of doing that in 
D during RT execution and dynamic change of function execution 
before the loops?

Thanks in advance,

Matheus.
Apr 06
next sibling parent matheus <matheus gmail.com> writes:
On Monday, 6 April 2026 at 19:08:53 UTC, matheus wrote:
 ...
I ask deepseek and got the answer and sharing for the future: Calling Functions Without Branches in D In D, you can call functions without branches using several techniques. Here's a comprehensive guide: 1. Function Pointer Tables (Direct Lookup) The most common branchless function calling method - use an array index instead of conditionals. d import std.stdio; // Define functions int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int div(int a, int b) { return b != 0 ? a / b : 0; } void main() { // Array of function pointers - branchless dispatch int delegate(int, int)[] operations = [&add, &sub, &mul, &div]; int opCode = 2; // 0=add, 1=sub, 2=mul, 3=div // NO branch - direct table lookup and call int result = operations[opCode](10, 5); writeln(result); // 50 (mul) } 2. Using final Switch (Compile-Time Optimized) D's final switch with an enum can be optimized to a jump table: d import std.stdio; enum Operation { add, sub, mul, div } int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int div(int a, int b) { return b != 0 ? a / b : 0; } int execute(Operation op, int a, int b) { final switch (op) { // Compiler can optimize to jump table case Operation.add: return add(a, b); case Operation.sub: return sub(a, b); case Operation.mul: return mul(a, b); case Operation.div: return div(a, b); } } void main() { // The compiler may generate branchless code here int result = execute(Operation.mul, 10, 5); writeln(result); // 50 } 3. Template-Based Compile-Time Selection Zero runtime overhead - function selection happens at compile time: d import std.stdio; int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } // Template that selects function at compile time auto execute(alias Func, T...)(T args) { return Func(args); } void main() { // These calls are resolved at compile time - NO branches int r1 = execute!add(10, 5); // Direct call to add int r2 = execute!sub(10, 5); // Direct call to sub int r3 = execute!mul(10, 5); // Direct call to mul writeln(r1, " ", r2, " ", r3); // 15 5 50 } 4. Delegate Arrays with Compile-Time Construction Pre-build delegate arrays for OOP-style branchless dispatch: d import std.stdio; class Handler { void process(int x) { writeln("Handler: ", x); } } class Logger { void process(int x) { writeln("Logging: ", x); } } void main() { auto handler = new Handler(); auto logger = new Logger(); // Array of delegates - branchless dispatch void delegate(int)[] processors = [ (int x) => handler.process(x), (int x) => logger.process(x) ]; int type = 1; // Select at runtime // NO if/else - direct table lookup processors[type](42); // Output: Logging: 42 } 5. Using std.functional for Compile-Time Dispatch d import std.functional; import std.stdio; int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } // Compose functions at compile time alias Add5 = curry!(add, 5); alias Mul10 = curry!(mul, 10); void main() { // All resolved at compile time - branchless int r1 = Add5(3); // 8 int r2 = Mul10(7); // 70 writeln(r1, " ", r2); } 6. Virtual Function Table (VTBL) - Already Branchless D's classes use vtables internally, which is naturally branchless: d import std.stdio; interface Operation { int execute(int a, int b); } class Add : Operation { int execute(int a, int b) { return a + b; } } class Mul : Operation { int execute(int a, int b) { return a * b; } } void main() { Operation op; int choice = 1; // Runtime decision // This if is the ONLY branch (to select object) if (choice == 0) op = new Add(); else op = new Mul(); // The virtual call itself is branchless (vtable lookup) int result = op.execute(10, 5); // No branch here! writeln(result); // 50 } 7. Function Pointer from Associative Array For sparse or non-sequential selections: d import std.stdio; int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } int power(int a, int b) { int r = 1; foreach(_; 0..b) r *= a; return r; } void main() { // Associative array for string-based lookup int delegate(int, int)[string] ops = [ "add": &add, "mul": &mul, "pow": &power ]; string opName = "pow"; // Branchless hash lookup + call int result = ops[opName](2, 3); writeln(result); // 8 } Performance Comparison d import std.stdio; import std.datetime; int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } // Branch version int withBranches(int op, int a, int b) { if (op == 0) return add(a, b); if (op == 1) return sub(a, b); return mul(a, b); } // Branchless table version int delegate(int, int)[] table = [&add, &sub, &mul]; int branchless(int op, int a, int b) { return table[op](a, b); } void main() { const ITERS = 100_000_000; // Test branch version auto sw = StopWatch.Auto.start; int sum1; foreach (i; 0..ITERS) sum1 += withBranches(i % 3, 10, 5); auto time1 = sw.peek; // Test branchless version sw.reset(); int sum2; foreach (i; 0..ITERS) sum2 += branchless(i % 3, 10, 5); auto time2 = sw.peek; writeln("Branch version: ", time1); writeln("Branchless table: ", time2); } Key Takeaway Function pointer tables (delegate[] or function[]) are the most straightforward way to call functions without branches in D. They replace: d if (op == 0) add(); else if (op == 1) sub(); // BAD: branches else mul(); With d: table[op](); // GOOD: branchless This works because array indexing is a constant-time operation with no conditional branches, and the indirect function call through the pointer/delegate is also branchless (the CPU just loads the address and jumps).
Apr 07
prev sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 07/04/2026 7:08 AM, matheus wrote:
 Hello,
 
 Back in C days I used to write code with function pointer to avoid 
 writing branches inside loops,
 
 To avoid this:
 
 for(;;){
    if(opt == 1){ func_1(); }else{func_2()};
 }
 Or maybe using switch if there were more options...
 
 So I used to do:
 
 if(opt == 1){ fp  = &func_1(); }else{ fp = &func_2()};
 
 for(;;){
   fp();
 }
 
 
 I would like to know if there is a different way of doing that in D 
 during RT execution and dynamic change of function execution before the 
 loops?
 
 Thanks in advance,
 
 Matheus.
How I typically do it: ```d void outer() { void inner(bool a, bool b)() { for(;b;) { static if (a) {} } } if (cond1 && !cond2) inner!(true, false); else if (cond2 && !cond1) inner!(false, true); else if (cond1 && cond2) inner!(true, true); else inner!(false, false); } ```
Apr 07
parent reply matheus <matheus gmail.com> writes:
On Tuesday, 7 April 2026 at 23:48:37 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 On 07/04/2026 7:08 AM, matheus wrote:
 Hello,
 
 Back in C days I used to write code with function pointer to 
 avoid writing branches inside loops,
 
 To avoid this:
 
 for(;;){
    if(opt == 1){ func_1(); }else{func_2()};
 }
 Or maybe using switch if there were more options...
 
 So I used to do:
 
 if(opt == 1){ fp  = &func_1(); }else{ fp = &func_2()};
 
 for(;;){
   fp();
 }
 
 
 I would like to know if there is a different way of doing that 
 in D during RT execution and dynamic change of function 
 execution before the loops?
 
 Thanks in advance,
 
 Matheus.
How I typically do it: ```d void outer() { void inner(bool a, bool b)() { for(;b;) { static if (a) {} } } if (cond1 && !cond2) inner!(true, false); else if (cond2 && !cond1) inner!(false, true); else if (cond1 && cond2) inner!(true, true); else inner!(false, false); } ```
But this is CT, my problem was to this during RT. Thanks anyway for responding, Matheus.
Apr 07
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 08/04/2026 12:18 PM, matheus wrote:
 On Tuesday, 7 April 2026 at 23:48:37 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 On 07/04/2026 7:08 AM, matheus wrote:
 Hello,

 Back in C days I used to write code with function pointer to avoid 
 writing branches inside loops,

 To avoid this:

 for(;;){
    if(opt == 1){ func_1(); }else{func_2()};
 }
 Or maybe using switch if there were more options...

 So I used to do:

 if(opt == 1){ fp  = &func_1(); }else{ fp = &func_2()};

 for(;;){
   fp();
 }


 I would like to know if there is a different way of doing that in D 
 during RT execution and dynamic change of function execution before 
 the loops?

 Thanks in advance,

 Matheus.
How I typically do it: ```d void outer() {     void inner(bool a, bool b)() {         for(;b;) {             static if (a) {}         }     }     if (cond1 && !cond2)         inner!(true, false);     else if (cond2 && !cond1)         inner!(false, true);     else if (cond1 && cond2)         inner!(true, true);     else         inner!(false, false); } ```
But this is CT, my problem was to this during RT. Thanks anyway for responding, Matheus.
It is optimizing RT, but we use CT to factor out the conditionals. I've done this specifically when I would otherwise have RT if statements in loops.
Apr 07