我经常听说,访问进程之间的共享内存段与线程之间访问进程内存相比没有性能损失。 换句话说,multithreading应用程序不会比使用共享内存的一组进程(不包括locking或其他同步问题)更快。
但我有疑惑:
1)shmat()将本地进程虚拟内存映射到共享段。 这个翻译必须为每个共享内存地址执行,并且可能代表一个重要的成本。 在multithreading应用程序中,不需要额外的转换:所有虚拟机地址都转换为物理地址,就像在不访问共享内存的常规进程中一样。
2)共享内存段必须由内核以某种方式维护。 例如,当附加到shm的所有进程都被closures时,shm段仍然是up的,并且最终可以被新启动的进程重新访问。 在shm段上可能会有一些与内核操作相关的开销。
多进程共享内存系统与multithreading应用程序一样快吗?
1)shmat()将本地进程虚拟内存映射到共享段。 这个翻译必须针对每个共享存储器地址执行,并且相对于shm访问的数量而言可能代表显着的成本。 在多线程应用程序中,不需要额外的转换:所有虚拟机地址都被转换为物理地址,就像不能访问共享内存的常规进程一样。
与普通内存访问相比,除了最初设置共享页面的开销之外,没有任何开销 – 在调用shmat()
的进程中填充页表 – 在大多数Linux中,每4KB为1页(4或8字节)的共享内存。
(对于所有相关的比较)页面被分配共享还是在相同的过程中相同的成本。
2)共享内存段必须由内核以某种方式维护。 我不知道在性能方面“某种程度上”意味着什么,但是例如,当附加到shm的所有进程都被取消时,shm段仍然是up的,并且最终可以通过新启动的进程被重新访问。 至少有一定程度的开销与内核在shm段的生命周期中需要检查的内容有关。
无论是否共享,每个内存页面都有一个“结构页面”,并附带一些关于页面的数据。 其中一项是参考计数。 当一个页面被发送给一个进程时(无论是通过“shmat”还是其他一些机制),引用计数都会增加。 当通过某种方式释放它时,引用计数递减。 如果递减的计数为零,则页面实际上被释放 – 否则“没有其他事情发生”。
与分配的任何其他内存相比,开销基本为零。 同样的机制被用于页面的其他目的 – 比如说你有一个内核也使用的页面 – 并且你的进程死了,内核需要知道不要释放那个页面直到内核被释放为止以及用户进程。
同样的事情发生时,“叉”创建。 当一个进程分叉时,父进程的整个页表基本上复制到子进程中,并且所有页面都是只读的。 每当写入发生时,内核就会发生错误,导致该页面被复制 – 所以现在有两个该页面的副本,写入的过程可以修改它的页面,而不影响其他过程。 一旦孩子(或父母)进程死亡,当然所有仍然由BOTH进程拥有的页面(例如从未被写入的代码空间,可能还有一些从未被触及过的常见数据等)显然不能释放,直到两个进程“死亡”。 所以引用计数的页面在这里也很有用,因为我们只在每个页面上倒计数,而当ref-count是零时 – 也就是说,当所有使用该页面的进程已经释放它 – 页面是实际上是作为“有用的页面”返回的。
完全相同的事情发生在共享库上。 如果一个进程使用共享库,则在该进程结束时将被释放。 但是,如果两个,三个或100个进程使用相同的共享库,代码显然将不得不留在内存中,直到不再需要页面。
因此,基本上,整个内核中的所有页面都已经被引用计数。 有很少的开销。
设置共享内存需要内核的一些额外的工作,所以从你的进程中连接/分离一个共享内存区域可能比正常的内存分配要慢(或者它可能不是……我从未测试过)。 但是,一旦将它附加到进程的虚拟内存映射中,共享内存与访问的任何其他内存没有区别,除非有多个处理器争用相同的缓存行大小的块。 所以,一般来说,共享内存应该和大多数访问的内存一样快,但是,根据你放置的内存以及多少个不同的线程/进程访问内存,你可以对特定的使用模式进行一些减速。
共享内存的成本与“元”变化的数量成正比:分配,取消分配,进程退出,…
内存访问的数量不起作用。 访问共享段与访问其他地方一样快。
CPU执行页表映射。 在物理上,CPU不知道映射是共享的。
如果遵循最佳实践(即很少更改映射),则可以获得与进程私有内存基本相同的性能。
除了共享内存的附加( shmat
)和分离( shmdt
)成本之外,访问应该同样快。 换句话说,硬件支持它应该是快速的。 每个访问都不应该有额外的层次。
同步应该同样快。 例如,在Linux中, futex可以用于进程和线程。 原子变量也应该正常工作。
只要附着/分离成本不占主导地位,使用过程就不会有缺点。 然而,线程更简单,如果你的进程大多是短命的,那么附加/分离的开销可能是一个问题。 但是,由于创建流程的成本很高,无论如何,如果您担心业绩,这不应该是一个可能的情况。
最后,这个讨论可能会很有趣: shmat和shmdt是昂贵的吗? 。 (注意:这已经过时了,不知道情况是否改变了。)
这个相关的问题也可能是有帮助的: IPC的共享内存和线程的共享内存有什么区别? (简短的回答:不多。)
如果考虑到当两个线程或进程访问同一个内存时微电子层面发生的事情,会有一些有趣的结果。
感兴趣的是CPU的架构如何允许多个内核(因此线程和进程)访问相同的内存。 这是通过L1缓存完成的,然后是L2,L3,最后是DRAM。 所有这些控制器之间都需要进行很多协调。
对于具有2个或更多CPU的机器,通过串行总线进行协调。 如果比较两个内核正在访问同一内存时发生的总线通信量,以及将数据复制到另一个内存时的情况,则说明通信量相同。
因此,根据两个线程运行的机器的位置,复制数据和共享数据可能会造成很小的速度损失。
复制可能是1)memcpy,2)管道写,3)内部DMA传输(英特尔芯片可以这样做)。
内部DMA很有趣,因为它需要零CPU时间(一个天真的memcpy只是一个循环,实际上需要时间)。 所以如果可以复制数据而不是共享数据,而且可以通过内部DMA来完成,那么就可以像共享数据一样快。
惩罚是更多的内存,但是回报是像Actor模型编程这样的东西在玩。 这是一种从程序中消除所有用信号量来防止共享内存的复杂性的方法。