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

29

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

但是我有我的疑虑:

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

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

多进程共享内存系统和多线程应用程序一样快吗?


对于内核而言,附加共享内存段只涉及为底层内存设置(额外的)页表。 (将其映射到进程地址空间中)没有额外的成本。2)没有额外的开销;检查是在附加时完成的。 - wildplasser
5个回答

17
1) shmat()将本地进程的虚拟内存映射到共享段。这个转换需要为每个共享内存地址执行,相对于shm访问数量而言可能代表着一个显著的成本。在多线程应用程序中,不需要额外的转换:所有VM地址都会被转换为物理地址,就像不访问共享内存的常规进程一样。
除了设置共享页面的初始成本 - 在调用shmat()的进程中填写页面表 - 在大多数Linux版本中,每4KB共享内存为1页(4或8字节),与普通内存访问相比没有额外开销。
2)内核必须以某种方式维护共享内存段。我不知道这个“某种方式”在性能方面意味着什么,但例如,当连接到shm的所有进程关闭时,shm段仍然存在,并且最终可以被新启动的进程重新访问。在shm段的生命周期中,内核需要检查的东西必须至少有一定的开销。
无论是共享还是非共享,每个内存页面都附有“struct page”,其中包含有关该页面的一些数据。其中之一就是引用计数。当将页面分配给进程[无论是通过“shmat”还是其他机制]时,引用计数会增加。当通过某种手段释放时,引用计数会减少。如果递减的计数为零,则实际上会释放该页面-否则“它什么也不会发生”。
与分配的任何其他内存相比,额外开销基本为零。该机制在页面的其他目的方面也是使用的-例如,假设你有一个页面也被内核使用-并且你的进程死亡,内核需要知道在它被内核和用户进程释放之前不要释放该页面。当创建一个“fork”时,同样的事情会发生。当一个进程被分叉时,父进程的整个页表基本上会被复制到子进程中,并且所有页面都被设置为只读。每当进行写操作时,内核会产生一个故障,导致该页面被复制 - 因此现在有两份该页面的副本,进行写操作的进程可以修改其页面,而不影响其他进程。当子(或父)进程终止时,当然仍然由BOTH进程拥有的所有页面[如从未写入的代码空间和可能是一堆未触及的公共数据等]显然不能被释放,直到BOTH进程都“死亡”。因此,这里的引用计数页面再次变得有用,因为我们只对每个页面的ref-count进行倒计数,当ref-count为零时 - 也就是当使用该页面的所有进程都已释放它时 - 该页面实际上会作为“有用页面”返回。
共享库发生的情况完全相同。如果一个进程使用共享库,则在该进程结束时将释放它。但是,如果两个、三个或100个进程使用同一个共享库,那么该代码显然必须保留在内存中,直到不再需要该页面为止。
因此,基本上,整个内核中的所有页面都已经进行过引用计数。开销非常小。

8
在多进程架构中,当切换到其他进程时,TLB缓存将被失效,因此你将面临更多的缓存未命中。基本上,这是线程上下文切换与进程上下文切换的比较:https://dev59.com/KW035IYBdhLWcg3wZ_Mt - czz

5
如果考虑两个线程或进程访问同一内存时微电子水平上发生的情况,会有一些有趣的后果。
有趣的地方在于CPU架构如何允许多个核心(因此包括线程和进程)访问同一内存。这是通过L1高速缓存、然后是L2、L3和最后的DRAM来实现的。所有控制器之间必须进行大量的协调。
对于具有2个或更多CPU的机器,该协调是通过串行总线完成的。如果将两个核心访问同一内存时发生的总线流量与数据复制到另一个内存区域时发生的总线流量进行比较,它们的总量大约相同。
因此,取决于两个线程在计算机中运行的位置,在复制数据与共享数据之间可能没有速度惩罚。
复制可以是1)memcpy,2)pipe write,3)内部DMA传输(英特尔芯片可以做到这一点)。内部DMA很有趣,因为它不需要任何CPU时间(naive memcpy只是一个循环,实际上需要时间)。因此,如果可以复制数据而不是共享数据,并且使用内部DMA这样做,则可以像共享数据一样快。
惩罚是更多的RAM,但回报是Actor模型编程等技术的实现。这是一种从程序中去除使用信号量保护共享内存的所有复杂性的方法。

4

建立共享内存需要内核进行一些额外的工作,因此将共享内存区域附加/分离到您的进程可能比常规内存分配更慢(或者可能不是...我从未对此进行过基准测试)。但是,一旦它附加到您的进程虚拟内存映射中,共享内存在访问方面与任何其他内存没有区别,除非有多个处理器竞争相同大小的高速缓存线块。因此,通常情况下,共享内存应与大多数访问的任何其他内存一样快,但是,取决于您放置的内容以及访问它的不同线程/进程数量,您可以针对特定的使用模式获得一些减速。


1
我很感谢您的回答,但它有点泛泛而谈。我在问题中提到的两个要点仍然存在... - Robert Kubrick

2
除了共享内存的附加(shmat)和分离(shmdt)成本外,访问应该同样快。换句话说,它应该与硬件支持的速度一样快。每次访问都不应该有额外的开销。
同步也应该同样快。例如,在Linux中,可以为进程和线程使用futex。原子变量也应该可以正常工作。
只要附加/分离成本不占主导地位,使用进程就没有任何劣势。但是线程更简单,如果您的进程大多数都是短暂的,则附加/分离开销可能是一个问题。但是由于创建进程的成本将很高,因此如果您关心性能,这不太可能发生。
最后,这个讨论可能会有趣:shmat和shmdt是否昂贵?。(警告:它已经过时了。我不知道情况是否有所改变。)
这个相关问题也可能有帮助:IPC和线程共享内存之间有什么区别?(简短的回答:没有太大区别。)

2

共享内存的成本与对其进行“元”更改的次数成正比:分配、释放、进程退出等。

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

CPU执行页面表映射。从物理上讲,CPU不知道映射是共享的。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接