开发者

Extra allocations and magical space reduction in the STL - using rvalue references

I replaced the Standard allocator with an allocator that will "phone home" about how much memory it consumes. Now I'm going through some of my code, wonderi开发者_JAVA技巧ng why the hell it allocates and then deallocates so many entries.

Just for reference, I'm not trying to pre-optimize my code or anything, I'm mostly curious, except I definitely need to know if my total size is off, because I need to know exactly how much my object is using for the C# GC.

Take this sample function:

void add_file(string filename, string source) {
    file_source_map.insert(std::pair<const string, string>(std::move(filename), std::move(source)));
}

It allocates six times (48bytes), and then deallocates four times(32bytes). Since the pair is an rvalue, and I moved the strings into it, surely the map will allocate a new node and move the rvalue pair into it, without triggering any more allocations and certainly not having to de-allocate any. The filename and source arguments also come from rvalues and should be moved in, instead of copied in. Just a note: the string is also being tracked by the allocator, it's not std::string but std::basic_string<char, std::char_traits<char>, Allocator<char>>.

Just for reference, I'm on MSVC.

Here's my allocator code:

template<typename T>
class Allocator {
public : 
    //    typedefs

    typedef T value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type& reference;
    typedef const value_type& const_reference;
    typedef std::size_t size_type;
    typedef std::ptrdiff_t difference_type;

public : 
    //    convert an allocator<T> to allocator<U>

    template<typename U>
    struct rebind {
        typedef Allocator<U> other;
    };

public : 
    Parser* parser;
    inline ~Allocator() {}
    inline Allocator(Allocator const& other) {
        parser = other.parser;
    }
    inline Allocator(Parser* ptr)
        : parser(ptr) {}
    template<typename U>
    inline Allocator(Allocator<U> const& other) {
        parser = other.parser;
    }

    //    address

    inline pointer address(reference r) { return &r; }
    inline const_pointer address(const_reference r) { return &r; }

    //    memory allocation

    inline pointer allocate(size_type cnt, 
        typename std::allocator<void>::const_pointer = 0) { 
            int newsize = cnt * sizeof (T);
            parser->size += newsize;
            std::cout << "Allocated " << newsize << "\n";
            return reinterpret_cast<pointer>(::operator new(newsize)); 
    }
    inline void deallocate(pointer p, size_type count) {
        size_type size = count * sizeof(T);
        ::operator delete(p); 
        parser->size -= size;
        std::cout << "Deallocated " << size << "\n";
    }

    //    size

    inline size_type max_size() const { 
        return std::numeric_limits<size_type>::max() / sizeof(T);
    }

    //    construction/destruction

    inline void construct(pointer p, const T& t) { new(p) T(t); }
    inline void destroy(pointer p) { p->~T(); }

    inline bool operator==(Allocator const& other) { return other.parser == parser; }
    inline bool operator!=(Allocator const& a) { return !operator==(a); }
};

When I call add_file (posted above) from C# via the wrapper functions, I can clearly see each allocation and deallocation and their appropriate sizes on the console, and that is four allocations of 8, one of 80 which I know comes from the map, two more allocations of 8, and then four deallocations of 8, which says to me that there are four redundant strings in the function, because they're all rvalues and there's no reason for any deallocations to occur.


I ran your code in VS 2010, and I believe that the allocations you see are only Visual Studio STL debugging facilities as all the 8 bytes allocations are issued from _String_val constructor :

  • In release (_ITERATOR_DEBUG_LEVEL == 0), the constructor is trivial
  • In debug (_ITERATOR_DEBUG_LEVEL != 0), it allocates a _Container_proxy (which happens to have a size of 8) through the allocator.

If I run your code in release mode, the node allocation for the map falls to 72 bytes and the 8 bytes allocation and deallocation disappear : the strings seem to be correctly moved.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