perf: always binary-search idref lookups in content.opf (#2433)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
This commit is contained in:
co-authored by
Erica Jensen
parent
bb06219c9c
commit
5fbc657aeb
@@ -137,8 +137,10 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error.");
|
LOG_ERR("COF", "Couldn't open temp items file for reading. This is probably going to be a fatal error.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort item index for binary search if we have enough items
|
// Sort the (unconditionally-built) item index so every idref lookup uses binary
|
||||||
if (self->itemIndex.size() >= LARGE_SPINE_THRESHOLD) {
|
// search. Without this, small/medium manifests fell back to an O(spine × manifest)
|
||||||
|
// linear rescan of .items.bin per itemref (up to ~200ms/item at large scale).
|
||||||
|
if (!self->itemIndex.empty()) {
|
||||||
std::sort(self->itemIndex.begin(), self->itemIndex.end(), [](const ItemIndexEntry& a, const ItemIndexEntry& b) {
|
std::sort(self->itemIndex.begin(), self->itemIndex.end(), [](const ItemIndexEntry& a, const ItemIndexEntry& b) {
|
||||||
return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen);
|
return a.idHash < b.idHash || (a.idHash == b.idHash && a.idLen < b.idLen);
|
||||||
});
|
});
|
||||||
@@ -284,9 +286,8 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Slow path: linear scan (for small manifests, keeps original behavior)
|
// Fallback linear scan, only reached when the index is empty (no manifest
|
||||||
// TODO: This lookup is slow as need to scan through all items each time.
|
// items). The fast binary-search path above is used for all real manifests.
|
||||||
// It can take up to 200ms per item when getting to 1500 items.
|
|
||||||
self->tempItemStore.seek(0);
|
self->tempItemStore.seek(0);
|
||||||
std::string itemId;
|
std::string itemId;
|
||||||
while (self->tempItemStore.available()) {
|
while (self->tempItemStore.available()) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class ContentOpfParser final : public Print {
|
|||||||
HalFile tempItemStore;
|
HalFile tempItemStore;
|
||||||
std::string coverItemId;
|
std::string coverItemId;
|
||||||
|
|
||||||
// Index for fast idref→href lookup (used only for large EPUBs)
|
// Index for fast idref→href lookup (binary search over .items.bin)
|
||||||
struct ItemIndexEntry {
|
struct ItemIndexEntry {
|
||||||
uint32_t idHash; // FNV-1a hash of itemId
|
uint32_t idHash; // FNV-1a hash of itemId
|
||||||
uint16_t idLen; // length for collision reduction
|
uint16_t idLen; // length for collision reduction
|
||||||
@@ -41,8 +41,6 @@ class ContentOpfParser final : public Print {
|
|||||||
std::deque<ItemIndexEntry> itemIndex;
|
std::deque<ItemIndexEntry> itemIndex;
|
||||||
bool useItemIndex = false;
|
bool useItemIndex = false;
|
||||||
|
|
||||||
static constexpr uint16_t LARGE_SPINE_THRESHOLD = 400;
|
|
||||||
|
|
||||||
// FNV-1a hash function
|
// FNV-1a hash function
|
||||||
static uint32_t fnvHash(const std::string& s) {
|
static uint32_t fnvHash(const std::string& s) {
|
||||||
uint32_t hash = 2166136261u;
|
uint32_t hash = 2166136261u;
|
||||||
|
|||||||
Reference in New Issue
Block a user