diff --git a/src/plugins/coreplugin/helpmanager.cpp b/src/plugins/coreplugin/helpmanager.cpp
index 997c1f971054dc76ba8ada6d3391b8ccb2b9aaae..019f8f79f11e92589c95cbf3470cc48de1eefece 100644
--- a/src/plugins/coreplugin/helpmanager.cpp
+++ b/src/plugins/coreplugin/helpmanager.cpp
@@ -111,7 +111,7 @@ HelpManager::~HelpManager()
     delete d;
 }
 
-QObject *HelpManager::instance()
+HelpManager *HelpManager::instance()
 {
     Q_ASSERT(m_instance);
     return m_instance;
@@ -248,51 +248,6 @@ QMap<QString, QUrl> HelpManager::linksForIdentifier(const QString &id)
     return d->m_helpEngine->linksForIdentifier(id);
 }
 
-// This should go into Qt 4.8 once we start using it for Qt Creator
-QStringList HelpManager::findKeywords(const QString &key, Qt::CaseSensitivity caseSensitivity,
-                                      int maxHits)
-{
-    QTC_ASSERT(!d->m_needsSetup, return QStringList());
-
-    const QLatin1String sqlite("QSQLITE");
-    const QLatin1String name("HelpManager::findKeywords");
-
-    QSet<QString> keywords;
-    QSet<QString> keywordsToSort;
-
-    DbCleaner cleaner(name);
-    QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name);
-    if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) {
-        QHelpEngineCore core(collectionFilePath());
-        core.setAutoSaveFilter(false);
-        core.setCurrentFilter(tr("Unfiltered"));
-        core.setupData();
-        const QStringList &registeredDocs = core.registeredDocumentations();
-        foreach (const QString &nameSpace, registeredDocs) {
-            db.setDatabaseName(core.documentationFileName(nameSpace));
-            if (db.open()) {
-                QSqlQuery query = QSqlQuery(db);
-                query.setForwardOnly(true);
-                query.exec(QString::fromLatin1("SELECT DISTINCT Name FROM IndexTable WHERE Name LIKE "
-                    "'%%1%' LIMIT %2").arg(key, QString::number(maxHits)));
-                while (query.next()) {
-                    const QString &keyValue = query.value(0).toString();
-                    if (!keyValue.isEmpty()) {
-                        if (keyValue.startsWith(key, caseSensitivity))
-                            keywordsToSort.insert(keyValue);
-                        else
-                            keywords.insert(keyValue);
-                    }
-                }
-            }
-        }
-    }
-
-    QStringList keywordsSorted = keywordsToSort.toList();
-    Utils::sort(keywordsSorted);
-    return keywordsSorted + keywords.toList();
-}
-
 QUrl HelpManager::findFile(const QUrl &url)
 {
     QTC_ASSERT(!d->m_needsSetup, return QUrl());
diff --git a/src/plugins/coreplugin/helpmanager.h b/src/plugins/coreplugin/helpmanager.h
index c63ceb0fb1b3883880c775a18a8aa9b287e0ec69..185276f674a0b4a66650791092833a96aeeae29c 100644
--- a/src/plugins/coreplugin/helpmanager.h
+++ b/src/plugins/coreplugin/helpmanager.h
@@ -63,7 +63,7 @@ public:
 
     typedef QHash<QString, QStringList> Filters;
 
-    static QObject *instance();
+    static HelpManager *instance();
     static QString collectionFilePath();
 
     static void registerDocumentation(const QStringList &fileNames);
@@ -74,9 +74,6 @@ public:
 
     static QMap<QString, QUrl> linksForKeyword(const QString &key);
     static QMap<QString, QUrl> linksForIdentifier(const QString &id);
-    static QStringList findKeywords(const QString &key,
-                             Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive,
-                             int maxHits = INT_MAX);
 
     static QUrl findFile(const QUrl &url);
     static QByteArray fileData(const QUrl &url);
diff --git a/src/plugins/help/help.pro b/src/plugins/help/help.pro
index 4c19710f5f794da379ecb98914bab2442cb962c8..ca6e1adb911cdce8716fb553c743257623761f79 100644
--- a/src/plugins/help/help.pro
+++ b/src/plugins/help/help.pro
@@ -1,4 +1,4 @@
-QT += help network printsupport
+QT += help network printsupport sql
 !isEmpty(QT.webkitwidgets.name): QT += webkitwidgets webkit
 else: DEFINES += QT_NO_WEBKIT
 
diff --git a/src/plugins/help/helpindexfilter.cpp b/src/plugins/help/helpindexfilter.cpp
index e6a5c29d371d34121bd01e0990084977d4cc4dd8..2c6ea45e1e49814d7320b219496682ab182d2431 100644
--- a/src/plugins/help/helpindexfilter.cpp
+++ b/src/plugins/help/helpindexfilter.cpp
@@ -37,8 +37,14 @@
 #include <extensionsystem/pluginmanager.h>
 #include <coreplugin/icore.h>
 #include <coreplugin/helpmanager.h>
+#include <utils/algorithm.h>
 
 #include <QIcon>
+#include <QSqlDatabase>
+#include <QSqlDriver>
+#include <QSqlError>
+#include <QSqlQuery>
+
 
 using namespace Core;
 using namespace Help;
@@ -47,6 +53,7 @@ using namespace Help::Internal;
 Q_DECLARE_METATYPE(ILocatorFilter*)
 
 HelpIndexFilter::HelpIndexFilter()
+    : m_needsUpdate(true)
 {
     setId("HelpIndexFilter");
     setDisplayName(tr("Help Index"));
@@ -54,6 +61,12 @@ HelpIndexFilter::HelpIndexFilter()
     setShortcutString(QString(QLatin1Char('?')));
 
     m_icon = QIcon(QLatin1String(":/help/images/bookmark.png"));
+    connect(HelpManager::instance(), &HelpManager::setupFinished,
+            this, &HelpIndexFilter::invalidateCache);
+    connect(HelpManager::instance(), &HelpManager::documentationChanged,
+            this, &HelpIndexFilter::invalidateCache);
+    connect(HelpManager::instance(), &HelpManager::collectionFileChanged,
+            this, &HelpIndexFilter::invalidateCache);
 }
 
 HelpIndexFilter::~HelpIndexFilter()
@@ -62,21 +75,62 @@ HelpIndexFilter::~HelpIndexFilter()
 
 void HelpIndexFilter::prepareSearch(const QString &entry)
 {
-    if (entry.length() < 2)
-        m_keywords = Core::HelpManager::findKeywords(entry, caseSensitivity(entry), 200);
-    else
-        m_keywords = Core::HelpManager::findKeywords(entry, caseSensitivity(entry));
+    Q_UNUSED(entry)
+    QStringList namespaces = HelpManager::registeredNamespaces();
+    m_helpDatabases = Utils::transform(namespaces, [](const QString &ns) {
+        return HelpManager::fileFromNamespace(ns);
+    });
 }
 
 QList<LocatorFilterEntry> HelpIndexFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
 {
-    Q_UNUSED(entry) // search is already done in the GUI thread in prepareSearch
+    m_mutex.lock(); // guard m_needsUpdate
+    bool forceUpdate = m_needsUpdate;
+    m_mutex.unlock();
+
+    if (forceUpdate || m_searchTermCache.size() < 2 || m_searchTermCache.isEmpty()
+            || !entry.contains(m_searchTermCache)) {
+        int limit = entry.size() < 2 ? 200 : INT_MAX;
+        QSet<QString> results;
+        foreach (const QString &filePath, m_helpDatabases) {
+            QSet<QString> result;
+            QMetaObject::invokeMethod(this, "searchMatches", Qt::BlockingQueuedConnection,
+                                      Q_RETURN_ARG(QSet<QString>, result),
+                                      Q_ARG(QString, filePath),
+                                      Q_ARG(QString, entry),
+                                      Q_ARG(int, limit));
+            results.unite(result);
+        }
+        m_mutex.lock(); // guard m_needsUpdate
+        m_needsUpdate = false;
+        m_mutex.unlock();
+        m_keywordCache = results;
+        m_searchTermCache = entry;
+    }
+
+    Qt::CaseSensitivity cs = caseSensitivity(entry);
     QList<LocatorFilterEntry> entries;
-    foreach (const QString &keyword, m_keywords) {
+    QStringList keywords;
+    QStringList unsortedKeywords;
+    keywords.reserve(m_keywordCache.size());
+    unsortedKeywords.reserve(m_keywordCache.size());
+    QSet<QString> allresults;
+    foreach (const QString &keyword, m_keywordCache) {
         if (future.isCanceled())
             break;
-        entries.append(LocatorFilterEntry(this, keyword, QVariant(), m_icon));
+        if (keyword.startsWith(entry, cs)) {
+            keywords.append(keyword);
+            allresults.insert(keyword);
+        } else if (keyword.contains(entry, cs)) {
+            unsortedKeywords.append(keyword);
+            allresults.insert(keyword);
+        }
     }
+    Utils::sort(keywords);
+    keywords << unsortedKeywords;
+    m_keywordCache = allresults;
+    foreach (const QString &keyword, keywords)
+        entries.append(LocatorFilterEntry(this, keyword, QVariant(), m_icon));
 
     return entries;
 }
@@ -95,5 +149,42 @@ void HelpIndexFilter::accept(LocatorFilterEntry selection) const
 void HelpIndexFilter::refresh(QFutureInterface<void> &future)
 {
     Q_UNUSED(future)
-    // Nothing to refresh
+    invalidateCache();
+}
+
+QSet<QString> HelpIndexFilter::searchMatches(const QString &databaseFilePath,
+                                           const QString &term, int limit)
+{
+    static const QLatin1String sqlite("QSQLITE");
+    static const QLatin1String name("HelpManager::findKeywords");
+
+    QSet<QString> keywords;
+
+    { // make sure db is destroyed before removeDatabase call
+        QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name);
+        if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) {
+            db.setDatabaseName(databaseFilePath);
+            if (db.open()) {
+                QSqlQuery query = QSqlQuery(db);
+                query.setForwardOnly(true);
+                query.exec(QString::fromLatin1("SELECT DISTINCT Name FROM IndexTable WHERE Name LIKE "
+                    "'%%1%' LIMIT %2").arg(term, QString::number(limit)));
+                while (query.next()) {
+                    const QString &keyValue = query.value(0).toString();
+                    if (!keyValue.isEmpty())
+                        keywords.insert(keyValue);
+                }
+                db.close();
+            }
+        }
+    }
+    QSqlDatabase::removeDatabase(name);
+    return keywords;
+}
+
+void HelpIndexFilter::invalidateCache()
+{
+    m_mutex.lock();
+    m_needsUpdate = true;
+    m_mutex.unlock();
 }
