Search code examples
javac#.netipc

Efficiently sharing data between processes in different languages


Context

I am writing a Java program that communicates with a C# program through standard in and standard out. The C# program is started as a child process. It gets "requests" through stdin and sends "responses" through stdout. The requests are very lightweight (a few bytes size), but the responses are large. In a normal run of the program, the responses amount for about 2GB of data.

I am looking for ways to improve performance, and my measurements indicate that writing to stdout is a bottleneck. Here are the numbers from a normal run:

  • Total time: 195 seconds
  • Data transferred through stdout: 2026MB
  • Time spent writing to stdout: 85 seconds
  • stdout throughput: 23.8 MB/s

By the way, I am writing all the bytes to an in-memory buffer first, and copying them in one go to stdout to make sure I only measure stdout write time.

Question

What is an efficient and elegant way to share data between the C# child process and the Java parent process? It is clear that stdout is not going to be enough.

I have read here and there about sharing memory through memory mapped files, but the Java and .NET APIs give me the impression that I'm looking in the wrong place.


Solution

  • As Matthew Watson mentioned in the comments, it is indeed possible and incredibly fast to use a memory mapped file. In fact, the throughput for my program went from 24 MB/s to 180 MB/s. Below is the gist of it.

    The following Java code creates the memory mapped file used for communication and opens a buffer we can read from:

    var path = Paths.get("test.mmap");
    var channel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
    var mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 200_000 * 8);
    

    The following C# code opens the memory mapped file and creates a stream that you can use to write bytes to it (note that buffer is the name of the array of bytes to be written):

    // This code assumes the file has already been created on the Java side
    var file = File.Open("test.mmap", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
    var memoryMappedFile = MemoryMappedFile.CreateFromFile(file, fileName, 0, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, false);
    var stream = memoryMappedFile.CreateViewStream();
    stream.Write(buffer, 0, buffer.Length);
    stream.Flush();
    

    Of course, you need to somehow synchronize the Java and the C# side. For the sake of simplicity, I didn't include that in the code above. In my code, I am using standard in and standard out to signal when it is safe to read / write.