SharedMemory C# Library

repository·main·Indexed 20 days ago

https://github.com/justinstenning/sharedmemory

A C# library for high-performance inter-process communication (IPC) using memory-mapped files. It provides specialized abstractions including SharedArray for generic arrays, CircularBuffer for lock-free FIFO communication, BufferReadWrite for structured data access, and RpcBuffer for bi-directional master/slave RPC channels.

Tokens
1.6K
Snippets
4
Records
6
Agent score
20%

What's inside SharedMemory

  1. Overview of SharedMemory library

    main

    SharedMemory is a C# class library designed for high-performance, low-level inter-process communication (IPC) using memory-mapped files. It provides several specialized abstractions for sharing data between processes, including arrays, buffers, circular buffers, and a bi-directional RPC implementation.

    Key features:

    • Uses .NET MemoryMappedFile (for .NET 4.0+) or a custom wrapper (for .NET 3.5).
    • Supports various synchronization models: lock-free (CircularBuffer) and lock-based (SharedArray, BufferReadWrite).
    • Provides a simple RPC channel for master/slave communication.
  2. Optimize RpcBuffer performance

    main

    The throughput of RpcBuffer is heavily influenced by the relationship between bufferCapacity and the message size.

    • Packet Structure: A message is sent in one or more packets. Each packet consists of a header (64-bytes for RpcProtocol.V1) and the payload.
    • Fragmentation: If the payload exceeds bufferCapacity - packetHeaderSize, the message is split into multiple packets, which reduces throughput.
    • Best Practice: Aim for bufferCapacity * numberOfNodes > maxMessageSize. Ideally, allocate enough room to hold at least one full message (preferably a few) in a single packet to maximize throughput.
  3. Implement bi-directional RPC with SharedMemory.RpcBuffer

    main

    The SharedMemory.RpcBuffer provides a simple bi-directional RPC channel built on top of CircularBuffer. It requires .NET 4.5+ or .NET Standard 2.0. It operates in a master/slave relationship.

    • Master: Initiates requests using RemoteRequest(byte[]) and receives a response.
    • Slave: Defines a handler function (msgId, payload) => byte[] to process incoming requests and return a response.

    Note: Ensure the rpcName is unique (e.g., by appending a GUID) to avoid collisions with other channels.

    // Ensure a unique channel name
    var rpcName = "RpcTest" + Guid.NewGuid().ToString();
    var rpcMaster = new RpcBuffer(rpcName);
    var rpcSlave = new RpcBuffer(rpcName, (msgId, payload) =>
    {
        // Add the two bytes together
        return BitConverter.GetBytes((payload[0] + payload[1]));
    });
    
    // Call the remote handler to add 123 and 10
    var result = rpcMaster.RemoteRequest(new byte[] { 123, 10 });
    Console.WriteLine(result); // outputs 133
  4. Use SharedMemory.BufferReadWrite for structured data access

    main

    The SharedMemory.BufferReadWrite class provides direct read/write access to a shared memory buffer with support for various types, including structures and IntPtr copies. It uses BufferWithLocks for synchronization.

    • Writing: Use .Write<T>(ref T value) or .Write<T>(ref T value, int offset).
    • Reading: Use .Read<T>(out T value) or .Read<T>(out T value, int offset).
    using (var producer = new SharedMemory.BufferReadWrite(name: "MySharedBuffer", bufferSize: 1024))
    using (var consumer = new SharedMemory.BufferReadWrite(name: "MySharedBuffer"))
    {
        int data = 123;
        producer.Write<int>(ref data);
        data = 456;
        producer.Write<int>(ref data, 1000);
        
        int readData;
        consumer.Read<int>(out readData);
        Console.WriteLine(readData);
        consumer.Read<int>(out readData, 1000);
        Console.WriteLine(readData);
    }
  5. Use SharedMemory.CircularBuffer for lock-free FIFO communication

    main

    The SharedMemory.CircularBuffer is a lock-free FIFO (ring buffer) implementation that supports multiple readers and writers using Interlocked.Exchange and EventWaitHandles.

    Important Configuration:

    • nodeCount must be at least one larger than the maximum number of writes that must fit in the buffer at any one time.
    • The producer uses .Write<T>(T[]) to push data.
    • The consumer uses .Read<T>(T[]) to pull data into an existing array.
    using (var producer = new SharedMemory.CircularBuffer(name: "MySharedMemory", nodeCount: 3, nodeBufferSize: 4))
    using (var consumer = new SharedMemory.CircularBuffer(name: "MySharedMemory"))
    {
        // nodeCount must be one larger than the number of writes that must fit in the buffer at any one time
        producer.Write<int>(new int[] { 123 });
        producer.Write<int>(new int[] { 456 });
       
        int[] data = new int[1];
        consumer.Read<int>(data);
        Console.WriteLine(data[0]);
        consumer.Read<int>(data);
        Console.WriteLine(data[0]);
    }
  6. Use SharedMemory.SharedArray for shared generic arrays

    main

    The SharedMemory.SharedArray<T> class provides a simple generic array implementation that resides in shared memory. It inherits from BufferWithLocks to provide thread synchronization.

    To use it:

    1. The producer must specify the name and the length of the array.
    2. The consumer only needs to specify the name to open the existing shared buffer.
    using (var producer = new SharedMemory.SharedArray<int>("MySharedArray", 10))
    using (var consumer = new SharedMemory.SharedArray<int>("MySharedArray"))
    {
        producer[0] = 123;
        producer[producer.Length - 1] = 456;
        
        Console.WriteLine(consumer[0]);
        Console.WriteLine(consumer[consumer.Length - 1]);
    }