Cut away 2/3 of the startup time on Linux

Optimize the parser of the X11 compose tables. Parsing these was
responsible for over 90% of the startup time in Qt 5.1.

Change-Id: Ifddc3f30828791e51a755f92791c26ffe43a9cd3
Reviewed-by: Konstantin Ritt <ritt.ks@gmail.com>
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
This commit is contained in:
Lars Knoll 2013-07-01 15:26:01 +02:00 committed by The Qt Project
parent 99e1608c52
commit 12905fa30f
4 changed files with 137 additions and 86 deletions

View File

@ -41,7 +41,6 @@
#include "qtablegenerator.h"
#include <QtCore/QRegularExpression>
#include <QtCore/QByteArray>
#include <QtCore/QTextCodec>
#include <QtCore/QDebug>
@ -54,7 +53,8 @@
#include <xkbcommon_workaround.h>
#endif
//#define DEBUG_GENERATOR
#include <string.h> // strchr, strncmp, etc.
#include <strings.h> // strncasecmp
TableGenerator::TableGenerator() : m_state(NoErrors),
m_systemComposeDir(QString())
@ -111,11 +111,9 @@ void TableGenerator::findComposeFile()
// check for the system provided compose files
if (!found && cleanState()) {
readLocaleMappings();
QString table = readLocaleMappings(locale().toUpper().toUtf8());
if (cleanState()) {
QString table = m_localeToTable.value(locale().toUpper());
if (table.isEmpty())
// no table mappings for the system's locale in the compose.dir
m_state = UnsupportedLocale;
@ -174,28 +172,49 @@ QString TableGenerator::locale() const
return QLatin1String(name);
}
void TableGenerator::readLocaleMappings()
QString TableGenerator::readLocaleMappings(const QByteArray &locale)
{
QFile mappings(systemComposeDir() + QLatin1String("/compose.dir"));
if (mappings.exists()) {
mappings.open(QIODevice::ReadOnly);
QTextStream in(&mappings);
QString file;
if (mappings.open(QIODevice::ReadOnly)) {
const int localeNameLength = locale.size();
const char * const localeData = locale.constData();
char l[1024];
// formating of compose.dir has some inconsistencies
while (!in.atEnd()) {
QString line = in.readLine();
if (!line.startsWith("#") && line.size() != 0 &&
line.at(0).isLower()) {
while (!mappings.atEnd()) {
int read = mappings.readLine(l, sizeof(l));
if (read <= 0)
break;
QStringList pair = line.split(QRegExp(QLatin1String("\\s+")));
QString table = pair.at(0);
if (table.endsWith(QLatin1String(":")))
table.remove(table.size() - 1, 1);
char *line = l;
if (*line >= 'a' && *line <= 'z') {
// file name
while (*line && *line != ':' && *line != ' ' && *line != '\t')
++line;
if (!*line)
continue;
const char * const composeFileNameEnd = line;
*line = '\0';
++line;
m_localeToTable.insert(pair.at(1).toUpper(), table);
// locale name
while (*line && (*line == ' ' || *line == '\t'))
++line;
const char * const lc = line;
while (*line && *line != ' ' && *line != '\t' && *line != '\n')
++line;
*line = '\0';
if (localeNameLength == (line - lc) && !strncasecmp(lc, localeData, line - lc)) {
file = QString::fromUtf8(l, composeFileNameEnd - l);
break;
}
}
}
mappings.close();
}
return file;
}
bool TableGenerator::processFile(QString composeFileName)
@ -215,7 +234,7 @@ TableGenerator::~TableGenerator()
{
}
QList<QComposeTableElement> TableGenerator::composeTable() const
QVector<QComposeTableElement> TableGenerator::composeTable() const
{
return m_composeTable;
}
@ -225,15 +244,14 @@ void TableGenerator::parseComposeFile(QFile *composeFile)
#ifdef DEBUG_GENERATOR
qDebug() << "TableGenerator::parseComposeFile: " << composeFile->fileName();
#endif
QTextStream in(composeFile);
while (!in.atEnd()) {
QString line = in.readLine();
if (line.startsWith(QLatin1String("<"))) {
char line[1024];
while (!composeFile->atEnd()) {
composeFile->readLine(line, sizeof(line));
if (*line == '<')
parseKeySequence(line);
} else if (line.startsWith(QLatin1String("include"))) {
parseIncludeInstruction(line);
}
else if (!strncmp(line, "include", 7))
parseIncludeInstruction(QString::fromUtf8(line));
}
composeFile->close();
@ -290,79 +308,103 @@ ushort TableGenerator::keysymToUtf8(quint32 sym)
return QString::fromUtf8(chars).at(0).unicode();
}
quint32 TableGenerator::stringToKeysym(QString keysymName)
static inline int fromBase8(const char *s, const char *end)
{
quint32 keysym;
QByteArray keysymArray = keysymName.toLatin1();
const char *name = keysymArray.constData();
if ((keysym = xkb_keysym_from_name(name, (xkb_keysym_flags)0)) == XKB_KEY_NoSymbol)
qWarning() << QString("Qt Warning - invalid keysym: %1").arg(keysymName);
return keysym;
int result = 0;
while (*s && s != end) {
if (*s <= '0' && *s >= '7')
return 0;
result *= 8;
result += *s - '0';
++s;
}
return result;
}
void TableGenerator::parseKeySequence(QString line)
static inline int fromBase16(const char *s, const char *end)
{
int result = 0;
while (*s && s != end) {
result *= 16;
if (*s >= '0' && *s <= '9')
result += *s - '0';
else if (*s >= 'a' && *s <= 'f')
result += *s - 'a' + 10;
else if (*s >= 'A' && *s <= 'F')
result += *s - 'A' + 10;
else
return 0;
++s;
}
return result;
}
void TableGenerator::parseKeySequence(char *line)
{
// we are interested in the lines with the following format:
// <Multi_key> <numbersign> <S> : "♬" U266c # BEAMED SIXTEENTH NOTE
int keysEnd = line.indexOf(QLatin1String(":"));
QString keys = line.left(keysEnd).trimmed();
// find the key sequence
QString regexp = QStringLiteral("<[^>]+>");
QRegularExpression reg(regexp);
QRegularExpressionMatchIterator i = reg.globalMatch(keys);
QStringList keyList;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString word = match.captured(0);
keyList << word;
}
char *keysEnd = strchr(line, ':');
if (!keysEnd)
return;
QComposeTableElement elem;
QString quote = QStringLiteral("\"");
// find the composed value - strings may be direct text encoded in the locale
// for which the compose file is to be used, or an escaped octal or hexadecimal
// character code. Octal codes are specified as "\123" and hexadecimal codes as "\0x123a".
int composeValueIndex = line.indexOf(quote, keysEnd) + 1;
const QChar valueType(line.at(composeValueIndex));
char *composeValue = strchr(keysEnd, '"');
if (!composeValue)
return;
++composeValue;
if (valueType == '\\' && line.at(composeValueIndex + 1).isDigit()) {
char *composeValueEnd = strchr(composeValue, '"');
if (!composeValueEnd)
return;
if (*composeValue == '\\' && composeValue[1] >= '0' && composeValue[1] <= '9') {
// handle octal and hex code values
QChar detectBase(line.at(composeValueIndex + 2));
QString codeValue = line.mid(composeValueIndex + 1, line.lastIndexOf(quote) - composeValueIndex - 1);
char detectBase = composeValue[2];
if (detectBase == 'x') {
// hexadecimal character code
elem.value = keysymToUtf8(codeValue.toUInt(0, 16));
elem.value = keysymToUtf8(fromBase16(composeValue + 3, composeValueEnd));
} else {
// octal character code
QString hexStr = QString::number(codeValue.toUInt(0, 8), 16);
elem.value = keysymToUtf8(hexStr.toUInt(0, 16));
elem.value = keysymToUtf8(fromBase8(composeValue + 1, composeValueEnd));
}
} else {
// handle direct text encoded in the locale
elem.value = valueType.unicode();
if (*composeValue == '\\')
++composeValue;
elem.value = QString::fromUtf8(composeValue).at(0).unicode();
++composeValue;
}
#ifdef DEBUG_GENERATOR
// find the comment
int commnetIndex = line.lastIndexOf(quote) + 1;
elem.comment = line.mid(commnetIndex).trimmed();
elem.comment = QString::fromUtf8(composeValueEnd + 1).trimmed();
#endif
// find the key sequence and convert to X11 keysym
char *k = line;
const char *kend = keysEnd;
// Convert to X11 keysym
int count = keyList.length();
for (int i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) {
if (i < count) {
QString keysym = keyList.at(i);
keysym.remove(keysym.length() - 1, 1);
keysym.remove(0, 1);
if (keysym == QLatin1String("dead_inverted_breve"))
keysym = QStringLiteral("dead_invertedbreve");
else if (keysym == QLatin1String("dead_double_grave"))
keysym = QStringLiteral("dead_doublegrave");
elem.keys[i] = stringToKeysym(keysym);
// find the next pair of angle brackets and get the contents within
while (k < kend && *k != '<')
++k;
char *sym = ++k;
while (k < kend && *k != '>')
++k;
*k = '\0';
if (k < kend) {
elem.keys[i] = xkb_keysym_from_name(sym, (xkb_keysym_flags)0);
if (elem.keys[i] == XKB_KEY_NoSymbol) {
if (!strcmp(sym, "dead_inverted_breve"))
elem.keys[i] = XKB_KEY_dead_invertedbreve;
else if (!strcmp(sym, "dead_double_grave"))
elem.keys[i] = XKB_KEY_dead_doublegrave;
else
qWarning() << QString("Qt Warning - invalid keysym: %1").arg(sym);
}
} else {
elem.keys[i] = 0;
}
@ -372,6 +414,7 @@ void TableGenerator::parseKeySequence(QString line)
void TableGenerator::printComposeTable() const
{
#ifdef DEBUG_GENERATOR
if (composeTable().isEmpty())
return;
@ -393,8 +436,8 @@ void TableGenerator::printComposeTable() const
.arg(comma)
.arg(elem.comment));
}
qDebug() << "output: \n" << output;
#endif
}
void TableGenerator::orderComposeTable()

View File

@ -42,19 +42,29 @@
#ifndef QTABLEGENERATOR_H
#define QTABLEGENERATOR_H
#include <QtCore/QList>
#include <QtCore/QVector>
#include <QtCore/QFile>
#include <QtCore/QMap>
#include <QtCore/QString>
#define QT_KEYSEQUENCE_MAX_LEN 6
//#define DEBUG_GENERATOR
struct QComposeTableElement {
uint keys[QT_KEYSEQUENCE_MAX_LEN];
uint value;
#ifdef DEBUG_GENERATOR
QString comment;
#endif
};
#ifndef DEBUG_GENERATOR
QT_BEGIN_NAMESPACE
Q_DECLARE_TYPEINFO(QComposeTableElement, Q_PRIMITIVE_TYPE);
QT_END_NAMESPACE
#endif
class Compare
{
public:
@ -97,12 +107,12 @@ public:
void printComposeTable() const;
void orderComposeTable();
QList<QComposeTableElement> composeTable() const;
QVector<QComposeTableElement> composeTable() const;
TableState tableState() const { return m_state; }
protected:
bool processFile(QString composeFileName);
void parseKeySequence(QString line);
void parseKeySequence(char *line);
void parseIncludeInstruction(QString line);
void findComposeFile();
@ -110,16 +120,14 @@ protected:
QString systemComposeDir();
ushort keysymToUtf8(quint32 sym);
quint32 stringToKeysym(QString keysymName);
void readLocaleMappings();
QString readLocaleMappings(const QByteArray &locale);
void initPossibleLocations();
bool cleanState() const { return ((m_state & NoErrors) == NoErrors); }
QString locale() const;
private:
QList<QComposeTableElement> m_composeTable;
QMap<QString, QString> m_localeToTable;
QVector<QComposeTableElement> m_composeTable;
TableState m_state;
QString m_systemComposeDir;
QList<QString> m_possibleLocations;

View File

@ -169,11 +169,11 @@ static bool isDuplicate(const QComposeTableElement &lhs, const QComposeTableElem
bool QComposeInputContext::checkComposeTable()
{
QList<QComposeTableElement>::iterator it =
qLowerBound(m_composeTable.begin(), m_composeTable.end(), m_composeBuffer, Compare());
QVector<QComposeTableElement>::const_iterator it =
qLowerBound(m_composeTable.constBegin(), m_composeTable.constEnd(), m_composeBuffer, Compare());
// prevent dereferencing an 'end' iterator, which would result in a crash
if (it == m_composeTable.end())
if (it == m_composeTable.constEnd())
it -= 1;
QComposeTableElement elem = *it;

View File

@ -75,7 +75,7 @@ protected:
private:
QObject *m_focusObject;
QList<QComposeTableElement> m_composeTable;
QVector<QComposeTableElement> m_composeTable;
uint m_composeBuffer[QT_KEYSEQUENCE_MAX_LEN + 1];
TableGenerator::TableState m_tableState;
};