在不同语言的进程之间高效共享数据
Efficiently sharing data between processes in different languages
上下文
我正在编写一个 Java 程序,它通过标准输入和标准输出与 C# 程序通信。 C# 程序作为子进程启动。它通过标准输入获取“请求”并通过标准输出发送“响应”。请求非常轻量级(几个字节大小),但响应很大。在正常的 运行 程序中,响应量约为 2GB 的数据。
我正在寻找提高性能的方法,我的测量表明写入标准输出是一个瓶颈。这是来自正常 运行:
的数字
- 总时长:195秒
- 通过标准输出传输的数据:2026MB
- 写入标准输出所花费的时间:85 秒
- 标准输出吞吐量:23.8 MB/s
顺便说一句,我首先将所有字节写入内存缓冲区,然后将它们一次性复制到标准输出以确保我只测量标准输出写入时间。
问题
在 C# 子进程和 Java 父进程之间共享数据的高效且优雅的方法是什么?很明显,标准输出是不够的。
我在这里和那里阅读过有关通过内存映射文件共享内存的信息,但是 Java 和 .NET API 给我的印象是我找错了地方。
在你对内存映射文件或命名管道投入更多之前,我会先检查你是否真的有效地读写。 java.lang.Process.getInputStream()
使用 BufferedInputStream,所以 reader 端应该没问题。但在您的 C# 程序中,您很可能会使用 Console.Write
。这里的问题是默认情况下启用 AutoFlush。因此,每一次写入都会明确地刷新流。我在几年前写了最后一个 C# 代码,所以我不是 up-to-date。但也许可以将 Console.Out 的 AutoFlush 属性 设置为 false 并在多次写入后手动刷新流。
如果禁用 AutoFlush 不可行,那么使用 Console.Out 提高性能的唯一方法是一次写入更多文本。
另一个潜在的瓶颈可能是介于两者之间的 shell,它必须解释写入的数据。确保直接执行 C# 程序,而不是通过脚本或调用命令执行器。
在您开始使用内存映射文件之前,我会首先尝试简单地写入一个文件。只要您有足够的空闲内存未被您的程序或其他程序使用,并且只要没有其他程序频繁访问磁盘,操作系统就能够在文件系统缓存中保存大量写入数据.只要您的 Java 程序在您的 C# 程序写入文件时从文件中读取的速度足够快,就很有可能只需要从磁盘加载一些数据,甚至不需要从磁盘加载数据。
正如 Matthew Watson 在评论中提到的,使用内存映射文件确实是可能的,而且速度非常快。事实上,我程序的吞吐量从 24 MB/s 增加到 180 MB/s。以下是它的要点。
以下 Java 代码创建用于通信的内存映射文件并打开我们可以读取的缓冲区:
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);
以下 C# 代码打开内存映射文件并创建可用于向其写入字节的流(请注意 buffer
是要写入的字节数组的名称):
// 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();
当然,您需要以某种方式同步 Java 和 C# 端。为了简单起见,我没有在上面的代码中包含它。在我的代码中,我使用标准输入和标准输出来发出可以安全读/写的信号。
上下文
我正在编写一个 Java 程序,它通过标准输入和标准输出与 C# 程序通信。 C# 程序作为子进程启动。它通过标准输入获取“请求”并通过标准输出发送“响应”。请求非常轻量级(几个字节大小),但响应很大。在正常的 运行 程序中,响应量约为 2GB 的数据。
我正在寻找提高性能的方法,我的测量表明写入标准输出是一个瓶颈。这是来自正常 运行:
的数字- 总时长:195秒
- 通过标准输出传输的数据:2026MB
- 写入标准输出所花费的时间:85 秒
- 标准输出吞吐量:23.8 MB/s
顺便说一句,我首先将所有字节写入内存缓冲区,然后将它们一次性复制到标准输出以确保我只测量标准输出写入时间。
问题
在 C# 子进程和 Java 父进程之间共享数据的高效且优雅的方法是什么?很明显,标准输出是不够的。
我在这里和那里阅读过有关通过内存映射文件共享内存的信息,但是 Java 和 .NET API 给我的印象是我找错了地方。
在你对内存映射文件或命名管道投入更多之前,我会先检查你是否真的有效地读写。 java.lang.Process.getInputStream()
使用 BufferedInputStream,所以 reader 端应该没问题。但在您的 C# 程序中,您很可能会使用 Console.Write
。这里的问题是默认情况下启用 AutoFlush。因此,每一次写入都会明确地刷新流。我在几年前写了最后一个 C# 代码,所以我不是 up-to-date。但也许可以将 Console.Out 的 AutoFlush 属性 设置为 false 并在多次写入后手动刷新流。
如果禁用 AutoFlush 不可行,那么使用 Console.Out 提高性能的唯一方法是一次写入更多文本。
另一个潜在的瓶颈可能是介于两者之间的 shell,它必须解释写入的数据。确保直接执行 C# 程序,而不是通过脚本或调用命令执行器。
在您开始使用内存映射文件之前,我会首先尝试简单地写入一个文件。只要您有足够的空闲内存未被您的程序或其他程序使用,并且只要没有其他程序频繁访问磁盘,操作系统就能够在文件系统缓存中保存大量写入数据.只要您的 Java 程序在您的 C# 程序写入文件时从文件中读取的速度足够快,就很有可能只需要从磁盘加载一些数据,甚至不需要从磁盘加载数据。
正如 Matthew Watson 在评论中提到的,使用内存映射文件确实是可能的,而且速度非常快。事实上,我程序的吞吐量从 24 MB/s 增加到 180 MB/s。以下是它的要点。
以下 Java 代码创建用于通信的内存映射文件并打开我们可以读取的缓冲区:
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);
以下 C# 代码打开内存映射文件并创建可用于向其写入字节的流(请注意 buffer
是要写入的字节数组的名称):
// 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();
当然,您需要以某种方式同步 Java 和 C# 端。为了简单起见,我没有在上面的代码中包含它。在我的代码中,我使用标准输入和标准输出来发出可以安全读/写的信号。