Update the qHash function for strings to use the CRC32 instruction

According to my profiling of Qt Creator, qHash and the SHA-1 calculation
are the hottest spots remaining in QtCore. The current qHash function is
not really vectorizable. We could come up with a different algorithm
that is more SIMD-friendly, but since we have the CRC32 instruction that
can read 32- and 64-bit entities, we're set.

This commit also updates the benchmark for QHash and benchmarks both
the hashing function itself and the QHash class. The updated
benchmarks for the CRC32 on my machine shows that the hashing function
is *always* improved, but the hashing isn't always. In particular, the
current algorithm is better for the "numbers" case, for which the data
sample differs in very few bits. The new code is 33% slower for that
particular case.

On average, the improvement (including the "numbers" case) is:

 compared to          qHash only          QHash
Qt 5.0 function          2.54x            1.06x
Qt 4.x function          4.34x            1.34x
Java function            2.71x            1.11x

Test machine: Sandybridge Core i7-2620M @ 2.66 GHz with turbo disabled
for the benchmarks

Change-Id: Ia80b98c0e20d785816f7a7f6ddf40b4b302c7297
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com>
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Thiago Macieira 2013-12-19 23:32:04 -08:00 committed by The Qt Project
parent 506c34fdd2
commit ea8e48a679
4 changed files with 111 additions and 14 deletions

View File

@ -60,6 +60,7 @@
#include <qbytearray.h>
#include <qdatetime.h>
#include <qbasicatomic.h>
#include <private/qsimd_p.h>
#ifndef QT_BOOTSTRAPPED
#include <qcoreapplication.h>
@ -93,10 +94,69 @@ QT_BEGIN_NAMESPACE
(for instance, gcc 4.4 does that even at -O0).
*/
#ifdef __SSE4_2__
static inline bool hasFastCrc32()
{
return true;
}
template <typename Char>
static uint crc32(const Char *ptr, size_t len, uint h)
{
// The CRC32 instructions from Nehalem calculate a 32-bit CRC32 checksum
const uchar *p = reinterpret_cast<const uchar *>(ptr);
const uchar *const e = p + (len * sizeof(Char));
# ifdef Q_PROCESSOR_X86_64
// The 64-bit instruction still calculates only 32-bit, but without this
// variable GCC 4.9 still tries to clear the high bits on every loop
qulonglong h2 = h;
p += 8;
for ( ; p <= e; p += 8)
h2 = _mm_crc32_u64(h2, *reinterpret_cast<const qlonglong *>(p - 8));
h = h2;
p -= 8;
len = e - p;
if (len & 4) {
h = _mm_crc32_u32(h, *reinterpret_cast<const uint *>(p));
p += 4;
}
# else
p += 4;
for ( ; p <= e; p += 4)
h = _mm_crc32_u32(h, *reinterpret_cast<const uint *>(p));
p -= 4;
len = e - p;
# endif
if (len & 2) {
h = _mm_crc32_u16(h, *reinterpret_cast<const ushort *>(p));
p += 2;
}
if (sizeof(Char) == 1 && len & 1)
h = _mm_crc32_u8(h, *p);
return h;
}
#else
static inline bool hasFastCrc32()
{
return false;
}
static uint crc32(...)
{
Q_UNREACHABLE();
return 0;
}
#endif
static inline uint hash(const uchar *p, int len, uint seed) Q_DECL_NOTHROW
{
uint h = seed;
if (hasFastCrc32())
return crc32(p, size_t(len), h);
for (int i = 0; i < len; ++i)
h = 31 * h + p[i];
@ -107,6 +167,9 @@ static inline uint hash(const QChar *p, int len, uint seed) Q_DECL_NOTHROW
{
uint h = seed;
if (hasFastCrc32())
return crc32(p, size_t(len), h);
for (int i = 0; i < len; ++i)
h = 31 * h + p[i].unicode();

View File

@ -55,13 +55,28 @@ class tst_QHash : public QObject
private slots:
void initTestCase();
void qhash_current_data() { data(); }
void qhash_current() { qhash_template<QString>(); }
void qhash_qt50_data() { data(); }
void qhash_qt50() { qhash_template<Qt50String>(); }
void qhash_qt4_data() { data(); }
void qhash_qt4();
void javaString_data() { data(); }
void javaString();
void qhash_qt4() { qhash_template<Qt4String>(); }
void qhash_javaString_data() { data(); }
void qhash_javaString() { qhash_template<JavaString>(); }
void hashing_current_data() { data(); }
void hashing_current() { hashing_template<QString>(); }
void hashing_qt50_data() { data(); }
void hashing_qt50() { hashing_template<Qt50String>(); }
void hashing_qt4_data() { data(); }
void hashing_qt4() { hashing_template<Qt4String>(); }
void hashing_javaString_data() { data(); }
void hashing_javaString() { hashing_template<JavaString>(); }
private:
void data();
template <typename String> void qhash_template();
template <typename String> void hashing_template();
QStringList smallFilePaths;
QStringList uuids;
@ -76,7 +91,7 @@ private:
void tst_QHash::initTestCase()
{
// small list of file paths
QFile smallPathsData("paths_small_data.txt");
QFile smallPathsData(QFINDTESTDATA("paths_small_data.txt"));
QVERIFY(smallPathsData.open(QIODevice::ReadOnly));
smallFilePaths = QString::fromLatin1(smallPathsData.readAll()).split(QLatin1Char('\n'));
QVERIFY(!smallFilePaths.isEmpty());
@ -133,12 +148,12 @@ void tst_QHash::data()
QTest::newRow("numbers") << numbers;
}
void tst_QHash::qhash_qt4()
template <typename String> void tst_QHash::qhash_template()
{
QFETCH(QStringList, items);
QHash<Qt4String, int> hash;
QHash<String, int> hash;
QList<Qt4String> realitems;
QList<String> realitems;
foreach (const QString &s, items)
realitems.append(s);
@ -149,23 +164,22 @@ void tst_QHash::qhash_qt4()
}
}
void tst_QHash::javaString()
template <typename String> void tst_QHash::hashing_template()
{
// just the hashing function
QFETCH(QStringList, items);
QHash<JavaString, int> hash;
QList<JavaString> realitems;
QVector<String> realitems;
realitems.reserve(items.size());
foreach (const QString &s, items)
realitems.append(s);
QBENCHMARK {
for (int i = 0, n = realitems.size(); i != n; ++i) {
hash[realitems.at(i)] = i;
}
for (int i = 0, n = realitems.size(); i != n; ++i)
(void)qHash(realitems.at(i));
}
}
QTEST_MAIN(tst_QHash)
#include "main.moc"

View File

@ -51,6 +51,16 @@ QT_BEGIN_NAMESPACE
uint qHash(const Qt4String &);
QT_END_NAMESPACE
struct Qt50String : QString
{
Qt50String() {}
Qt50String(const QString &s) : QString(s) {}
};
QT_BEGIN_NAMESPACE
uint qHash(const Qt50String &, uint seed = 0);
QT_END_NAMESPACE
struct JavaString : QString
{

View File

@ -57,6 +57,16 @@ uint qHash(const Qt4String &str)
return h;
}
uint qHash(const Qt50String &key, uint seed)
{
const QChar *p = key.unicode();
int len = key.size();
uint h = seed;
for (int i = 0; i < len; ++i)
h = 31 * h + p[i].unicode();
return h;
}
// The Java's hashing algorithm for strings is a variation of D. J. Bernstein
// hashing algorithm appeared here http://cr.yp.to/cdb/cdb.txt
// and informally known as DJB33XX - DJB's 33 Times Xor.