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:
getMixed(int32_t minLength)27 static UnicodeString getMixed(int32_t minLength) {
28 return extend(UnicodeString(latin1).append(japanese).append(arabic), minLength);
29 }
getLatin1(int32_t minLength)30 static UnicodeString getLatin1(int32_t minLength) { return extend(latin1, minLength); }
getLowercaseLatin1(int32_t minLength)31 static UnicodeString getLowercaseLatin1(int32_t minLength) { return extend(lowercaseLatin1, minLength); }
getASCII(int32_t minLength)32 static UnicodeString getASCII(int32_t minLength) { return extend(ascii, minLength); }
getJapanese(int32_t 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.
toUTF8WithOffsets(const UnicodeString & s16,std::string & s8,int32_t & numCodePoints)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:
extend(const UnicodeString & s,int32_t minLength)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 // Goethe’s Bergschloß in normal sentence case.
73 u"Da droben auf jenem Berge, da steht ein altes Schloß, "
74 u"wo hinter Toren und Türen sonst lauerten Ritter und Roß.\n"
75 u"Verbrannt sind Türen und Tore, und überall ist es so still; "
76 u"das alte verfallne Gemäuer durchklettr ich, wie ich nur will.\n"
77 u"Hierneben lag ein Keller, so voll von köstlichem Wein; "
78 u"nun steiget nicht mehr mit Krügen die Kellnerin heiter hinein.\n"
79 u"Sie setzt den Gästen im Saale nicht mehr die Becher umher, "
80 u"sie füllt zum Heiligen Mahle dem Pfaffen das Fläschchen nicht mehr.\n"
81 u"Sie reicht dem lüsternen Knappen nicht mehr auf dem Gange den Trank, "
82 u"und nimmt für flüchtige Gabe nicht mehr den flüchtigen 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 Trümmer verwandt.\n"
85 u"Doch als mit Zither und Flasche nach diesen felsigen Höhn "
86 u"ich an dem heitersten Tage mein Liebchen steigen gesehn,\n"
87 u"da drängte sich frohes Behagen hervor aus verödeter Ruh, "
88 u"da gings wie in alten Tagen recht feierlich wieder zu.\n"
89 u"Als wären für stattliche Gäste die weitesten Räume bereit, "
90 u"als käm ein Pärchen gegangen aus jener tüchtigen Zeit.\n"
91 u"Als stünd in seiner Kapelle der würdige Pfaffe schon da "
92 u"und fragte: Wollt ihr einander? Wir aber lächelten: Ja!\n"
93 u"Und tief bewegten Gesänge 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 glühende Sonne zum schroffen Gipfel empor.\n"
97 u"Und Knapp und Kellnerin glänzen 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 // Goethe’s Bergschloß in all lowercase
102 u"da droben auf jenem berge, da steht ein altes schloß, "
103 u"wo hinter toren und türen sonst lauerten ritter und roß.\n"
104 u"verbrannt sind türen und tore, und überall ist es so still; "
105 u"das alte verfallne gemäuer durchklettr ich, wie ich nur will.\n"
106 u"hierneben lag ein keller, so voll von köstlichem wein; "
107 u"nun steiget nicht mehr mit krügen die kellnerin heiter hinein.\n"
108 u"sie setzt den gästen im saale nicht mehr die becher umher, "
109 u"sie füllt zum heiligen mahle dem pfaffen das fläschchen nicht mehr.\n"
110 u"sie reicht dem lüsternen knappen nicht mehr auf dem gange den trank, "
111 u"und nimmt für flüchtige gabe nicht mehr den flüchtigen 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 trümmer verwandt.\n"
114 u"doch als mit zither und flasche nach diesen felsigen höhn "
115 u"ich an dem heitersten tage mein liebchen steigen gesehn,\n"
116 u"da drängte sich frohes behagen hervor aus verödeter ruh, "
117 u"da gings wie in alten tagen recht feierlich wieder zu.\n"
118 u"als wären für stattliche gäste die weitesten räume bereit, "
119 u"als käm ein pärchen gegangen aus jener tüchtigen zeit.\n"
120 u"als stünd in seiner kapelle der würdige pfaffe schon da "
121 u"und fragte: wollt ihr einander? wir aber lächelten: ja!\n"
122 u"und tief bewegten gesänge 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 glühende sonne zum schroffen gipfel empor.\n"
126 u"und knapp und kellnerin glänzen 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 // Goethe’s 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:
Operation()187 Operation() {}
188 virtual ~Operation();
189 virtual double call(int32_t iterations, int32_t pieceLength) = 0;
190
191 protected:
192 UTimer startTime;
193 };
194
~Operation()195 Operation::~Operation() {}
196
197 const int32_t kLengths[] = { 5, 12, 30, 100, 1000, 10000 };
198
getMaxLength()199 int32_t getMaxLength() { return kLengths[UPRV_LENGTHOF(kLengths) - 1]; }
200
201 // Returns seconds per code point.
measure(Operation & op,int32_t pieceLength)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
benchmark(const char * name,Operation & op)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:
NormalizeUTF16(const Normalizer2 & n2,const UnicodeString & text)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
~NormalizeUTF16()248 NormalizeUTF16::~NormalizeUTF16() {}
249
250 // Assumes all BMP characters.
call(int32_t iterations,int32_t pieceLength)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:
NormalizeUTF8(const Normalizer2 & n2,const UnicodeString & text)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
~NormalizeUTF8()284 NormalizeUTF8::~NormalizeUTF8() {
285 delete[] offsets;
286 }
287
call(int32_t iterations,int32_t pieceLength)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
main(int,const char * [])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