IPC共享内存和线程内存的性能差异

Posted

技术标签:

【中文标题】IPC共享内存和线程内存的性能差异【英文标题】:Performance difference between IPC shared memory and threads memory 【发布时间】:2013-01-08 16:48:04 【问题描述】:

我经常听说访问进程之间的共享内存段与访问线程之间的进程内存相比没有性能损失。换句话说,多线程应用程序不会比使用共享内存的一组进程更快(不包括锁定或其他同步问题)。

但我有疑问:

1) shmat() 将本地进程虚拟内存映射到共享段。必须为每个共享内存地址执行此转换,并且可能会带来很大的成本。在多线程应用程序中,不需要额外的转换:所有 VM 地址都被转换为物理地址,就像在不访问共享内存的常规进程中一样。

2) 共享内存段必须以某种方式由内核维护。例如,当所有附加到 shm 的进程都被关闭时,shm 段仍然处于运行状态,并且最终可以被新启动的进程重新访问。可能会有一些与 shm 段上的内核操作相关的开销。

多进程共享内存系统是否与多线程应用程序一样快?

【问题讨论】:

对于内核,附加一个共享内存段只涉及为底层内存设置一组(额外的)页表。 (将其映射到进程地址空间)没有额外的成本。 2)没有额外的开销;检查是在附加时完成的。 【参考方案1】:

1) shmat() 将本地进程虚拟内存映射到共享 部分。必须为每个共享内存执行此转换 地址,并且相对于数量而言,可能代表着巨大的成本 shm 访问。在多线程应用程序中没有额外的 需要翻译:所有VM地址都转换为物理地址 地址,就像在不访问共享内存的常规进程中一样。

与常规内存访问相比,除了设置共享页面的初始成本(在调用 shmat() 的进程中填充页表)之外,没有任何开销 - 在大多数 Linux 风格中,它是 1 页(4 或 8 字节) ) 每 4KB 共享内存。

无论页面是共享分配还是在同一进程中分配,(对于所有相关比较而言)成本相同。

2) 共享内存段必须由内核以某种方式维护。 我不知道“不知何故”在表演方面意味着什么,但是 例如,当所有附加到 shm 的进程都被删除时, shm 段仍处于启动状态,最终可以被新的重新访问 已启动的进程。必须至少有一定程度的开销 与内核在生命周期内需要检查的事情有关 shm 段。

无论是否共享,每个内存页面都附有一个“结构页面”,其中包含有关该页面的一些数据。其中一项是引用计数。当一个页面被分配给一个进程时[无论是通过“shmat”还是其他机制],引用计数都会增加。当它通过某种方式被释放时,引用计数会减少。如果减少的计数为零,则页面实际上被释放 - 否则“没有更多的事情发生”。

与分配的任何其他内存相比,开销基本上为零。无论如何,相同的机制用于页面的其他目的-例如,您有一个也被内核使用的页面-并且您的进程死了,内核需要知道在内核释放该页面之前不要释放该页面以及用户进程。

创建“分叉”时也会发生同样的事情。当一个进程被分叉时,父进程的整个页表本质上被复制到子进程中,并且所有页面都是只读的。每当发生写入时,内核都会发生错误,从而导致该页面被复制 - 因此该页面现在有两个副本,并且执行写入的进程可以修改它的页面,而不会影响其他进程。一旦子(或父)进程死亡,当然所有页面仍然由两个进程拥有[例如从未被写入的代码空间,可能还有一堆从未被触及的公共数据等]显然不能释放,直到两个进程都“死”。再一次,引用计数的页面在这里很有用,因为我们只对每个页面的引用计数进行倒计时,并且当引用计数为零时 - 也就是说,当所有使用该页面的进程都释放它时 - 该页面是实际上作为“有用的页面”返回。

共享库也会发生同样的事情。如果一个进程使用一个共享库,它将在该进程结束时被释放。但如果两个、三个或 100 个进程使用同一个共享库,代码显然必须留在内存中,直到不再需要该页面。

