11

Let's say I have a large data array updated 1000+ times per second.
Another application wants to access and read the array in a short interval. Both applications are on the same machine.

I have tried using WCF for interprocess communication, but serializing and sending the whole array (or a large object) thousands of times per second is unfeasible performance wise.
Is there a way to directly access objects from different applications in c#?

brandon
  • 508
  • 1
  • 6
  • 15
  • What are you doing that is updating a data array 1000+ times a second? This sounds like a archetchture issue. Do you really need every update to be sent in real time? Can you queue updates? Can you batch updates so more than one update gets sent in a "message"? – Scott Chamberlain Feb 05 '15 at 02:23
  • https://sharedmemory.codeplex.com/ perhaps – pm100 Feb 05 '15 at 02:38
  • 1
    @Scott Chamberlain Gaming/Simulations, they need real time processing. Yea, maybe it is a flawed approach to separate components and run them in different applications. – brandon Feb 05 '15 at 02:53
  • If it is gaming, I would re-evaluate what net gains you are getting from separate applications and are those gains worth the cost of this slowness. If it is simulations I would try to batch or queue data to send it in larger chunks so the overhead cost per update can be amortized. – Scott Chamberlain Feb 05 '15 at 02:58
  • @pm100 sharedmemory sounds great but it's only able to share buffers and arrays of primitives, not objects, not object arrays - they'd have to be serialized and then written in that shared buffer, Same as pipes. – brandon Feb 05 '15 at 19:23

1 Answers1

34

There are a few IPC technologies you can use that though pre-date WCF are still relevant today.

Pipes

Pipes is one such technology. It's binary, runs in Kernel mode and very fast! Though it's quite low-level and does not give access to "objects".

.NET Remoting

.NET Remoting will give access to objects but is perhaps not as fast as pipes.

Both pipes and .NET remoting are faster than serialization-technologies WCF which converts things to verbose XML/SOAP.

COM

COM is a binary protocol for IPC. COM is a client server model where the client requests data from the COM or OLE server. The beauty about COM is that you have direct access to objects in the server - they are not serialised. For example requesting an element in a SAFEARRAY.

A SAFEARRAY is an Automation-safe structure of arbitrary dimensions consisting of type-safe data. Luckily .NET will hide the SAFEARRAY gobble-de-gook for us.

In my example I have created a Manager class that will expose the array. To get to the Manager I've used a factory pattern so that Manager is essentially a singleton.

You should lay out your project as follows:

  • MyComLib.Contracts.dll - contains all the interfaces
  • MyComLib.dll - contains the implementation of Factory, Manager

First the contracts:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IArrayItem
{
    #region Properties

    string Name { get; set; }

    int Whatsit { get; set; }

    #endregion
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IFactory 
{
    #region Methods

    IManager CreateManager();

    #endregion
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IManager
{
    #region Properties

    IArrayItem[] Array { get; }

    #endregion
}

public static class MyComLibConstants
{
    public const string FactoryProgId = "MickyD.MyComLib.Factory.1";

}

Now for the factory pattern:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof (IFactory))]
[Guid("...")]
[ProgId(MyComLibConstants.FactoryProgId)]
public class Factory : MarshalByRefObject, IFactory
{
    #region IFactory Members

    /// <summary>
    /// Creates the manager.
    /// </summary>
    /// <returns></returns>
    public IManager CreateManager()
    {
        return Manager.Instance;
    }


    #endregion
}

The manager:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof (IManager))]
[Guid("...")]
internal sealed class Manager : MarshalByRefObject, IManager
{
    private static Manager _instance;

    #region Constructor

    /// <summary>
    /// Prevents a default instance of the <see cref="Manager"/> class from being created.
    /// </summary>
    private Manager()
    {
        const int n = 5000;
        Array = new IArrayItem[n];
        for (int i = 0; i < n; i++)
        {
            Array[i]=new ArrayItem();
        }
    }

    #endregion

    #region Properties


    /// <summary>
    /// Gets the instance.
    /// </summary>
    /// <value>
    /// The instance.
    /// </value>
    public static IManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Manager();
            }
            return _instance;
        }
    }

    #endregion

    #region IManager Members

    /// <summary>
    /// Gets the array.
    /// </summary>
    /// <value>
    /// The array.
    /// </value>
    public IArrayItem[] Array { get; private set; }

    #endregion
}

