Coverage for cpyquickhelper/numbers/speed_measure.py: 100%

81 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-30 05:30 +0200

1""" 

2@file 

3@brief Measures speed. 

4""" 

5import sys 

6from timeit import Timer 

7 

8 

9def measure_time(stmt, context=None, repeat=10, number=50, div_by_number=False, 

10 max_time=None): 

11 """ 

12 Measures a statement and returns the results as a dictionary. 

13 

14 :param stmt: string 

15 :param context: variable to know in a dictionary 

16 :param repeat: average over *repeat* experiment 

17 :param number: number of executions in one row 

18 :param div_by_number: divide by the number of executions 

19 :param max_time: execute the statement until the total goes 

20 beyond this time (approximatively), *repeat* is ignored, 

21 *div_by_number* must be set to True 

22 :return: dictionary 

23 

24 .. runpython:: 

25 :showcode: 

26 

27 from cpyquickhelper.numbers import measure_time 

28 from math import cos 

29 

30 res = measure_time(lambda: cos(0.5)) 

31 print(res) 

32 

33 See `Timer.repeat <https://docs.python.org/3/library/ 

34 timeit.html?timeit.Timer.repeat>`_ 

35 for a better understanding of parameter *repeat* and *number*. 

36 The function returns a duration corresponding to 

37 *number* times the execution of the main statement. 

38 

39 .. versionchanged:: 0.4 

40 Parameter *max_time* was added. 

41 """ 

42 if not callable(stmt) and not isinstance(stmt, str): 

43 raise TypeError( 

44 f"stmt is not callable or a string but is of type {type(stmt)!r}.") 

45 if context is None: 

46 context = {} 

47 

48 import numpy # pylint: disable=C0415 

49 if isinstance(stmt, str): 

50 tim = Timer(stmt, globals=context) 

51 else: 

52 tim = Timer(stmt) 

53 

54 if max_time is not None: 

55 if not div_by_number: 

56 raise ValueError( 

57 "div_by_number must be set to True of max_time is defined.") 

58 i = 1 

59 total_time = 0 

60 results = [] 

61 while True: 

62 for j in (1, 2): 

63 number = i * j 

64 time_taken = tim.timeit(number) 

65 results.append((number, time_taken)) 

66 total_time += time_taken 

67 if total_time >= max_time: 

68 break 

69 if total_time >= max_time: 

70 break 

71 ratio = (max_time - total_time) / total_time 

72 ratio = max(ratio, 1) 

73 i = int(i * ratio) 

74 

75 res = numpy.array(results) 

76 tw = res[:, 0].sum() 

77 ttime = res[:, 1].sum() 

78 mean = ttime / tw 

79 ave = res[:, 1] / res[:, 0] 

80 dev = (((ave - mean) ** 2 * res[:, 0]).sum() / tw) ** 0.5 

81 mes = dict(average=mean, deviation=dev, # pylint: disable=R1735 

82 min_exec=numpy.min(ave), 

83 max_exec=numpy.max(ave), 

84 repeat=1, number=tw, 

85 ttime=ttime) 

86 else: 

87 res = numpy.array(tim.repeat(repeat=repeat, number=number)) 

88 if div_by_number: 

89 res /= number 

90 

91 mean = numpy.mean(res) 

92 dev = numpy.mean(res ** 2) 

93 dev = (dev - mean**2) ** 0.5 

94 mes = dict(average=mean, deviation=dev, # pylint: disable=R1735 

95 min_exec=numpy.min(res), 

96 max_exec=numpy.max(res), repeat=repeat, 

97 number=number, ttime=res.sum()) 

98 

99 if 'values' in context: 

100 if hasattr(context['values'], 'shape'): 

101 mes['size'] = context['values'].shape[0] 

102 else: 

103 mes['size'] = len(context['values']) # pragma: no cover 

104 else: 

105 mes['context_size'] = sys.getsizeof(context) 

106 return mes 

107 

108 

109def _fcts(): 

110 """ 

111 Returns functions to measure. 

112 """ 

113 import numpy # pylint: disable=C0415 

114 from .cbenchmark_dot import vector_dot_product # pylint: disable=E0611,C0415 

115 from .cbenchmark_dot import vector_dot_product16 # pylint: disable=E0611,C0415 

116 from .cbenchmark_dot import vector_dot_product16_nofcall # pylint: disable=E0611,C0415 

117 from .cbenchmark_dot import vector_dot_product16_sse # pylint: disable=E0611,C0415 

118 

119 def simple_dot(values): 

120 return numpy.dot(values, values) 

121 

122 def c11_dot(vect): 

123 return vector_dot_product(vect, vect) 

124 

125 def c11_dot16(vect): 

126 return vector_dot_product16(vect, vect) 

127 

128 def c11_dot16_nofcall(vect): 

129 return vector_dot_product16_nofcall(vect, vect) 

130 

131 def c11_dot16_sse(vect): 

132 return vector_dot_product16_sse(vect, vect) 

133 

134 return [simple_dot, c11_dot, c11_dot16, c11_dot16_nofcall, c11_dot16_sse] 

135 

136 

137def check_speed(dims=[100000], repeat=10, number=50, fLOG=print): # pylint: disable=W0102 

138 """ 

139 Prints out some information about speed computation 

140 of this laptop. See :ref:`cbenchmarkbranchingrst` to compare. 

141 

142 @param dims sets of dimensions to try 

143 @param repeat average over *repeat* experiment 

144 @param number number of execution in one row 

145 @param fLOG logging function 

146 @return iterator on results 

147 

148 :epkg:`numpy` is multithreaded. For an accurate comparison, 

149 this needs to be disabled. This can be done by setting environment variable 

150 ``MKL_NUM_THREADS=1`` or by running: 

151 

152 :: 

153 

154 import mkl 

155 mkl.set_num_threads(1) 

156 

157 .. index:: MKL_NUM_THREADS 

158 

159 One example of use: 

160 

161 .. runpython:: 

162 :showcode: 

163 

164 from cpyquickhelper.numbers import check_speed 

165 res = list(check_speed(dims=[100, 1000])) 

166 import pprint 

167 pprint.pprint(res) 

168 """ 

169 import numpy # pylint: disable=C0415 

170 fcts = _fcts() 

171 mx = max(dims) 

172 vect = numpy.ones((mx,)) 

173 for i in range(0, vect.shape[0]): 

174 vect[i] = i 

175 for i in dims: 

176 values = vect[:i].copy() 

177 for fct in fcts: 

178 ct = {"fct": fct, 'values': values} 

179 t = measure_time(lambda f=fct, v=values: f(v), 

180 repeat=repeat, number=number, context=ct) 

181 t['name'] = fct.__name__ 

182 if fLOG: 

183 fLOG(t) 

184 yield t