Menu

#100 New function: FreeImage_GetMemorySize

None
closed
None
5
2015-03-15
2014-12-08
No

Hi Hervé,

here is another FreeImage function I'd like to add to FreeImage. FreeImage_GetMemorySize aims to calculate or at least estimate the total memory usage of a FreeImage bitmap. Basically, this function uses FreeImage_GetInternalImageSize as a starting point. Then, the ICC profile size is added, the size of the thumbnail (if any) by a recursive call to the same function as well as the memory used for all the metadata.

If you find a better name (FreeImage_GetMemoryUsage, ..Footprint, ..._GetSizeInMemory), feel free to rename the function accordingly.

Getting the size of the metadata is a bit tricky, since these are contained in two std::map objects.

First, there is a new (likely not exported, it's your decision) helper function in Metadata\FreeImageTag.cpp, which calculates the size of a FITAG, including chars for key and description. That's still simple.

However, this new function also tries to calculate the size of the std::map instances as well. There is sizeof(METADATAMAP) and nModels * sizeof(TAGMAP). There is also the size (capacity) of the std::string used as the key in TAGMAP. However, that's not all: the std::map has also some internal memory usage. Since std::map is a red-black tree, we have to sum the memory required for the tree's nodes.

Unfortunately, this depends on the implementation of the tree, that is, it depends on the used C++ Standard Library. To manage this, I've implemented class MapIntrospector<_Maptype>. Actually, with #if #elif #endif branches, this class needs to be implemented separately for each known C++ Standard Library. The preprocessor #if statements try to use well-known tests for these three major C++ Standard Libraries:

  • Microsoft C++ Standard Library

  • GNU Standard C++ Library v3, libstdc++-v3

  • "libc++" C++ Standard Library (LLVM)

As a last resort #else branch, there is a implementation, that just makes some assumptions about the typical layouts and sizes of such tree nodes.

True for all implementations: it is likely not exact, but as exact as possible. Likely this or a similar (better) statement should be present in the documentation for function FreeImage_GetMemorySize.

The source code of this class is well documented, so there is an explanation for all implementations, that are actually present. Have a look at it. Additionally, the "libc++" C++ Standard Library (LLVM) implementation is not yet tested, since I have no easy way to use this library on my machines. Maybe someone stuck on a Mac could test this...

I've added file MapIntrospector.h as a separate header file; however, you could as well add this code to Utilities.h, if you like.

Carsten

1 Attachments

Discussion

  • Hervé Drolon

    Hervé Drolon - 2015-02-20

    Hi Carsten,

    What is the use of this (rather complex) function compared to FreeImage_GetDIBSize ?
    I personnaly use FreeImage_GetDIBSize to calculate or update the size of a memory cache (LRU cache) when I need to load / cache images before they are displayed.
    When the cache is full, then I remove the oldest seen images from the cache to make room for the next images to be displayed.

    I don't need to have a very precise size for the images to be loaded, as metadata for example, can be allocated anywhere in the memory : they do not belong to the FIBITMAP blob.
    So the DIB size + a % margin is enough to know the size of an image and to size the cache.

    The only use case I see where you must know precisely the size of a bitmap + its metadata is when you need to serialize a FIBITMAP.
    In this case, there are other solutions for this.

    Maybe you have other use cases in mind ?

    Hervé

     
  • Hervé Drolon

    Hervé Drolon - 2015-02-20
    • assigned_to: Hervé Drolon
    • Group: -->
     
  • Anonymous

    Anonymous - 2015-02-20

    Hi Herve,

    basically, the idea of this function is just to increase the precision of memory calculations. My memory image cache works pretty the same than yours and probably like any other memory cache out there. Typically, such a cache has a size limit. The more precisely you know how much memory your images actually consume, the more precisely you hit that limit. For me, exceeding the limit is worse than not utilizing all dedicated memory; however, a cache using FreeImage_GetDIBSize by design tends to exceed the limit (without a proper % margin).

    Of course, this highly depends on the images. In my quick tests, the error between the DIB size and the actual memory size varies between 0 and 8 percent (actually, FreeImage's test image 'exif.jpg' has an error of 7.5%). So, it is not easy to just assume a % margin. For me, doing some precise calculation is better than making an assumption.

    There is another difference of the DIB size and the memory size, which was introduced with Mihail Naydenov's new external bits support (through function FreeImage_ConvertFromRawBitsEx and my new function FreeImage_CreateView, which you have not yet added :-( ). Function FreeImage_GetMemorySize returns meaningful values for views, that are images that share the DIB data with other FIBITMAPs or even with other non-FreeImage memory. Additionally, FreeImage_GetMemorySize works with LOAD_NOPIXELS images as well.

    Having both functions around is a good idea. FreeImage_GetDIBSize returns what its name suggests: the size in bytes of an image's pixels and palette. As the LOAD_NOPIXELS and FreeImage_ConvertFromRawBitsEx/view images show, for some image types, the DIB size has nothing to do with the actual memory consumption.

    Still, FreeImage_GetDIBSize gets useful information for these special images (and so should not be changed). For a view, for example, the DIB size tells you, how much bytes need to be encoded and written to disk on saving.

    The value returned by function FreeImage_GetDIBSize is just an approximation of an image's memory consumption and may be used for maintaining a memory cache for standard images (no LOAD_NOPIXELS flag and no shared DIB data), but is not suitable for these special images. So, for me it is time for a new function, explicitly targeting the actual memory consumption of an image.

    Although the function gets more complex, its result should be as exact as possible. Of course, there are alternate solutions, like memory guards, which overwrite the C++ runtime's allocator functions, so you have to look at the memory deltas before and after loading an image. However, that sounds not really thread-save and kicks in a lot of code and dependencies, which is surely not an option for FreeImage.

    BTW, as exact as possible... I just found a bug in function FreeImage_GetMemorySize: I did not take into account the size of the FIBITMAP structure itself. So, if you decide to add this function to FreeImage, could you please replace line

    unsigned size = FreeImage_GetInternalImageSize(header_only, width, height,
        bpp, need_masks);
    

    in function FreeImage_GetMemorySize (BitmapAccess.cpp) with these lines:

    // start off with the size of the FIBITMAP structure
    unsigned size = sizeof(FIBITMAP);
    
    // add sizes of FREEIMAGEHEADER, BITMAPINFOHEADER, palette and DIB data
    size += FreeImage_GetInternalImageSize(header_only, width, height,
        bpp, need_masks);
    

    Carsten

     
  • Hervé Drolon

    Hervé Drolon - 2015-03-01
    • status: open --> pending
     
  • Hervé Drolon

    Hervé Drolon - 2015-03-01

    Hi Carsten,

    Thanks for the patch, it is now available in the CVS.

    I've added minor modifications to your patch :

    • General : Use a size_t type to calculate sizes

    • FreeImage_GetTagMemorySize
      => for ASCII strings, the value of the count part of an ASCII tag entry includes the NULL.
      => size += tag_header->length; (no need to add +1)

    • FreeImage_GetMemorySize
      => little improvment in TAGMAP iterator
      const std::string & key = j->first;
      => avoid to call the std::string constructor at each call

    Hervé

     
  • Carsten Klein

    Carsten Klein - 2015-03-02

    Hi Herve,

    first, many thanks for adding this patch. Second, I've got some remarks on your minor modifications (disagree with no 2.).

    1. Using size_t is a good idea.

    2. FIDT_ASCII size

    Actually, we need the + 1 if we really want the size in bytes for FIDT_ASCII values; in both FreeImage_SetTagValue and FreeImage_CloneTag, there are tag_header->length + 1 bytes allocated for a FIDT_ASCII value.

    Have a look at, for example, function jpeg_read_comment in file PluginJPEG.cpp:

    There, a trailing '\0' is appended to the raw JPEG comment. Then, (raw)length + 1 is passed into FreeImage_SetTagValue as the tag's count and length properties.

    In FreeImage_SetTagValue, this length + 1 (that is (raw)length + 2) is allocated and another '\0' is appended to the tag's value. So, for whatever reason, the tag's FIDT_ASCII value is actually terminated by two tailing '\0' characters. The last one is located at FreeImage_GetTagLength(my_tag) + 1.

    Of course, we could probably remove this second trailing '\0' character; however, this my cause problems in many user applications. If so, we could also remove the whole switch block in FreeImage_GetTagMemorySize.

    1. Calling std::string constructor

    Ah, ok, I was not really aware of that. But you are right. Since I was using a non-pointer type (which are just not present in other OOP languages, like Java, C# etc.) C++ first creates a new object an then assigns the value of the other string (the latter being a thin pointer operation hopefully).

    Carsten

     
  • Hervé Drolon

    Hervé Drolon - 2015-03-08

    Hi Carsten,

    I've re-added the "+1" in the FreeImage_GetTagMemorySize string length calculation.
    The double '\0\0' was there to be sure that developpers do not forget to add a '\0' when using metadata functions with strings.

    So you were right to take this into account.

    Hervé

     
  • Hervé Drolon

    Hervé Drolon - 2015-03-15
    • status: pending --> closed
     
  • Hervé Drolon

    Hervé Drolon - 2015-03-15

    fixed in release 3.17.0

     

Anonymous
Anonymous

Add attachments
Cancel