diff --git a/builds/win32/msvc15/engine_static.vcxproj b/builds/win32/msvc15/engine_static.vcxproj
index 6d6785ff44..a8bf00b46a 100644
--- a/builds/win32/msvc15/engine_static.vcxproj
+++ b/builds/win32/msvc15/engine_static.vcxproj
@@ -310,6 +310,7 @@
+
diff --git a/builds/win32/msvc15/engine_static.vcxproj.filters b/builds/win32/msvc15/engine_static.vcxproj.filters
index 36d1aca2ef..861d5f0b4d 100644
--- a/builds/win32/msvc15/engine_static.vcxproj.filters
+++ b/builds/win32/msvc15/engine_static.vcxproj.filters
@@ -1085,6 +1085,7 @@
Header files
+
@@ -1130,4 +1131,4 @@
DSQL
-
+
\ No newline at end of file
diff --git a/src/jrd/Attachment.h b/src/jrd/Attachment.h
index 7a6678eaef..b00308a427 100644
--- a/src/jrd/Attachment.h
+++ b/src/jrd/Attachment.h
@@ -73,6 +73,7 @@ namespace Jrd
class jrd_file;
class Format;
class BufferControl;
+ class PageToBufferMap;
class SparseBitmap;
class jrd_rel;
class ExternalFile;
@@ -639,6 +640,8 @@ public:
USHORT att_current_timezone;
int att_parallel_workers;
+ PageToBufferMap* att_bdb_cache; // managed in CCH, created in att_pool, freed with it
+
Firebird::RefPtr att_replicator;
Firebird::AutoPtr att_repl_matcher;
Firebird::Array att_repl_appliers;
diff --git a/src/jrd/PageToBufferMap.h b/src/jrd/PageToBufferMap.h
new file mode 100644
index 0000000000..f15a878802
--- /dev/null
+++ b/src/jrd/PageToBufferMap.h
@@ -0,0 +1,217 @@
+/*
+ * PROGRAM: JRD Access Method
+ * MODULE: PageToBufferMap.h
+ * DESCRIPTION: Support of disk cache manager
+ *
+ * The contents of this file are subject to the Interbase Public
+ * License Version 1.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy
+ * of the License at http://www.Inprise.com/IPL.html
+ *
+ * Software distributed under the License is distributed on an
+ * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
+ * or implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code was created by Vladyslav Khorsun for the
+ * Firebird Open Source RDBMS project.
+ *
+ * Copyright (c) 2023 Vladyslav Khorsun
+ * and all contributors signed below.
+ *
+ * All Rights Reserved.
+ * Contributor(s): ______________________________________.
+ */
+
+#ifndef JRD_PAGE_TO_BUFFER_MAP
+#define JRD_PAGE_TO_BUFFER_MAP
+
+#include "../common/classes/Hash.h"
+#include "../jrd/cch.h"
+
+namespace Jrd {
+
+// PageToBufferMap used to cache pointers to the often used page buffers.
+// Its purpose is to avoid more costly usage of shared hash table.
+
+class PageToBufferMap
+{
+ static const size_t MAP_SIZE = 64;
+
+public:
+ explicit PageToBufferMap(MemoryPool& pool) :
+ m_map(pool)
+ {
+ Item* items = FB_NEW_POOL(pool) Item[MAP_SIZE];
+ for (FB_SIZE_T i = 1; i < MAP_SIZE; i++)
+ items[i - 1].m_next = &items[i];
+
+ m_free = items;
+ }
+
+ BufferDesc* get(PageNumber page)
+ {
+ Item* item = m_map.lookup(page);
+
+ if (!item)
+ return nullptr;
+
+ if (m_list != item)
+ {
+ listRemove(item);
+ listInsert(item);
+ }
+
+ return item->m_bdb;
+ }
+
+ void put(BufferDesc* bdb)
+ {
+ Item* item = m_map.lookup(bdb->bdb_page);
+ if (item)
+ {
+ if (m_list != item)
+ listRemove(item);
+ }
+ else
+ {
+ item = getFreeItem();
+ item->m_page = bdb->bdb_page;
+ m_map.add(item);
+ }
+
+ item->m_bdb = bdb;
+ if (m_list != item)
+ listInsert(item);
+ }
+
+ void remove(PageNumber page)
+ {
+ Item* item = m_map.remove(page);
+
+ fb_assert(item);
+ if (!item)
+ return;
+
+ listRemove(item);
+
+ item->m_bdb = nullptr;
+ item->m_next = m_free;
+ m_free = item;
+ }
+
+private:
+ struct Item;
+ using HashTableType = Firebird::HashTable- ;
+
+ struct Item : public HashTableType::Entry
+ {
+ PageNumber m_page;
+ BufferDesc* m_bdb = nullptr;
+ Item* m_next = nullptr; // LRU list support
+ Item* m_prev = nullptr;
+
+ // KeyOfValue
+ static PageNumber generate(Item& item)
+ {
+ return item.m_page;
+ }
+
+ // Hash function
+ static FB_SIZE_T hash(const PageNumber& value, FB_SIZE_T hashSize)
+ {
+ return value.getPageNum() % hashSize;
+ }
+
+ // HashTable::Entry
+ bool isEqual(const PageNumber& page) const override
+ {
+ return this->m_page == page;
+ }
+
+ Item* get() override
+ {
+ return this;
+ }
+ };
+
+ Item* getFreeItem()
+ {
+ Item* item = m_free;
+ if (item)
+ {
+ m_free = m_free->m_next;
+ item->m_next = nullptr;
+ return item;
+ }
+
+ // get least recently used item from list
+ fb_assert(m_list != nullptr);
+
+ item = m_list->m_prev;
+ listRemove(item);
+
+ if (m_map.remove(item->m_page) != item)
+ fb_assert(false);
+
+ item->m_bdb = nullptr;
+ return item;
+ }
+
+
+ void listRemove(Item* item)
+ {
+ // remove item from list
+ fb_assert(m_list != nullptr);
+ fb_assert(item->m_next);
+ fb_assert(item->m_prev);
+
+ if (item->m_next == item)
+ {
+ fb_assert(item->m_prev == item);
+ fb_assert(m_list == item);
+ m_list = nullptr;
+ }
+ else
+ {
+ if (m_list == item)
+ m_list = item->m_next;
+
+ item->m_next->m_prev = item->m_prev;
+ item->m_prev->m_next = item->m_next;
+ }
+
+ item->m_next = item->m_prev = nullptr;
+ }
+
+ void listInsert(Item* item)
+ {
+ // put item at the list head
+ if (m_list != nullptr)
+ {
+ fb_assert(m_list->m_next);
+ fb_assert(m_list->m_prev);
+
+ item->m_next = m_list;
+ item->m_prev = m_list->m_prev;
+
+ item->m_next->m_prev = item;
+ item->m_prev->m_next = item;
+ }
+ else
+ {
+ item->m_next = item;
+ item->m_prev = item;
+ }
+
+ m_list = item;
+ }
+
+ HashTableType m_map;
+ Item* m_list = nullptr; // head of LRU list
+ Item* m_free = nullptr; // unused items
+};
+
+} // namespace Jrd
+
+#endif // JRD_PAGE_TO_BUFFER_MAP
diff --git a/src/jrd/cch.cpp b/src/jrd/cch.cpp
index 5b4a355151..6b324931f2 100644
--- a/src/jrd/cch.cpp
+++ b/src/jrd/cch.cpp
@@ -62,6 +62,7 @@
#include "../common/classes/MsgPrint.h"
#include "../jrd/CryptoManager.h"
#include "../common/utils_proto.h"
+#include "../jrd/PageToBufferMap.h"
// Use lock-free lists in hash table implementation
#define HASH_USE_CDS_LIST
@@ -131,6 +132,7 @@ static void prefetch_init(Prefetch*, thread_db*);
static void prefetch_io(Prefetch*, FbStatusVector *);
static void prefetch_prologue(Prefetch*, SLONG *);
#endif
+static void cacheBuffer(Attachment* att, BufferDesc* bdb);
static void check_precedence(thread_db*, WIN*, PageNumber);
static void clear_precedence(thread_db*, BufferDesc*);
static void down_grade(thread_db*, BufferDesc*, int high = 0);
@@ -3152,6 +3154,18 @@ void BufferControl::exceptionHandler(const Firebird::Exception& ex, BcbThreadSyn
}
+static void cacheBuffer(Attachment* att, BufferDesc* bdb)
+{
+ if (att)
+ {
+ if (!att->att_bdb_cache)
+ att->att_bdb_cache = FB_NEW_POOL(*att->att_pool) PageToBufferMap(*att->att_pool);
+
+ att->att_bdb_cache->put(bdb);
+ }
+}
+
+
static void check_precedence(thread_db* tdbb, WIN* window, PageNumber page)
{
/**************************************
@@ -3782,6 +3796,32 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
BufferControl* bcb = dbb->dbb_bcb;
+ Attachment* att = tdbb->getAttachment();
+
+ if (att && att->att_bdb_cache)
+ {
+ if (BufferDesc* bdb = att->att_bdb_cache->get(page))
+ {
+ if (bdb->addRef(tdbb, syncType, wait))
+ {
+ if (bdb->bdb_page == page)
+ {
+ recentlyUsed(bdb);
+ tdbb->bumpStats(RuntimeStatistics::PAGE_FETCHES);
+ return bdb;
+ }
+
+ bdb->release(tdbb, true);
+ att->att_bdb_cache->remove(page);
+ }
+ else
+ {
+ fb_assert(wait <= 0);
+ if (bdb->bdb_page == page)
+ return nullptr;
+ }
+ }
+ }
while (true)
{
@@ -3811,6 +3851,7 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s
{
recentlyUsed(bdb);
tdbb->bumpStats(RuntimeStatistics::PAGE_FETCHES);
+ cacheBuffer(att, bdb);
return bdb;
}
@@ -3850,6 +3891,7 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s
bdb->downgrade(syncType);
recentlyUsed(bdb);
tdbb->bumpStats(RuntimeStatistics::PAGE_FETCHES);
+ cacheBuffer(att, bdb);
return bdb;
}
}
@@ -3893,6 +3935,7 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s
recentlyUsed(bdb);
}
tdbb->bumpStats(RuntimeStatistics::PAGE_FETCHES);
+ cacheBuffer(att, bdb);
return bdb;
}
}
@@ -3910,6 +3953,7 @@ static BufferDesc* get_buffer(thread_db* tdbb, const PageNumber page, SyncType s
}
recentlyUsed(bdb2);
tdbb->bumpStats(RuntimeStatistics::PAGE_FETCHES);
+ cacheBuffer(att, bdb2);
}
else
bdb2 = nullptr;