LCOV - code coverage report
Current view: directory - port - cpl_vsil_cache.cpp (source / functions) Found Hit Coverage
Test: gdal_filtered.info Lines: 152 138 90.8 %
Date: 2012-12-26 Functions: 16 11 68.8 %

       1                 : /******************************************************************************
       2                 :  * $Id$
       3                 :  *
       4                 :  * Project:  VSI Virtual File System
       5                 :  * Purpose:  Implementation of caching IO layer.
       6                 :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       7                 :  *
       8                 :  ******************************************************************************
       9                 :  * Copyright (c) 2011, Frank Warmerdam <warmerdam@pobox.com>
      10                 :  *
      11                 :  * Permission is hereby granted, free of charge, to any person obtaining a
      12                 :  * copy of this software and associated documentation files (the "Software"),
      13                 :  * to deal in the Software without restriction, including without limitation
      14                 :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      15                 :  * and/or sell copies of the Software, and to permit persons to whom the
      16                 :  * Software is furnished to do so, subject to the following conditions:
      17                 :  *
      18                 :  * The above copyright notice and this permission notice shall be included
      19                 :  * in all copies or substantial portions of the Software.
      20                 :  *
      21                 :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      22                 :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      23                 :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      24                 :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      25                 :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      26                 :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      27                 :  * DEALINGS IN THE SOFTWARE.
      28                 :  ****************************************************************************/
      29                 : 
      30                 : #include "cpl_vsi_virtual.h"
      31                 : 
      32                 : CPL_CVSID("$Id$");
      33                 : 
      34                 : /************************************************************************/
      35                 : /* ==================================================================== */
      36                 : /*                             VSICacheChunk                            */
      37                 : /* ==================================================================== */
      38                 : /************************************************************************/
      39                 : 
      40                 : #define CHUNK_SIZE  32768
      41                 : 
      42                 : class VSICacheChunk
      43                 : {
      44                 : public:
      45             131 :     VSICacheChunk() { 
      46             131 :         poLRUPrev = poLRUNext = NULL;
      47             131 :         nDataFilled = 0;
      48             131 :         bDirty = FALSE;
      49             131 :     }
      50                 : 
      51                 :     int            bDirty;
      52                 :     size_t         iBlock;
      53                 : 
      54                 :     VSICacheChunk *poLRUPrev;
      55                 :     VSICacheChunk *poLRUNext;
      56                 : 
      57                 :     vsi_l_offset   nDataFilled;
      58                 :     GByte          abyData[CHUNK_SIZE];
      59                 : };
      60                 : 
      61                 : /************************************************************************/
      62                 : /* ==================================================================== */
      63                 : /*                             VSICachedFile                            */
      64                 : /* ==================================================================== */
      65                 : /************************************************************************/
      66                 : 
      67                 : class VSICachedFile : public VSIVirtualHandle
      68                 : { 
      69                 :   public:
      70                 :     VSICachedFile( VSIVirtualHandle * );
      71               3 :     ~VSICachedFile() { Close(); }
      72                 : 
      73                 :     void          FlushLRU();
      74                 :     int           LoadBlocks( size_t nStartBlock, size_t nBlockCount, 
      75                 :                               void *pBuffer, size_t nBufferSize );
      76                 :     void          Demote( VSICacheChunk * );
      77                 : 
      78                 :     VSIVirtualHandle *poBase;
      79                 :     
      80                 :     vsi_l_offset  nOffset;
      81                 :     vsi_l_offset  nFileSize;
      82                 : 
      83                 :     GUIntBig      nCacheUsed;
      84                 :     GUIntBig      nCacheMax;
      85                 : 
      86                 :     VSICacheChunk *poLRUStart;
      87                 :     VSICacheChunk *poLRUEnd;
      88                 : 
      89                 :     std::vector<VSICacheChunk*> apoCache;
      90                 : 
      91                 :     int            bEOF;
      92                 : 
      93                 :     virtual int       Seek( vsi_l_offset nOffset, int nWhence );
      94                 :     virtual vsi_l_offset Tell();
      95                 :     virtual size_t    Read( void *pBuffer, size_t nSize, size_t nMemb );
      96                 :     virtual size_t    Write( const void *pBuffer, size_t nSize, size_t nMemb );
      97                 :     virtual int       Eof();
      98                 :     virtual int       Flush();
      99                 :     virtual int       Close();
     100                 : };
     101                 : 
     102                 : /************************************************************************/
     103                 : /*                           VSICachedFile()                            */
     104                 : /************************************************************************/
     105                 : 
     106               3 : VSICachedFile::VSICachedFile( VSIVirtualHandle *poBaseHandle )
     107                 : 
     108                 : {
     109               3 :     poBase = poBaseHandle;
     110                 : 
     111               3 :     nCacheUsed = 0;
     112                 :     nCacheMax = CPLScanUIntBig( 
     113               3 :         CPLGetConfigOption( "VSI_CACHE_SIZE", "25000000" ), 40 );
     114                 : 
     115               3 :     poLRUStart = NULL;
     116               3 :     poLRUEnd = NULL;
     117                 : 
     118               3 :     poBase->Seek( 0, SEEK_END );
     119               3 :     nFileSize = poBase->Tell();
     120                 : 
     121               3 :     nOffset = 0;
     122               3 :     bEOF = FALSE;
     123               3 : }
     124                 : 
     125                 : /************************************************************************/
     126                 : /*                               Close()                                */
     127                 : /************************************************************************/
     128                 : 
     129               6 : int VSICachedFile::Close()
     130                 : 
     131                 : {
     132                 :     size_t i;
     133             174 :     for( i = 0; i < apoCache.size(); i++ )
     134             168 :         delete apoCache[i];
     135                 : 
     136               6 :     apoCache.resize( 0 );
     137                 : 
     138               6 :     poLRUStart = NULL;
     139               6 :     poLRUEnd = NULL;
     140                 : 
     141               6 :     nCacheUsed = 0;
     142                 : 
     143               6 :     if( poBase )
     144                 :     {
     145               3 :         poBase->Close();
     146               3 :         delete poBase;
     147               3 :         poBase = NULL;
     148                 :     }
     149                 : 
     150               6 :     return 0;
     151                 : }
     152                 : 
     153                 : /************************************************************************/
     154                 : /*                                Seek()                                */
     155                 : /************************************************************************/
     156                 : 
     157              15 : int VSICachedFile::Seek( vsi_l_offset nReqOffset, int nWhence )
     158                 : 
     159                 : {
     160              15 :     bEOF = FALSE;
     161                 : 
     162              15 :     if( nWhence == SEEK_SET )
     163                 :     {
     164                 :         // use offset directly.
     165                 :     }
     166                 : 
     167               6 :     else if( nWhence == SEEK_CUR )
     168                 :     {
     169               3 :         nReqOffset += nOffset;
     170                 :     }
     171                 : 
     172               3 :     else if( nWhence == SEEK_END )
     173                 :     {
     174               3 :         nReqOffset += nFileSize;
     175                 :     }
     176                 : 
     177              15 :     nOffset = nReqOffset;
     178                 : 
     179              15 :     return 0;
     180                 : }
     181                 : 
     182                 : /************************************************************************/
     183                 : /*                                Tell()                                */
     184                 : /************************************************************************/
     185                 : 
     186               9 : vsi_l_offset VSICachedFile::Tell()
     187                 : 
     188                 : {
     189               9 :     return nOffset;
     190                 : }
     191                 : 
     192                 : /************************************************************************/
     193                 : /*                              FlushLRU()                              */
     194                 : /************************************************************************/
     195                 : 
     196              83 : void VSICachedFile::FlushLRU()
     197                 : 
     198                 : {
     199              83 :     CPLAssert( poLRUStart != NULL );
     200                 : 
     201              83 :     VSICacheChunk *poBlock = poLRUStart;
     202                 : 
     203              83 :     CPLAssert( nCacheUsed >= poBlock->nDataFilled );
     204                 : 
     205              83 :     nCacheUsed -= poBlock->nDataFilled;
     206                 :     
     207              83 :     poLRUStart = poBlock->poLRUNext;
     208              83 :     if( poLRUEnd == poBlock )
     209               2 :         poLRUEnd = NULL;
     210                 : 
     211              83 :     if( poBlock->poLRUNext != NULL )
     212              81 :         poBlock->poLRUNext->poLRUPrev = NULL;
     213                 : 
     214              83 :     CPLAssert( !poBlock->bDirty );
     215                 : 
     216              83 :     apoCache[poBlock->iBlock] = NULL;
     217                 : 
     218              83 :     delete poBlock;
     219              83 : }
     220                 : 
     221                 : /************************************************************************/
     222                 : /*                               Demote()                               */
     223                 : /*                                                                      */
     224                 : /*      Demote the indicated block to the end of the LRU list.          */
     225                 : /*      Potentially integrate the link into the list if it is not       */
     226                 : /*      already there.                                                  */
     227                 : /************************************************************************/
     228                 : 
     229             131 : void VSICachedFile::Demote( VSICacheChunk *poBlock )
     230                 : 
     231                 : {
     232                 :     // already at end?
     233             131 :     if( poLRUEnd == poBlock )
     234               0 :         return;
     235                 :     
     236             131 :     if( poLRUStart == poBlock )
     237               0 :         poLRUStart = poBlock->poLRUNext;
     238                 : 
     239             131 :     if( poBlock->poLRUPrev != NULL )
     240               0 :         poBlock->poLRUPrev->poLRUNext = poBlock->poLRUNext;
     241                 : 
     242             131 :     if( poBlock->poLRUNext != NULL )
     243               0 :         poBlock->poLRUNext->poLRUPrev = poBlock->poLRUPrev;
     244                 : 
     245             131 :     poBlock->poLRUNext = NULL;
     246             131 :     poBlock->poLRUPrev = NULL;
     247                 : 
     248             131 :     if( poLRUEnd != NULL )
     249             126 :         poLRUEnd->poLRUNext = poBlock;
     250             131 :     poLRUEnd = poBlock;
     251                 :     
     252             131 :     if( poLRUStart == NULL )
     253               5 :         poLRUStart = poBlock;
     254                 : }
     255                 : 
     256                 : /************************************************************************/
     257                 : /*                             LoadBlocks()                             */
     258                 : /*                                                                      */
     259                 : /*      Load the desired set of blocks.  Use pBuffer as a temporary     */
     260                 : /*      buffer if it would be helpful.                                  */
     261                 : /************************************************************************/
     262                 : 
     263              63 : int VSICachedFile::LoadBlocks( size_t nStartBlock, size_t nBlockCount,
     264                 :                                void *pBuffer, size_t nBufferSize )
     265                 : 
     266                 : {
     267              63 :     if( nBlockCount == 0 )
     268               0 :         return 1;
     269                 : 
     270              63 :     if( apoCache.size() < nStartBlock + nBlockCount )
     271               9 :         apoCache.resize( nStartBlock + nBlockCount );
     272                 : 
     273                 : /* -------------------------------------------------------------------- */
     274                 : /*      When we want to load only one block, we can directly load it    */
     275                 : /*      into the target buffer with no concern about intermediaries.    */
     276                 : /* -------------------------------------------------------------------- */
     277              63 :     if( nBlockCount == 1 )
     278                 :     {
     279               7 :         poBase->Seek( nStartBlock * CHUNK_SIZE, SEEK_SET );
     280                 : 
     281               7 :         apoCache[nStartBlock] = new VSICacheChunk();
     282                 : 
     283               7 :         VSICacheChunk *poBlock = apoCache[nStartBlock];
     284                 : 
     285               7 :         poBlock->iBlock = nStartBlock;
     286               7 :         poBlock->nDataFilled = poBase->Read( poBlock->abyData, 1, CHUNK_SIZE );
     287               7 :         nCacheUsed += poBlock->nDataFilled;
     288                 : 
     289                 :         // Merges into the LRU list. 
     290               7 :         Demote( poBlock );
     291                 : 
     292               7 :         return 1;
     293                 :     }
     294                 : 
     295                 : /* -------------------------------------------------------------------- */
     296                 : /*      If the buffer is quite large but not quite large enough to      */
     297                 : /*      hold all the blocks we will take the pain of splitting the      */
     298                 : /*      io request in two in order to avoid allocating a large          */
     299                 : /*      temporary buffer.                                               */
     300                 : /* -------------------------------------------------------------------- */
     301              56 :     if( nBufferSize > CHUNK_SIZE * 20 
     302                 :         && nBufferSize < nBlockCount * CHUNK_SIZE )
     303                 :     {
     304               1 :         if( !LoadBlocks( nStartBlock, 2, pBuffer, nBufferSize ) )
     305               0 :             return 0;
     306                 : 
     307               1 :         return LoadBlocks( nStartBlock+2, nBlockCount-2, pBuffer, nBufferSize );
     308                 :     }
     309                 : 
     310                 : /* -------------------------------------------------------------------- */
     311                 : /*      Do we need to allocate our own buffer?                          */
     312                 : /* -------------------------------------------------------------------- */
     313              55 :     GByte *pabyWorkBuffer = (GByte *) pBuffer;
     314                 : 
     315              55 :     if( nBufferSize < CHUNK_SIZE * nBlockCount )
     316               1 :         pabyWorkBuffer = (GByte *) CPLMalloc(CHUNK_SIZE * nBlockCount);
     317                 : 
     318                 : /* -------------------------------------------------------------------- */
     319                 : /*      Read the whole request into the working buffer.                 */
     320                 : /* -------------------------------------------------------------------- */
     321              55 :     if( poBase->Seek( nStartBlock * CHUNK_SIZE, SEEK_SET ) != 0 )
     322               0 :         return 0;
     323                 : 
     324              55 :     size_t nDataRead = poBase->Read( pabyWorkBuffer, 1, nBlockCount*CHUNK_SIZE);
     325                 : 
     326              55 :     if( nBlockCount * CHUNK_SIZE > nDataRead + CHUNK_SIZE - 1 )
     327              48 :         nBlockCount = (nDataRead + CHUNK_SIZE - 1) / CHUNK_SIZE;
     328                 : 
     329             179 :     for( size_t i = 0; i < nBlockCount; i++ )
     330                 :     {
     331             124 :         VSICacheChunk *poBlock = new VSICacheChunk();
     332                 : 
     333             124 :         poBlock->iBlock = nStartBlock + i;
     334                 : 
     335             124 :         CPLAssert( apoCache[i+nStartBlock] == NULL );
     336                 : 
     337             124 :         apoCache[i + nStartBlock] = poBlock;
     338                 : 
     339             124 :         if( nDataRead >= (i+1) * CHUNK_SIZE )
     340             124 :             poBlock->nDataFilled = CHUNK_SIZE;
     341                 :         else
     342               0 :             poBlock->nDataFilled = nDataRead - i*CHUNK_SIZE;
     343                 : 
     344                 :         memcpy( poBlock->abyData, pabyWorkBuffer + i*CHUNK_SIZE,
     345             124 :                 (size_t) poBlock->nDataFilled );
     346                 : 
     347             124 :         nCacheUsed += poBlock->nDataFilled;
     348                 : 
     349                 :         // Merges into the LRU list. 
     350             124 :         Demote( poBlock );
     351                 :     }
     352                 : 
     353              55 :     if( pabyWorkBuffer != pBuffer )
     354               1 :         CPLFree( pabyWorkBuffer );
     355                 : 
     356              55 :     return 1;
     357                 : }
     358                 : 
     359                 : /************************************************************************/
     360                 : /*                                Read()                                */
     361                 : /************************************************************************/
     362                 : 
     363              12 : size_t VSICachedFile::Read( void * pBuffer, size_t nSize, size_t nCount )
     364                 : 
     365                 : {
     366              12 :     if( nOffset >= nFileSize )
     367                 :     {
     368               3 :         bEOF = TRUE;
     369               3 :         return 0;
     370                 :     }
     371                 : 
     372                 : /* ==================================================================== */
     373                 : /*      Make sure the cache is loaded for the whole request region.     */
     374                 : /* ==================================================================== */
     375               9 :     size_t nStartBlock = (size_t) (nOffset / CHUNK_SIZE);
     376               9 :     size_t nEndBlock = (size_t) ((nOffset + nSize * nCount - 1) / CHUNK_SIZE);
     377                 :     
     378             189 :     for( size_t iBlock = nStartBlock; iBlock <= nEndBlock; iBlock++ )
     379                 :     {
     380             180 :         if( apoCache.size() <= iBlock || apoCache[iBlock] == NULL )
     381                 :         {
     382              58 :             size_t nBlocksToLoad = 1;
     383             639 :             while( iBlock + nBlocksToLoad <= nEndBlock
     384                 :                    && (apoCache.size() <= iBlock+nBlocksToLoad 
     385                 :                        || apoCache[iBlock+nBlocksToLoad] == NULL) )
     386             523 :                 nBlocksToLoad++;
     387                 : 
     388              58 :             LoadBlocks( iBlock, nBlocksToLoad, pBuffer, nSize * nCount );
     389                 :         }
     390                 :     }
     391                 :     
     392                 : /* ==================================================================== */
     393                 : /*      Copy data into the target buffer to the extent possible.        */
     394                 : /* ==================================================================== */
     395               9 :     size_t nAmountCopied = 0;
     396                 : 
     397             150 :     while( nAmountCopied < nSize * nCount )
     398                 :     {
     399             135 :         size_t iBlock = (size_t) ((nOffset + nAmountCopied) / CHUNK_SIZE);
     400                 :         size_t nThisCopy;
     401             135 :         VSICacheChunk *poBlock = apoCache[iBlock];
     402             135 :         if( poBlock == NULL )
     403                 :         {
     404                 :             /* We can reach that point when the amount to read exceeds */
     405                 :             /* the cache size */
     406                 :             LoadBlocks( iBlock, 1, ((GByte *) pBuffer) + nAmountCopied,
     407               3 :                         MIN(nSize * nCount - nAmountCopied, CHUNK_SIZE) );
     408               3 :             poBlock = apoCache[iBlock];
     409               3 :             CPLAssert(poBlock != NULL);
     410                 :         }
     411                 : 
     412                 :         nThisCopy = (size_t)
     413                 :             ((iBlock * CHUNK_SIZE + poBlock->nDataFilled) 
     414             135 :              - nAmountCopied - nOffset);
     415                 :         
     416             135 :         if( nThisCopy > nSize * nCount - nAmountCopied )
     417               3 :             nThisCopy = nSize * nCount - nAmountCopied;
     418                 : 
     419             135 :         if( nThisCopy == 0 )
     420               3 :             break;
     421                 : 
     422                 :         memcpy( ((GByte *) pBuffer) + nAmountCopied,
     423                 :                 poBlock->abyData 
     424                 :                 + (nOffset + nAmountCopied) - iBlock * CHUNK_SIZE, 
     425             132 :                 nThisCopy );
     426                 : 
     427             132 :         nAmountCopied += nThisCopy;
     428                 :     }
     429                 :     
     430               9 :     nOffset += nAmountCopied;
     431                 : 
     432                 : /* -------------------------------------------------------------------- */
     433                 : /*      Ensure the cache is reduced to our limit.                       */
     434                 : /* -------------------------------------------------------------------- */
     435             101 :     while( nCacheUsed > nCacheMax )
     436              83 :         FlushLRU();
     437                 : 
     438               9 :     size_t nRet = nAmountCopied / nSize;
     439               9 :     if (nRet != nCount)
     440               3 :         bEOF = TRUE;
     441               9 :     return nRet;
     442                 : }
     443                 : 
     444                 : /************************************************************************/
     445                 : /*                               Write()                                */
     446                 : /************************************************************************/
     447                 : 
     448               0 : size_t VSICachedFile::Write( const void * pBuffer, size_t nSize, size_t nCount )
     449                 : 
     450                 : {
     451               0 :     return 0;
     452                 : }
     453                 : 
     454                 : /************************************************************************/
     455                 : /*                                Eof()                                 */
     456                 : /************************************************************************/
     457                 : 
     458               0 : int VSICachedFile::Eof()
     459                 : 
     460                 : {
     461               0 :     return bEOF;
     462                 : }
     463                 : 
     464                 : /************************************************************************/
     465                 : /*                               Flush()                                */
     466                 : /************************************************************************/
     467                 : 
     468               0 : int VSICachedFile::Flush()
     469                 : 
     470                 : {
     471               0 :     return 0;
     472                 : }
     473                 : 
     474                 : /************************************************************************/
     475                 : /*                        VSICreateCachedFile()                         */
     476                 : /************************************************************************/
     477                 : 
     478                 : VSIVirtualHandle *
     479               3 : VSICreateCachedFile( VSIVirtualHandle *poBaseHandle )
     480                 : 
     481                 : {
     482               3 :     return new VSICachedFile( poBaseHandle );
     483                 : }

Generated by: LCOV version 1.7