1. 程式人生 > >GDALWarp設定GDALWarpOptions::dfWarpMemoryLimit過大時處理失敗

GDALWarp設定GDALWarpOptions::dfWarpMemoryLimit過大時處理失敗

使用GDALWarp寫了一個裁切影象的演算法,在小記憶體的電腦沒事,大記憶體的電腦就處理失敗(32位也沒問題),檢視GDAL的日誌發現下面的錯誤資訊:

Fri Apr 08 17:39:02 2016: GDAL: GDALOpen(E:/Out/TRIPLESAT_1_PAN_L1_20160330024710_000315VI_005.tif, this=000000000508EB40) succeeds as GTiff.
Fri Apr 08 17:39:02 2016: GDAL: GDALDriver::Create(GTiff,E:\xxx/TRIPLESAT_1_PAN_L1_20160330024710_000315VI_005_20160406003001_001.tif,31260
,24550,1,UInt16,0000000000000000) Fri Apr 08 17:39:04 2016: WARP_TIMING: Output buffer read: 2s Fri Apr 08 17:39:07 2016: WARP_TIMING: Input buffer read: 3s Fri Apr 08 17:39:07 2016: CPLError: Out of memory allocating -1225110252 bytes for UnifiedSrcDensity mask. ERROR 2: Out of memory allocating -1225110252 bytes for UnifiedSrcDensity mask.

從上面的日誌中可以看出,居然申請記憶體失敗了,見過小記憶體的機器申請記憶體失敗的,沒見過大記憶體的機器申請記憶體失敗,很鬱悶。
對比了小記憶體機器和大記憶體機器輸入的資料,所有的引數都一樣,除了GDALWarpOptions::dfWarpMemoryLimit這個。由於程式需要處理大量的資料,而且機器的記憶體也很大(128GB),所以就在程式啟動之後先獲取了系統的空閒記憶體,然後將空閒記憶體的四分之一設定給了dfWarpMemoryLimit這個引數(大記憶體機器這個值大概25GB左右)。將這個值手動改小,程式正確處理了,看來就是這個引數的原因。

通過除錯GDAL的程式碼,在檔案gdalwarpoperation.cpp中的函式GDALWarpOperation::CreateKernelMask中有下面的程式碼(SVN版本庫中的程式碼行數大致在2059行,其他所有的版本都有這個問題,已經提交gdal的bug庫)

    if( *ppMask == NULL )
    {
        int nBytes; //this line should be GIntBig nBytes;

        if( nBitsPerPixel == 32 )
            nBytes = (nXSize * nYSize + nExtraElts) * 4;
        else
            nBytes = (nXSize * nYSize + nExtraElts + 31) / 8;

        *ppMask = VSI_MALLOC_VERBOSE( nBytes );

        if( *ppMask == NULL )
        {
            return CE_Failure;
        }

        memset( *ppMask, nDefault, nBytes );
    }

從上面的程式碼中看的話,其實一般情況下沒有任何問題,除了一種情況,如果nXSize和nYSize很大,比如我日誌中的(31260*24550+nExtraElts)*4,即使nExtraElts=0,結果也會超過int的儲存範圍導致資料溢位,從而nBytes的值就會變成負數,進而分配記憶體的時候就失敗了。下面是修改後的程式碼:

    if( *ppMask == NULL )
    {
        GIntBig  nBytes; 

        if( nBitsPerPixel == 32 )
            nBytes = ((GIntBig )nXSize * nYSize + nExtraElts) * 4;
        else
            nBytes = ((GIntBig )nXSize * nYSize + nExtraElts + 31) / 8;

        *ppMask = VSI_MALLOC_VERBOSE( nBytes );

        if( *ppMask == NULL )
        {
            return CE_Failure;
        }

        memset( *ppMask, nDefault, nBytes );
    }

下面的圖是GDAL出錯時的各個變數的資訊。
GDAL除錯時出錯各個變數的值