Home | History | Annotate | Download | only in normperf
      1 //  2018 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html
      3 
      4 // simplenormperf.cpp
      5 // created: 2018mar15 Markus W. Scherer
      6 
      7 #include <stdio.h>
      8 #include <string>
      9 
     10 #include "unicode/utypes.h"
     11 #include "unicode/bytestream.h"
     12 #include "unicode/normalizer2.h"
     13 #include "unicode/stringpiece.h"
     14 #include "unicode/unistr.h"
     15 #include "unicode/utf8.h"
     16 #include "unicode/utimer.h"
     17 #include "cmemory.h"
     18 
     19 using icu::Normalizer2;
     20 using icu::UnicodeString;
     21 
     22 namespace {
     23 
     24 // Strings with commonly occurring BMP characters.
     25 class CommonChars {
     26 public:
     27     static UnicodeString getMixed(int32_t minLength) {
     28         return extend(UnicodeString(latin1).append(japanese).append(arabic), minLength);
     29     }
     30     static UnicodeString getLatin1(int32_t minLength) { return extend(latin1, minLength); }
     31     static UnicodeString getLowercaseLatin1(int32_t minLength) { return extend(lowercaseLatin1, minLength); }
     32     static UnicodeString getASCII(int32_t minLength) { return extend(ascii, minLength); }
     33     static UnicodeString getJapanese(int32_t minLength) { return extend(japanese, minLength); }
     34 
     35     // Returns an array of UTF-8 offsets, one per code point.
     36     // Assumes all BMP characters.
     37     static int32_t *toUTF8WithOffsets(const UnicodeString &s16, std::string &s8, int32_t &numCodePoints) {
     38         s8.clear();
     39         s8.reserve(s16.length());
     40         s16.toUTF8String(s8);
     41         const char *s = s8.data();
     42         int32_t length = s8.length();
     43         int32_t *offsets = new int32_t[length + 1];
     44         int32_t numCP = 0;
     45         for (int32_t i = 0; i < length;) {
     46             offsets[numCP++] = i;
     47             U8_FWD_1(s, i, length);
     48         }
     49         offsets[numCP] = length;
     50         numCodePoints = numCP;
     51         return offsets;
     52     }
     53 
     54 private:
     55     static UnicodeString extend(const UnicodeString &s, int32_t minLength) {
     56         UnicodeString result(s);
     57         while (result.length() < minLength) {
     58             UnicodeString twice = result + result;
     59             result = std::move(twice);
     60         }
     61         return result;
     62     }
     63 
     64     static const UChar *const latin1;
     65     static const UChar *const lowercaseLatin1;
     66     static const UChar *const ascii;
     67     static const UChar *const japanese;
     68     static const UChar *const arabic;
     69 };
     70 
     71 const UChar *const CommonChars::latin1 =
     72       // Goethes Bergschlo in normal sentence case.
     73       u"Da droben auf jenem Berge, da steht ein altes Schlo, "
     74       u"wo hinter Toren und Tren sonst lauerten Ritter und Ro.\n"
     75       u"Verbrannt sind Tren und Tore, und berall ist es so still; "
     76       u"das alte verfallne Gemuer durchklettr ich, wie ich nur will.\n"
     77       u"Hierneben lag ein Keller, so voll von kstlichem Wein; "
     78       u"nun steiget nicht mehr mit Krgen die Kellnerin heiter hinein.\n"
     79       u"Sie setzt den Gsten im Saale nicht mehr die Becher umher, "
     80       u"sie fllt zum Heiligen Mahle dem Pfaffen das Flschchen nicht mehr.\n"
     81       u"Sie reicht dem lsternen Knappen nicht mehr auf dem Gange den Trank, "
     82       u"und nimmt fr flchtige Gabe nicht mehr den flchtigen Dank.\n"
     83       u"Denn alle Balken und Decken, sie sind schon lange verbrannt, "
     84       u"und Trepp und Gang und Kapelle in Schutt und Trmmer verwandt.\n"
     85       u"Doch als mit Zither und Flasche nach diesen felsigen Hhn "
     86       u"ich an dem heitersten Tage mein Liebchen steigen gesehn,\n"
     87       u"da drngte sich frohes Behagen hervor aus verdeter Ruh, "
     88       u"da gings wie in alten Tagen recht feierlich wieder zu.\n"
     89       u"Als wren fr stattliche Gste die weitesten Rume bereit, "
     90       u"als km ein Prchen gegangen aus jener tchtigen Zeit.\n"
     91       u"Als stnd in seiner Kapelle der wrdige Pfaffe schon da "
     92       u"und fragte: Wollt ihr einander? Wir aber lchelten: Ja!\n"
     93       u"Und tief bewegten Gesnge des Herzens innigsten Grund, "
     94       u"Es zeugte, statt der Menge, der Echo schallender Mund.\n"
     95       u"Und als sich gegen Abend im stillen alles verlor,"
     96       u"da blickte die glhende Sonne zum schroffen Gipfel empor.\n"
     97       u"Und Knapp und Kellnerin glnzen als Herren weit und breit; "
     98       u"sie nimmt sich zum Kredenzen und er zum Danke sich Zeit.\n";
     99 
    100 const UChar *const CommonChars::lowercaseLatin1 =
    101       // Goethes Bergschlo in all lowercase
    102       u"da droben auf jenem berge, da steht ein altes schlo, "
    103       u"wo hinter toren und tren sonst lauerten ritter und ro.\n"
    104       u"verbrannt sind tren und tore, und berall ist es so still; "
    105       u"das alte verfallne gemuer durchklettr ich, wie ich nur will.\n"
    106       u"hierneben lag ein keller, so voll von kstlichem wein; "
    107       u"nun steiget nicht mehr mit krgen die kellnerin heiter hinein.\n"
    108       u"sie setzt den gsten im saale nicht mehr die becher umher, "
    109       u"sie fllt zum heiligen mahle dem pfaffen das flschchen nicht mehr.\n"
    110       u"sie reicht dem lsternen knappen nicht mehr auf dem gange den trank, "
    111       u"und nimmt fr flchtige gabe nicht mehr den flchtigen dank.\n"
    112       u"denn alle balken und decken, sie sind schon lange verbrannt, "
    113       u"und trepp und gang und kapelle in schutt und trmmer verwandt.\n"
    114       u"doch als mit zither und flasche nach diesen felsigen hhn "
    115       u"ich an dem heitersten tage mein liebchen steigen gesehn,\n"
    116       u"da drngte sich frohes behagen hervor aus verdeter ruh, "
    117       u"da gings wie in alten tagen recht feierlich wieder zu.\n"
    118       u"als wren fr stattliche gste die weitesten rume bereit, "
    119       u"als km ein prchen gegangen aus jener tchtigen zeit.\n"
    120       u"als stnd in seiner kapelle der wrdige pfaffe schon da "
    121       u"und fragte: wollt ihr einander? wir aber lchelten: ja!\n"
    122       u"und tief bewegten gesnge des herzens innigsten grund, "
    123       u"es zeugte, statt der menge, der echo schallender mund.\n"
    124       u"und als sich gegen abend im stillen alles verlor,"
    125       u"da blickte die glhende sonne zum schroffen gipfel empor.\n"
    126       u"und knapp und kellnerin glnzen als herren weit und breit; "
    127       u"sie nimmt sich zum kredenzen und er zum danke sich zeit.\n";
    128 
    129 const UChar *const CommonChars::ascii =
    130       // Goethes Bergschlo in normal sentence case but ASCII-fied
    131       u"Da droben auf jenem Berge, da steht ein altes Schloss, "
    132       u"wo hinter Toren und Tueren sonst lauerten Ritter und Ross.\n"
    133       u"Verbrannt sind Tueren und Tore, und ueberall ist es so still; "
    134       u"das alte verfallne Gemaeuer durchklettr ich, wie ich nur will.\n"
    135       u"Hierneben lag ein Keller, so voll von koestlichem Wein; "
    136       u"nun steiget nicht mehr mit Kruegen die Kellnerin heiter hinein.\n"
    137       u"Sie setzt den Gaesten im Saale nicht mehr die Becher umher, "
    138       u"sie fuellt zum Heiligen Mahle dem Pfaffen das Flaeschchen nicht mehr.\n"
    139       u"Sie reicht dem luesternen Knappen nicht mehr auf dem Gange den Trank, "
    140       u"und nimmt fuer fluechtige Gabe nicht mehr den fluechtigen Dank.\n"
    141       u"Denn alle Balken und Decken, sie sind schon lange verbrannt, "
    142       u"und Trepp und Gang und Kapelle in Schutt und Truemmer verwandt.\n"
    143       u"Doch als mit Zither und Flasche nach diesen felsigen Hoehn "
    144       u"ich an dem heitersten Tage mein Liebchen steigen gesehn,\n"
    145       u"da draengte sich frohes Behagen hervor aus veroedeter Ruh, "
    146       u"da gings wie in alten Tagen recht feierlich wieder zu.\n"
    147       u"Als waeren fuer stattliche Gaeste die weitesten Raeume bereit, "
    148       u"als kaem ein Paerchen gegangen aus jener tuechtigen Zeit.\n"
    149       u"Als stuend in seiner Kapelle der wuerdige Pfaffe schon da "
    150       u"und fragte: Wollt ihr einander? Wir aber laechelten: Ja!\n"
    151       u"Und tief bewegten Gesaenge des Herzens innigsten Grund, "
    152       u"Es zeugte, statt der Menge, der Echo schallender Mund.\n"
    153       u"Und als sich gegen Abend im stillen alles verlor,"
    154       u"da blickte die gluehende Sonne zum schroffen Gipfel empor.\n"
    155       u"Und Knapp und Kellnerin glaenzen als Herren weit und breit; "
    156       u"sie nimmt sich zum Kredenzen und er zum Danke sich Zeit.\n";
    157 
    158 const UChar *const CommonChars::japanese =
    159       // Ame ni mo makezu = Be not Defeated by the Rain, by Kenji Miyazawa.
    160       u""
    161       u""
    162       u""
    163       u""
    164       u""
    165       u""
    166       u""
    167       u""
    168       u""
    169       u""
    170       u""
    171       u""
    172       u""
    173       u"";
    174 
    175 const UChar *const CommonChars::arabic =
    176       // Some Arabic for variety. "What is Unicode?"
    177       // http://www.unicode.org/standard/translations/arabic.html
    178       u"      "
    179       u"     "
    180       u"     "
    181       u".      ";
    182 
    183 // TODO: class BenchmarkPerCodePoint?
    184 
    185 class Operation {
    186 public:
    187     Operation() {}
    188     virtual ~Operation();
    189     virtual double call(int32_t iterations, int32_t pieceLength) = 0;
    190 
    191 protected:
    192     UTimer startTime;
    193 };
    194 
    195 Operation::~Operation() {}
    196 
    197 const int32_t kLengths[] = { 5, 12, 30, 100, 1000, 10000 };
    198 
    199 int32_t getMaxLength() { return kLengths[UPRV_LENGTHOF(kLengths) - 1]; }
    200 
    201 // Returns seconds per code point.
    202 double measure(Operation &op, int32_t pieceLength) {
    203     // Increase the number of iterations until we use at least one second.
    204     int32_t iterations = 1;
    205     for (;;) {
    206         double seconds = op.call(iterations, pieceLength);
    207         if (seconds >= 1) {
    208             if (iterations > 1) {
    209                 return seconds / (iterations * pieceLength);
    210             } else {
    211                 // Run it once more, to avoid measuring only the warm-up.
    212                 return op.call(1, pieceLength) / (iterations * pieceLength);
    213             }
    214         }
    215         if (seconds < 0.01) {
    216             iterations *= 10;
    217         } else if (seconds < 0.55) {
    218             iterations *= 1.1 / seconds;
    219         } else {
    220             iterations *= 2;
    221         }
    222     }
    223 }
    224 
    225 void benchmark(const char *name, Operation &op) {
    226     for (int32_t i = 0; i < UPRV_LENGTHOF(kLengths); ++i) {
    227         int32_t pieceLength = kLengths[i];
    228         double secPerCp = measure(op, pieceLength);
    229         printf("%s  %6d  %12f ns/cp\n", name, (int)pieceLength, secPerCp * 1000000000);
    230     }
    231     puts("");
    232 }
    233 
    234 class NormalizeUTF16 : public Operation {
    235 public:
    236     NormalizeUTF16(const Normalizer2 &n2, const UnicodeString &text) :
    237             norm2(n2), src(text), s(src.getBuffer()) {}
    238     virtual ~NormalizeUTF16();
    239     virtual double call(int32_t iterations, int32_t pieceLength);
    240 
    241 private:
    242     const Normalizer2 &norm2;
    243     UnicodeString src;
    244     const UChar *s;
    245     UnicodeString dest;
    246 };
    247 
    248 NormalizeUTF16::~NormalizeUTF16() {}
    249 
    250 // Assumes all BMP characters.
    251 double NormalizeUTF16::call(int32_t iterations, int32_t pieceLength) {
    252     int32_t start = 0;
    253     int32_t limit = src.length() - pieceLength;
    254     UnicodeString piece;
    255     UErrorCode errorCode = U_ZERO_ERROR;
    256     utimer_getTime(&startTime);
    257     for (int32_t i = 0; i < iterations; ++i) {
    258         piece.setTo(FALSE, s + start, pieceLength);
    259         norm2.normalize(piece, dest, errorCode);
    260         start = (start + pieceLength) % limit;
    261     }
    262     return utimer_getElapsedSeconds(&startTime);
    263 }
    264 
    265 class NormalizeUTF8 : public Operation {
    266 public:
    267     NormalizeUTF8(const Normalizer2 &n2, const UnicodeString &text) : norm2(n2), sink(&dest) {
    268         offsets = CommonChars::toUTF8WithOffsets(text, src, numCodePoints);
    269         s = src.data();
    270     }
    271     virtual ~NormalizeUTF8();
    272     virtual double call(int32_t iterations, int32_t pieceLength);
    273 
    274 private:
    275     const Normalizer2 &norm2;
    276     std::string src;
    277     const char *s;
    278     int32_t *offsets;
    279     int32_t numCodePoints;
    280     std::string dest;
    281     icu::StringByteSink<std::string> sink;
    282 };
    283 
    284 NormalizeUTF8::~NormalizeUTF8() {
    285     delete[] offsets;
    286 }
    287 
    288 double NormalizeUTF8::call(int32_t iterations, int32_t pieceLength) {
    289     int32_t start = 0;
    290     int32_t limit = numCodePoints - pieceLength;
    291     UErrorCode errorCode = U_ZERO_ERROR;
    292     utimer_getTime(&startTime);
    293     for (int32_t i = 0; i < iterations; ++i) {
    294         int32_t start8 = offsets[start];
    295         int32_t limit8 = offsets[start + pieceLength];
    296         icu::StringPiece piece(s + start8, limit8 - start8);
    297         norm2.normalizeUTF8(0, piece, sink, nullptr, errorCode);
    298         start = (start + pieceLength) % limit;
    299     }
    300     return utimer_getElapsedSeconds(&startTime);
    301 }
    302 
    303 }  // namespace
    304 
    305 extern int main(int /*argc*/, const char * /*argv*/[]) {
    306     // More than the longest piece length so that we read from different parts of the string
    307     // for that piece length.
    308     int32_t maxLength = getMaxLength() * 10;
    309     UErrorCode errorCode = U_ZERO_ERROR;
    310     const Normalizer2 *nfc = Normalizer2::getNFCInstance(errorCode);
    311     const Normalizer2 *nfkc_cf = Normalizer2::getNFKCCasefoldInstance(errorCode);
    312     if (U_FAILURE(errorCode)) {
    313         fprintf(stderr,
    314                 "simplenormperf: failed to get Normalizer2 instances - %s\n",
    315                 u_errorName(errorCode));
    316     }
    317     {
    318         // Base line: Should remain in the fast loop without trie lookups.
    319         NormalizeUTF16 op(*nfc, CommonChars::getLatin1(maxLength));
    320         benchmark("NFC/UTF-16/latin1", op);
    321     }
    322     {
    323         // Base line 2: Read UTF-8, trie lookups, but should have nothing to do.
    324         NormalizeUTF8 op(*nfc, CommonChars::getJapanese(maxLength));
    325         benchmark("NFC/UTF-8/japanese", op);
    326     }
    327     {
    328         NormalizeUTF16 op(*nfkc_cf, CommonChars::getMixed(maxLength));
    329         benchmark("NFKC_CF/UTF-16/mixed", op);
    330     }
    331     {
    332         NormalizeUTF16 op(*nfkc_cf, CommonChars::getLowercaseLatin1(maxLength));
    333         benchmark("NFKC_CF/UTF-16/lowercaseLatin1", op);
    334     }
    335     {
    336         NormalizeUTF16 op(*nfkc_cf, CommonChars::getJapanese(maxLength));
    337         benchmark("NFKC_CF/UTF-16/japanese", op);
    338     }
    339     {
    340         NormalizeUTF8 op(*nfkc_cf, CommonChars::getMixed(maxLength));
    341         benchmark("NFKC_CF/UTF-8/mixed", op);
    342     }
    343     {
    344         NormalizeUTF8 op(*nfkc_cf, CommonChars::getLowercaseLatin1(maxLength));
    345         benchmark("NFKC_CF/UTF-8/lowercaseLatin1", op);
    346     }
    347     {
    348         NormalizeUTF8 op(*nfkc_cf, CommonChars::getJapanese(maxLength));
    349         benchmark("NFKC_CF/UTF-8/japanese", op);
    350     }
    351     return 0;
    352 }
    353