• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "iwkv.h"
2 #include "iwutils.h"
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <ctype.h>
7 
8 char *g_program;
9 
10 uint32_t _execsize();
11 
12 typedef struct _BMCTX BMCTX;
13 
14 typedef bool(bench_method (BMCTX *bmctx));
15 
16 #define RND_DATA_SZ (10 * 1048576)
17 char RND_DATA[RND_DATA_SZ];
18 
19 struct BM {
20   bool     initiated;
21   int      ksz;
22   int      argc;
23   char   **argv;
24   char    *param_db;
25   int      param_num;
26   int      param_num_reads;
27   int      param_value_size;
28   uint32_t param_seed;
29   char    *param_benchmarks;
30   void     (*val_free)(void *ptr);
31   void     (*env_setup)(void);
32   void   * (*db_open)(BMCTX *ctx);
33   bool     (*db_close)(BMCTX *ctx);
34   bool     (*db_put)(BMCTX *ctx, const IWKV_val *key, const IWKV_val *val, bool sync);
35   bool     (*db_get)(BMCTX *ctx, const IWKV_val *key, IWKV_val *val, bool *found);
36   bool     (*db_cursor_to_key)(BMCTX *ctx, const IWKV_val *key, IWKV_val *val, bool *found);
37   bool     (*db_del)(BMCTX *ctx, const IWKV_val *key, bool sync);
38   bool     (*db_read_seq)(BMCTX *ctx, bool reverse);
39   uint64_t (*db_size_bytes)(BMCTX *ctx);
40 } bm = { 0 };
41 
42 struct _BMCTX {
43   bool     success;
44   bool     freshdb;
45   bool     logdbsize;
46   char    *name;
47   int      num;
48   int      value_size;
49   uint64_t start_ms;
50   uint64_t end_ms;
51   void    *db;
52   void    *extra;
53   bench_method *method;
54   int rnd_data_pos;
55 };
56 
_val_dispose(IWKV_val * val)57 static void _val_dispose(IWKV_val *val) {
58   if (bm.val_free) {
59     if (val->data) {
60       bm.val_free(val->data);
61       val->size = 0;
62     }
63   } else {
64     iwkv_val_dispose(val);
65   }
66 }
67 
_bmctx_dispose(BMCTX * ctx)68 static void _bmctx_dispose(BMCTX *ctx) {
69   free(ctx->name);
70   free(ctx);
71 }
72 
_bmctx_rndbuf_nextptr(BMCTX * ctx,int len)73 static const char *_bmctx_rndbuf_nextptr(BMCTX *ctx, int len) {
74   if (len > RND_DATA_SZ) {
75     fprintf(stderr, "record value length exceeds maximum allowed: %d\n", RND_DATA_SZ);
76     exit(1);
77   }
78   if (ctx->rnd_data_pos + len > RND_DATA_SZ) {
79     ctx->rnd_data_pos = 0;
80   }
81   const char *ret = RND_DATA + ctx->rnd_data_pos;
82   ctx->rnd_data_pos += len;
83   return ret;
84 }
85 
_bm_check()86 static bool _bm_check() {
87   if (!bm.env_setup) {
88     fprintf(stderr, "env_setup function is not set\n");
89     return false;
90   }
91   if (!bm.db_open) {
92     fprintf(stderr, "db_open function is not set\n");
93     return false;
94   }
95   if (!bm.db_close) {
96     fprintf(stderr, "db_close function is not set\n");
97     return false;
98   }
99   if (!bm.db_put) {
100     fprintf(stderr, "db_put function is not set\n");
101     return false;
102   }
103   if (!bm.db_get) {
104     fprintf(stderr, "db_get function is not set\n");
105     return false;
106   }
107   if (!bm.db_del) {
108     fprintf(stderr, "db_del function is not set\n");
109     return false;
110   }
111   if (!bm.db_read_seq) {
112     fprintf(stderr, "db_read_seq function is not set\n");
113     return false;
114   }
115   if (!bm.db_cursor_to_key) {
116     fprintf(stderr, "db_cursor_to_key function is not set\n");
117     return false;
118   }
119   return true;
120 }
121 
_bm_help()122 static void _bm_help() {
123   fprintf(stderr, "\n");
124   fprintf(stderr, "Usage %s [options]\n\n", g_program);
125   fprintf(stderr, "Options:\n");
126   fprintf(stderr, "  -h Show this help\n");
127   fprintf(stderr, "  -w Write Ahead Log (WAL) enabled\n");
128   fprintf(stderr, "  -n <num> Number of stored records\n");
129   fprintf(stderr,
130           "  -r <num> Number of records to read (equals to number of stored records if not specified, not for readseq,readreverse)\n");
131   fprintf(stderr, "  -vz <size> Size of a single record value in bytes\n");
132   fprintf(stderr, "  -kz <16|192|1024> Size of record key. Either 16 (default) or 192 or 1024 bytes\n");
133   fprintf(stderr, "  -b <comma separated benchmarks to run>\n\n");
134   fprintf(stderr, "  -db <file/dir> Database file/directory\n\n");
135   fprintf(stderr, "  -rs <random seed> Random seed used for iwu random generator\n\n");
136   fprintf(stderr, "Available benchmarks:\n");
137   fprintf(stderr, "  fillseq        write N fixed length values in sequential key order\n");
138   fprintf(stderr, "  fillseq2       write N random length values in sequential key order\n");
139   fprintf(stderr, "  fillrandom     write N fixed length values in random key order\n");
140   fprintf(stderr, "  fillrandom2    write N random length values in random key order\n");
141   fprintf(stderr, "  overwrite      overwrite N values in random key order\n");
142   fprintf(stderr, "  fillsync       write N/100 values in random key order in sync mode\n");
143   fprintf(stderr, "  fill100K       write N/100 100K values in random order\n");
144   fprintf(stderr, "  deleteseq      delete N keys in sequential order\n");
145   fprintf(stderr, "  deleterandom   delete N keys in random order\n");
146   fprintf(stderr, "  readseq        read all records sequentially\n");
147   fprintf(stderr, "  readreverse    read all records sequentially in reverse order\n");
148   fprintf(stderr, "  readrandom     read N times in random order\n");
149   fprintf(stderr, "  readmissing    read N missing keys in random order\n");
150   fprintf(stderr, "  readhot        read N times in random order from 1%% section of DB\n");
151   fprintf(stderr, "  seekrandom     N random seeks\n");
152   fprintf(stderr, "\n");
153 }
154 
_bm_init(int argc,char * argv[])155 static bool _bm_init(int argc, char *argv[]) {
156   if (bm.initiated) {
157     fprintf(stderr, "Benchmark already initialized\n");
158     return false;
159   }
160   if (iw_init()) {
161     return false;
162   }
163   bm.argc = argc;
164   bm.argv = argv;
165   bm.param_num = 2000000;  // 2M records
166   bm.param_num_reads = -1; // Same as param_num
167   bm.param_value_size = 400;
168   bm.param_benchmarks = "fillrandom2,"
169                         "readrandom,"
170                         "deleterandom,"
171                         "fillseq2,"
172                         "readrandom,"
173                         "deleteseq,"
174                         "fillseq,"
175                         "overwrite,"
176                         "readrandom,"
177                         "readseq,"
178                         "readreverse,"
179                         "readhot,"
180                         "readmissing,"
181                         "deleteseq,"
182                         "fill100K";
183 #ifndef NDEBUG
184   fprintf(stderr, "WARNING: Assertions are enabled, benchmarks can be slow\n");
185 #endif
186   for (int i = 1; i < argc; ++i) {
187     if (!strcmp(argv[i], "-h")) {
188       _bm_help();
189       return false;
190     }
191     if (!strcmp(argv[i], "-n")) {
192       if (++i >= argc) {
193         fprintf(stderr, "'-n <num records>' option has no value\n");
194         return false;
195       }
196       bm.param_num = atoi(argv[i]);
197       if (bm.param_num < 1) {
198         fprintf(stderr, "'-n <num records>' invalid option value\n");
199         return false;
200       }
201     } else if (!strcmp(argv[i], "-r")) {
202       if (++i >= argc) {
203         fprintf(stderr, "'-r <num read records>' option has no value\n");
204         return false;
205       }
206       bm.param_num_reads = atoi(argv[i]);
207     } else if (!strcmp(argv[i], "-vz")) {
208       if (++i >= argc) {
209         fprintf(stderr, "'-vz <value size>' option has not value\n");
210         return false;
211       }
212       bm.param_value_size = atoi(argv[i]);
213       if (bm.param_value_size <= 0) {
214         fprintf(stderr, "'-vz <value size>' invalid option value\n");
215         return false;
216       }
217     } else if (!strcmp(argv[i], "-b")) {
218       if (++i >= argc) {
219         fprintf(stderr, "'-b <benchmarks>' options has no value\n");
220         return false;
221       }
222       bm.param_benchmarks = argv[i];
223     } else if (!strcmp(argv[i], "-db")) {
224       if (++i >= argc) {
225         fprintf(stderr, "'-db <db file>' options has no value\n");
226         return false;
227       }
228       bm.param_db = argv[i];
229     } else if (!strcmp(argv[i], "-rs")) {
230       if (++i >= argc) {
231         fprintf(stderr, "'-rs <random seed>' options has no value\n");
232         return false;
233       }
234       bm.param_seed = atoll(argv[i]);
235     } else if (!strcmp(argv[i], "-kz")) {
236       if (++i >= argc) {
237         fprintf(stderr, "'-kz 16|192|1024' options has no value\n");
238         return false;
239       }
240       int ksz = atoi(argv[i]);
241       if (ksz == 192) {
242         bm.ksz = 192;
243       } else if (ksz == 1024) {
244         bm.ksz = 1024;
245       } else if (ksz != 16) {
246         fprintf(stderr, "'-kz 16|192|1024' options has invalid value %d\n", ksz);
247         return false;
248       }
249     }
250   }
251   if (bm.ksz == 0) {
252     bm.ksz = 16;
253   }
254   if (bm.param_num_reads < 0) {
255     bm.param_num_reads = bm.param_num;
256   }
257   if (!_bm_check()) {
258     fprintf(stderr, "Benchmark `bm` structure is not configured properly\n");
259     return false;
260   }
261   if (!bm.param_seed) {
262     uint64_t ts;
263     iwp_current_time_ms(&ts, false);
264     ts = IW_SWAB64(ts);
265     ts >>= 32;
266     bm.param_seed = ts;
267   }
268   iwu_rand_seed(bm.param_seed);
269   srandom(bm.param_seed);
270   fprintf(stderr,
271           "\n exec size: %u"
272           "\n random seed: %u"
273           "\n num records: %d\n read num records: %d\n value size: %d\n benchmarks: %s"
274           "\n key size: %d \n\n",
275           _execsize(),
276           bm.param_seed, bm.param_num, bm.param_num_reads, bm.param_value_size, bm.param_benchmarks,
277           bm.ksz);
278 
279   // Fill up random data array
280   for (int i = 0; i < RND_DATA_SZ; ++i) {
281     RND_DATA[i] = ' ' + iwu_rand_range(95); // ascii space ... ~
282   }
283   return true;
284 }
285 
_logdbsize(BMCTX * ctx)286 static void _logdbsize(BMCTX *ctx) {
287   if (bm.db_size_bytes) {
288     uint64_t dbz = bm.db_size_bytes(ctx);
289     fprintf(stderr, " db size: %lu (%lu MB)\n", dbz, (dbz / 1024 / 1024));
290   }
291 }
292 
_execsize()293 uint32_t _execsize() {
294   const char *path = g_program;
295   IWP_FILE_STAT fst;
296   iwrc rc = iwp_fstat(path, &fst);
297   if (rc) {
298     iwlog_ecode_error3(rc);
299     return 0;
300   }
301   return fst.size;
302 }
303 
_bm_run(BMCTX * ctx)304 static void _bm_run(BMCTX *ctx) {
305   assert(ctx->method);
306   uint64_t llv;
307   ctx->db = bm.db_open(ctx);
308   if (!ctx->db) {
309     ctx->success = false;
310     return;
311   }
312   iwp_current_time_ms(&llv, false);
313   ctx->start_ms = llv;
314   ctx->success = ctx->method(ctx);
315   if (!bm.db_close(ctx)) {
316     ctx->success = false;
317   }
318   iwp_current_time_ms(&llv, false);
319   ctx->end_ms = llv;
320 }
321 
322 #define FILLKEY() snprintf(key.data, sizeof(kbuf), bm.ksz == 16 ? "%016d" : bm.ksz == 192 ? "%0192d" : "%01024d", k)
323 
_do_write(BMCTX * ctx,bool seq,bool sync,bool rvlen)324 static bool _do_write(BMCTX *ctx, bool seq, bool sync, bool rvlen) {
325   char kbuf[1025];
326   IWKV_val key, val;
327   key.data = kbuf;
328   int value_size = ctx->value_size;
329   for (int i = 0; i < ctx->num; ++i) {
330     if (rvlen) {
331       value_size = iwu_rand_range(ctx->value_size + 1);
332       if (value_size == 0) {
333         value_size = 1;
334       }
335     }
336     const int k = seq ? i : iwu_rand_range(bm.param_num);
337     FILLKEY();
338 
339     key.size = strlen(key.data);
340     val.data = (void*) _bmctx_rndbuf_nextptr(ctx, value_size);
341     val.size = value_size;
342     if (!bm.db_put(ctx, &key, &val, sync)) {
343       return false;
344     }
345   }
346   return true;
347 }
348 
_do_delete(BMCTX * ctx,bool sync,bool seq)349 static bool _do_delete(BMCTX *ctx, bool sync, bool seq) {
350   char kbuf[1025];
351   IWKV_val key;
352   key.data = kbuf;
353   for (int i = 0; i < ctx->num; ++i) {
354     const int k = seq ? i : iwu_rand_range(bm.param_num);
355     FILLKEY();
356     key.size = strlen(key.data);
357     if (!bm.db_del(ctx, &key, sync)) {
358       return false;
359     }
360   }
361   return true;
362 }
363 
_do_read_seq(BMCTX * ctx)364 static bool _do_read_seq(BMCTX *ctx) {
365   return bm.db_read_seq(ctx, false);
366 }
367 
_do_read_reverse(BMCTX * ctx)368 static bool _do_read_reverse(BMCTX *ctx) {
369   return bm.db_read_seq(ctx, true);
370 }
371 
_do_read_random(BMCTX * ctx)372 static bool _do_read_random(BMCTX *ctx) {
373   char kbuf[1025];
374   IWKV_val key, val;
375   bool found;
376   key.data = kbuf;
377   for (int i = 0; i < bm.param_num_reads; ++i) {
378     const int k = iwu_rand_range(bm.param_num);
379     FILLKEY();
380     key.size = strlen(key.data);
381     val.data = 0;
382     val.size = 0;
383     if (!bm.db_get(ctx, &key, &val, &found)) {
384       _val_dispose(&val);
385       return false;
386     }
387     _val_dispose(&val);
388   }
389   return true;
390 }
391 
_do_read_missing(BMCTX * ctx)392 static bool _do_read_missing(BMCTX *ctx) {
393   char kbuf[1025];
394   IWKV_val key, val;
395   bool found;
396   key.data = kbuf;
397   for (int i = 0; i < bm.param_num_reads; ++i) {
398     const int k = iwu_rand_range(bm.param_num);
399     snprintf(key.data, sizeof(kbuf),
400              bm.ksz == 16 ? "%016d." : bm.ksz == 192 ? "%0192d." : "%01024d.", k);
401     key.size = strlen(key.data);
402     val.data = 0;
403     val.size = 0;
404     if (!bm.db_get(ctx, &key, &val, &found)) {
405       _val_dispose(&val);
406       return false;
407     }
408     _val_dispose(&val);
409     if (found) {
410       return false;
411     }
412   }
413   return true;
414 }
415 
_do_read_hot(BMCTX * ctx)416 static bool _do_read_hot(BMCTX *ctx) {
417   char kbuf[1025];
418   IWKV_val key, val;
419   bool found;
420   key.data = kbuf;
421   const int range = (bm.param_num + 99) / 100;
422   for (int i = 0; i < bm.param_num_reads; ++i) {
423     const int k = iwu_rand_range(range);
424     FILLKEY();
425     key.size = strlen(key.data);
426     val.data = 0;
427     val.size = 0;
428     if (!bm.db_get(ctx, &key, &val, &found)) {
429       _val_dispose(&val);
430       return false;
431     }
432     _val_dispose(&val);
433   }
434   return true;
435 }
436 
_do_seek_random(BMCTX * ctx)437 static bool _do_seek_random(BMCTX *ctx) {
438   char kbuf[1025];
439   IWKV_val key, val;
440   bool found;
441   key.data = kbuf;
442   for (int i = 0; i < bm.param_num_reads; ++i) {
443     const int k = iwu_rand_range(bm.param_num);
444     FILLKEY();
445     key.size = strlen(key.data);
446     val.data = 0;
447     val.size = 0;
448     if (!bm.db_cursor_to_key(ctx, &key, &val, &found)) {
449       _val_dispose(&val);
450       return false;
451     }
452     _val_dispose(&val);
453   }
454   return true;
455 }
456 
_bm_fillseq(BMCTX * ctx)457 static bool _bm_fillseq(BMCTX *ctx) {
458   if (!ctx->freshdb) {
459     return false;
460   }
461   return _do_write(ctx, true, false, false);
462 }
463 
_bm_fillseq2(BMCTX * ctx)464 static bool _bm_fillseq2(BMCTX *ctx) {
465   if (!ctx->freshdb) {
466     return false;
467   }
468   return _do_write(ctx, true, false, true);
469 }
470 
_bm_fillrandom(BMCTX * ctx)471 static bool _bm_fillrandom(BMCTX *ctx) {
472   if (!ctx->freshdb) {
473     return false;
474   }
475   return _do_write(ctx, false, false, false);
476 }
477 
_bm_fillrandom2(BMCTX * ctx)478 static bool _bm_fillrandom2(BMCTX *ctx) {
479   if (!ctx->freshdb) {
480     return false;
481   }
482   return _do_write(ctx, false, false, true);
483 }
484 
_bm_overwrite(BMCTX * ctx)485 static bool _bm_overwrite(BMCTX *ctx) {
486   if (ctx->freshdb) {
487     return false;
488   }
489   return _do_write(ctx, false, false, false);
490 }
491 
_bm_fillsync(BMCTX * ctx)492 static bool _bm_fillsync(BMCTX *ctx) {
493   if (!ctx->freshdb) {
494     return false;
495   }
496   ctx->num /= 100;
497   if (ctx->num < 1) {
498     ctx->num = 1;
499   }
500   fprintf(stderr, "\tfillsync num records: %d\n", ctx->num);
501   return _do_write(ctx, false, true, false);
502 }
503 
_bm_fill100K(BMCTX * ctx)504 static bool _bm_fill100K(BMCTX *ctx) {
505   if (!ctx->freshdb) {
506     return false;
507   }
508   ctx->num /= 100;
509   if (ctx->num < 1) {
510     ctx->num = 1;
511   }
512   fprintf(stderr, "\tfill100K num records: %d\n", ctx->num);
513   ctx->value_size = 100 * 1000;
514   return _do_write(ctx, false, false, false);
515 }
516 
_bm_deleteseq(BMCTX * ctx)517 static bool _bm_deleteseq(BMCTX *ctx) {
518   if (ctx->freshdb) {
519     return false;
520   }
521   return _do_delete(ctx, false, true);
522 }
523 
_bm_deleterandom(BMCTX * ctx)524 static bool _bm_deleterandom(BMCTX *ctx) {
525   if (ctx->freshdb) {
526     return false;
527   }
528   return _do_delete(ctx, false, false);
529 }
530 
_bm_readseq(BMCTX * ctx)531 static bool _bm_readseq(BMCTX *ctx) {
532   if (ctx->freshdb) {
533     return false;
534   }
535   return _do_read_seq(ctx);
536 }
537 
_bm_readreverse(BMCTX * ctx)538 static bool _bm_readreverse(BMCTX *ctx) {
539   if (ctx->freshdb) {
540     return false;
541   }
542   return _do_read_reverse(ctx);
543 }
544 
_bm_readrandom(BMCTX * ctx)545 static bool _bm_readrandom(BMCTX *ctx) {
546   if (ctx->freshdb) {
547     return false;
548   }
549   return _do_read_random(ctx);
550 }
551 
_bm_readmissing(BMCTX * ctx)552 static bool _bm_readmissing(BMCTX *ctx) {
553   if (ctx->freshdb) {
554     return false;
555   }
556   return _do_read_missing(ctx);
557 }
558 
_bm_readhot(BMCTX * ctx)559 static bool _bm_readhot(BMCTX *ctx) {
560   if (ctx->freshdb) {
561     return false;
562   }
563   return _do_read_hot(ctx);
564 }
565 
_bm_seekrandom(BMCTX * ctx)566 static bool _bm_seekrandom(BMCTX *ctx) {
567   if (ctx->freshdb) {
568     return false;
569   }
570   return _do_seek_random(ctx);
571 }
572 
_bmctx_create(const char * name)573 static BMCTX *_bmctx_create(const char *name) {
574   bench_method *method = 0;
575   bool logdbsize = false;
576   bool freshdb = false;
577   if (!strcmp(name, "fillseq")) {
578     freshdb = true;
579     logdbsize = true;
580     method = _bm_fillseq;
581   } else if (!strcmp(name, "fillseq2")) {
582     freshdb = true;
583     logdbsize = true;
584     method = _bm_fillseq2;
585   } else if (!strcmp(name, "fillrandom")) {
586     freshdb = true;
587     logdbsize = true;
588     method = _bm_fillrandom;
589   } else if (!strcmp(name, "fillrandom2")) {
590     freshdb = true;
591     logdbsize = true;
592     method = _bm_fillrandom2;
593   } else if (!strcmp(name, "overwrite")) {
594     logdbsize = true;
595     method = _bm_overwrite;
596   } else if (!strcmp(name, "fillsync")) {
597     freshdb = true;
598     logdbsize = true;
599     method = _bm_fillsync;
600   } else if (!strcmp(name, "fill100K")) {
601     freshdb = true;
602     logdbsize = true;
603     method = _bm_fill100K;
604   } else if (!strcmp(name, "deleteseq")) {
605     logdbsize = true;
606     method = _bm_deleteseq;
607   } else if (!strcmp(name, "deleterandom")) {
608     logdbsize = true;
609     method = _bm_deleterandom;
610   } else if (!strcmp(name, "readseq")) {
611     method = _bm_readseq;
612   } else if (!strcmp(name, "readreverse")) {
613     method = _bm_readreverse;
614   } else if (!strcmp(name, "readrandom")) {
615     method = _bm_readrandom;
616   } else if (!strcmp(name, "readmissing")) {
617     method = _bm_readmissing;
618   } else if (!strcmp(name, "readhot")) {
619     method = _bm_readhot;
620   } else if (!strcmp(name, "seekrandom")) {
621     method = _bm_seekrandom;
622   } else {
623     fprintf(stderr, "Unknown benchmark: '%s'\n", name);
624     return 0;
625   }
626   BMCTX *bmctx = calloc(1, sizeof(*bmctx));
627   bmctx->name = strdup(name);
628   bmctx->method = method;
629   bmctx->num = bm.param_num;
630   bmctx->value_size = bm.param_value_size;
631   bmctx->freshdb = freshdb;
632   bmctx->logdbsize = logdbsize;
633   return bmctx;
634 }
635 
bm_bench_run(int argc,char * argv[])636 static bool bm_bench_run(int argc, char *argv[]) {
637   if (!_bm_init(argc, argv)) {
638     fprintf(stderr, "Benchmark cannot be initialized\n");
639     exit(1);
640   }
641   bm.env_setup();
642   int c = 0;
643   const char *ptr = bm.param_benchmarks;
644   char bname[100];
645   bool bmres = true;
646   while (bmres) {
647     if ((*ptr == ',') || (*ptr == '\0') || (c >= 99)) {
648       bname[c] = '\0';
649       BMCTX *ctx = _bmctx_create(bname);
650       c = 0;
651       if (ctx) {
652         fprintf(stderr, " benchmark: %s\n", bname);
653         _bm_run(ctx);
654         bmres = ctx->success;
655         if (!bmres) {
656           fprintf(stderr, "Failed to run benchmark: %s\n", bname);
657         } else {
658           fprintf(stderr, " done: %s in %lu\n", bname, (ctx->end_ms - ctx->start_ms));
659         }
660         if (ctx->logdbsize) {
661           _logdbsize(ctx);
662         }
663         _bmctx_dispose(ctx);
664       }
665       if ((*ptr == '\0') || !bmres) {
666         break;
667       }
668     } else if (!isspace(*ptr)) {
669       bname[c++] = *ptr;
670     }
671     ptr += 1;
672   }
673   return bmres;
674 }
675