Skip to content

Commit

Permalink
Performance|libcore|libgui: Various optimizations
Browse files Browse the repository at this point in the history
After profiling engine startup, the following changes were made:

- Avoid calls to Time() when initializing objects (checking the
  current full date & time is slow) instead preferred the high-
  performance timer.
- PathTree only does wildcard pattern tests if wildcards as actually
  present in the path segments.
- Block uses memcpy() to read larger sections of the buffer.
- AtlasTexture does not make a separate copy of the subregion to
  upload to a texture, instead reading the data directly from the
  backing store image.
  • Loading branch information
skyjake committed Jun 5, 2016
1 parent 4fea419 commit ca64e85
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 94 deletions.
24 changes: 7 additions & 17 deletions doomsday/sdk/libcore/include/de/data/archive.h
Expand Up @@ -14,7 +14,7 @@
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
* http://www.gnu.org/licenses</small>
*/

#ifndef LIBDENG2_ARCHIVE_H
Expand Down Expand Up @@ -248,7 +248,7 @@ class DENG2_PUBLIC Archive : public IWritable
* Interface for derived classes:
*/
/// Base class for archive entries.
struct Entry : public PathTree::Node
struct DENG2_PUBLIC Entry : public PathTree::Node
{
dsize offset; ///< Offset from the start of the source array.
dsize size; ///< Deserialized size.
Expand All @@ -262,21 +262,11 @@ class DENG2_PUBLIC Archive : public IWritable
/// Cached copy of the serialized data. Can be @c NULL. Entry has ownership.
Block mutable *dataInArchive;

Entry(PathTree::NodeArgs const &args) : Node(args),
offset(0),
size(0),
sizeInArchive(0),
maybeChanged(false),
data(0),
dataInArchive(0)
{}

virtual ~Entry()
{
// Entry has ownership of the cached data.
delete data;
delete dataInArchive;
}
Entry(PathTree::NodeArgs const &args);
virtual ~Entry();

// Must be constructed with args.
Entry() = delete;
};

/**
Expand Down
9 changes: 8 additions & 1 deletion doomsday/sdk/libcore/include/de/data/path.h
Expand Up @@ -106,6 +106,8 @@ class DENG2_PUBLIC Path : public ISerializable, public LogEntry::Arg::Base
*/
hash_type hash() const;

bool hasWildCard() const;

/**
* Case insensitive equality test.
*
Expand Down Expand Up @@ -136,8 +138,11 @@ class DENG2_PUBLIC Path : public ISerializable, public LogEntry::Arg::Base
friend class Path;
friend struct Path::Instance;

enum Flag { GotHashKey = 0x1, WildCardChecked = 0x2, IncludesWildCard = 0x4 };
Q_DECLARE_FLAGS(Flags, Flag)

private:
mutable bool gotHashKey;
mutable Flags flags;
mutable hash_type hashKey;
QStringRef range;
};
Expand Down Expand Up @@ -457,6 +462,8 @@ class DENG2_PUBLIC Path : public ISerializable, public LogEntry::Arg::Base
Instance *d;
};

Q_DECLARE_OPERATORS_FOR_FLAGS(Path::Segment::Flags)

/**
* Utility class for specifying paths that use a dot (.) as the path separator.
* @ingroup data
Expand Down
2 changes: 1 addition & 1 deletion doomsday/sdk/libcore/include/de/data/pathtree.h
Expand Up @@ -14,7 +14,7 @@
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
* http://www.gnu.org/licenses</small>
*/

#ifndef LIBDENG2_PATHTREE_H
Expand Down
4 changes: 2 additions & 2 deletions doomsday/sdk/libcore/include/de/filesys/file.h
Expand Up @@ -111,10 +111,10 @@ class DENG2_PUBLIC File : public filesys::Node, public IIOStream, public IObject
};

public:
Status(dsize s = 0, Time const &modTime = Time())
Status(dsize s = 0, Time const &modTime = Time::invalidTime())
: size(s), modifiedAt(modTime), _type(FILE) {}

Status(Type t, dsize s = 0, Time const &modTime = Time())
Status(Type t, dsize s = 0, Time const &modTime = Time::invalidTime())
: size(s), modifiedAt(modTime), _type(t) {}

Type type() const { return _type; }
Expand Down
6 changes: 3 additions & 3 deletions doomsday/sdk/libcore/src/core/log.cpp
Expand Up @@ -588,7 +588,7 @@ Log::Section::~Section()

