Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

C-Sharpener

A high-throughput C# threading engine built on a Token/Ticket model. Tokens handle IO (async), Tickets handle CPU (sync). The engine routes work through a staged pipeline that prefers IO leads over CPU leads, with formation ordering to prevent starvation.

Verified: 1.29M items/sec across 21M items, 0 errors, 8 cores.


Core concepts

Token — an async IO work item. Wraps a Func<SharpToken, Task>. Carries a CancellationToken via implicit conversion so standard .NET async APIs work without unwrapping.

Ticket — a synchronous CPU work item. Wraps an Action<SharpTicket>. Runs on a dedicated thread pool via Task.Run so it never blocks IO workers.

Group — a lead plus any number of children. The lead claims a core slot; children inherit the core and group identity. The group lifecycle gates completion: no group is done until the lead and all children resolve.

Formation — items within a group execute in sequence-number order. The FormationScheduler enforces this with O(1) checks against sorted pending sets.


Wiring

var corePool = new CorePool();
var registry = new OperationRegistry(corePool);
var cts      = new CancellationTokenSource();

var tokenQueue  = new AdmissionQueue<Token> (corePool);
var ticketQueue = new AdmissionQueue<Ticket>(corePool);

var tokenizer     = new Tokenizer    (tokenQueue,  registry);
var ticketCreator = new TicketCreator(ticketQueue, registry);

// Start background drain loops — these run for the lifetime of the engine
var background = Task.WhenAll(
    tokenQueue .DrainAsync(cts.Token),
    ticketQueue.DrainAsync(cts.Token),
    corePool   .StartAsync(cts.Token));

// ... do work ...

// Shutdown
cts.Cancel();
try { await background; } catch { }

Creating groups

// IO-lead group
var g = new BatchGroup(_tokenizer.CreateGroup());
g.Add(await tokenizer.CreateLeadAsync(
    async st => { /* your IO work */ }, g.Lifecycle));
g.Add(await tokenizer.CreateChildAsync(
    async st => { /* child IO */ }, g.Lifecycle));
g.Add(await ticketCreator.CreateChildAsync(
    st => { /* child CPU */ }, g.Lifecycle));

// CPU-lead group
var g = new BatchGroup(ticketCreator.CreateGroup());
g.Add(await ticketCreator.CreateLeadAsync(
    st => { /* your CPU work */ }, g.Lifecycle));

Rule: add the lead first, then children. The BatchProcessor seals the lifecycle — don't seal manually.


Running a batch

var result = await BatchProcessor.RunAsync(
    () => BuildGroupA(),
    () => BuildGroupB(),
    () => BuildGroupC());

// All groups race to their cores simultaneously.
// RunAsync returns when every item in every group reaches Resolved or Failed.

SharpToken and SharpTicket

Both structs are passed to your work delegate at execution time. CoreId and Group are already resolved — read at registration, baked in before your code runs.

async st =>
{
    Console.WriteLine($"Running on Core {st.CoreId}, Group {st.Group}");
    await SomeAsyncApi(st);  // implicit CancellationToken conversion
}

st =>
{
    Console.WriteLine($"CPU work on Core {st.CoreId}, Group {st.Group}");
}

WorkState lifecycle

FORMED → CREATED → ADMITTED → CHECKED → STARTED → COMPLETED → RESOLVED
                                                              ↘ FAILED
  • FORMED — object allocated, not yet registered
  • CREATED — registered, work delegate attached
  • ADMITTED — enqueued to the admission queue
  • CHECKED — entered the staging gate (IO/CPU comparand runs here)
  • STARTED — executing
  • COMPLETED — work delegate returned
  • RESOLVED — fully done, timestamps available
  • FAILED — exception thrown during execution

Timestamps are available at each state: CreatedAt, AdmittedAt, CheckedAt, StartedAt, CompletedAt, ResolvedAt. Derived spans: QueueWait, StagingWait, Elapsed, TotalLifetime.


Load testing

var loadTest = new LoadTest(tokenizer, ticketCreator);

// Named presets
await loadTest.RunAsync(LoadTestConfig.Light);    //   5 waves ×   12 groups ×    4 children
await loadTest.RunAsync(LoadTestConfig.Default);  //  50 waves ×  120 groups ×   40 children
await loadTest.RunAsync(LoadTestConfig.Heavy);    // 100 waves ×  500 groups ×  100 children
await loadTest.RunAsync(LoadTestConfig.Stress);   // 200 waves × 1000 groups ×  200 children

// Custom
await loadTest.RunAsync(new LoadTestConfig
{
    Waves          = 10,
    GroupsPerWave  = 350,
    ChildrenPerGrp = 6000,
});

Statement testing

var statementTest = new StatementTest(tokenizer, ticketCreator);
await statementTest.RunAsync(waves: 10, groupsPerDomain: 25);

Four domains run concurrently per wave: Document, Cache, Log, Index. Each domain has a distinct lead type and writes to shared concurrent state. Use this to verify correctness under realistic mixed IO/CPU load.


Performance notes

csproj settings that matter:

<ServerGarbageCollection>true</ServerGarbageCollection>
<GarbageCollectionAdaptationMode>0</GarbageCollectionAdaptationMode>
<TieredPGO>true</TieredPGO>
  • ServerGarbageCollection — dedicated GC thread per core; eliminates stop-the-world pauses from worker threads. Most impactful single setting.
  • GarbageCollectionAdaptationMode=0 — disables dynamic GC strategy switching; stabilises wave-to-wave variance under bursty load.
  • TieredPGO — profile-guided JIT recompilation on hot paths; wave 1 to wave N throughput delta compresses significantly.

Letter poolOperationRegistry maintains a 65536-char pool for group identity. At that depth, pool exhaustion under realistic concurrent group counts is not a concern. If you hit it, the engine throws InvalidOperationException with a clear message.

Formation scheduler — O(1) rule evaluation via SortedSet<int>. No list scans. WorkerInbox uses HashSet<T> for O(1) completion removal.


Limits

  • Max concurrent groups: 65536 (letter pool depth)
  • Max workers per core: configurable via CorePool(workersPerCore:), default 4
  • Group letter cycles: single char, wraps at Unicode boundary past 65471
  • HT detection: not yet implemented; CorePool uses Environment.ProcessorCount (logical cores)

About

This is csharpener a model for handling threaded workloads in C#.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages