Advertisement
Guest User

Mandelbrot in parallel

a guest
Jun 15th, 2017
484
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.74 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2. """
  3. The following code, building on discussion in [1] demonstrates how parallel processing
  4. on a multicore CPU could speed up computations in Python. However, it hardly scratches the surface
  5. of the complexities that parallel processing can entail.
  6.  
  7. The code produces the mandelbrot fractal in a trivial way. See [2] for how to do it a lot faster.
  8.  
  9. Some notes on parallel processing in Python:
  10. * Some things that work on Linux won't work in Windows, because of windows' lack of
  11. some "forking" capability.
  12. * You can run multiple threads in Python, but as explained in [3],
  13. Python will not be running these in parallel, so this can't help you with using multicores
  14. * This code uses the multiprocessing pool to create different "subprocesses"
  15. that windows then delegate to different cores. An alternative is "Process" in Python, see comments in [1]
  16.  
  17.  
  18. On my 4-core CPU, with 8 "logical cores" (so it's really 4) the parallel code runs twice as fast
  19. as the single-core code. You can use the Windows Resource Monitor to verify that the first run
  20. only uses a single core, while the second run utilizes all cores.
  21.  
  22. [1] https://stackoverflow.com/questions/44521931/what-is-some-example-code-for-demonstrating-multicore-speedup-in-python-on-windo
  23. [2] https://www.ibm.com/developerworks/community/blogs/jfp/entry/How_To_Compute_Mandelbrodt_Set_Quickly?lang=en
  24. [3] https://www.ploggingdev.com/2017/01/multiprocessing-and-multithreading-in-python-3/
  25.  
  26. """
  27.  
  28. import numpy as np
  29. import matplotlib.pyplot as plt
  30. import time
  31. from multiprocessing import Pool, cpu_count
  32. from functools import partial
  33. import sys
  34.  
  35. #The mandelbrot fractal is stored in an array of size height x width x 3,
  36. #where dataarray[height,width,2] stores the actual value of a pixel and [..,0] and [..,1] stores the
  37. #real and imaginary components of the pixel's coordinate.
  38.  
  39.  
  40. #The calculatePixel function calculates the value of a single pixel in the picture of
  41. #the mandelbrot fractal.The function both returns the value and writes it directly to dataarray.
  42. #To have parallel processes share data like this is usually bad coding practice and
  43. #could even hinder parallel processing if each subprocess tries to "lock" access to it.
  44. #However, in this case it seems to run fine.
  45.  
  46. #Note: Python must be able to "pickle" the function you want to parallelize [4]
  47. #[4] https://stackoverflow.com/questions/8804830/python-multiprocessing-pickling-error
  48. def calculatePixel(l,k,dataarray):
  49. #This function
  50. #print('koord:',l,k)
  51. z = 0
  52. temp = 0
  53. c = complex(dataarray[l,k,1], dataarray[l,k,0])
  54. for m in range(40):
  55. z = z*z + c
  56. if abs(z) > 2:
  57. dataarray[l,k,2] = m
  58. temp = m
  59. break
  60.  
  61. return temp
  62.  
  63.  
  64.  
  65. def runforestrun():
  66. # multi-platform precision clock
  67. get_timer = time.clock if sys.platform == "win32" else time.time
  68.  
  69. runSequential = True
  70. runParallel = True
  71.  
  72. #Populates the data array with real and complex coordinates for each pixel.
  73. #A more elegant Python "comprehension" probably exists :-)
  74.  
  75. for j in range(height):
  76. #kolonner fylles nedover med verdiar frå 1 til -1.
  77. #Dette representerar Im(c)
  78. #kunne bytta ut med range() men får størrelse autoamtisk her
  79. data[j,:,0] = [1 - 2*j/height for x in data[j,:,0]]
  80.  
  81. for j in range(width):
  82. #rader representerar Re(c)
  83. data[:,j,1] = [-2 + 3*j/width for x in data[:,j,1]]
  84. #data[:,j,1] = [j/width - (3/2) for x in data[:,j,0]]
  85.  
  86.  
  87.  
  88.  
  89.  
  90. if runSequential:
  91. print("Starting sequential run...")
  92. tic = get_timer()
  93. #Here we just loop over all pixels one by one
  94. for k in range(width):
  95. for l in range(height):
  96. calculatePixel(l,k,data)
  97. print("Time spent sequentially",get_timer()-tic)
  98. #Lets print the result
  99. printmandel()
  100.  
  101. if runParallel:
  102. print("Starting parallel run with Pool...")
  103. tic = get_timer()
  104.  
  105. #We initialize a pool of workers, the number of which is determined by the number of cores
  106. #on the CPU. You may want to make multiple runs with different numbers, to demonstrate speedup.
  107. p = Pool(cpu_count())
  108.  
  109. #The multiprocessing package of Python has a function called "starmap"
  110. #that does exactly what "map" does in Python, except it only accepts a single input
  111. #for the functions it "maps". We jump through hoops with "partial" to circumvent this.
  112. #See [5] for a discussion and alternative approaches.
  113. #[5] https://stackoverflow.com/questions/5442910/python-multiprocessing-pool-map-for-multiple-arguments
  114.  
  115. partial_calculatePixel = partial(calculatePixel, dataarray=data)
  116. koord = []
  117. for j in range(height):
  118. for k in range(width):
  119. koord.append((j,k))
  120.  
  121. #Runs the calls to calculatePixel in a pool. "hmm" collects the output
  122. hmm = p.starmap(partial_calculatePixel,koord)
  123.  
  124. print("Time spent in parallel run:",get_timer()-tic)
  125.  
  126. #Since calculatePixel edits "data" directly, we don't really need the starmap output
  127. #but for complection, this is how you would put the output back into the data array:
  128. data[:,:,2] = np.array(hmm).reshape((height,width))
  129.  
  130. #Print the result
  131. printmandel()
  132.  
  133. def printmandel():
  134. #Also see: stackoverflow.com/questions/6916978/how-do-i-tell-matplotlib-to-create-a-second-new-plot-then-later-plot-on-the-o
  135. plt.figure()
  136. plt.imshow(data[:,:,2],cmap=plt.plasma())
  137. plt.colorbar()
  138.  
  139. #We only want to run calculatePixel in parallel, but Python loads the whole file.
  140. #We therefore use the "if main" check as below, to avoid an infinite loop of new runs being spawned
  141. # "if __name__ == '__main__':" ensures that it's only run when YOU start it.
  142.  
  143. if __name__ == '__main__':
  144. print('Loading multicorespeedup.py')
  145. #height determines the resolution of the mandelbrot fractal image.
  146. #large values may give an out of memory error. A hint that something in this code
  147. #is far from optimal :-)
  148. height = 2000
  149. width = int(height*1.5)
  150. #"data" is declared here, so that all functions in the module will have access to it.
  151. data = np.zeros( (height,width,3), dtype=np.float )
  152. #The main part of the code:
  153. runforestrun()
  154.  
  155.  
  156. #Typical output from a run with height = 2000 on a 4-core (8 logical) Intel CPU (i7-3632QM @2.20GHz)
  157. #Loading multicorespeedup.py
  158. #Starting sequential run...
  159. #Time spent sequentially 40.12519295999846
  160. #Starting parallel run with Pool...
  161. #Time spent in parallel run: 20.85525935829719
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement