diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/BTreeTests.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/BTreeTests.java index de8685ab20d..b10cbf4500c 100644 --- a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/BTreeTests.java +++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/BTreeTests.java @@ -34,6 +34,16 @@ import org.eclipse.core.runtime.CoreException; * */ public class BTreeTests extends BaseTestCase { + + /** + * Workaround: If the page table size of the database is too small, + * there will be frequent page ins and page outs. This trips a bug + * in FileChannelImpl.map0() which causes an IOException. If the + * page table size is large enough so that page outs are infrequent, + * the problem does not occur. + */ + static final int PAGE_TABLE_SIZE = 10 * 1024 * 1024 / Database.CHUNK_SIZE; // 10 MB + protected File dbFile; protected Database db; protected BTree btree; @@ -50,7 +60,7 @@ public class BTreeTests extends BaseTestCase { // and invoke it multiple times per Junit test protected void init(int degree) throws Exception { dbFile = File.createTempFile("pdomtest", "db"); - db = new Database(dbFile); + db = new Database(dbFile, PAGE_TABLE_SIZE); rootRecord = Database.DATA_AREA; comparator = new BTMockRecordComparator(); btree = new BTree(db, rootRecord, degree, comparator); diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Chunk.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Chunk.java index 76d509c6b5d..4e9b93d2560 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Chunk.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Chunk.java @@ -34,6 +34,16 @@ public class Chunk { private Database db; int index; + /** + * The index of this Chunk within the page table. + */ + int pageTableIndex; + + /** + * This flag is true if this Chunk has been referenced recently. + */ + boolean referenceFlag; + Chunk(RandomAccessFile file, int offset) throws CoreException { try { index = offset / Database.CHUNK_SIZE; diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java index 52fc230bd6b..531d5275866 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java @@ -36,6 +36,11 @@ import org.eclipse.core.runtime.Status; * mapping chunk index to chunk address is maintained. Chunk structure exists only conceptually - * its not a structure that appears in the file. * + * Chunks are paged in one at a time as they are accessed. Frequently used + * chunks remain in memory while infrequently used chunks are paged out to + * to the file. The database uses the CLOCK algorithm to determine which + * chunk to evict from the page table on a cache miss. + * * ===== The first chunk is used by Database itself for house-keeping purposes and has structure * * offset content @@ -61,8 +66,37 @@ public class Database { private final File location; private final RandomAccessFile file; + + /** + * Table of Chunks allocated by the Database. + */ Chunk[] toc; + /** + * Table of actively used (paged-in) Chunks. + */ + int[] pageTable; + + /** + * The position where the next page in or page out will occur. + */ + int pageTableIndex; + + /** + * Indicates whether all slots in the page table have been allocated. + */ + boolean pageTableIsFull; + + /** + * Number of times a request for a Chunk was served from the cache. + */ + long cacheHits; + + /** + * Number of times a request for a Chunk needed to be paged in. + */ + long cacheMisses; + private long malloced; private long freed; @@ -77,8 +111,17 @@ public class Database { public static final int DATA_AREA = CHUNK_SIZE / MIN_SIZE * INT_SIZE + INT_SIZE; public static final int MAX_SIZE = CHUNK_SIZE - 4; // Room for overhead - + + /** + * The initial number of slots in the page table. + */ + public static final int DEFAULT_PAGE_TABLE_SIZE = 1 * 1024 * 1024 / CHUNK_SIZE; + public Database(File location) throws CoreException { + this(location, DEFAULT_PAGE_TABLE_SIZE); + } + + public Database(File location, int pageTableSize) throws CoreException { try { this.location = location; this.file = new RandomAccessFile(location, "rw"); //$NON-NLS-1$ @@ -93,6 +136,7 @@ public class Database { toc = new Chunk[(int)nChunks]; toc[0] = new Chunk(file, 0); + pageTable = new int[pageTableSize]; } catch (IOException e) { throw new CoreException(new DBStatus(e)); } @@ -126,6 +170,9 @@ public class Database { } } malloced = freed = 0; + + // Clear the page table + resizePageTable(pageTable.length); } /** @@ -194,9 +241,13 @@ public class Database { int index = offset / CHUNK_SIZE; Chunk chunk = toc[index]; if (chunk == null) { - chunk = toc[index] = new Chunk(file, index * CHUNK_SIZE); + cacheMisses++; + chunk = pageIn(offset); + } + else { + cacheHits++; + chunk.referenceFlag = true; } - return chunk; } @@ -254,6 +305,11 @@ public class Database { return freeblock + 4; } + /** + * Extends the database by one chunk. + * @return The index of the new chunk. + * @throws CoreException + */ private int createChunk() throws CoreException { try { Chunk[] oldtoc = toc; @@ -263,7 +319,7 @@ public class Database { file.write(new byte[CHUNK_SIZE]); toc = new Chunk[n + 1]; System.arraycopy(oldtoc, 0, toc, 0, n); - toc[n] = new Chunk(file, offset); + getChunk(offset); return n; } catch (IOException e) { throw new CoreException(new DBStatus(e)); @@ -430,4 +486,101 @@ public class Database { public File getLocation() { return location; } + + /** + * Clears the page table and changes it to have size number + * of slots. + * @param size The number of slots the page table should have. + */ + public void resizePageTable(int size) { + // Page out everything in the chunk/page tables. + pageTable = new int[size]; + for (int i = 1; i < toc.length; i++) { + toc[i] = null; + } + + pageTableIndex = 0; + pageTableIsFull = false; + cacheHits = 0; + cacheMisses = 0; + } + + /** + * Returns the number of cache hits since the page table was created. + * @return The number of cache hits since the page table was created. + */ + public long getCacheHits() { + return cacheHits; + } + + /** + * Returns the number of cache misses since the page table was created. + * @return The number of cache misses since the page table was created. + */ + public long getCacheMisses() { + return cacheMisses; + } + + /** + * Adds the chunk which contains the given given offset to the page table. + * This method will evict chunks from the page table as necessary to page + * in the requested chunk. + * + * @param offset The database offset that will be accessed. + * @return The chunk which contains the given offset. + * @throws CoreException + */ + private Chunk pageIn(int offset) throws CoreException { + int index = offset / CHUNK_SIZE; + if (toc[index] != null) { + return toc[index]; + } + + Chunk chunk = new Chunk(file, index * CHUNK_SIZE); + toc[index] = chunk; + + if (pageTableIsFull) { + evictChunk(); + chunk.pageTableIndex = pageTableIndex; + pageTable[pageTableIndex] = index; + } + else { + chunk.pageTableIndex = pageTableIndex; + pageTable[pageTableIndex] = index; + + pageTableIndex++; + if (pageTableIndex == pageTable.length) { + pageTableIndex = 0; + pageTableIsFull = true; + } + } + return chunk; + } + + /** + * Evicts a chunk from the page table and the chunk table. + * After this method returns, pageTableIndex will contain + * the index of the evicted chunk within the page table. + */ + private void evictChunk() { + /* + * Use the CLOCK algorithm to determine which chunk to evict. + * i.e., if the chunk in the current slot of the page table has been + * recently referenced (i.e. the reference flag is set), unset the + * reference flag and move to the next slot. Otherwise, evict the + * chunk in the current slot. + */ + while (true) { + int chunkIndex = pageTable[pageTableIndex]; + Chunk chunk = toc[chunkIndex]; + if (chunk.referenceFlag) { + chunk.referenceFlag = false; + pageTableIndex = (pageTableIndex + 1) % pageTable.length; + } else { + toc[chunkIndex] = null; + pageTable[pageTableIndex] = 0; + return; + } + } + } }