<div dir="ltr"><br><br><div class="gmail_quote"><div dir="ltr">On Wed, Jun 24, 2015 at 8:27 AM Sturla Molden <<a href="mailto:sturla.molden@gmail.com">sturla.molden@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">On 24/06/15 07:01, Eric Snow wrote:<br>
<br>
> In return, my question is, what is the level of effort to get fork+IPC<br>
> to do what we want vs. subinterpreters?  Note that we need to<br>
> accommodate Windows as more than an afterthought<br>
<br>
Windows is really the problem. The absence of fork() is especially<br>
hurtful for an interpreted language like Python, in my opinion.<br></blockquote><div><br></div><div>You cannot assume that fork() is safe on any OS as a general solution for anything.  This isn't a Windows specific problem, It simply cannot be relied upon in a general purpose library at all.  It is incompatible with threads.</div><div><br></div><div>The ways fork() can be used safely are in top level application decisions: There must be a guarantee of no threads running before all forking is done.  (thus the impossibility of relying on it as a mechanism to do anything useful in a generic library - you are a library, you don't know what the whole application is doing or when you were called as part of it)</div><div><br></div><div>A concurrency model that assumes that it is fine to fork() and let child processes continue to execute is not usable by everyone. (ie: multiprocessing until <a href="http://bugs.python.org/issue8713">http://bugs.python.org/issue8713</a> was implemented).</div><div><br></div><div>-gps</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
>> If you think avoiding IPC is clever, you are wrong. IPC is very fast, in<br>
>> fact programs written to use MPI tends to perform and scale better than<br>
>> programs written to use OpenMP in parallel computing.<br>
><br>
> I'd love to learn more about that.  I'm sure there are some great<br>
> lessons on efficiently and safely sharing data between isolated<br>
> execution environments.  That said, how does IPC compare to passing<br>
> objects around within the same process?<br>
<br>
There are two major competing standards for parallel computing in<br>
science and engineering: OpenMP and MPI. OpenMP is based on a shared<br>
memory model. MPI is based on a distributed memory model and use message<br>
passing (hence its name).<br>
<br>
The common implementations of OpenMP (GNU, Intel, Microsoft) are all<br>
implemented with threads. There are also OpenMP implementations for<br>
clusters (e.g. Intel), but from the programmer's perspective OpenMP is a<br>
shared memory model.<br>
<br>
The common implementations of MPI (MPICH, OpenMPI, Microsoft MPI) use<br>
processes instead of threads. Processes can run on the same computer or<br>
on different computers (aka "clusters"). On localhost shared memory is<br>
commonly used for message passing, on clusters MPI implementations will<br>
use networking protocols.<br>
<br>
The take-home message is that OpenMP is conceptually easier to use, but<br>
programs written to use MPI tend to be faster and scale better. This is<br>
even true when using a single computer, e.g. a laptop with one multicore<br>
CPU.<br>
<br>
<br>
Here is tl;dr explanation:<br>
<br>
As for ease of programming, it is easier to create a deadlock or<br>
livelock with MPI than OpenMP, even though programs written to use MPI<br>
tend to need fewer synchronization points. There is also less<br>
boilerplate code to type when using OpenMP, because we do not have to<br>
code object serialization, message passing, and object deserialization.<br>
<br>
For performance, programs written to use MPI seems to have a larger<br>
overhead because they require object serialization and message passing,<br>
whereas OpenMP threads can just share the same objects. The reality is<br>
actually the opposite, and is due to the internals of modern CPU,<br>
particularly hierarchichal memory, branch prediction and long pipelines.<br>
<br>
Because of hierarchichal memory, the cache used by CPUs and CPU cores<br>
must be kept in synch. Thus when using OpenMP (threads) there will be a<br>
lot of synchronization going on that the programmer does not see, but<br>
which the hardware will do behind the scenes. There will also be a lot<br>
of data passing between various cache levels on the CPU and RAM. If a<br>
core writes to a pice of memory it keeps in a cache line, a cascade of<br>
data traffic and synchronization can be triggered across all CPUs and<br>
cores. Not only will this stop the CPUs and prompt them to synchronize<br>
cache with RAM, it also invalidates their branch prediction and they<br>
must flush their pipelines and throw away work they have already done.<br>
The end result is a program that does not scale or perform very well,<br>
even though it does not seem to have any explicit synchronization points<br>
that could explain this. The term "false sharing" is often used to<br>
describe this problem.<br>
<br>
Programs written to use MPI are the opposite. There every instance of<br>
synchronization and message passing is visible. When a CPU core writes<br>
to memory kept in a cache line, it will never trigger synchronization<br>
and data traffic across all the CPUs. The scalability is as the program<br>
predicts. And even though memory and objects are not shared, there is<br>
actually much less data traffic going on.<br>
<br>
Which to use? Most people find it easier to use OpenMP, and it does not<br>
require a big runtime environment to be installed. But programs using<br>
MPI tend to be the faster and more scalable. If you need to ensure<br>
scalability on multicores, multiple processes are better than multiple<br>
threads. The scalability of MPI also applies to Python's<br>
multiprocessing. It is the isolated virtual memory of each process that<br>
allows the cores to run at full speed.<br>
<br>
Another thing to note is that Windows is not a second-class citizen when<br>
using MPI. The MPI runtime (usually an executable called mpirun or<br>
mpiexec) starts and manages a group of processes. It does not matter if<br>
they are started by fork() or CreateProcess().<br>
<br>
<br>
<br>
> Solving reference counts in this situation is a separate issue that<br>
> will likely need to be resolved, regardless of which machinery we use<br>
> to isolate task execution.<br>
<br>
As long as we have a GIL, and we need the GIL to update a reference<br>
count, it does not hurt so much as it otherwise would. The GIL hides<br>
most of the scalability impact by serializing flow of execution.<br>
<br>
<br>
<br>
> IPC sounds great, but how well does it interact with Python's memory<br>
> management/allocator?  I haven't looked closely but I expect that<br>
> multiprocessing does not use IPC anywhere.<br>
<br>
multiprocessing does use IPC. Otherwise the processes could not<br>
communicate. One example is multiprocessing.Queue, which uses a pipe and<br>
a semaphore.<br>
<br>
<br>
<br>
Sturla<br>
<br>
_______________________________________________<br>
Python-ideas mailing list<br>
<a href="mailto:Python-ideas@python.org" target="_blank">Python-ideas@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-ideas" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/python-ideas</a><br>
Code of Conduct: <a href="http://python.org/psf/codeofconduct/" rel="noreferrer" target="_blank">http://python.org/psf/codeofconduct/</a><br>
</blockquote></div></div>