Doubt Branchless code
matheus
matheus at gmail.com
Tue Apr 7 22:10:33 UTC 2026
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).
More information about the Digitalmars-d-learn
mailing list