DENG2_PIMPL_NOREF(Log)
{
typedef QList<char const *> SectionStack;
typedef QVector<char const *> SectionStack;
SectionStack sectionStack;
LogEntry *throwawayEntry;
duint32 currentEntryMedata; ///< Applies to the current entry being staged in the thread.
Expand Down Expand Up @@ -630,13 +630,13 @@ bool Log::isStaging() const

void Log::beginSection(char const *name)
{
d->sectionStack.append(name);
d->sectionStack.push_back(name);
}

void Log::endSection(char const *DENG2_DEBUG_ONLY(name))
{
DENG2_ASSERT(d->sectionStack.back() == name);
d->sectionStack.takeLast();
d->sectionStack.pop_back();
}

void Log::beginInteractive()
Expand Down
22 changes: 20 additions & 2 deletions doomsday/sdk/libcore/src/data/archive.cpp
Expand Up @@ -192,7 +192,7 @@ Block &Archive::entryBlock(Path const &path)
// Mark for recompression.
Entry &entry = static_cast<Entry &>(d->index->find(path, PathTree::MatchFull | PathTree::NoBranch));
entry.maybeChanged = true;
entry.modifiedAt = Time();
entry.modifiedAt = Time::currentHighPerformanceTime();

d->modified = true;

Expand All @@ -215,7 +215,7 @@ void Archive::add(Path const &path, IByteArray const &data)

Entry &entry = static_cast<Entry &>(d->index->insert(path));
entry.data = new Block(data);
entry.modifiedAt = Time();
entry.modifiedAt = Time::currentHighPerformanceTime();
entry.maybeChanged = true;

// The rest of the data gets updated when the archive is written.
Expand Down Expand Up @@ -269,4 +269,22 @@ PathTree const &Archive::index() const
return *d->index;
}

Archive::Entry::Entry(PathTree::NodeArgs const &args)
: Node(args)
, offset(0)
, size(0)
, sizeInArchive(0)
, modifiedAt(Time::invalidTime())
, maybeChanged(false)
, data(0)
, dataInArchive(0)
{}

Archive::Entry::~Entry()
{
// Entry has ownership of the cached data.
delete data;
delete dataInArchive;
}

} // namespace de
9 changes: 4 additions & 5 deletions doomsday/sdk/libcore/src/data/block.cpp
Expand Up @@ -20,6 +20,8 @@
#include "de/Block"
#include "de/File"

#include <cstring>

using namespace de;

Block::Block(Size initialSize)
Expand Down Expand Up @@ -79,10 +81,7 @@ void Block::get(Offset atPos, Byte *values, Size count) const
String("(%1[+%2] > %3)").arg(atPos).arg(count).arg(size()));
}

for (Offset i = atPos; count > 0; ++i, --count)
{
*values++ = Byte(at(i));
}
std::memcpy(values, constData() + atPos, count);
}

void Block::set(Offset at, Byte const *values, Size count)
Expand All @@ -92,7 +91,7 @@ void Block::set(Offset at, Byte const *values, Size count)
/// @throw OffsetError The accessed region of the block was out of range.
throw OffsetError("Block::set", "Out of range");
}
replace(at, count, QByteArray((char const *) values, count));
replace(at, count, QByteArray(reinterpret_cast<char const *>(values), count));
}

void Block::copyFrom(IByteArray const &array, Offset at, Size count)
Expand Down
16 changes: 14 additions & 2 deletions doomsday/sdk/libcore/src/data/path.cpp
Expand Up @@ -36,7 +36,7 @@ Path::hash_type const Path::hash_range = 512;
ushort Path::Segment::hash() const
{
// Is it time to compute the hash?
if (!gotHashKey)
if (!(flags & GotHashKey))
{
hashKey = 0;
int op = 0;
Expand All @@ -51,11 +51,23 @@ ushort Path::Segment::hash() const
}
}
hashKey %= hash_range;
gotHashKey = true;
flags |= GotHashKey;
}
return hashKey;
}

bool Path::Segment::hasWildCard() const
{
if (flags & WildCardChecked)
{
return flags.testFlag(IncludesWildCard);
}
bool isWild = toStringRef().contains(QChar('*'));
applyFlagOperation(flags, IncludesWildCard, isWild? SetFlags : UnsetFlags);
flags |= WildCardChecked;
return isWild;
}

bool Path::Segment::operator == (Path::Segment const &other) const
{
return !range.compare(other.range, Qt::CaseInsensitive);
Expand Down
99 changes: 54 additions & 45 deletions doomsday/sdk/libcore/src/data/pathtreenode.cpp
Expand Up @@ -50,6 +50,11 @@ DENG2_PIMPL_NOREF(PathTree::Node)
{
delete children;
}

void cacheSegmentText()
{
segmentText = &tree.segmentName(segmentId);
}
};

