uperf.cpp (17679B)
1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /******************************************************************** 4 * COPYRIGHT: 5 * Copyright (c) 2002-2012, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ********************************************************************/ 8 9 // Defines _XOPEN_SOURCE for access to POSIX functions. 10 // Must be before any other #includes. 11 #include "uposixdefs.h" 12 13 #include "unicode/uperf.h" 14 #include "uoptions.h" 15 #include "cmemory.h" 16 #include <stdio.h> 17 #include <stdlib.h> 18 19 #if !UCONFIG_NO_CONVERSION 20 21 UPerfFunction::~UPerfFunction() {} 22 23 static const char delim = '/'; 24 static int32_t execCount = 0; 25 UPerfTest* UPerfTest::gTest = nullptr; 26 static const int MAXLINES = 40000; 27 const char UPerfTest::gUsageString[] = 28 "Usage: %s [OPTIONS] [FILES]\n" 29 "\tReads the input file and prints out time taken in seconds\n" 30 "Options:\n" 31 "\t-h or -? or --help this usage text\n" 32 "\t-v or --verbose print extra information when processing files\n" 33 "\t-s or --sourcedir source directory for files followed by path\n" 34 "\t followed by path\n" 35 "\t-e or --encoding encoding of source files\n" 36 "\t-u or --uselen perform timing analysis on non-null terminated buffer using length\n" 37 "\t-f or --file-name file to be used as input data\n" 38 "\t-p or --passes Number of passes to be performed. Requires Numeric argument.\n" 39 "\t Cannot be used with --time\n" 40 "\t-i or --iterations Number of iterations to be performed. Requires Numeric argument\n" 41 "\t-t or --time Threshold time for looping until in seconds. Requires Numeric argument.\n" 42 "\t Cannot be used with --iterations\n" 43 "\t-l or --line-mode The data file should be processed in line mode\n" 44 "\t-b or --bulk-mode The data file should be processed in file based.\n" 45 "\t Cannot be used with --line-mode\n" 46 "\t-L or --locale Locale for the test\n"; 47 48 enum 49 { 50 HELP1, 51 HELP2, 52 VERBOSE, 53 SOURCEDIR, 54 ENCODING, 55 USELEN, 56 FILE_NAME, 57 PASSES, 58 ITERATIONS, 59 TIME, 60 LINE_MODE, 61 BULK_MODE, 62 LOCALE, 63 OPTIONS_COUNT 64 }; 65 66 67 static UOption options[OPTIONS_COUNT+20]={ 68 UOPTION_HELP_H, 69 UOPTION_HELP_QUESTION_MARK, 70 UOPTION_VERBOSE, 71 UOPTION_SOURCEDIR, 72 UOPTION_ENCODING, 73 UOPTION_DEF( "uselen", 'u', UOPT_NO_ARG), 74 UOPTION_DEF( "file-name", 'f', UOPT_REQUIRES_ARG), 75 UOPTION_DEF( "passes", 'p', UOPT_REQUIRES_ARG), 76 UOPTION_DEF( "iterations", 'i', UOPT_REQUIRES_ARG), 77 UOPTION_DEF( "time", 't', UOPT_REQUIRES_ARG), 78 UOPTION_DEF( "line-mode", 'l', UOPT_NO_ARG), 79 UOPTION_DEF( "bulk-mode", 'b', UOPT_NO_ARG), 80 UOPTION_DEF( "locale", 'L', UOPT_REQUIRES_ARG) 81 }; 82 83 UPerfTest::UPerfTest(int32_t argc, const char* argv[], UErrorCode& status) 84 : _argc(argc), _argv(argv), _addUsage(nullptr), 85 ucharBuf(nullptr), encoding(""), 86 uselen(false), 87 fileName(nullptr), sourceDir("."), 88 lines(nullptr), numLines(0), line_mode(true), 89 buffer(nullptr), bufferLen(0), 90 verbose(false), bulk_mode(false), 91 passes(1), iterations(0), time(0), 92 locale(nullptr) { 93 init(nullptr, 0, status); 94 } 95 96 UPerfTest::UPerfTest(int32_t argc, const char* argv[], 97 UOption addOptions[], int32_t addOptionsCount, 98 const char *addUsage, 99 UErrorCode& status) 100 : _argc(argc), _argv(argv), _addUsage(addUsage), 101 ucharBuf(nullptr), encoding(""), 102 uselen(false), 103 fileName(nullptr), sourceDir("."), 104 lines(nullptr), numLines(0), line_mode(true), 105 buffer(nullptr), bufferLen(0), 106 verbose(false), bulk_mode(false), 107 passes(1), iterations(0), time(0), 108 locale(nullptr) { 109 init(addOptions, addOptionsCount, status); 110 } 111 112 void UPerfTest::init(UOption addOptions[], int32_t addOptionsCount, 113 UErrorCode& status) { 114 //initialize the argument list 115 U_MAIN_INIT_ARGS(_argc, _argv); 116 117 resolvedFileName = nullptr; 118 119 // add specific options 120 int32_t optionsCount = OPTIONS_COUNT; 121 if (addOptionsCount > 0) { 122 memcpy(options+optionsCount, addOptions, addOptionsCount*sizeof(UOption)); 123 optionsCount += addOptionsCount; 124 } 125 126 //parse the arguments 127 _remainingArgc = u_parseArgs(_argc, const_cast<char**>(_argv), optionsCount, options); 128 129 // copy back values for additional options 130 if (addOptionsCount > 0) { 131 memcpy(addOptions, options+OPTIONS_COUNT, addOptionsCount*sizeof(UOption)); 132 } 133 134 // Now setup the arguments 135 if(_argc==1 || options[HELP1].doesOccur || options[HELP2].doesOccur) { 136 status = U_ILLEGAL_ARGUMENT_ERROR; 137 return; 138 } 139 140 if(options[VERBOSE].doesOccur) { 141 verbose = true; 142 } 143 144 if(options[SOURCEDIR].doesOccur) { 145 sourceDir = options[SOURCEDIR].value; 146 } 147 148 if(options[ENCODING].doesOccur) { 149 encoding = options[ENCODING].value; 150 } 151 152 if(options[USELEN].doesOccur) { 153 uselen = true; 154 } 155 156 if(options[FILE_NAME].doesOccur){ 157 fileName = options[FILE_NAME].value; 158 } 159 160 if(options[PASSES].doesOccur) { 161 passes = atoi(options[PASSES].value); 162 } 163 if(options[ITERATIONS].doesOccur) { 164 iterations = atoi(options[ITERATIONS].value); 165 if(options[TIME].doesOccur) { 166 status = U_ILLEGAL_ARGUMENT_ERROR; 167 return; 168 } 169 } else if(options[TIME].doesOccur) { 170 time = atoi(options[TIME].value); 171 } else { 172 iterations = 1000; // some default 173 } 174 175 if(options[LINE_MODE].doesOccur) { 176 line_mode = true; 177 bulk_mode = false; 178 } 179 180 if(options[BULK_MODE].doesOccur) { 181 bulk_mode = true; 182 line_mode = false; 183 } 184 185 if(options[LOCALE].doesOccur) { 186 locale = options[LOCALE].value; 187 } 188 189 int32_t len = 0; 190 if(fileName!=nullptr){ 191 //pre-flight 192 UErrorCode bufferStatus = U_ZERO_ERROR; 193 ucbuf_resolveFileName(sourceDir, fileName, nullptr, &len, &bufferStatus); 194 resolvedFileName = static_cast<char*>(uprv_malloc(len)); 195 if(resolvedFileName==nullptr){ 196 status= U_MEMORY_ALLOCATION_ERROR; 197 return; 198 } 199 if(bufferStatus == U_BUFFER_OVERFLOW_ERROR){ 200 bufferStatus = U_ZERO_ERROR; 201 } 202 ucbuf_resolveFileName(sourceDir, fileName, resolvedFileName, &len, &bufferStatus); 203 if (U_FAILURE(bufferStatus)) { 204 status = bufferStatus; 205 } 206 ucharBuf = ucbuf_open(resolvedFileName,&encoding,true,false,&status); 207 208 if(U_FAILURE(status)){ 209 printf("Could not open the input file %s. Error: %s\n", fileName, u_errorName(status)); 210 return; 211 } 212 } 213 } 214 215 ULine* UPerfTest::getLines(UErrorCode& status){ 216 if (U_FAILURE(status)) { 217 return nullptr; 218 } 219 if (lines != nullptr) { 220 return lines; // don't do it again 221 } 222 lines = new ULine[MAXLINES]; 223 int maxLines = MAXLINES; 224 numLines=0; 225 const char16_t* line=nullptr; 226 int32_t len =0; 227 for (;;) { 228 line = ucbuf_readline(ucharBuf,&len,&status); 229 if(line == nullptr || U_FAILURE(status)){ 230 break; 231 } 232 lines[numLines].name = new char16_t[len]; 233 lines[numLines].len = len; 234 memcpy(lines[numLines].name, line, len * U_SIZEOF_UCHAR); 235 236 numLines++; 237 len = 0; 238 if (numLines >= maxLines) { 239 maxLines += MAXLINES; 240 ULine *newLines = new ULine[maxLines]; 241 if(newLines == nullptr) { 242 fprintf(stderr, "Out of memory reading line %d.\n", static_cast<int>(numLines)); 243 status= U_MEMORY_ALLOCATION_ERROR; 244 delete []lines; 245 return nullptr; 246 } 247 248 memcpy(newLines, lines, numLines*sizeof(ULine)); 249 delete []lines; 250 lines = newLines; 251 } 252 } 253 return lines; 254 } 255 const char16_t* UPerfTest::getBuffer(int32_t& len, UErrorCode& status){ 256 if (U_FAILURE(status)) { 257 return nullptr; 258 } 259 len = ucbuf_size(ucharBuf); 260 buffer = static_cast<char16_t*>(uprv_malloc(U_SIZEOF_UCHAR * (len + 1))); 261 u_strncpy(buffer,ucbuf_getBuffer(ucharBuf,&bufferLen,&status),len); 262 buffer[len]=0; 263 len = bufferLen; 264 return buffer; 265 } 266 UBool UPerfTest::run(){ 267 if(_remainingArgc==1){ 268 // Testing all methods 269 return runTest(); 270 } 271 UBool res=false; 272 // Test only the specified function 273 for (int i = 1; i < _remainingArgc; ++i) { 274 if (_argv[i][0] != '-') { 275 char* name = const_cast<char*>(_argv[i]); 276 if(verbose==true){ 277 //fprintf(stdout, "\n=== Handling test: %s: ===\n", name); 278 //fprintf(stdout, "\n%s:\n", name); 279 } 280 char* parameter = strchr( name, '@' ); 281 if (parameter) { 282 *parameter = 0; 283 parameter += 1; 284 } 285 execCount = 0; 286 res = runTest( name, parameter ); 287 if (!res || (execCount <= 0)) { 288 fprintf(stdout, "\n---ERROR: Test doesn't exist: %s!\n", name); 289 return false; 290 } 291 } 292 } 293 return res; 294 } 295 UBool UPerfTest::runTest(char* name, char* par ){ 296 UBool rval; 297 char* pos = nullptr; 298 299 if (name) 300 pos = strchr( name, delim ); // check if name contains path (by looking for '/') 301 if (pos) { 302 path = pos+1; // store subpath for calling subtest 303 *pos = 0; // split into two strings 304 }else{ 305 path = nullptr; 306 } 307 308 if (!name || (name[0] == 0) || (strcmp(name, "*") == 0)) { 309 rval = runTestLoop( nullptr, nullptr ); 310 311 }else if (strcmp( name, "LIST" ) == 0) { 312 this->usage(); 313 rval = true; 314 315 }else{ 316 rval = runTestLoop( name, par ); 317 } 318 319 if (pos) 320 *pos = delim; // restore original value at pos 321 return rval; 322 } 323 324 325 void UPerfTest::setPath( char* pathVal ) 326 { 327 this->path = pathVal; 328 } 329 330 // call individual tests, to be overridden to call implementations 331 UPerfFunction* UPerfTest::runIndexedTest( int32_t /*index*/, UBool /*exec*/, const char* & /*name*/, char* /*par*/ ) 332 { 333 // to be overridden by a method like: 334 /* 335 switch (index) { 336 case 0: name = "First Test"; if (exec) FirstTest( par ); break; 337 case 1: name = "Second Test"; if (exec) SecondTest( par ); break; 338 default: name = ""; break; 339 } 340 */ 341 fprintf(stderr,"*** runIndexedTest needs to be overridden! ***"); 342 return nullptr; 343 } 344 345 346 UBool UPerfTest::runTestLoop( char* testname, char* par ) 347 { 348 int32_t index = 0; 349 const char* name; 350 UBool run_this_test; 351 UBool rval = false; 352 UErrorCode status = U_ZERO_ERROR; 353 UPerfTest* saveTest = gTest; 354 gTest = this; 355 int32_t loops = 0; 356 double t=0; 357 int32_t n = 1; 358 long ops; 359 do { 360 this->runIndexedTest( index, false, name ); 361 if (!name || (name[0] == 0)) 362 break; 363 if (!testname) { 364 run_this_test = true; 365 }else{ 366 run_this_test = static_cast<UBool>(strcmp(name, testname) == 0); 367 } 368 if (run_this_test) { 369 UPerfFunction* testFunction = this->runIndexedTest( index, true, name, par ); 370 execCount++; 371 rval=true; 372 if(testFunction==nullptr){ 373 fprintf(stderr,"%s function returned nullptr", name); 374 return false; 375 } 376 ops = testFunction->getOperationsPerIteration(); 377 if (ops < 1) { 378 fprintf(stderr, "%s returned an illegal operations/iteration()\n", name); 379 return false; 380 } 381 if(iterations == 0) { 382 n = time; 383 // Run for specified duration in seconds 384 if(verbose==true){ 385 fprintf(stdout, "= %s calibrating %i seconds \n", name, static_cast<int>(n)); 386 } 387 388 //n *= 1000; // s => ms 389 //System.out.println("# " + meth.getName() + " " + n + " sec"); 390 int32_t failsafe = 1; // last resort for very fast methods 391 t = 0; 392 while (t < static_cast<int>(n * 0.9)) { // 90% is close enough 393 if (loops == 0 || t == 0) { 394 loops = failsafe; 395 failsafe *= 10; 396 } else { 397 //System.out.println("# " + meth.getName() + " x " + loops + " = " + t); 398 loops = static_cast<int>(static_cast<double>(n) / t * loops + 0.5); 399 if (loops == 0) { 400 fprintf(stderr,"Unable to converge on desired duration"); 401 return false; 402 } 403 } 404 //System.out.println("# " + meth.getName() + " x " + loops); 405 t = testFunction->time(loops,&status); 406 if(U_FAILURE(status)){ 407 printf("Performance test failed with error: %s \n", u_errorName(status)); 408 break; 409 } 410 } 411 } else { 412 loops = iterations; 413 } 414 415 double min_t=1000000.0, sum_t=0.0; 416 long events = -1; 417 418 for(int32_t ps =0; ps < passes; ps++){ 419 if(verbose==true){ 420 fprintf(stdout,"= %s begin " ,name); 421 if(iterations > 0) { 422 fprintf(stdout, "%i\n", static_cast<int>(loops)); 423 } else { 424 fprintf(stdout, "%i\n", static_cast<int>(n)); 425 } 426 } 427 t = testFunction->time(loops, &status); 428 if(U_FAILURE(status)){ 429 printf("Performance test failed with error: %s \n", u_errorName(status)); 430 break; 431 } 432 sum_t+=t; 433 if(t<min_t) { 434 min_t=t; 435 } 436 events = testFunction->getEventsPerIteration(); 437 //print info only in verbose mode 438 if(verbose==true){ 439 if(events == -1){ 440 fprintf(stdout, "= %s end: %f loops: %i operations: %li \n", name, t, static_cast<int>(loops), ops); 441 }else{ 442 fprintf(stdout, "= %s end: %f loops: %i operations: %li events: %li\n", name, t, static_cast<int>(loops), ops, events); 443 } 444 } 445 } 446 if(verbose && U_SUCCESS(status)) { 447 double avg_t = sum_t/passes; 448 if (loops == 0 || ops == 0) { 449 fprintf(stderr, "%s did not run\n", name); 450 } 451 else if(events == -1) { 452 fprintf(stdout, "%%= %s avg: %.4g loops: %i avg/op: %.4g ns\n", 453 name, avg_t, static_cast<int>(loops), (avg_t * 1E9) / (loops * ops)); 454 fprintf(stdout, "_= %s min: %.4g loops: %i min/op: %.4g ns\n", 455 name, min_t, static_cast<int>(loops), (min_t * 1E9) / (loops * ops)); 456 } 457 else { 458 fprintf(stdout, "%%= %s avg: %.4g loops: %i avg/op: %.4g ns avg/event: %.4g ns\n", 459 name, avg_t, static_cast<int>(loops), (avg_t * 1E9) / (loops * ops), (avg_t * 1E9) / (loops * events)); 460 fprintf(stdout, "_= %s min: %.4g loops: %i min/op: %.4g ns min/event: %.4g ns\n", 461 name, min_t, static_cast<int>(loops), (min_t * 1E9) / (loops * ops), (min_t * 1E9) / (loops * events)); 462 } 463 } 464 else if(U_SUCCESS(status)) { 465 // Print results in ndjson format for GHA Benchmark to process. 466 fprintf(stdout, 467 "{\"biggerIsBetter\":false,\"name\":\"%s\",\"unit\":\"ns/iter\",\"value\":%.4f}\n", 468 name, (min_t*1E9)/(loops*ops)); 469 } 470 delete testFunction; 471 } 472 index++; 473 }while(name); 474 475 gTest = saveTest; 476 return rval; 477 } 478 479 /** 480 * Print a usage message for this test class. 481 */ 482 void UPerfTest::usage() 483 { 484 puts(gUsageString); 485 if (_addUsage != nullptr) { 486 puts(_addUsage); 487 } 488 489 UBool save_verbose = verbose; 490 verbose = true; 491 fprintf(stdout,"Test names:\n"); 492 fprintf(stdout,"-----------\n"); 493 494 int32_t index = 0; 495 const char* name = nullptr; 496 do{ 497 this->runIndexedTest( index, false, name ); 498 if (!name) 499 break; 500 fprintf(stdout, "%s\n", name); 501 index++; 502 }while (name && (name[0] != 0)); 503 verbose = save_verbose; 504 } 505 506 507 508 509 void UPerfTest::setCaller( UPerfTest* callingTest ) 510 { 511 caller = callingTest; 512 if (caller) { 513 verbose = caller->verbose; 514 } 515 } 516 517 UBool UPerfTest::callTest( UPerfTest& testToBeCalled, char* par ) 518 { 519 execCount--; // correct a previously assumed test-exec, as this only calls a subtest 520 testToBeCalled.setCaller( this ); 521 return testToBeCalled.runTest( path, par ); 522 } 523 524 UPerfTest::~UPerfTest(){ 525 delete[] lines; 526 if(buffer!=nullptr){ 527 uprv_free(buffer); 528 } 529 if(resolvedFileName!=nullptr){ 530 uprv_free(resolvedFileName); 531 } 532 ucbuf_close(ucharBuf); 533 } 534 535 #endif