diff --git a/src/plugins/help/helpindexfilter.h b/src/plugins/help/helpindexfilter.h
index 80558d41517137ff2de9c5a2999710ca8a1872ec..efae73dde3f4e714cf6ceaa40cdd43bc1bf5069c 100644
--- a/src/plugins/help/helpindexfilter.h
+++ b/src/plugins/help/helpindexfilter.h
@@ -34,6 +34,7 @@
 #include <coreplugin/locator/ilocatorfilter.h>
 
 #include <QIcon>
+#include <QMutex>
 
 namespace Help {
 namespace Internal {
@@ -48,17 +49,26 @@ public:
 
     // ILocatorFilter
     void prepareSearch(const QString &entry);
-    QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry);
+    QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
+                                               const QString &entry);
     void accept(Core::LocatorFilterEntry selection) const;
     void refresh(QFutureInterface<void> &future);
 
+    Q_INVOKABLE QSet<QString> searchMatches(const QString &databaseFilePath,
+                                          const QString &term, int limit);
 signals:
     void linkActivated(const QUrl &link) const;
     void linksActivated(const QMap<QString, QUrl> &links, const QString &key) const;
 
 private:
+    void invalidateCache();
+
+    QStringList m_helpDatabases;
+    QSet<QString> m_keywordCache;
+    QString m_searchTermCache;
+    bool m_needsUpdate;
+    QMutex m_mutex;
     QIcon m_icon;
-    QStringList m_keywords;
 };
 
 } // namespace Internal