runners.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # MODULE: grass.benchmark
  2. #
  3. # AUTHOR(S): Aaron Saw Min Sern <aaronsms u nus edu>
  4. # Vaclav Petras <wenzeslaus gmail com>
  5. #
  6. # PURPOSE: Benchmarking for GRASS GIS modules
  7. #
  8. # COPYRIGHT: (C) 2021 Vaclav Petras, and by the GRASS Development Team
  9. #
  10. # This program is free software under the GNU General Public
  11. # License (>=v2). Read the file COPYING that comes with GRASS
  12. # for details.
  13. """Basic functions for benchmarking modules"""
  14. import shutil
  15. from types import SimpleNamespace
  16. import grass.script as gs
  17. def benchmark_single(module, label, repeat=5):
  18. """Benchmark module as is without chaning anything.
  19. *module* is an instance of PyGRASS Module class or any object which
  20. has a *run* method which takes no arguments and executes the benchmarked code,
  21. and attribute *time* which is set to execution time after the *run*
  22. function returned. Additionally, the object should be convertible to *str*
  23. for printing.
  24. *repeat* sets how many times the each run is repeated.
  25. *label* is a text to add to the result (for user-facing display).
  26. Returns an object with attributes *time* (an average execution time),
  27. *all_times* (list of measured execution times),
  28. and *label* (the provided parameter as is).
  29. """
  30. term_size = shutil.get_terminal_size()
  31. if hasattr(module, "get_bash"):
  32. print(module.get_bash())
  33. else:
  34. print(module)
  35. min_avg = float("inf")
  36. print("\u2500" * term_size.columns)
  37. time_sum = 0
  38. measured_times = []
  39. for _ in range(repeat):
  40. module.run()
  41. print(f"{module.time}s")
  42. time_sum += module.time
  43. measured_times.append(module.time)
  44. avg = time_sum / repeat
  45. if avg < min_avg:
  46. min_avg = avg
  47. print(f"\nResult - {avg}s")
  48. print("\u2500" * term_size.columns)
  49. print(f"Best average time - {min_avg}s\n")
  50. return SimpleNamespace(
  51. all_times=measured_times,
  52. time=avg,
  53. label=label,
  54. )
  55. def benchmark_nprocs(module, label, max_nprocs, repeat=5):
  56. """Benchmark module using values of nprocs up to *max_nprocs*.
  57. *module* is an instance of PyGRASS Module class or any object which
  58. has a *update* method taking *nprocs* as a keyword argument,
  59. a *run* which takes no arguments and executes the benchmarked code,
  60. and attribute *time* which is set to execution time after the *run*
  61. function returned. Additionally, the object should be convertible to *str*
  62. for printing.
  63. The module is executed for each generated value of nprocs. *max_nprocs* is used
  64. to generate a continuous range of integer values from 1 up to *max_nprocs*.
  65. *repeat* sets how many times the each run is repeated.
  66. So, the module will run ``max_nprocs * repeat`` times.
  67. *label* is a text to add to the result (for user-facing display).
  68. Optional *nprocs* is passed to the module if present.
  69. Returns an object with attributes *times* (list of average execution times),
  70. *all_times* (list of lists of measured execution times), *nprocs*
  71. (list of *nprocs* values used), and *label* (the provided parameter as is).
  72. """
  73. term_size = shutil.get_terminal_size()
  74. if hasattr(module, "get_bash"):
  75. print(module.get_bash())
  76. else:
  77. print(module)
  78. min_avg = float("inf")
  79. min_time = None
  80. serial_avg = None
  81. avg_times = []
  82. all_times = []
  83. nprocs_list = list(range(1, max_nprocs + 1))
  84. for nprocs in nprocs_list:
  85. print("\u2500" * term_size.columns)
  86. print(f"Benchmark with {nprocs} thread(s)...\n")
  87. time_sum = 0
  88. measured_times = []
  89. for _ in range(repeat):
  90. module.update(nprocs=nprocs)
  91. module.run()
  92. print(f"{module.time}s")
  93. time_sum += module.time
  94. measured_times.append(module.time)
  95. avg = time_sum / repeat
  96. avg_times.append(avg)
  97. all_times.append(measured_times)
  98. if nprocs == 1:
  99. serial_avg = avg
  100. if avg < min_avg:
  101. min_avg = avg
  102. min_time = nprocs
  103. print(f"\nResult - {avg}s")
  104. print("\u2500" * term_size.columns)
  105. if serial_avg is not None:
  106. print(f"\nSerial average time - {serial_avg}s")
  107. print(f"Best average time - {min_avg}s ({min_time} threads)\n")
  108. return SimpleNamespace(
  109. all_times=all_times,
  110. times=avg_times,
  111. nprocs=nprocs_list,
  112. label=label,
  113. )
  114. def benchmark_resolutions(module, resolutions, label, repeat=5, nprocs=None):
  115. """Benchmark module using different resolutions.
  116. *module* is an instance of PyGRASS Module class or any object
  117. with attributes as specified in :func:`benchmark_nprocs`
  118. except that the *update* method is required only when *nprocs* is set.
  119. *resolutions* is a list of resolutions to set (current region is currently
  120. used and changed but that may change in the future).
  121. *repeat* sets how many times the each run is repeated.
  122. So, the module will run ``len(resolutions) * repeat`` times.
  123. *label* is a text to add to the result (for user-facing display).
  124. Optional *nprocs* is passed to the module if present
  125. (the called module does not have to support nprocs parameter).
  126. Returns an object with attributes *times* (list of average execution times),
  127. *all_times* (list of lists of measured execution times), *resolutions*
  128. (the provided parameter as is), *cells* (number of cells in the region),
  129. and *label* (the provided parameter as is).
  130. """
  131. term_size = shutil.get_terminal_size()
  132. if hasattr(module, "get_bash"):
  133. print(module.get_bash())
  134. else:
  135. print(module)
  136. avg_times = []
  137. all_times = []
  138. n_cells = []
  139. for resolution in resolutions:
  140. gs.run_command("g.region", res=resolution)
  141. region = gs.region()
  142. n_cells.append(region["cells"])
  143. print("\u2500" * term_size.columns)
  144. print(f"Benchmark with {resolution} resolution...\n")
  145. time_sum = 0
  146. measured_times = []
  147. for _ in range(repeat):
  148. if nprocs:
  149. module.update(nprocs=nprocs)
  150. module.run()
  151. print(f"{module.time}s")
  152. time_sum += module.time
  153. measured_times.append(module.time)
  154. avg = time_sum / repeat
  155. avg_times.append(avg)
  156. all_times.append(measured_times)
  157. print(f"\nResult - {avg}s")
  158. return SimpleNamespace(
  159. all_times=all_times,
  160. times=avg_times,
  161. resolutions=resolutions,
  162. cells=n_cells,
  163. label=label,
  164. )