I was experimenting with the new shiny concurrent.futures module introduced in Python 3.2, and I've noticed that, almost with identical code, using the Pool from concurrent.futures is way slower than using multiprocessing.Pool.
This is the version using multiprocessing:
def hard_work(n): # Real hard work here pass if __name__ == '__main__': from multiprocessing import Pool, cpu_count try: workers = cpu_count() except NotImplementedError: workers = 1 pool = Pool(processes=workers) result = pool.map(hard_work, range(100, 1000000))
And this is using concurrent.futures:
def hard_work(n): # Real hard work here pass if __name__ == '__main__': from concurrent.futures import ProcessPoolExecutor, wait from multiprocessing import cpu_count try: workers = cpu_count() except NotImplementedError: workers = 1 pool = ProcessPoolExecutor(max_workers=workers) result = pool.map(hard_work, range(100, 1000000))
Using a naïve factorization function taken from this Eli Bendersky article, these are the results on my computer (i7, 64-bit, Arch Linux):
[juanlu@nebulae]─[~/Development/Python/test] └[10:31:10] $ time python pool_multiprocessing.py real 0m10.330s user 1m13.430s sys 0m0.260s [juanlu@nebulae]─[~/Development/Python/test] └[10:31:29] $ time python pool_futures.py real 4m3.939s user 6m33.297s sys 0m54.853s
I cannot profile these with the Python profiler because I get pickle errors. Any ideas?
If you are running on Linux systems then the hangs might not occur but the execution complexity is still more in the "multiprocessing" module. Also having said this, it is also important to note my tasks were highly CPU intensive tasks. On a personal note, I would recommend concurrent.
Yes, it's thread-safe.
When using map
from concurrent.futures
, each element from the iterable is submitted separately to the executor, which creates a Future
object for each call. It then returns an iterator which yields the results returned by the futures.Future
objects are rather heavyweight, they do a lot of work to allow all the features they provide (like callbacks, ability to cancel, check status, ...).
Compared to that, multiprocessing.Pool
has much less overhead. It submits jobs in batches (reducing IPC overhead), and directly uses the result returned by the function. For big batches of jobs, multiprocessing is definitely the better options.
Futures are great if you want to sumbit long running jobs where the overhead isn't that important, where you want to be notified by callback or check from time to time to see if they're done or be able to cancel the execution individually.
Personal note:
I can't really think of much reasons to use Executor.map
- it doesn't give you any of the features of futures - except for the ability to specify a timeout. If you're just interested in the results, you're better off using one of multiprocessing.Pool
's map functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With