title: python concurrent.futures.ProcessPoolExecutor: Performance of .submit() vs .map()
body: <p>I am using <code>concurrent.futures.ProcessPoolExecutor</code> to find the occurrence of a number from a number range. The intent is to investigate the amount of speed-up performance gained from concurrency. To benchmark performance, I have a control - a serial code to perform said task (shown below). I have written 2 concurrent codes, one using <code>concurrent.futures.ProcessPoolExecutor.submit()</code> and the other using <code>concurrent.futures.ProcessPoolExecutor.map()</code> to perform the same task. They are shown below. Advice on drafting the former and latter can be seen <a href="https://stackoverflow.com/q/42049066/5722359">here</a> and <a href="https://stackoverflow.com/q/42056738/5722359">here</a>, respectively. </p>
  
  <p>The task issued to all three codes was to find the number of occurrences of the number 5 in the number range of 0 to 1E8. Both <code>.submit()</code> and <code>.map()</code> were assigned 6 workers, and <code>.map()</code> had a chunksize of 10,000. The manner to discretise the workload were identical in the concurrent codes. However, the function used to find occurrences in both codes were different. This was because the way arguments were passed to a function called by <code>.submit()</code> and <code>.map()</code> were different.  </p>
  
  <p>All 3 codes reported the same number of occurrences, i.e. 56,953,279 times. However, the time taken to complete the task were very different. <code>.submit()</code> performed 2 times faster than the control while <code>.map()</code> took twice as long as the control to complete it's task.</p>
  
  <p><strong>Questions:</strong></p>
  
  <ol>
  <li>I would like to know if the slow performance of <code>.map()</code> is an artifact of my coding or it is inherently slow?" If the former, how can I improve it. I am just surprise that it performed slower than the control as there will be no much incentive to use it.</li>
  <li>I like to know if there is anyway to make <code>.submit()</code> code perform even faster. A condition I have is that the function <code>_concurrent_submit()</code> must return an iterable with the numbers/occurrences containing the number 5.</li>
  </ol>
  
  <p><strong>Benchmark Results</strong><br>
  <a href="https://i.stack.imgur.com/3x3v2.png" rel="noreferrer"><img src="https://i.stack.imgur.com/3x3v2.png" alt="benchmark results"></a>    </p>
  
  <p><strong>concurrent.futures.ProcessPoolExecutor.submit()</strong></p>
  
  <pre><code>#!/usr/bin/python3.5
  # -*- coding: utf-8 -*-
  
  import concurrent.futures as cf
  from time import time
  from traceback import print_exc
  
  def _findmatch(nmin, nmax, number):
      '''Function to find the occurrence of number in range nmin to nmax and return
         the found occurrences in a list.'''
      print('\n def _findmatch', nmin, nmax, number)
      start = time()
      match=[]
      for n in range(nmin, nmax):
          if number in str(n):
              match.append(n)
      end = time() - start
      print("found {0} in {1:.4f}sec".format(len(match),end))
      return match
  
  def _concurrent_submit(nmax, number, workers):
      '''Function that utilises concurrent.futures.ProcessPoolExecutor.submit to
         find the occurences of a given number in a number range in a parallelised
         manner.'''
      # 1. Local variables
      start = time()
      chunk = nmax // workers
      futures = []
      found =[]
      #2. Parallelization
      with cf.ProcessPoolExecutor(max_workers=workers) as executor:
          # 2.1. Discretise workload and submit to worker pool
          for i in range(workers):
              cstart = chunk * i
              cstop = chunk * (i + 1) if i != workers - 1 else nmax
              futures.append(executor.submit(_findmatch, cstart, cstop, number))
          # 2.2. Instruct workers to process results as they come, when all are
          #      completed or .....
          cf.as_completed(futures) # faster than cf.wait()
          # 2.3. Consolidate result as a list and return this list.
          for future in futures:
              for f in future.result():
                  try:
                      found.append(f)
                  except:
                      print_exc()
          foundsize = len(found)
          end = time() - start
          print('within statement of def _concurrent_submit():')
          print("found {0} in {1:.4f}sec".format(foundsize, end))
      return found
  
  if __name__ == '__main__':
      nmax = int(1E8) # Number range maximum.
      number = str(5) # Number to be found in number range.
      workers = 6     # Pool of workers
  
      start = time()
      a = _concurrent_submit(nmax, number, workers)
      end = time() - start
      print('\n main')
      print('workers = ', workers)
      print("found {0} in {1:.4f}sec".format(len(a),end))
  </code></pre>
  
  <p><strong>concurrent.futures.ProcessPoolExecutor.map()</strong></p>
  
  <pre><code>#!/usr/bin/python3.5
  # -*- coding: utf-8 -*-
  
  import concurrent.futures as cf
  import itertools
  from time import time
  from traceback import print_exc
  
  def _findmatch(listnumber, number):    
      '''Function to find the occurrence of number in another number and return
         a string value.'''
      #print('def _findmatch(listnumber, number):')
      #print('listnumber = {0} and ref = {1}'.format(listnumber, number))
      if number in str(listnumber):
          x = listnumber
          #print('x = {0}'.format(x))
          return x 
  
  def _concurrent_map(nmax, number, workers):
      '''Function that utilises concurrent.futures.ProcessPoolExecutor.map to
         find the occurrences of a given number in a number range in a parallelised
         manner.'''
      # 1. Local variables
      start = time()
      chunk = nmax // workers
      futures = []
      found =[]
      #2. Parallelization
      with cf.ProcessPoolExecutor(max_workers=workers) as executor:
          # 2.1. Discretise workload and submit to worker pool
          for i in range(workers):
              cstart = chunk * i
              cstop = chunk * (i + 1) if i != workers - 1 else nmax
              numberlist = range(cstart, cstop)
              futures.append(executor.map(_findmatch, numberlist,
                                          itertools.repeat(number),
                                          chunksize=10000))
          # 2.3. Consolidate result as a list and return this list.
          for future in futures:
              for f in future:
                  if f:
                      try:
                          found.append(f)
                      except:
                          print_exc()
          foundsize = len(found)
          end = time() - start
          print('within statement of def _concurrent(nmax, number):')
          print("found {0} in {1:.4f}sec".format(foundsize, end))
      return found
  
  if __name__ == '__main__':
      nmax = int(1E8) # Number range maximum.
      number = str(5) # Number to be found in number range.
      workers = 6     # Pool of workers
  
      start = time()
      a = _concurrent_map(nmax, number, workers)
      end = time() - start
      print('\n main')
      print('workers = ', workers)
      print("found {0} in {1:.4f}sec".format(len(a),end))
  </code></pre>
  
  <p><strong>Serial Code:</strong></p>
  
  <pre><code>#!/usr/bin/python3.5
  # -*- coding: utf-8 -*-
  
  from time import time
  
  def _serial(nmax, number):    
      start = time()
      match=[]
      nlist = range(nmax)
      for n in nlist:
          if number in str(n):match.append(n)
      end=time()-start
      print("found {0} in {1:.4f}sec".format(len(match),end))
      return match
  
  if __name__ == '__main__':
      nmax = int(1E8) # Number range maximum.
      number = str(5) # Number to be found in number range.
  
      start = time()
      a = _serial(nmax, number)
      end = time() - start
      print('\n main')
      print("found {0} in {1:.4f}sec".format(len(a),end))
  </code></pre>
  
  <p><strong>Update 13th Feb 2017:</strong></p>
  
  <p>In addition to @niemmi answer, I have provide an answer following some personal research to show:</p>
  
  <ol>
  <li>how to further speed-up @niemmi's <code>.map()</code> and <code>.submit()</code> solutions, and</li>
  <li>when <code>ProcessPoolExecutor.map()</code> can led to more speed-up than <code>ProcessPoolExecutor.submit()</code>.</li>
  </ol>