所以,基本上,整个内核中的所有页面都已经被引用计数了。开销很少。

【讨论】:

TLB缓存在切换到其他进程时会失效,因此在多进程架构中,您将面临更多的缓存未命中。基本上是线程上下文切换 vs 进程上下文切换:***.com/questions/5440128/…【参考方案2】:

如果考虑当两个线程或进程访问同一内存时在微电子层面发生了什么,就会产生一些有趣的结果。

关注点是 CPU 的架构如何允许多个内核(因此线程和进程)访问同一内存。这是通过 L1 缓存完成的,然后是 L2、L3,最后是 DRAM。所有这一切的控制器之间必须进行大量的协调。

对于具有 2 个或更多 CPU 的机器,这种协调通过串行总线进行。如果比较两个内核访问同一内存时发生的总线流量,以及将数据复制到另一块内存时发生的总线流量,则流量大致相同。

因此,根据两个线程在机器中运行的位置,复制数据与共享数据的速度损失可能很小。

复制可能是 1) memcpy,2) 管道写入,3) 内部 DMA 传输(英特尔芯片现在可以做到这一点)。

内部 DMA 很有趣,因为它需要零 CPU 时间(简单的 memcpy 只是一个循环,实际上需要时间)。因此,如果一个人可以复制数据而不是共享数据,并且使用内部 DMA 来做到这一点,那么您就可以像共享数据一样快。

代价是更多的 RAM,但回报是 Actor 模型编程之类的东西正在发挥作用。这是一种消除程序中使用信号量保护共享内存的所有复杂性的方法。

【讨论】:

【参考方案3】:

设置共享内存需要内核进行一些额外的工作,因此从您的进程中附加/分离共享内存区域可能比常规内存分配要慢(或者它可能不会......我从来没有做过基准测试) )。但是,一旦它附加到您的进程虚拟内存映射,共享内存就与任何其他用于访问的内存没有什么不同,除非您有多个处理器竞争相同的高速缓存行大小的块。因此,一般而言,对于大多数访问,共享内存应该与任何其他内存一样快,但是,根据您放在那里的内容以及访问它的不同线程/进程的数量,您可能会因特定的使用模式而减慢速度。

【讨论】:

我很欣赏这个答案,但它有点笼统。我在问题中提到的两点仍然存在......【参考方案4】:

除了附加 (shmat) 和分离 (shmdt) 共享内存的成本之外,访问速度应该同样快。换句话说,它应该很快,因为硬件支持它。每次访问都不应有额外层形式的开销。

同步也应该同样快。例如,在 Linux 中,futex 可用于进程和线程。原子变量也应该可以正常工作。

只要附加/分离成本不占主导地位,使用流程应该没有缺点。但是,线程更简单,如果您的进程大多是短暂的,则附加/分离开销可能是一个问题。但是,由于创建流程的成本会很高,无论如何,如果您担心性能,这不应该是一种可能的情况。

最后,这个讨论可能会很有趣:Are shmat and shmdt expensive?。 (警告:它已经过时了。我不知道从那以后情况是否发生了变化。)

这个相关问题也可能会有所帮助:What's the difference between shared memory for IPCs and threads' shared memory? (简短的回答:不多。)

【讨论】:

【参考方案5】:

共享内存的成本与其“元”更改的数量成正比:分配、释放、进程退出……

内存访问次数不起作用。访问共享段的速度与访问其他任何地方一样快。

CPU 执行页表映射。在物理上,CPU 并不知道映射是共享的。

如果您遵循最佳实践(即很少更改映射),您将获得与进程专用内存基本相同的性能。

【讨论】:

以上是关于IPC共享内存和线程内存的性能差异的主要内容,如果未能解决你的问题,请参考以下文章

Linux编程入门一POSIX共享内存

[svc]共享内存

Linux编程之共享内存

IPC在两个不相关的节点js进程之间使用共享内存通信

Linux中的共享内存(ipc)

共享内存是最快的一种IPC方式