A test app. This should only reference the MyComLib.Contracts.dll and not MyComLib.dll.

class Program
{
    static void Main(string[] args)
    {
        var type = Type.GetTypeFromProgID(MyComLibConstants.FactoryProgId);
        var factory = Activator.CreateInstance(type) as IFactory;
        var manager = factory.CreateManager();
        var x = manager.Array[500].Whasit;
    }
}

One final step is to change this in-process COM server to an out-of-process COM server so that multiple processes each share the same Manager and don't create their own singletons. In otherwords, a singleton that spans processes. When the Manager is running, it is essentially in it's own process space separate from all the other client processes.

For that you'll need to configure a COM surrogate which is explained in detail here.

File Mapping/Shared Memory

Lastly, File Mapping allows you to manipulate a file as if it were nothing more than a large block of memory in the process's address space. No fiddly file seek; read/write operations. Just grab a pointer to the memory block and start reading/writing. The system will do the rest.

MSDN:

You can use a special case of file mapping to provide named shared memory between processes. If you specify the system swapping file when creating a file-mapping object, the file-mapping object is treated as a shared memory block. Other processes can access the same block of memory by opening the same file-mapping object. Tell me more

Sadly, it still does require you to write your data in the first place and for it to be most effective you would need to change your application to treat the memory block as the source of truth rather than your array in memory. Otherwise you'll be serializing all the time.

However, shared memory via the swap file does technically allow you to eliminate any serialize-de-serialize between your client-server apps and duplication of data "on the heap". Though as I said you may need to adjust your app to work with raw memory buffers rather than objects.

Tell me more

NOTE: Contrary to popular belief, .NET Remoting is not entirely obsolete. One contemory use for it is communication between objects in different AppDomains within the same process, something you typically do in plug-in systems.

MickyD
  • 13,463
  • 6
  • 39
  • 60
  • WCF already uses named pipes, but everything gets serialized. Net Remoting from what I have read also serializes communication data. – brandon Feb 05 '15 at 02:39
  • I did mention that. Serialization is not always the same - WCF uses reflection and can persist as verbose XML/SOAP. In my pipes suggestion I am saying you `bypass WCF` completely and **send raw binary data** - you control the serialization at the byte level. That will significantly out-perform WCF. Good luck! – MickyD Feb 05 '15 at 02:43
  • 1
    @brandon WCF is a wrapper over some connection (be it named pipes, TCP, or something else) to make it easier to use. That wrapper costs some amount of overhead to do. What Micky is suggesting is peel away that wrapper and work with the raw constructs instead and do your own custom specialized seralization that can be tuned to be as fast as possible. – Scott Chamberlain Feb 05 '15 at 02:44
  • As I said, I'm looking for a way to directly read the data array as it lies in the memory. I don't want to serialize it by process A, write it in some pipe buffer, deserialize it by process B and recreate a copy of it somewhere else in the managed heap just to read it. – brandon Feb 05 '15 at 19:15
  • COM sounds promising but there is very little info about it. Any sample code? Btw, why did you use C++ types? Variant and safearray are object and Array in c#. I appreciate your effort. – brandon Feb 06 '15 at 18:20
  • @brandon c++ types? .NET will hide all the variant and SAFEARRAY stuff. I have included a code snippet that should get you going – MickyD Feb 07 '15 at 07:25
  • For COM the objects do not have to be serialized but does that make it faster than pipes or shared memory when sharing objects? Also I've heard of Google Protobuf for serializing data. Would it make sense to use it in conjunction with pipes or memory mapped files to increase performance? – user2481095 Mar 29 '16 at 17:57
  • 1
    @user2481095 I would expect shared memory to be the fastest because no method call/vtable is involved. Protobuf might be useful so long as the network layer is not involved. It certainly would be faster than xml. :) – MickyD Mar 30 '16 at 00:13
  • 2
    [LPC (Local procedure calls) Part 1 architecture – Ntdebugging Blog](https://blogs.msdn.microsoft.com/ntdebugging/2007/07/26/lpc-local-procedure-calls-part-1-architecture/) – user34660 Jan 15 '17 at 00:51