混合模式进程与托管到非托管 IPC
Posted
技术标签:
【中文标题】混合模式进程与托管到非托管 IPC【英文标题】:Mixed-Mode Process vs. Managed-to-Unmanaged IPC 【发布时间】:2013-02-20 14:18:12 【问题描述】:我正在尝试为我正在进行的当前项目提出设计候选者。它的客户端接口基于公开公共方法和回调的 WCF 服务。请求一直路由到执行计算、操作等的 C++ 库(使用 boost)。
当前方案基于 WCF 服务通过 IPC 与单独的本机 C++ 进程通信。
为了让事情更简单一点,这里有一个建议采用混合模式(即有一个 .NET 进程在其中加载本机 C++ 层,很可能通过一个非常薄的 C++/ CLI 层)。主要关注的是垃圾收集或其他 .NET 方面是否会阻碍进程的非托管 C++ 部分的性能。
我开始查找安全点和 GC 辅助方法(例如 KeepAlive() 等)的概念,但我找不到任何关于此或基准的直接讨论。据我目前所知,安全点之一是如果一个线程正在执行未管理的代码,并且在这种情况下垃圾收集不会不暂停任何线程(这是正确的吗?)来执行清理。
我想我的主要问题是在同一进程中运行这两种类型的代码而不是在单独的进程中运行时,本机端存在性能问题。
【问题讨论】:
单个进程几乎总是更快。唯一的问题是 多少 快(至少从我所见)。 谢谢,杰弗里。因此,在您看来,将托管到非托管的转换/thunking 排除在等式之外,您相信 本机代码 执行性能实际上是相同的,无论它是在纯本机进程还是混合 -模式过程,对吗? 【参考方案1】:如果您有一个从未执行过任何托管代码的线程,则它不会在 .NET 垃圾回收期间被冻结。
如果使用托管代码的线程当前正在本机代码中运行,垃圾收集器不会冻结它,而是标记该线程以在它下一次到达托管代码时停止。但是,如果您正在考虑长时间不返回的本机调度循环,您可能会发现您正在阻塞垃圾收集器(或将东西固定在固定位置导致缓慢的 GC 和碎片)。因此,我建议让您的线程在本机代码中执行重要任务时完全纯。
确保编译器不会为某些标准 C++ 代码静默生成 MSIL(从而使其作为托管代码执行)有点棘手。但最终你可以通过谨慎使用#pragma managed(push, off)
来完成此操作。
【讨论】:
【参考方案2】:启动和运行混合模式应用程序非常容易,但要使其正常运行却非常困难。
我建议在选择该设计之前仔细考虑 - 特别是关于如何分层应用程序以及您期望非托管对象的生命周期类型。过去经验的一些想法:
C++ 对象生命周期 - 按体系结构。 在本地范围内短暂使用 C++ 对象,然后立即处理它们。 这听起来很明显但值得说明的是,C++ 对象是非托管资源,旨在用作非托管资源。通常,他们期望确定性的创建和销毁 - 通常会广泛使用 RAII。从托管程序控制这可能非常尴尬。 IDispose 模式的存在就是为了尝试解决这个问题。这对于短寿命的对象可以很好地工作,但对于长寿命的对象来说相当乏味且难以正确处理。特别是如果您开始将非托管对象作为托管类的成员而不是仅存在于函数范围内的对象,那么很快您程序中的每个类都必须是 IDisposable 并且突然之间托管编程变得比非托管编程更难。
GC 过于激进。 永远值得记住的是,当我们谈论托管对象超出范围时,我们的意思是在 IL 编译器/运行时的眼中,而不是您正在阅读代码的语言。如果非托管对象作为成员和托管对象保留旨在删除它事情可能会变得复杂。如果您的程序从上到下的处置模式不完整,则 GC 可能会变得相当激进。例如,假设您尝试编写一个托管类,该类在其终结器中删除一个非托管对象。假设您对托管对象所做的最后一件事是访问非托管指针以调用方法。然后 GC 可能会决定在该非托管调用期间是收集托管对象的好时机。突然,您的非托管指针在方法调用中被删除。
GC 不够积极。 如果您在地址限制范围内工作(例如,您需要 32 位版本),那么您需要记住 GC 会保留内存,除非它认为需要放手。它对这些想法的唯一输入是托管世界。如果非托管分配器需要空间,则与 GC 没有连接。非托管分配可能会因为 GC 没有收集到长期超出范围的对象而失败。有一个内存压力 API,但它同样只对非常简单的设计真正有用/有用。
缓冲区复制。您还需要考虑在哪里分配任何大内存块。托管块可以固定为看起来像非托管块。非托管块只有在需要看起来像托管块时才能被复制。但是,这个大型托管块何时会真正释放?
【讨论】:
以上是关于混合模式进程与托管到非托管 IPC的主要内容,如果未能解决你的问题,请参考以下文章