PathTree::Node::Node(PathTree::NodeArgs const &args) : d(nullptr)
Expand Down Expand Up @@ -127,7 +132,7 @@ String const &PathTree::Node::name() const
// Cache the string, because PathTree::segmentName() locks the tree and that has
// performance implications. The segment text string will not change while the
// node exists.
d->segmentText = &tree().segmentName(d->segmentId);
d->cacheSegmentText();
}
return *d->segmentText;
}
Expand All @@ -143,91 +148,95 @@ static int matchName(QChar const *string, dsize stringSize,
{
QChar const *in = string;
QChar const *inEnd = string + stringSize;
QChar const *st = pattern;
QChar const *pat = pattern;

while (in < inEnd)
{
if (*st == QChar('*'))
if (*pat == QChar('*'))
{
st++;
pat++;
continue;
}

if (*st != QChar('?') && (st->toLower() != in->toLower()))
if (/**st != QChar('?') && */ pat->toLower() != in->toLower())
{
// A mismatch. Hmm. Go back to a previous '*'.
while (st >= pattern && *st != QChar('*')) { st--; }
while (pat >= pattern && *pat != QChar('*')) { --pat; }

// No match?
if (st < pattern) return false;
if (pat < pattern) return false;

// The asterisk lets us continue.
}

// This character of the pattern is OK.
st++;
pat++;
in++;
}

// Skip remaining asterisks.
while (*st == QChar('*')) { st++; }
while (*pat == QChar('*')) { pat ++; }

// Match is good if the end of the pattern was reached.
return st == (pattern + patternSize);
return pat == (pattern + patternSize);
}

int PathTree::Node::comparePath(de::Path const &searchPattern, ComparisonFlags flags) const
{
if (((flags & PathTree::NoLeaf) && isLeaf()) ||
((flags & PathTree::NoBranch) && isBranch()))
((flags & PathTree::NoBranch) && isBranch()))
{
return 1;
}

try
{
de::Path::Segment const *snode = &searchPattern.lastSegment();
de::Path::Segment const *snode = &searchPattern.lastSegment();

// In reverse order, compare each path node in the search term.
int pathNodeCount = searchPattern.segmentCount();
// In reverse order, compare each path node in the search term.
int pathNodeCount = searchPattern.segmentCount();

PathTree::Node const *node = this;
for (int i = 0; i < pathNodeCount; ++i)
PathTree::Node const *node = this;
for (int i = 0; i < pathNodeCount; ++i)
{
// If the hashes don't match it can't possibly be this.
if (snode->hash() != node->hash())
{
bool const snameIsWild = !snode->toStringRef().compare(QStringLiteral("*"));
if (!snameIsWild)
{
// If the hashes don't match it can't possibly be this.
if (snode->hash() != node->hash())
{
return 1;
}

// Compare the names.
if (!matchName(node->name().constData(), node->name().size(),
snode->toStringRef().constData(), snode->toStringRef().size()))
{
return 1;
}
}
return 1;
}

// Have we arrived at the search target?
if (i == pathNodeCount - 1)
if (!snode->hasWildCard())
{
if (node->name().compare(snode->toStringRef(), Qt::CaseInsensitive))
{
return !(!(flags & MatchFull) || node->isAtRootLevel());
return 1;
}

// Is the hierarchy too shallow?
if (node->isAtRootLevel())
}
else
{
// Compare the names using wildcard pattern matching.
// Note: This has relatively slow performance.
if (!matchName(node->name().constData(), node->name().size(),
snode->toStringRef().constData(), snode->toStringRef().size()))
{
return 1;
}
}

// So far so good. Move one level up the hierarchy.
node = &node->parent();
snode = &searchPattern.reverseSegment(i + 1);
// Have we arrived at the search target?
if (i == pathNodeCount - 1)
{
return !(!(flags & MatchFull) || node->isAtRootLevel());
}

// Is the hierarchy too shallow?
if (node->isAtRootLevel())
{
return 1;
}

// So far so good. Move one level up the hierarchy.
node = &node->parent();
snode = &searchPattern.reverseSegment(i + 1);
}
catch (de::Path::OutOfBoundsError const &)
{} // Ignore this error.

return 1;
}
Expand Down
2 changes: 1 addition & 1 deletion doomsday/sdk/libcore/src/filesys/nativefile.cpp
Expand Up @@ -203,7 +203,7 @@ void NativeFile::get(Offset at, Byte *values, Size count) const
throw OffsetError("NativeFile::get", description() + ": cannot read past end of file " +
String("(%1[+%2] > %3)").arg(at).arg(count).arg(size()));
}
in.seek(at);
if (in.pos() != qint64(at)) in.seek(qint64(at));
in.read(reinterpret_cast<char *>(values), count);
}

Expand Down

0 comments on commit ca64e85

Please sign in to comment.