Optimized for .NET 10 using modern C# performance techniques, low-allocation patterns, and high-throughput evaluation pipelines.
A high-performance Texas Hold’em Poker Hand Evaluator built with ASP.NET Core Razor Pages, faithfully derived from Cactus Kev’s classic algorithm and re-engineered in pure, allocation-free C# for deterministic speed and modern clarity.
A modern .NET 10 implementation of the legendary Cactus Kev Poker Evaluator, rebuilt from the ground up for clarity, determinism, and high-throughput execution.
This version achieves near-native C++ performance through deep algorithmic refactoring, zero-allocation evaluation paths, aggressive loop flattening, and end-to-end BenchmarkDotNet validation (see Performance section for verified data).
This repository now showcases a fully optimized .NET 10 Poker Hand Evaluation Engine, implemented entirely in pure C# with no lookup tables, no unsafe code, and no GC pressure in the hot path. Derived five-card throughput from the latest BenchmarkDotNet runs measures roughly 180–210 million evaluations per second, depending on workload characteristics and thread scheduling (see Performance for logs).
All benchmarks were performed on an Intel Core i9-9940X (14-core, 28-thread) running Windows 10 (22H2) with .NET 10.0 in High-Performance mode.
Ideal for developers exploring algorithmic optimization, combinatorial search performance, or advanced .NET JIT/runtime behavior in tight computational loops.
A working version of this application is available at:
👉 https://poker-calculator.johnbelthoff.com/
| Language | Environment | Representative Throughput | Verification Source |
|---|---|---|---|
| C++ | MSVC /O2 AVX2 64-bit | ~188–191 M 7-card hands/sec (≈ 5.3 ns/hand) | Results-C++.txt |
| C# (.NET 10) | RyuJIT 64-bit / Concurrent GC | 943.7 ns (engine-only) · 1.255 µs (full 9-player evaluation) · Derived ~180–205 M five-card eval/sec | Results-FinalRiverBench.txt |
Test Environment: Intel Core i9-9940X (14 cores / 28 threads), Windows 10 (22H2), .NET 10.0.0 (Release x64), High-Performance power plan.
All values above are directly derived from benchmark logs with no extrapolation beyond standard five-card conversion.
Any further comparison (e.g., relative speed-ups or ratios) should be recalculated from these verified numbers.
This project re-creates the full logic and flow of a Texas Hold’em Poker game — from shuffling and dealing cards to evaluating all hands and determining the winner.
The application is built with ASP.NET Core (C#) and uses a fully modernized, allocation-free C# reimplementation of Cactus Kev’s Poker Hand Evaluator.
The evaluator preserves the core ranking model while employing contemporary techniques such as loop flattening, value-type pipelines, and zero-allocation execution tuned for .NET 10.
At its current stage, the app:
- Simulates 9 players
- Rotates the dealer
- Evaluates all hands deterministically
- Displays detailed, structured results
Under the hood, the evaluation engine is optimized for high-throughput, predictable performance using the latest .NET 10 JIT improvements, ensuring fully managed execution without lookup tables, unsafe code, or runtime allocations.
Clone and launch the project:
git clone https://github.com/JBelthoff/poker.net.git
cd poker.net
dotnet runTip:
After runningdotnet run, check the console log for the exact URL (“Now listening on…”).
Then open that URL (usuallyhttps://localhost:5001) in your browser.Note:
By default, the app runs in No-DB (Static) mode using an in-memory deck.
To enable SQL Server support for recording games, set"UseSqlServer": truein your configuration and ensure SQL Server is installed.
See SQL Server Setup for step-by-step instructions.
All benchmarks were executed on Windows 10 (22H2) with an Intel Core i9-9940X (14 cores / 28 threads) using .NET 10.0.0 and BenchmarkDotNet v0.15.6.
Power plan: High Performance.
Garbage Collector: Concurrent Workstation.
C++ reference results were gathered from the same system using a Visual Studio x64 Release build of bwedding/PokerEvalMultiThread (MSVC /O2 optimization, AVX2-capable CPU).
| Benchmark (C# /.NET 10.0.0) | Mean (ns / op) | Mean (µs / op) | Description |
|---|---|---|---|
| Optimized core evaluator (values-only, no allocs) | 944 ns | 0.944 µs | Core evaluation loop (values-only, zero allocations) |
| Full 9-player evaluation (EvalEngine pipeline) | 1 255 ns | 1.255 µs | Complete 9-player best-hand evaluation |
| Parallel.For batched (values-only) | — | 730.955 ms total | Parallel batch throughput test across multiple cores |
Each full 9-player river evaluation covers 189 five-card combinations (9 × 21).
Allocation notes:
- The optimized values-only path shows no Gen0 collections, confirming a zero-allocation inner loop.
- The full 9-player evaluation introduces minimal per-operation allocations, as reflected in GC/Allocated stats from BenchmarkDotNet.
Note: “Values-only” benchmarks measure the pure numeric evaluation core. “Full” benchmarks include ranking and best-hand reconstruction for display.
| Benchmark (C++ Reference / bwedding port) | Hands Tested | Time (s) | Hands / sec (M) | ns / hand |
|---|---|---|---|---|
| 10 M 7-card hands | 10,000,000 | 0.0524 | 190.72 M | 5.24 ns |
| 50 M 7-card hands | 50,000,000 | 0.2651 | 188.60 M | 5.30 ns |
| 100 M 7-card hands | 100,000,000 | 0.5308 | 188.39 M | 5.31 ns |
Checksums match across all C++ runs, confirming deterministic evaluation.
- The optimized .NET 10 evaluator achieves ~944 ns per operation (0.944 µs) in its tightest values-only path, and ≈ 1.255 µs for full 9-player evaluation.
- The C++ reference still achieves ~188–191 million 7-card hands/sec (~5.3 ns/hand) under optimized
/O2 AVX2builds. - These results show the .NET 10 implementation maintaining near–microsecond performance in the core evaluator and delivering end-to-end throughput very close to the theoretical limits of managed execution.
This evaluator is a modern C# translation of Cactus Kev’s Poker Hand Evaluator —
the classic C implementation that popularized prime-number-based hand evaluation.
All core logic is faithfully preserved:
- Flush and straight detection tables
- Perfect-hash prime product encoding
- Rank thresholds identical to Kev’s original
Where Kev used C arrays, macros, and pointer arithmetic, Poker.net employs managed data structures, Span<T> buffers, and modern .NET 10 JIT optimizations to achieve equivalent throughput — without huge in-memory lookup tables or unsafe code.
Comprehensive validation confirms one-to-one rank and frequency equivalence with the original algorithm, and checksum tests verify that every evaluated hand produces identical results to the C reference.
To reproduce, follow these steps:
| Repository | Description |
|---|---|
poker.net |
Core ASP.NET / C# evaluator (switch to the optimization branch) |
PokerBenchmarks |
Standalone benchmark harness and verified results |
bwedding/PokerEvalMultiThread |
Reference C++ implementation |
suffecool/pokerlib |
Original C evaluator by Cactus Kev |
If you clone both repositories under a single source directory like this:
source/poker.net
source/PokerBenchmarks
Then everything is already configured:
PokerBenchmarksalready includes the correct NuGet packages (BenchmarkDotNet)- The project references are pre-wired for
poker.net
Simply:
- Open the solution
- Set
PokerBenchmarksas the Startup Project - Build in Release mode
- Press Ctrl + F5 to run the benchmarks
BenchmarkDotNet will automatically generate results under:
source/PokerBenchmarks/bin/Release/net10.0/BenchmarkDotNet.Artifacts/results/
Skip this section if you're running in No-DB (Static) mode.
- Create a SQL Server database named
PokerApp. - Create a Login and User for the database.
- Run the appropriate initialization script from the
x_dBasefolder:CreateDB-2019.sql— for SQL Server 2019 or earlierCreateDB-2022.sql— for SQL Server 2022 and newer (supportsSTRING_SPLIT(@s, '|', 1)and other modern features)
- Update your connection string (via
appsettings.json, User Secrets, or environment variables). - Set
"UseSqlServer": truein your configuration. - Build and run the project (
dotnet run, Docker, or IIS Express). - Visit the app in your browser and start playing!
The 2019 script maintains full backward compatibility down to SQL Server 2008 by using
datetime2and the legacyDelimitedSplit8Kfunction.
The 2022 script reflects modern best practices per Davide Mauri (@yorek), including transaction safety (XACT_ABORT ON) and nativeSTRING_SPLITwith ordinal output.
-
C# vs C Poker Evaluator Benchmark
Deep dive into optimizing and benchmarking the Poker Evaluator engine across .NET 8 → 10 and native C (MSVC /O2).
Example output shows native C at 3.76 M evals/sec vs .NET 8 at ~3.08 M evals/sec (~82 % of C speed). -
SQL Insert Benchmark (.NET 9) — Pipe vs JSON vs TVP
Microbenchmark of common SQL Server insert strategies (Pipe-delimited, JSON, and TVP).
Pipe wins for tiny inserts, TVP scales best, JSON wins for maintainability.
💼 Interested in performance engineering or .NET optimization work?
Contact me via LinkedIn or visit johnbelthoff.com.
© 2025 John Belthoff
www.johnbelthoff.com