--- /dev/null
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+// simplenormperf.cpp
+// created: 2018mar15 Markus W. Scherer
+
+#include <stdio.h>
+#include <string>
+
+#include "unicode/utypes.h"
+#include "unicode/bytestream.h"
+#include "unicode/normalizer2.h"
+#include "unicode/stringpiece.h"
+#include "unicode/unistr.h"
+#include "unicode/utf8.h"
+#include "unicode/utimer.h"
+#include "cmemory.h"
+
+using icu::Normalizer2;
+using icu::UnicodeString;
+
+namespace {
+
+// Strings with commonly occurring BMP characters.
+class CommonChars {
+public:
+ static UnicodeString getMixed(int32_t minLength) {
+ return extend(UnicodeString(latin1).append(japanese).append(arabic), minLength);
+ }
+ static UnicodeString getLatin1(int32_t minLength) { return extend(latin1, minLength); }
+ static UnicodeString getLowercaseLatin1(int32_t minLength) { return extend(lowercaseLatin1, minLength); }
+ static UnicodeString getASCII(int32_t minLength) { return extend(ascii, minLength); }
+ static UnicodeString getJapanese(int32_t minLength) { return extend(japanese, minLength); }
+
+ // Returns an array of UTF-8 offsets, one per code point.
+ // Assumes all BMP characters.
+ static int32_t *toUTF8WithOffsets(const UnicodeString &s16, std::string &s8, int32_t &numCodePoints) {
+ s8.clear();
+ s8.reserve(s16.length());
+ s16.toUTF8String(s8);
+ const char *s = s8.data();
+ int32_t length = s8.length();
+ int32_t *offsets = new int32_t[length + 1];
+ int32_t numCP = 0;
+ for (int32_t i = 0; i < length;) {
+ offsets[numCP++] = i;
+ U8_FWD_1(s, i, length);
+ }
+ offsets[numCP] = length;
+ numCodePoints = numCP;
+ return offsets;
+ }
+
+private:
+ static UnicodeString extend(const UnicodeString &s, int32_t minLength) {
+ UnicodeString result(s);
+ while (result.length() < minLength) {
+ UnicodeString twice = result + result;
+ result = std::move(twice);
+ }
+ return result;
+ }
+
+ static const UChar *const latin1;
+ static const UChar *const lowercaseLatin1;
+ static const UChar *const ascii;
+ static const UChar *const japanese;
+ static const UChar *const arabic;
+};
+
+const UChar *const CommonChars::latin1 =
+ // Goethe’s Bergschloß in normal sentence case.
+ u"Da droben auf jenem Berge, da steht ein altes Schloß, "
+ u"wo hinter Toren und Türen sonst lauerten Ritter und Roß.\n"
+ u"Verbrannt sind Türen und Tore, und überall ist es so still; "
+ u"das alte verfallne Gemäuer durchklettr ich, wie ich nur will.\n"
+ u"Hierneben lag ein Keller, so voll von köstlichem Wein; "
+ u"nun steiget nicht mehr mit Krügen die Kellnerin heiter hinein.\n"
+ u"Sie setzt den Gästen im Saale nicht mehr die Becher umher, "
+ u"sie füllt zum Heiligen Mahle dem Pfaffen das Fläschchen nicht mehr.\n"
+ u"Sie reicht dem lüsternen Knappen nicht mehr auf dem Gange den Trank, "
+ u"und nimmt für flüchtige Gabe nicht mehr den flüchtigen Dank.\n"
+ u"Denn alle Balken und Decken, sie sind schon lange verbrannt, "
+ u"und Trepp und Gang und Kapelle in Schutt und Trümmer verwandt.\n"
+ u"Doch als mit Zither und Flasche nach diesen felsigen Höhn "
+ u"ich an dem heitersten Tage mein Liebchen steigen gesehn,\n"
+ u"da drängte sich frohes Behagen hervor aus verödeter Ruh, "
+ u"da gings wie in alten Tagen recht feierlich wieder zu.\n"
+ u"Als wären für stattliche Gäste die weitesten Räume bereit, "
+ u"als käm ein Pärchen gegangen aus jener tüchtigen Zeit.\n"
+ u"Als stünd in seiner Kapelle der würdige Pfaffe schon da "
+ u"und fragte: Wollt ihr einander? Wir aber lächelten: Ja!\n"
+ u"Und tief bewegten Gesänge des Herzens innigsten Grund, "
+ u"Es zeugte, statt der Menge, der Echo schallender Mund.\n"
+ u"Und als sich gegen Abend im stillen alles verlor,"
+ u"da blickte die glühende Sonne zum schroffen Gipfel empor.\n"
+ u"Und Knapp und Kellnerin glänzen als Herren weit und breit; "
+ u"sie nimmt sich zum Kredenzen und er zum Danke sich Zeit.\n";
+
+const UChar *const CommonChars::lowercaseLatin1 =
+ // Goethe’s Bergschloß in all lowercase
+ u"da droben auf jenem berge, da steht ein altes schloß, "
+ u"wo hinter toren und türen sonst lauerten ritter und roß.\n"
+ u"verbrannt sind türen und tore, und überall ist es so still; "
+ u"das alte verfallne gemäuer durchklettr ich, wie ich nur will.\n"
+ u"hierneben lag ein keller, so voll von köstlichem wein; "
+ u"nun steiget nicht mehr mit krügen die kellnerin heiter hinein.\n"
+ u"sie setzt den gästen im saale nicht mehr die becher umher, "
+ u"sie füllt zum heiligen mahle dem pfaffen das fläschchen nicht mehr.\n"
+ u"sie reicht dem lüsternen knappen nicht mehr auf dem gange den trank, "
+ u"und nimmt für flüchtige gabe nicht mehr den flüchtigen dank.\n"
+ u"denn alle balken und decken, sie sind schon lange verbrannt, "
+ u"und trepp und gang und kapelle in schutt und trümmer verwandt.\n"
+ u"doch als mit zither und flasche nach diesen felsigen höhn "
+ u"ich an dem heitersten tage mein liebchen steigen gesehn,\n"
+ u"da drängte sich frohes behagen hervor aus verödeter ruh, "
+ u"da gings wie in alten tagen recht feierlich wieder zu.\n"
+ u"als wären für stattliche gäste die weitesten räume bereit, "
+ u"als käm ein pärchen gegangen aus jener tüchtigen zeit.\n"
+ u"als stünd in seiner kapelle der würdige pfaffe schon da "
+ u"und fragte: wollt ihr einander? wir aber lächelten: ja!\n"
+ u"und tief bewegten gesänge des herzens innigsten grund, "
+ u"es zeugte, statt der menge, der echo schallender mund.\n"
+ u"und als sich gegen abend im stillen alles verlor,"
+ u"da blickte die glühende sonne zum schroffen gipfel empor.\n"
+ u"und knapp und kellnerin glänzen als herren weit und breit; "
+ u"sie nimmt sich zum kredenzen und er zum danke sich zeit.\n";
+
+const UChar *const CommonChars::ascii =
+ // Goethe’s Bergschloß in normal sentence case but ASCII-fied
+ u"Da droben auf jenem Berge, da steht ein altes Schloss, "
+ u"wo hinter Toren und Tueren sonst lauerten Ritter und Ross.\n"
+ u"Verbrannt sind Tueren und Tore, und ueberall ist es so still; "
+ u"das alte verfallne Gemaeuer durchklettr ich, wie ich nur will.\n"
+ u"Hierneben lag ein Keller, so voll von koestlichem Wein; "
+ u"nun steiget nicht mehr mit Kruegen die Kellnerin heiter hinein.\n"
+ u"Sie setzt den Gaesten im Saale nicht mehr die Becher umher, "
+ u"sie fuellt zum Heiligen Mahle dem Pfaffen das Flaeschchen nicht mehr.\n"
+ u"Sie reicht dem luesternen Knappen nicht mehr auf dem Gange den Trank, "
+ u"und nimmt fuer fluechtige Gabe nicht mehr den fluechtigen Dank.\n"
+ u"Denn alle Balken und Decken, sie sind schon lange verbrannt, "
+ u"und Trepp und Gang und Kapelle in Schutt und Truemmer verwandt.\n"
+ u"Doch als mit Zither und Flasche nach diesen felsigen Hoehn "
+ u"ich an dem heitersten Tage mein Liebchen steigen gesehn,\n"
+ u"da draengte sich frohes Behagen hervor aus veroedeter Ruh, "
+ u"da gings wie in alten Tagen recht feierlich wieder zu.\n"
+ u"Als waeren fuer stattliche Gaeste die weitesten Raeume bereit, "
+ u"als kaem ein Paerchen gegangen aus jener tuechtigen Zeit.\n"
+ u"Als stuend in seiner Kapelle der wuerdige Pfaffe schon da "
+ u"und fragte: Wollt ihr einander? Wir aber laechelten: Ja!\n"
+ u"Und tief bewegten Gesaenge des Herzens innigsten Grund, "
+ u"Es zeugte, statt der Menge, der Echo schallender Mund.\n"
+ u"Und als sich gegen Abend im stillen alles verlor,"
+ u"da blickte die gluehende Sonne zum schroffen Gipfel empor.\n"
+ u"Und Knapp und Kellnerin glaenzen als Herren weit und breit; "
+ u"sie nimmt sich zum Kredenzen und er zum Danke sich Zeit.\n";
+
+const UChar *const CommonChars::japanese =
+ // Ame ni mo makezu = Be not Defeated by the Rain, by Kenji Miyazawa.
+ u"雨にもまけず風にもまけず雪にも夏の暑さにもまけぬ"
+ u"丈夫なからだをもち慾はなく決して瞋らず"
+ u"いつもしずかにわらっている一日に玄米四合と"
+ u"味噌と少しの野菜をたべあらゆることを"
+ u"じぶんをかんじょうにいれずによくみききしわかり"
+ u"そしてわすれず野原の松の林の蔭の"
+ u"小さな萱ぶきの小屋にいて東に病気のこどもあれば"
+ u"行って看病してやり西につかれた母あれば"
+ u"行ってその稲の束を負い南に死にそうな人あれば"
+ u"行ってこわがらなくてもいいといい"
+ u"北にけんかやそしょうがあれば"
+ u"つまらないからやめろといいひでりのときはなみだをながし"
+ u"さむさのなつはおろおろあるきみんなにでくのぼうとよばれ"
+ u"ほめられもせずくにもされずそういうものにわたしはなりたい";
+
+const UChar *const CommonChars::arabic =
+ // Some Arabic for variety. "What is Unicode?"
+ // http://www.unicode.org/standard/translations/arabic.html
+ u"تتعامل الحواسيب بالأسام مع الأرقام فقط، "
+ u"و تخزن الحروف و المحارف "
+ u"الأخرى بتخصيص رقم لكل واحد "
+ u"منها. قبل اختراع يونيكود كان هناك ";
+
+// TODO: class BenchmarkPerCodePoint?
+
+class Operation {
+public:
+ Operation() {}
+ virtual ~Operation();
+ virtual double call(int32_t iterations, int32_t pieceLength) = 0;
+
+protected:
+ UTimer startTime;
+};
+
+Operation::~Operation() {}
+
+const int32_t kLengths[] = { 5, 12, 30, 100, 1000, 10000 };
+
+int32_t getMaxLength() { return kLengths[UPRV_LENGTHOF(kLengths) - 1]; }
+
+// Returns seconds per code point.
+double measure(Operation &op, int32_t pieceLength) {
+ // Increase the number of iterations until we use at least one second.
+ int32_t iterations = 1;
+ for (;;) {
+ double seconds = op.call(iterations, pieceLength);
+ if (seconds >= 1) {
+ if (iterations > 1) {
+ return seconds / (iterations * pieceLength);
+ } else {
+ // Run it once more, to avoid measuring only the warm-up.
+ return op.call(1, pieceLength) / (iterations * pieceLength);
+ }
+ }
+ if (seconds < 0.01) {
+ iterations *= 10;
+ } else if (seconds < 0.55) {
+ iterations *= 1.1 / seconds;
+ } else {
+ iterations *= 2;
+ }
+ }
+}
+
+void benchmark(const char *name, Operation &op) {
+ for (int32_t i = 0; i < UPRV_LENGTHOF(kLengths); ++i) {
+ int32_t pieceLength = kLengths[i];
+ double secPerCp = measure(op, pieceLength);
+ printf("%s %6d %12f ns/cp\n", name, (int)pieceLength, secPerCp * 1000000000);
+ }
+ puts("");
+}
+
+class NormalizeUTF16 : public Operation {
+public:
+ NormalizeUTF16(const Normalizer2 &n2, const UnicodeString &text) :
+ norm2(n2), src(text), s(src.getBuffer()) {}
+ virtual ~NormalizeUTF16();
+ virtual double call(int32_t iterations, int32_t pieceLength);
+
+private:
+ const Normalizer2 &norm2;
+ UnicodeString src;
+ const UChar *s;
+ UnicodeString dest;
+};
+
+NormalizeUTF16::~NormalizeUTF16() {}
+
+// Assumes all BMP characters.
+double NormalizeUTF16::call(int32_t iterations, int32_t pieceLength) {
+ int32_t start = 0;
+ int32_t limit = src.length() - pieceLength;
+ UnicodeString piece;
+ UErrorCode errorCode = U_ZERO_ERROR;
+ utimer_getTime(&startTime);
+ for (int32_t i = 0; i < iterations; ++i) {
+ piece.setTo(FALSE, s + start, pieceLength);
+ norm2.normalize(piece, dest, errorCode);
+ start = (start + pieceLength) % limit;
+ }
+ return utimer_getElapsedSeconds(&startTime);
+}
+
+class NormalizeUTF8 : public Operation {
+public:
+ NormalizeUTF8(const Normalizer2 &n2, const UnicodeString &text) : norm2(n2), sink(&dest) {
+ offsets = CommonChars::toUTF8WithOffsets(text, src, numCodePoints);
+ s = src.data();
+ }
+ virtual ~NormalizeUTF8();
+ virtual double call(int32_t iterations, int32_t pieceLength);
+
+private:
+ const Normalizer2 &norm2;
+ std::string src;
+ const char *s;
+ int32_t *offsets;
+ int32_t numCodePoints;
+ std::string dest;
+ icu::StringByteSink<std::string> sink;
+};
+
+NormalizeUTF8::~NormalizeUTF8() {
+ delete[] offsets;
+}
+
+double NormalizeUTF8::call(int32_t iterations, int32_t pieceLength) {
+ int32_t start = 0;
+ int32_t limit = numCodePoints - pieceLength;
+ UErrorCode errorCode = U_ZERO_ERROR;
+ utimer_getTime(&startTime);
+ for (int32_t i = 0; i < iterations; ++i) {
+ int32_t start8 = offsets[start];
+ int32_t limit8 = offsets[start + pieceLength];
+ icu::StringPiece piece(s + start8, limit8 - start8);
+ norm2.normalizeUTF8(0, piece, sink, nullptr, errorCode);
+ start = (start + pieceLength) % limit;
+ }
+ return utimer_getElapsedSeconds(&startTime);
+}
+
+} // namespace
+
+extern int main(int /*argc*/, const char * /*argv*/[]) {
+ // More than the longest piece length so that we read from different parts of the string
+ // for that piece length.
+ int32_t maxLength = getMaxLength() * 10;
+ UErrorCode errorCode = U_ZERO_ERROR;
+ const Normalizer2 *nfc = Normalizer2::getNFCInstance(errorCode);
+ const Normalizer2 *nfkc_cf = Normalizer2::getNFKCCasefoldInstance(errorCode);
+ if (U_FAILURE(errorCode)) {
+ fprintf(stderr,
+ "simplenormperf: failed to get Normalizer2 instances - %s\n",
+ u_errorName(errorCode));
+ }
+ {
+ // Base line: Should remain in the fast loop without trie lookups.
+ NormalizeUTF16 op(*nfc, CommonChars::getLatin1(maxLength));
+ benchmark("NFC/UTF-16/latin1", op);
+ }
+ {
+ // Base line 2: Read UTF-8, trie lookups, but should have nothing to do.
+ NormalizeUTF8 op(*nfc, CommonChars::getJapanese(maxLength));
+ benchmark("NFC/UTF-8/japanese", op);
+ }
+ {
+ NormalizeUTF16 op(*nfkc_cf, CommonChars::getMixed(maxLength));
+ benchmark("NFKC_CF/UTF-16/mixed", op);
+ }
+ {
+ NormalizeUTF16 op(*nfkc_cf, CommonChars::getLowercaseLatin1(maxLength));
+ benchmark("NFKC_CF/UTF-16/lowercaseLatin1", op);
+ }
+ {
+ NormalizeUTF16 op(*nfkc_cf, CommonChars::getJapanese(maxLength));
+ benchmark("NFKC_CF/UTF-16/japanese", op);
+ }
+ {
+ NormalizeUTF8 op(*nfkc_cf, CommonChars::getMixed(maxLength));
+ benchmark("NFKC_CF/UTF-8/mixed", op);
+ }
+ {
+ NormalizeUTF8 op(*nfkc_cf, CommonChars::getLowercaseLatin1(maxLength));
+ benchmark("NFKC_CF/UTF-8/lowercaseLatin1", op);
+ }
+ {
+ NormalizeUTF8 op(*nfkc_cf, CommonChars::getJapanese(maxLength));
+ benchmark("NFKC_CF/UTF-8/japanese", op);
+ }
+ return 0;
+}