Summary
Using static analysis and profiling best practices, I've identified 10 potential performance bottlenecks in the hot-path code (ab_search.cpp and quick_tricks.cpp). These represent optimization opportunities that could improve solver throughput by 5-30% depending on implementation.
This issue provides:
- ✅ Detailed identification of each bottleneck with exact code locations
- ✅ Quick-win optimizations with ready-to-use code examples
- ✅ A structured 4-week profiling plan with shell commands
- ✅ Questions for the maintainer team
🎯 Quick Wins (5-15% Estimated Gain)
1. Extract Duplicated TT Lookup Logic
Location: ab_search.cpp lines 207-247 and 317-357
Issue: Identical transposition table lookup code duplicated at different depths
Effort: Low | Gain: 5-10%
// Extract to helper function
static inline std::optional<bool> tt_lookup(
Pos* posPoint, int depth, int tricks, int hand,
int target, SolverContext& ctx)
{
int limit;
if (ctx.search().node_type_store(0) == MAXNODE)
limit = target - posPoint->tricks_max - 1;
else
limit = tricks - (target - posPoint->tricks_max - 1);
bool lowerFlag;
TIMER_START(TIMER_NO_LOOKUP, depth);
NodeCards const* cardsP = ctx.trans_table()->lookup(
tricks, hand, posPoint->aggr, posPoint->hand_dist,
limit, lowerFlag);
TIMER_END(TIMER_NO_LOOKUP, depth);
if (!cardsP) return std::nullopt;
for (int ss = 0; ss < DDS_SUITS; ss++)
posPoint->win_ranks[depth][ss] =
win_ranks[posPoint->aggr[ss]]
[static_cast<unsigned char>(cardsP->least_win[ss])];
if (cardsP->best_move_rank != 0) {
ctx.search().best_move_tt(depth).suit =
static_cast<unsigned char>(cardsP->best_move_suit);
ctx.search().best_move_tt(depth).rank =
static_cast<unsigned char>(cardsP->best_move_rank);
}
bool scoreFlag =
(ctx.search().node_type_store(0) == MAXNODE ? lowerFlag : !lowerFlag);
AB_COUNT(AB_MAIN_LOOKUP, scoreFlag, depth);
return scoreFlag;
}
2. Add Branch Prediction Hints
Location: quick_tricks.cpp lines 145-666 and ab_search.cpp cutoff checks
Issue: Multiple unpredictable branches with no compiler hints
Effort: Low | Gain: 3-8%
// Add C++20 attributes
if (posPoint->tricks_max >= target) [[likely]] {
return true;
} else if (posPoint->tricks_max + tricks + 1 < target) [[unlikely]] {
return false;
}
for (int s = 0; s < DDS_SUITS; s++) {
if (!opps && (countPart == 0)) [[unlikely]] {
// Rare case
} else [[likely]] {
// Common case
}
}
3. Cache Hot Values
Location: Throughout both files
Issue: (depth + 3) >> 2 computed repeatedly; bit patterns recomputed
Effort: Low | Gain: 2-5%
// Cache at function entry
bool ab_search_1_ctx(Pos* posPoint, const int target, const int depth, SolverContext& ctx) {
const int tricks = (depth + 3) >> 2; // Compute once
ThreadData* thrp = ctx.thread_ptr(); // Cache pointer
// Reuse throughout function
ctx.move_gen().move_gen_123(tricks, 1, *posPoint);
// ... no recalculation needed
}
4. Optimize Memory Alignment
Location: trans_table.hpp line 52 - NodeCards structure
Issue: 8-byte entries may cause cache line conflicts
Effort: Low | Gain: 5-15% (if contention is high)
struct NodeCards {
char upper_bound;
char lower_bound;
char best_move_suit;
char best_move_rank;
char least_win[DDS_SUITS];
char _padding[56]; // Align to 64-byte cache line
} __attribute__((aligned(64)));
Update 2026-07-19: This suggestion is incorrect.
NodeCards is deliberately 8 bytes to maximize TT density — padding to 64 bytes
would increase memory usage 8× and hurt cache hit rate, not help it.
There is no false sharing issue since the TT is single-threaded per context.
Per @tameware: the correct direction is Stockfish-style — keep NodeCards at 8 bytes
and check WinMatch/block layout for awkward cache line straddling without multiplying per-entry size.``
5. Reduce Pointer Dereferencing
Location: ab_search.cpp make/undo functions
Issue: Multiple indirections accumulate with deep recursion
Effort: Low | Gain: 2-5%
void make_1(Pos* posPoint, const int depth, MoveType const* mply) {
Pos& pos = *posPoint; // Single indirection
pos.rank_in_suit[h][s] &= (~bit_map_rank[r]);
pos.aggr[s] ^= bit_map_rank[r];
// ... reuse pos reference
}
📊 All 10 Issues (Detailed Breakdown)
| # |
Issue |
Location |
Impact |
Effort |
Est. Gain |
| 1 |
Duplicated TT Lookup |
ab_search.cpp 207-247, 317-357 |
HIGH |
Low |
5-10% |
| 2 |
Excessive Loop Nesting |
quick_tricks.cpp 229-666 |
MEDIUM-HIGH |
Medium |
5-15% |
| 3 |
Repeated Bit Operations |
Both files |
MEDIUM |
Low |
2-5% |
| 4 |
Memory Alignment |
trans_table.hpp 52 |
MEDIUM |
Low |
5-15% |
| 5 |
Recomputed Arithmetic |
Both files |
MEDIUM |
Low |
2-3% |
| 6 |
Pointer Dereferencing |
Both files |
MEDIUM |
Low |
2-5% |
| 7 |
Branch Misprediction |
Both files |
MEDIUM |
Low |
3-8% |
| 8 |
Memory Fragmentation |
TransTable malloc/calloc |
LOW-MEDIUM |
Medium |
3-10% |
| 9 |
Missing Compiler Hints |
Throughout |
LOW-MEDIUM |
Low |
2-5% |
| 10 |
TLS Overhead |
ab_search.cpp 93, 195, 496 |
LOW |
Low |
1-2% |
📋 4-Week Profiling Plan
Week 1: Baseline Metrics (Linux)
# Build with profiling symbols
bazelisk build --copt=-fno-omit-frame-pointer -c opt //library/tests:dtest
# Generate flame graph (requires FlameGraph: github.com/brendangregg/FlameGraph)
cd bazel-bin/library/tests
perf record -F 99 -g ./dtest -f ~/dds/hands/list1000.txt -s solve -n 1
perf script > ~/dds/out.perf
~/FlameGraph/stackcollapse-perf.pl ~/dds/out.perf > ~/dds/out.folded
~/FlameGraph/flamegraph.pl ~/dds/out.folded > ~/dds/dds_flame.svg
Note: requires kernel.perf_event_paranoid=1 (sudo sysctl kernel.perf_event_paranoid=1)
Week 2: Event Counters & Profiling (Linux)
perf stat -e cycles,instructions,branch-instructions,branch-misses \
./bazel-bin/library/tests/dtest -f hands/list1000.txt -s solve -n 1
Results (Intel i7-4770, Z87, 1000 hands): branch miss rate 4.32%, IPC 1.57
Week 3: Data Structure & Memory Analysis (Linux)
valgrind --tool=cachegrind \
./bazel-bin/library/tests/dtest -f hands/list1000.txt -s solve -n 1
cg_annotate cachegrind.out.* > cache_report.txt
Deliverable: Per-function cache behavior, memory access patterns
Week 4+: Implementation & Validation
- Implement 1-2 high-confidence optimizations
- Measure improvement vs. baseline
- Prepare pull request with benchmark results
🔍 Questions for Maintainers
- Prior Profiling: Has profiling been done on the current codebase? Which functions dominate CPU time?
- TT Performance: What transposition table hit rates do you observe in practice (target: >50%)?
- Known Bottlenecks: Are there specific board configurations or scenarios known to be slow?
- Contribution Welcome: Would optimization PRs based on this analysis be welcome?
📚 Resources
⚠️ Important Notes
- This analysis is based on static code review and profiling best practices
- Actual performance improvements depend on:
- Hardware characteristics (CPU, cache sizes)
- Specific board configurations being solved
- Compiler optimization settings (-O3, -flto, etc.)
- Whether bottlenecks are CPU-bound vs. memory-bound
- Profiling on representative data is essential before optimization work begins
Status: Active — #252 merged (branch prediction hints in quick_tricks.cpp, ~1% improvement confirmed on Linux). Next candidates: TransTableL::lookup_cards (4.99%) and MoveGen123 (10.65%) per post-merge perf report.
Summary
Using static analysis and profiling best practices, I've identified 10 potential performance bottlenecks in the hot-path code (
ab_search.cppandquick_tricks.cpp). These represent optimization opportunities that could improve solver throughput by 5-30% depending on implementation.This issue provides:
🎯 Quick Wins (5-15% Estimated Gain)
1. Extract Duplicated TT Lookup Logic
Location:
ab_search.cpplines 207-247 and 317-357Issue: Identical transposition table lookup code duplicated at different depths
Effort: Low | Gain: 5-10%
2. Add Branch Prediction Hints
Location:
quick_tricks.cpplines 145-666 andab_search.cppcutoff checksIssue: Multiple unpredictable branches with no compiler hints
Effort: Low | Gain: 3-8%
3. Cache Hot Values
Location: Throughout both files
Issue:
(depth + 3) >> 2computed repeatedly; bit patterns recomputedEffort: Low | Gain: 2-5%
4. Optimize Memory Alignment
Location:
trans_table.hppline 52 -NodeCardsstructureIssue: 8-byte entries may cause cache line conflicts
Effort: Low | Gain: 5-15% (if contention is high)
5. Reduce Pointer Dereferencing
Location:
ab_search.cppmake/undo functionsIssue: Multiple indirections accumulate with deep recursion
Effort: Low | Gain: 2-5%
📊 All 10 Issues (Detailed Breakdown)
ab_search.cpp207-247, 317-357quick_tricks.cpp229-666trans_table.hpp52ab_search.cpp93, 195, 496📋 4-Week Profiling Plan
Week 1: Baseline Metrics (Linux)
Week 2: Event Counters & Profiling (Linux)
Week 3: Data Structure & Memory Analysis (Linux)
Week 4+: Implementation & Validation
🔍 Questions for Maintainers
📚 Resources
Status: Active — #252 merged (branch prediction hints in
quick_tricks.cpp, ~1% improvement confirmed on Linux). Next candidates:TransTableL::lookup_cards(4.99%) andMoveGen123(10.65%) per post-mergeperf report.