352 lines
15 KiB
C++
352 lines
15 KiB
C++
// © 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;
|
||
}
|