1 /*
2  * This software is Copyright (c) 2018 magnum
3  * and is hereby released to the general public under the following terms:
4  * Redistribution and use in source and binary forms, with or without
5  * modifications, are permitted.
6  */
7 
8 #ifdef _OPENMP
9 #include <omp.h>
10 #endif
11 
12 #include "john.h"
13 #include "options.h"
14 #include "logger.h"
15 #include "formats.h"
16 #include "timer.h"
17 #include "config.h"
18 
19 #define CONF_SECTION SECTION_OPTIONS, ":CPUtune"
20 
21 static int use_preset, max_no_progress;
22 static double sample_time, req_gain, max_tune_time;
23 
24 static struct fmt_main *fmt;
25 static int omp_autotune_running;
26 static int mkpc;
27 static int omp_scale;
28 static int report;
29 static int fmt_preset;
30 static int tune_preset;
31 static int scale = 1;
32 
omp_autotune_init(void)33 void omp_autotune_init(void)
34 {
35 	int ci;
36 
37 #if __i386__ || __x86_64__ || __MIC__
38 	use_preset = cfg_get_bool(CONF_SECTION, "UsePreset", 1);
39 #else
40 	// Our presets are from intel[tm] CPUs. Anything else should autotune
41 	use_preset = 0;
42 #endif
43 	if ((ci = cfg_get_int(CONF_SECTION, "AutoTuneSampleTime")) < 0)
44 		ci = 10;
45 	sample_time = (double)ci / 1000.0;
46 	if ((ci = cfg_get_int(CONF_SECTION, "AutoTuneReqGain")) < 0)
47 		ci = 5;
48 	req_gain = (double)ci / 100.0 + 1.0;
49 	if ((ci = cfg_get_int(CONF_SECTION, "AutoTuneMaxDuration")) < 0)
50 		ci = 100;
51 	max_tune_time = (double)ci / 1000.0;
52 	if ((max_no_progress =
53 	     cfg_get_int(CONF_SECTION, "AutoTuneMaxNoProgress")) < 0)
54 		max_no_progress = 3;
55 
56 	if (options.tune) {
57 		if (!strcmp(options.tune, "auto"))
58 			use_preset = 0;
59 		else if (!strcmp(options.tune, "report")) {
60 			use_preset = 0;
61 			report = 1;
62 		} else {
63 			use_preset = 1;
64 			tune_preset = atoi(options.tune);
65 		}
66 	}
67 }
68 
omp_autotune(struct fmt_main * format,int preset)69 int omp_autotune(struct fmt_main *format, int preset)
70 {
71 #ifdef _OPENMP
72 	int threads = (format->params.flags & FMT_OMP) ? omp_get_max_threads() : 1;
73 #else
74 	int threads = 1;
75 #endif
76 	int ret_scale;
77 
78 	fmt_preset = preset;
79 
80 	omp_scale = tune_preset ? tune_preset : (use_preset ? preset : 0);
81 	ret_scale = tune_preset ? tune_preset : (threads == 1 ? 1 : (omp_scale ? omp_scale : 1));
82 
83 	if (omp_autotune_running)
84 		return threads * scale;
85 
86 	if (!use_preset || !preset) {
87 		fmt = format;
88 		mkpc = format->params.max_keys_per_crypt;
89 	}
90 	format->params.min_keys_per_crypt *= threads;
91 	format->params.max_keys_per_crypt *= threads * ret_scale;
92 
93 	return threads * ret_scale;
94 }
95 
omp_autotune_run(struct db_main * db)96 void omp_autotune_run(struct db_main *db)
97 {
98 #ifdef _OPENMP
99 	int threads =
100 		fmt ? ((fmt->params.flags & FMT_OMP) ? omp_get_max_threads() : 1) : 1;
101 #else
102 	int threads = 1;
103 #endif
104 	int best_scale = 1;
105 	int best_cps = 0;
106 	int no_progress = 0;
107 	int min_crypts = 0;
108 	int tune_cost;
109 	void *salt;
110 	char key[PLAINTEXT_BUFFER_SIZE] = "tUne0000";
111 	sTimer timer;
112 	double duration;
113 
114 	if (!fmt || omp_scale == 1 || tune_preset)
115 		goto cleanup;
116 
117 	if (john_main_process && (options.flags & FLG_TEST_CHK) &&
118 	    ((options.tune && !strcmp(options.tune, "report")) ||
119 	     options.verbosity > VERB_DEFAULT))
120 		printf("\n");
121 
122 	scale = 1;
123 	omp_autotune_running = 1;
124 
125 	// Find most expensive salt, for auto-tune
126 	{
127 		struct db_salt *s = db->salts;
128 
129 		tune_cost = MIN(db->max_cost[0], options.loader.max_cost[0]);
130 
131 		while (s->next && s->cost[0] < tune_cost)
132 			s = s->next;
133 		salt = s->salt;
134 	}
135 
136 	if (john_main_process && options.verbosity >= VERB_MAX) {
137 		printf("%s %s autotune using %s db",
138 		       fmt->params.label, threads > 1 ? "OMP" : "MKPC",
139 		       db->real ? "real" : "test");
140 		if (fmt->methods.tunable_cost_value[0])
141 			printf(" with %s of %d\n",
142 			       fmt->params.tunable_cost_name[0], tune_cost);
143 		printf("\n");
144 	}
145 
146 	sTimer_Init(&timer);
147 	do {
148 		int i;
149 		int min_kpc = fmt->params.min_keys_per_crypt;
150 		int this_kpc = mkpc * threads * scale;
151 		int cps, crypts = 0;
152 
153 		if (threads == 1)
154 			this_kpc = min_kpc * scale; // We're tuning MKPC
155 
156 		fmt->params.max_keys_per_crypt = this_kpc;
157 
158 		// Release old buffers
159 		fmt->methods.done();
160 
161 		// Set up buffers for this test
162 		fmt->methods.init(fmt);
163 
164 		// Format may have bumped kpc in init()
165 		this_kpc = fmt->params.max_keys_per_crypt;
166 
167 		// Load keys
168 		fmt->methods.clear_keys();
169 		for (i = 0; i < this_kpc; i++) {
170 			key[4] = '0' + (i / 1000) % 10;
171 			key[5] = '0' + (i / 100) % 10;
172 			key[6] = '0' + (i / 10) % 10;
173 			key[7] = '0' + i % 10;
174 			fmt->methods.set_key(key, i);
175 		}
176 
177 		// Set the salt we picked earlier
178 		fmt->methods.set_salt(salt);
179 
180 		// Tell format this is a speed test
181 		benchmark_running++;
182 
183 		sTimer_Start(&timer, 1);
184 		do {
185 			int count = this_kpc;
186 
187 			fmt->methods.crypt_all(&count, NULL);
188 			crypts += count;
189 		} while (crypts < min_crypts || sTimer_GetSecs(&timer) < sample_time);
190 		sTimer_Stop(&timer);
191 
192 		benchmark_running--;
193 
194 		duration = sTimer_GetSecs(&timer);
195 		cps = crypts / duration;
196 
197 		if (john_main_process && options.verbosity >= VERB_MAX) {
198 			if (threads > 1)
199 				printf("OMP scale %d: %d crypts (%dx%d) in %f seconds, %d c/s",
200 				       scale, crypts, crypts / this_kpc, this_kpc, duration, cps);
201 			else
202 				printf("MKPC %d: %d crypts (%dx%d) in %f seconds, %d c/s",
203 				       this_kpc, crypts, crypts / this_kpc, this_kpc,
204 				       duration, cps);
205 		}
206 
207 		if (cps >= (best_cps * req_gain)) {
208 			if (john_main_process && options.verbosity >= VERB_MAX)
209 				printf(" +\n");
210 			best_cps = cps;
211 			best_scale = scale;
212 			no_progress = 0;
213 		}
214 		else {
215 			if (john_main_process && options.verbosity >= VERB_MAX)
216 				printf("\n");
217 			no_progress++;
218 		}
219 
220 		min_crypts = crypts;
221 
222 		if (duration > max_tune_time || no_progress >= max_no_progress)
223 			break;
224 
225 		if (threads == 1 && min_kpc == 1) {
226 			int quick_move = 1;
227 
228 			while (crypts / this_kpc / quick_move > 8192) {
229 				quick_move *= 2;
230 				scale *= 2;
231 			}
232 		}
233 
234 		// Double each time
235 		scale *= 2;
236 	} while (1);
237 
238 	if (options.tune && !strcmp(options.tune, "report")) {
239 		if (threads == 1) {
240 			if (best_scale * fmt->params.min_keys_per_crypt != mkpc)
241 				printf("Autotuned MKPC %d, preset is %d\n",
242 				       best_scale * fmt->params.min_keys_per_crypt, mkpc);
243 		} else {
244 			if (best_scale != fmt_preset)
245 				printf("Autotuned OMP scale %d, preset is %d\n",
246 				       best_scale, fmt_preset);
247 		}
248 	} else {
249 		if (threads == 1) {
250 			if (john_main_process && options.verbosity > VERB_DEFAULT)
251 				printf("Autotune found best speed at MKPC of %d (%d * %d)\n",
252 				       best_scale * fmt->params.min_keys_per_crypt,
253 				       best_scale, fmt->params.min_keys_per_crypt);
254 			log_event("Autotune found best speed at MKPC of %d (%d * %d)",
255 			          best_scale * fmt->params.min_keys_per_crypt,
256 			          best_scale, fmt->params.min_keys_per_crypt);
257 		} else {
258 			if (john_main_process && options.verbosity > VERB_DEFAULT)
259 				printf("Autotune found best speed at OMP scale of %d\n",
260 				       best_scale);
261 			log_event("Autotune found best speed at OMP scale of %d", best_scale);
262 		}
263 	}
264 
265 	if (best_scale != scale) {
266 		scale = best_scale;
267 
268 		// Release old buffers
269 		fmt->methods.done();
270 
271 		// Set up buffers for chosen scale
272 		fmt->methods.init(fmt);
273 	}
274 
275 cleanup:
276 	omp_autotune_running = 0;
277 	fmt = NULL;
278 	omp_scale = 0;
279 	mkpc = 0;
280 	scale = 1;
281 
282 	return;
283 }
284