在不同语言的进程之间有效地共享数据

Posted

技术标签:

【中文标题】在不同语言的进程之间有效地共享数据【英文标题】:Efficiently sharing data between processes in different languages 【发布时间】:2020-10-07 16:06:44 【问题描述】:

上下文

我正在编写一个通过标准输入和标准输出与 C# 程序通信的 Java 程序。 C# 程序作为子进程启动。它通过标准输入获取“请求”并通过标准输出发送“响应”。请求非常轻量(几个字节大小),但响应很大。在程序的正常运行中,响应量约为 2GB 数据。

我正在寻找提高性能的方法,我的测量结果表明写入标准输出是一个瓶颈。以下是正常运行的数字:

总时间:195 秒 通过标准输出传输的数据:2026MB 写入标准输出所花费的时间:85 秒 标准输出吞吐量:23.8 MB/s

顺便说一句,我首先将所有字节写入内存缓冲区,然后将它们一次性复制到标准输出,以确保我只测量标准输出的写入时间。

问题

在 C# 子进程和 Java 父进程之间共享数据的一种高效且优雅的方式是什么?很明显,标准输出是不够的。

我在这里和那里阅读过有关通过内存映射文件共享内存的信息,但 Java 和 .NET API 给我的印象是我找错地方了。

【问题讨论】:

也许考虑一个命名管道,或者(可能是你能得到的最快的,但更复杂的是编排)一个内存映射文件。有一些库可以访问 Java(当然还有 C#)。 您可以将响应内容保存为文件或缓存,然后在响应中包含指向此内容的指针 【参考方案1】:

在您对内存映射文件或命名管道进行更多投资之前,我会首先检查您是否真正有效地读写。 java.lang.Process.getInputStream() 使用了 BufferedInputStream,所以阅读器端应该没问题。但在您的 C# 程序中,您很可能会使用 Console.Write。这里的问题是 AutoFlush 默认启用。因此,每一次写入都会显式地刷新流。几年前我写了我的最后一个 C# 代码,所以我不是最新的。但也许可以将 Console.Out 的 AutoFlush 属性设置为 false 并在多次写入后手动刷新流。

如果无法禁用 AutoFlush,则使用 Console.Out 提高性能的唯一方法是通过一次写入写入更多文本。

另一个潜在的瓶颈可能是必须解释写入数据的外壳。确保直接执行 C# 程序,而不是通过脚本或调用命令执行器。

在开始使用内存映射文件之前,我会先尝试简单地写入文件。只要您有足够的空闲内存不被您的程序或其他人使用,并且只要没有其他程序频繁访问磁盘,操作系统就能够在文件系统缓存中保存大量写入数据.只要您的 Java 程序在 C# 程序写入文件时从文件中读取的速度足够快,就很有可能只需要从磁盘加载一些数据甚至不需要加载数据。

【讨论】:

感谢您的回答!我正在通过字节直接写入标准输出(抱歉,刚刚更新了问题以使其更清楚)所以没关系。我将尝试使用文件的方法,看看效果如何。【参考方案2】:

正如 Matthew Watson 在 cmets 中提到的,使用内存映射文件确实是可能的,而且速度非常快。事实上,我的程序的吞吐量从 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# 端。为了简单起见,我没有在上面的代码中包含它。在我的代码中,我使用标准输入和标准输出来指示何时可以安全地读取/写入。

【讨论】:

以上是关于在不同语言的进程之间有效地共享数据的主要内容,如果未能解决你的问题,请参考以下文章

Linux 进程间通信 --共享内存

Linux进程间通信——使用共享内存

进程通信之共享内存

Linux进程间通信——使用共享内存

Linux进程间通信——使用共享内存

Linux学习笔记(14)-进程通信|共享内存