From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- intl/icu/source/tools/ctestfw/uperf.cpp | 533 ++++++++++++++++++++++++++++++++ 1 file changed, 533 insertions(+) create mode 100644 intl/icu/source/tools/ctestfw/uperf.cpp (limited to 'intl/icu/source/tools/ctestfw/uperf.cpp') diff --git a/intl/icu/source/tools/ctestfw/uperf.cpp b/intl/icu/source/tools/ctestfw/uperf.cpp new file mode 100644 index 0000000000..9e92b7714b --- /dev/null +++ b/intl/icu/source/tools/ctestfw/uperf.cpp @@ -0,0 +1,533 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +/******************************************************************** + * COPYRIGHT: + * Copyright (c) 2002-2012, International Business Machines Corporation and + * others. All Rights Reserved. + ********************************************************************/ + +// Defines _XOPEN_SOURCE for access to POSIX functions. +// Must be before any other #includes. +#include "uposixdefs.h" + +#include "unicode/uperf.h" +#include "uoptions.h" +#include "cmemory.h" +#include +#include + +#if !UCONFIG_NO_CONVERSION + +UPerfFunction::~UPerfFunction() {} + +static const char delim = '/'; +static int32_t execCount = 0; +UPerfTest* UPerfTest::gTest = nullptr; +static const int MAXLINES = 40000; +const char UPerfTest::gUsageString[] = + "Usage: %s [OPTIONS] [FILES]\n" + "\tReads the input file and prints out time taken in seconds\n" + "Options:\n" + "\t-h or -? or --help this usage text\n" + "\t-v or --verbose print extra information when processing files\n" + "\t-s or --sourcedir source directory for files followed by path\n" + "\t followed by path\n" + "\t-e or --encoding encoding of source files\n" + "\t-u or --uselen perform timing analysis on non-null terminated buffer using length\n" + "\t-f or --file-name file to be used as input data\n" + "\t-p or --passes Number of passes to be performed. Requires Numeric argument.\n" + "\t Cannot be used with --time\n" + "\t-i or --iterations Number of iterations to be performed. Requires Numeric argument\n" + "\t-t or --time Threshold time for looping until in seconds. Requires Numeric argument.\n" + "\t Cannot be used with --iterations\n" + "\t-l or --line-mode The data file should be processed in line mode\n" + "\t-b or --bulk-mode The data file should be processed in file based.\n" + "\t Cannot be used with --line-mode\n" + "\t-L or --locale Locale for the test\n"; + +enum +{ + HELP1, + HELP2, + VERBOSE, + SOURCEDIR, + ENCODING, + USELEN, + FILE_NAME, + PASSES, + ITERATIONS, + TIME, + LINE_MODE, + BULK_MODE, + LOCALE, + OPTIONS_COUNT +}; + + +static UOption options[OPTIONS_COUNT+20]={ + UOPTION_HELP_H, + UOPTION_HELP_QUESTION_MARK, + UOPTION_VERBOSE, + UOPTION_SOURCEDIR, + UOPTION_ENCODING, + UOPTION_DEF( "uselen", 'u', UOPT_NO_ARG), + UOPTION_DEF( "file-name", 'f', UOPT_REQUIRES_ARG), + UOPTION_DEF( "passes", 'p', UOPT_REQUIRES_ARG), + UOPTION_DEF( "iterations", 'i', UOPT_REQUIRES_ARG), + UOPTION_DEF( "time", 't', UOPT_REQUIRES_ARG), + UOPTION_DEF( "line-mode", 'l', UOPT_NO_ARG), + UOPTION_DEF( "bulk-mode", 'b', UOPT_NO_ARG), + UOPTION_DEF( "locale", 'L', UOPT_REQUIRES_ARG) +}; + +UPerfTest::UPerfTest(int32_t argc, const char* argv[], UErrorCode& status) + : _argc(argc), _argv(argv), _addUsage(nullptr), + ucharBuf(nullptr), encoding(""), + uselen(false), + fileName(nullptr), sourceDir("."), + lines(nullptr), numLines(0), line_mode(true), + buffer(nullptr), bufferLen(0), + verbose(false), bulk_mode(false), + passes(1), iterations(0), time(0), + locale(nullptr) { + init(nullptr, 0, status); +} + +UPerfTest::UPerfTest(int32_t argc, const char* argv[], + UOption addOptions[], int32_t addOptionsCount, + const char *addUsage, + UErrorCode& status) + : _argc(argc), _argv(argv), _addUsage(addUsage), + ucharBuf(nullptr), encoding(""), + uselen(false), + fileName(nullptr), sourceDir("."), + lines(nullptr), numLines(0), line_mode(true), + buffer(nullptr), bufferLen(0), + verbose(false), bulk_mode(false), + passes(1), iterations(0), time(0), + locale(nullptr) { + init(addOptions, addOptionsCount, status); +} + +void UPerfTest::init(UOption addOptions[], int32_t addOptionsCount, + UErrorCode& status) { + //initialize the argument list + U_MAIN_INIT_ARGS(_argc, _argv); + + resolvedFileName = nullptr; + + // add specific options + int32_t optionsCount = OPTIONS_COUNT; + if (addOptionsCount > 0) { + memcpy(options+optionsCount, addOptions, addOptionsCount*sizeof(UOption)); + optionsCount += addOptionsCount; + } + + //parse the arguments + _remainingArgc = u_parseArgs(_argc, (char**)_argv, optionsCount, options); + + // copy back values for additional options + if (addOptionsCount > 0) { + memcpy(addOptions, options+OPTIONS_COUNT, addOptionsCount*sizeof(UOption)); + } + + // Now setup the arguments + if(_argc==1 || options[HELP1].doesOccur || options[HELP2].doesOccur) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + if(options[VERBOSE].doesOccur) { + verbose = true; + } + + if(options[SOURCEDIR].doesOccur) { + sourceDir = options[SOURCEDIR].value; + } + + if(options[ENCODING].doesOccur) { + encoding = options[ENCODING].value; + } + + if(options[USELEN].doesOccur) { + uselen = true; + } + + if(options[FILE_NAME].doesOccur){ + fileName = options[FILE_NAME].value; + } + + if(options[PASSES].doesOccur) { + passes = atoi(options[PASSES].value); + } + if(options[ITERATIONS].doesOccur) { + iterations = atoi(options[ITERATIONS].value); + if(options[TIME].doesOccur) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + } else if(options[TIME].doesOccur) { + time = atoi(options[TIME].value); + } else { + iterations = 1000; // some default + } + + if(options[LINE_MODE].doesOccur) { + line_mode = true; + bulk_mode = false; + } + + if(options[BULK_MODE].doesOccur) { + bulk_mode = true; + line_mode = false; + } + + if(options[LOCALE].doesOccur) { + locale = options[LOCALE].value; + } + + int32_t len = 0; + if(fileName!=nullptr){ + //pre-flight + ucbuf_resolveFileName(sourceDir, fileName, nullptr, &len, &status); + resolvedFileName = (char*) uprv_malloc(len); + if(resolvedFileName==nullptr){ + status= U_MEMORY_ALLOCATION_ERROR; + return; + } + if(status == U_BUFFER_OVERFLOW_ERROR){ + status = U_ZERO_ERROR; + } + ucbuf_resolveFileName(sourceDir, fileName, resolvedFileName, &len, &status); + ucharBuf = ucbuf_open(resolvedFileName,&encoding,true,false,&status); + + if(U_FAILURE(status)){ + printf("Could not open the input file %s. Error: %s\n", fileName, u_errorName(status)); + return; + } + } +} + +ULine* UPerfTest::getLines(UErrorCode& status){ + if (U_FAILURE(status)) { + return nullptr; + } + if (lines != nullptr) { + return lines; // don't do it again + } + lines = new ULine[MAXLINES]; + int maxLines = MAXLINES; + numLines=0; + const char16_t* line=nullptr; + int32_t len =0; + for (;;) { + line = ucbuf_readline(ucharBuf,&len,&status); + if(line == nullptr || U_FAILURE(status)){ + break; + } + lines[numLines].name = new char16_t[len]; + lines[numLines].len = len; + memcpy(lines[numLines].name, line, len * U_SIZEOF_UCHAR); + + numLines++; + len = 0; + if (numLines >= maxLines) { + maxLines += MAXLINES; + ULine *newLines = new ULine[maxLines]; + if(newLines == nullptr) { + fprintf(stderr, "Out of memory reading line %d.\n", (int)numLines); + status= U_MEMORY_ALLOCATION_ERROR; + delete []lines; + return nullptr; + } + + memcpy(newLines, lines, numLines*sizeof(ULine)); + delete []lines; + lines = newLines; + } + } + return lines; +} +const char16_t* UPerfTest::getBuffer(int32_t& len, UErrorCode& status){ + if (U_FAILURE(status)) { + return nullptr; + } + len = ucbuf_size(ucharBuf); + buffer = (char16_t*) uprv_malloc(U_SIZEOF_UCHAR * (len+1)); + u_strncpy(buffer,ucbuf_getBuffer(ucharBuf,&bufferLen,&status),len); + buffer[len]=0; + len = bufferLen; + return buffer; +} +UBool UPerfTest::run(){ + if(_remainingArgc==1){ + // Testing all methods + return runTest(); + } + UBool res=false; + // Test only the specified function + for (int i = 1; i < _remainingArgc; ++i) { + if (_argv[i][0] != '-') { + char* name = (char*) _argv[i]; + if(verbose==true){ + //fprintf(stdout, "\n=== Handling test: %s: ===\n", name); + //fprintf(stdout, "\n%s:\n", name); + } + char* parameter = strchr( name, '@' ); + if (parameter) { + *parameter = 0; + parameter += 1; + } + execCount = 0; + res = runTest( name, parameter ); + if (!res || (execCount <= 0)) { + fprintf(stdout, "\n---ERROR: Test doesn't exist: %s!\n", name); + return false; + } + } + } + return res; +} +UBool UPerfTest::runTest(char* name, char* par ){ + UBool rval; + char* pos = nullptr; + + if (name) + pos = strchr( name, delim ); // check if name contains path (by looking for '/') + if (pos) { + path = pos+1; // store subpath for calling subtest + *pos = 0; // split into two strings + }else{ + path = nullptr; + } + + if (!name || (name[0] == 0) || (strcmp(name, "*") == 0)) { + rval = runTestLoop( nullptr, nullptr ); + + }else if (strcmp( name, "LIST" ) == 0) { + this->usage(); + rval = true; + + }else{ + rval = runTestLoop( name, par ); + } + + if (pos) + *pos = delim; // restore original value at pos + return rval; +} + + +void UPerfTest::setPath( char* pathVal ) +{ + this->path = pathVal; +} + +// call individual tests, to be overridden to call implementations +UPerfFunction* UPerfTest::runIndexedTest( int32_t /*index*/, UBool /*exec*/, const char* & /*name*/, char* /*par*/ ) +{ + // to be overridden by a method like: + /* + switch (index) { + case 0: name = "First Test"; if (exec) FirstTest( par ); break; + case 1: name = "Second Test"; if (exec) SecondTest( par ); break; + default: name = ""; break; + } + */ + fprintf(stderr,"*** runIndexedTest needs to be overridden! ***"); + return nullptr; +} + + +UBool UPerfTest::runTestLoop( char* testname, char* par ) +{ + int32_t index = 0; + const char* name; + UBool run_this_test; + UBool rval = false; + UErrorCode status = U_ZERO_ERROR; + UPerfTest* saveTest = gTest; + gTest = this; + int32_t loops = 0; + double t=0; + int32_t n = 1; + long ops; + do { + this->runIndexedTest( index, false, name ); + if (!name || (name[0] == 0)) + break; + if (!testname) { + run_this_test = true; + }else{ + run_this_test = (UBool) (strcmp( name, testname ) == 0); + } + if (run_this_test) { + UPerfFunction* testFunction = this->runIndexedTest( index, true, name, par ); + execCount++; + rval=true; + if(testFunction==nullptr){ + fprintf(stderr,"%s function returned nullptr", name); + return false; + } + ops = testFunction->getOperationsPerIteration(); + if (ops < 1) { + fprintf(stderr, "%s returned an illegal operations/iteration()\n", name); + return false; + } + if(iterations == 0) { + n = time; + // Run for specified duration in seconds + if(verbose==true){ + fprintf(stdout,"= %s calibrating %i seconds \n", name, (int)n); + } + + //n *= 1000; // s => ms + //System.out.println("# " + meth.getName() + " " + n + " sec"); + int32_t failsafe = 1; // last resort for very fast methods + t = 0; + while (t < (int)(n * 0.9)) { // 90% is close enough + if (loops == 0 || t == 0) { + loops = failsafe; + failsafe *= 10; + } else { + //System.out.println("# " + meth.getName() + " x " + loops + " = " + t); + loops = (int)((double)n / t * loops + 0.5); + if (loops == 0) { + fprintf(stderr,"Unable to converge on desired duration"); + return false; + } + } + //System.out.println("# " + meth.getName() + " x " + loops); + t = testFunction->time(loops,&status); + if(U_FAILURE(status)){ + printf("Performance test failed with error: %s \n", u_errorName(status)); + break; + } + } + } else { + loops = iterations; + } + + double min_t=1000000.0, sum_t=0.0; + long events = -1; + + for(int32_t ps =0; ps < passes; ps++){ + if(verbose==true){ + fprintf(stdout,"= %s begin " ,name); + if(iterations > 0) { + fprintf(stdout, "%i\n", (int)loops); + } else { + fprintf(stdout, "%i\n", (int)n); + } + } + t = testFunction->time(loops, &status); + if(U_FAILURE(status)){ + printf("Performance test failed with error: %s \n", u_errorName(status)); + break; + } + sum_t+=t; + if(tgetEventsPerIteration(); + //print info only in verbose mode + if(verbose==true){ + if(events == -1){ + fprintf(stdout, "= %s end: %f loops: %i operations: %li \n", name, t, (int)loops, ops); + }else{ + fprintf(stdout, "= %s end: %f loops: %i operations: %li events: %li\n", name, t, (int)loops, ops, events); + } + } + } + if(verbose && U_SUCCESS(status)) { + double avg_t = sum_t/passes; + if (loops == 0 || ops == 0) { + fprintf(stderr, "%s did not run\n", name); + } + else if(events == -1) { + fprintf(stdout, "%%= %s avg: %.4g loops: %i avg/op: %.4g ns\n", + name, avg_t, (int)loops, (avg_t*1E9)/(loops*ops)); + fprintf(stdout, "_= %s min: %.4g loops: %i min/op: %.4g ns\n", + name, min_t, (int)loops, (min_t*1E9)/(loops*ops)); + } + else { + fprintf(stdout, "%%= %s avg: %.4g loops: %i avg/op: %.4g ns avg/event: %.4g ns\n", + name, avg_t, (int)loops, (avg_t*1E9)/(loops*ops), (avg_t*1E9)/(loops*events)); + fprintf(stdout, "_= %s min: %.4g loops: %i min/op: %.4g ns min/event: %.4g ns\n", + name, min_t, (int)loops, (min_t*1E9)/(loops*ops), (min_t*1E9)/(loops*events)); + } + } + else if(U_SUCCESS(status)) { + // Print results in ndjson format for GHA Benchmark to process. + fprintf(stdout, + "{\"biggerIsBetter\":false,\"name\":\"%s\",\"unit\":\"ns/iter\",\"value\":%.4f}\n", + name, (min_t*1E9)/(loops*ops)); + } + delete testFunction; + } + index++; + }while(name); + + gTest = saveTest; + return rval; +} + +/** +* Print a usage message for this test class. +*/ +void UPerfTest::usage() +{ + puts(gUsageString); + if (_addUsage != nullptr) { + puts(_addUsage); + } + + UBool save_verbose = verbose; + verbose = true; + fprintf(stdout,"Test names:\n"); + fprintf(stdout,"-----------\n"); + + int32_t index = 0; + const char* name = nullptr; + do{ + this->runIndexedTest( index, false, name ); + if (!name) + break; + fprintf(stdout, "%s\n", name); + index++; + }while (name && (name[0] != 0)); + verbose = save_verbose; +} + + + + +void UPerfTest::setCaller( UPerfTest* callingTest ) +{ + caller = callingTest; + if (caller) { + verbose = caller->verbose; + } +} + +UBool UPerfTest::callTest( UPerfTest& testToBeCalled, char* par ) +{ + execCount--; // correct a previously assumed test-exec, as this only calls a subtest + testToBeCalled.setCaller( this ); + return testToBeCalled.runTest( path, par ); +} + +UPerfTest::~UPerfTest(){ + if(lines!=nullptr){ + delete[] lines; + } + if(buffer!=nullptr){ + uprv_free(buffer); + } + if(resolvedFileName!=nullptr){ + uprv_free(resolvedFileName); + } + ucbuf_close(ucharBuf); +} + +#endif -- cgit v1.2.3