ole32: Store the location of all blocks in a big block chain in memory.

A big block chain is a linked list, and we pretty much need random
access to them. This should theoretically make accessing a random
point in the chain O(log2 n) instead of O(n) (with disk access scaling
based on the size of the read/write, not its location). It
theoretically takes O(n) memory based on the size, but it can do
better if the chain isn't very fragmented (which I believe will
generally be the case for long chains). It also involves fetching all
the big block locations when we open the chain, but we already do that
anyway (and it should be faster to read it all in one go than
piecemeal).
This commit is contained in:
Vincent Povirk 2010-05-04 15:15:41 -05:00 committed by Alexandre Julliard
parent 93cc582a8d
commit 42550953a6
2 changed files with 182 additions and 106 deletions

View File

@ -5026,45 +5026,143 @@ void StorageUtl_CopyDirEntryToSTATSTG(
** BlockChainStream implementation
*/
/* Read and save the index of all blocks in this stream. */
HRESULT BlockChainStream_UpdateIndexCache(BlockChainStream* This)
{
ULONG next_sector, next_offset;
HRESULT hr;
struct BlockChainRun *last_run;
if (This->indexCacheLen == 0)
{
last_run = NULL;
next_offset = 0;
next_sector = BlockChainStream_GetHeadOfChain(This);
}
else
{
last_run = &This->indexCache[This->indexCacheLen-1];
next_offset = last_run->lastOffset+1;
hr = StorageImpl_GetNextBlockInChain(This->parentStorage,
last_run->firstSector + last_run->lastOffset - last_run->firstOffset,
&next_sector);
if (FAILED(hr)) return hr;
}
while (next_sector != BLOCK_END_OF_CHAIN)
{
if (!last_run || next_sector != last_run->firstSector + next_offset - last_run->firstOffset)
{
/* Add the current block to the cache. */
if (This->indexCacheSize == 0)
{
This->indexCache = HeapAlloc(GetProcessHeap(), 0, sizeof(struct BlockChainRun)*16);
if (!This->indexCache) return E_OUTOFMEMORY;
This->indexCacheSize = 16;
}
else if (This->indexCacheSize == This->indexCacheLen)
{
struct BlockChainRun *new_cache;
ULONG new_size;
new_size = This->indexCacheSize * 2;
new_cache = HeapAlloc(GetProcessHeap(), 0, sizeof(struct BlockChainRun)*new_size);
if (!new_cache) return E_OUTOFMEMORY;
memcpy(new_cache, This->indexCache, sizeof(struct BlockChainRun)*This->indexCacheLen);
HeapFree(GetProcessHeap(), 0, This->indexCache);
This->indexCache = new_cache;
This->indexCacheSize = new_size;
}
This->indexCacheLen++;
last_run = &This->indexCache[This->indexCacheLen-1];
last_run->firstSector = next_sector;
last_run->firstOffset = next_offset;
}
last_run->lastOffset = next_offset;
/* Find the next block. */
next_offset++;
hr = StorageImpl_GetNextBlockInChain(This->parentStorage, next_sector, &next_sector);
if (FAILED(hr)) return hr;
}
if (This->indexCacheLen)
{
This->tailIndex = last_run->firstSector + last_run->lastOffset - last_run->firstOffset;
This->numBlocks = last_run->lastOffset+1;
}
else
{
This->tailIndex = BLOCK_END_OF_CHAIN;
This->numBlocks = 0;
}
return S_OK;
}
/* Locate the nth block in this stream. */
ULONG BlockChainStream_GetSectorOfOffset(BlockChainStream *This, ULONG offset)
{
ULONG min_offset = 0, max_offset = This->numBlocks-1;
ULONG min_run = 0, max_run = This->indexCacheLen-1;
if (offset >= This->numBlocks)
return BLOCK_END_OF_CHAIN;
while (min_run < max_run)
{
ULONG run_to_check = min_run + (offset - min_offset) * (max_run - min_run) / (max_offset - min_offset);
if (offset < This->indexCache[run_to_check].firstOffset)
{
max_offset = This->indexCache[run_to_check].firstOffset-1;
max_run = run_to_check-1;
}
else if (offset > This->indexCache[run_to_check].lastOffset)
{
min_offset = This->indexCache[run_to_check].lastOffset+1;
min_run = run_to_check+1;
}
else
/* Block is in this run. */
min_run = max_run = run_to_check;
}
return This->indexCache[min_run].firstSector + offset - This->indexCache[min_run].firstOffset;
}
BlockChainStream* BlockChainStream_Construct(
StorageImpl* parentStorage,
ULONG* headOfStreamPlaceHolder,
DirRef dirEntry)
{
BlockChainStream* newStream;
ULONG blockIndex;
newStream = HeapAlloc(GetProcessHeap(), 0, sizeof(BlockChainStream));
newStream->parentStorage = parentStorage;
newStream->headOfStreamPlaceHolder = headOfStreamPlaceHolder;
newStream->ownerDirEntry = dirEntry;
newStream->lastBlockNoInSequence = 0xFFFFFFFF;
newStream->tailIndex = BLOCK_END_OF_CHAIN;
newStream->numBlocks = 0;
newStream->indexCache = NULL;
newStream->indexCacheLen = 0;
newStream->indexCacheSize = 0;
blockIndex = BlockChainStream_GetHeadOfChain(newStream);
while (blockIndex != BLOCK_END_OF_CHAIN)
{
newStream->numBlocks++;
newStream->tailIndex = blockIndex;
if(FAILED(StorageImpl_GetNextBlockInChain(
parentStorage,
blockIndex,
&blockIndex)))
if (FAILED(BlockChainStream_UpdateIndexCache(newStream)))
{
HeapFree(GetProcessHeap(), 0, newStream->indexCache);
HeapFree(GetProcessHeap(), 0, newStream);
return NULL;
}
}
return newStream;
}
void BlockChainStream_Destroy(BlockChainStream* This)
{
if (This)
HeapFree(GetProcessHeap(), 0, This->indexCache);
HeapFree(GetProcessHeap(), 0, This);
}
@ -5106,6 +5204,7 @@ static ULONG BlockChainStream_GetHeadOfChain(BlockChainStream* This)
* Returns the number of blocks that comprises this chain.
* This is not the size of the stream as the last block may not be full!
*
* FIXME: Use the cache to get this information.
*/
static ULONG BlockChainStream_GetCount(BlockChainStream* This)
{
@ -5152,34 +5251,11 @@ HRESULT BlockChainStream_ReadAt(BlockChainStream* This,
/*
* Find the first block in the stream that contains part of the buffer.
*/
if ( (This->lastBlockNoInSequence == 0xFFFFFFFF) ||
(This->lastBlockNoInSequenceIndex == BLOCK_END_OF_CHAIN) ||
(blockNoInSequence < This->lastBlockNoInSequence) )
{
blockIndex = BlockChainStream_GetHeadOfChain(This);
This->lastBlockNoInSequence = blockNoInSequence;
}
else
{
ULONG temp = blockNoInSequence;
blockIndex = BlockChainStream_GetSectorOfOffset(This, blockNoInSequence);
blockIndex = This->lastBlockNoInSequenceIndex;
blockNoInSequence -= This->lastBlockNoInSequence;
This->lastBlockNoInSequence = temp;
}
while ( (blockNoInSequence > 0) && (blockIndex != BLOCK_END_OF_CHAIN))
{
if(FAILED(StorageImpl_GetNextBlockInChain(This->parentStorage, blockIndex, &blockIndex)))
return STG_E_DOCFILECORRUPT;
blockNoInSequence--;
}
if ((blockNoInSequence > 0) && (blockIndex == BLOCK_END_OF_CHAIN))
if (blockIndex == BLOCK_END_OF_CHAIN)
return STG_E_DOCFILECORRUPT; /* We failed to find the starting block */
This->lastBlockNoInSequenceIndex = blockIndex;
/*
* Start reading the buffer.
*/
@ -5245,31 +5321,7 @@ HRESULT BlockChainStream_WriteAt(BlockChainStream* This,
/*
* Find the first block in the stream that contains part of the buffer.
*/
if ( (This->lastBlockNoInSequence == 0xFFFFFFFF) ||
(This->lastBlockNoInSequenceIndex == BLOCK_END_OF_CHAIN) ||
(blockNoInSequence < This->lastBlockNoInSequence) )
{
blockIndex = BlockChainStream_GetHeadOfChain(This);
This->lastBlockNoInSequence = blockNoInSequence;
}
else
{
ULONG temp = blockNoInSequence;
blockIndex = This->lastBlockNoInSequenceIndex;
blockNoInSequence -= This->lastBlockNoInSequence;
This->lastBlockNoInSequence = temp;
}
while ( (blockNoInSequence > 0) && (blockIndex != BLOCK_END_OF_CHAIN))
{
if(FAILED(StorageImpl_GetNextBlockInChain(This->parentStorage, blockIndex,
&blockIndex)))
return STG_E_DOCFILECORRUPT;
blockNoInSequence--;
}
This->lastBlockNoInSequenceIndex = blockIndex;
blockIndex = BlockChainStream_GetSectorOfOffset(This, blockNoInSequence);
/* BlockChainStream_SetSize should have already been called to ensure we have
* enough blocks in the chain to write into */
@ -5330,15 +5382,8 @@ HRESULT BlockChainStream_WriteAt(BlockChainStream* This,
static BOOL BlockChainStream_Shrink(BlockChainStream* This,
ULARGE_INTEGER newSize)
{
ULONG blockIndex, extraBlock;
ULONG blockIndex;
ULONG numBlocks;
ULONG count = 1;
/*
* Reset the last accessed block cache.
*/
This->lastBlockNoInSequence = 0xFFFFFFFF;
This->lastBlockNoInSequenceIndex = BLOCK_END_OF_CHAIN;
/*
* Figure out how many blocks are needed to contain the new size
@ -5348,23 +5393,12 @@ static BOOL BlockChainStream_Shrink(BlockChainStream* This,
if ((newSize.u.LowPart % This->parentStorage->bigBlockSize) != 0)
numBlocks++;
blockIndex = BlockChainStream_GetHeadOfChain(This);
if (numBlocks)
{
/*
* Go to the new end of chain
*/
while (count < numBlocks)
{
if(FAILED(StorageImpl_GetNextBlockInChain(This->parentStorage, blockIndex,
&blockIndex)))
return FALSE;
count++;
}
/* Get the next block before marking the new end */
if(FAILED(StorageImpl_GetNextBlockInChain(This->parentStorage, blockIndex,
&extraBlock)))
return FALSE;
blockIndex = BlockChainStream_GetSectorOfOffset(This, numBlocks-1);
/* Mark the new end of chain */
StorageImpl_SetNextBlockInChain(
@ -5373,18 +5407,48 @@ static BOOL BlockChainStream_Shrink(BlockChainStream* This,
BLOCK_END_OF_CHAIN);
This->tailIndex = blockIndex;
}
else
{
if (This->headOfStreamPlaceHolder != 0)
{
*This->headOfStreamPlaceHolder = BLOCK_END_OF_CHAIN;
}
else
{
DirEntry chainEntry;
assert(This->ownerDirEntry != DIRENTRY_NULL);
StorageImpl_ReadDirEntry(
This->parentStorage,
This->ownerDirEntry,
&chainEntry);
chainEntry.startingBlock = BLOCK_END_OF_CHAIN;
StorageImpl_WriteDirEntry(
This->parentStorage,
This->ownerDirEntry,
&chainEntry);
}
This->tailIndex = BLOCK_END_OF_CHAIN;
}
This->numBlocks = numBlocks;
/*
* Mark the extra blocks as free
*/
while (extraBlock != BLOCK_END_OF_CHAIN)
while (This->indexCacheLen && This->indexCache[This->indexCacheLen-1].lastOffset >= numBlocks)
{
if(FAILED(StorageImpl_GetNextBlockInChain(This->parentStorage, extraBlock,
&blockIndex)))
return FALSE;
StorageImpl_FreeBigBlock(This->parentStorage, extraBlock);
extraBlock = blockIndex;
struct BlockChainRun *last_run = &This->indexCache[This->indexCacheLen-1];
StorageImpl_FreeBigBlock(This->parentStorage,
last_run->firstSector + last_run->lastOffset - last_run->firstOffset);
if (last_run->lastOffset == last_run->firstOffset)
This->indexCacheLen--;
else
last_run->lastOffset--;
}
return TRUE;
@ -5498,6 +5562,9 @@ static BOOL BlockChainStream_Enlarge(BlockChainStream* This,
This->numBlocks = newNumBlocks;
}
if (FAILED(BlockChainStream_UpdateIndexCache(This)))
return FALSE;
return TRUE;
}

View File

@ -513,13 +513,22 @@ void StorageUtl_CopyDirEntryToSTATSTG(StorageBaseImpl *storage,STATSTG* destinat
* The BlockChainStream class is a utility class that is used to create an
* abstraction of the big block chains in the storage file.
*/
struct BlockChainRun
{
/* This represents a range of blocks that happen reside in consecutive sectors. */
ULONG firstSector;
ULONG firstOffset;
ULONG lastOffset;
};
struct BlockChainStream
{
StorageImpl* parentStorage;
ULONG* headOfStreamPlaceHolder;
DirRef ownerDirEntry;
ULONG lastBlockNoInSequence;
ULONG lastBlockNoInSequenceIndex;
struct BlockChainRun* indexCache;
ULONG indexCacheLen;
ULONG indexCacheSize;
ULONG tailIndex;
ULONG numBlocks;
};