STL Allocator的简单实现及效率对比

2023-10-19  本文已影响0人  FredricZhu

本例一共实现了两个简单的allocator,一个使用全局的::new和::delete操作符实现的Allocator。
一个使用固定大小的内存块实现的block_allocator。
在实现一个allocator的时候,一般先要定义一堆的typedef,如下,


image.png

接着需要实现几个固定的接口方法,例如address,construct, destroy,max_size, operator==,operator!=,allocate和deallocate等。

配合allocate和deallocate方法需要实现一个特定的内存控制器,本例的内存控制器[归属于block_allocator类],叫做block_o_memory。

本例分别使用三种不同分配器的std::string,来测试最原始的remove_ctrl算法的效率,主要想看下不同的分配器[allocator],对std::string算法效率的影响。

代码如下,
conanfile.txt

[requires]
boost/1.72.0

[generators]
cmake

CMakeLists.txt

cmake_minimum_required(VERSION 2.6)

project(optimize)
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig/")

set ( CMAKE_CXX_FLAGS "-pthread")
set(CMAKE_CXX_STANDARD 17)
add_definitions(-g)

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
LINK_DIRECTORIES(${LINK_DIRS})

file( GLOB main_file_list ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) 
file( GLOB source_file_list ${CMAKE_CURRENT_SOURCE_DIR}/impl/*.cpp)

foreach( main_file ${main_file_list} )
    file(RELATIVE_PATH filename ${CMAKE_CURRENT_SOURCE_DIR} ${main_file})
    string(REPLACE ".cpp" "" file ${filename})
    add_executable(${file}  ${main_file} ${source_file_list})
    target_link_libraries(${file}  ${CONAN_LIBS} pthread atomic)
endforeach( main_file ${main_file_list})

old_stl_allocator_test.cpp

#include "test_driver.h"
#include "simple_allocator.h"
#include "old_block_allocator.h"
#include "stopwatch11.h"

#include <iostream>
#include <string>


using SimpleStringT = std::basic_string<
    char,
    std::char_traits<char>,
    Allocator<char>>;

using fixed_block_string = std::basic_string<
    char,
    std::char_traits<char>,
    block_allocator<char, 10>>;


// 原始的remove ctrl的三种写法 std::string, SimpleStringT, fixed_block_string
std::string remove_ctrl(std::string s) {
    std::string result;
    for(std::size_t i=0; i<s.length(); ++i) {
        if(s[i] >= 0x20) {
            result = result + s[i];
        }
    }
    return result;
}

SimpleStringT remove_ctrl_simple(std::string s) {
    SimpleStringT result;
    for(std::size_t i=0; i<s.length(); ++i) {
        if(s[i] >= 0x20) {
            result = result + s[i];
        }
    }
    return result;
}

fixed_block_string remove_ctrl_fixed_block(std::string s) {
    fixed_block_string result;
    for(std::size_t i=0; i<s.length(); ++i) {
        if(s[i] >= 0x20) {
            result = result + s[i];
        }
    }
    return result;
}

int test_fixed_block_allocator(int test_no, unsigned long multiplier) {
    typedef unsigned counter_t;
    counter_t const iterations = 100 * multiplier;

    std::string s("\07Now is the time\07 for all good men\r\n to come to the aid of their country. \07");
    std::string test("Now is the time for all good men to come to the aid of their country. ");
    s = s + s + s;
    test = test + test + test;

    std::string stdresult;
    fixed_block_string fbresult;
    SimpleStringT spresult;

    bool rc = true;

    switch(test_no) {
        default: return -1;
        case 0: return 2;

        case 1: {
            {
                void* p = block_o_memory::allocate(100);
                void* q = block_o_memory::allocate(200);
                void* r = block_o_memory::allocate(333);
                block_o_memory::deallocate(p);
                block_o_memory::deallocate(q);
                block_o_memory::deallocate(r);
                rc &= (block_o_memory::count() <= 1); 
            }

            std::cout << s.length() << " character argument to remove_ctrl()" << std::endl;
            std::cout << iterations << " iterations" << std::endl;

            {
                std::string verifier;
                verifier = remove_ctrl(s);
                rc &= (verifier.compare(test) == 0);

                SimpleStringT result1 = remove_ctrl_simple(s);
                verifier = result1.data();
                rc &= (verifier.compare(test) == 0);

                fixed_block_string result2 = remove_ctrl_fixed_block(s);
                verifier = result2.data();
                rc &= (verifier.compare(test) == 0);
            }
        }
            break;
        
        case 2: {
            {
                StopWatch sw("remove_ctrl()");
                for(counter_t i=0; i<iterations; ++i) {
                    stdresult = remove_ctrl(s);
                }
            }

            {
                StopWatch sw("remove_ctrl_simple()");
                for(counter_t i=0; i<iterations; ++i) {
                    spresult = remove_ctrl_simple(s);
                }
            }

              {
                StopWatch sw("remove_ctrl_fixed_block()");
                for(counter_t i=0; i<iterations; ++i) {
                    fbresult = remove_ctrl_fixed_block(s);
                }
            }
        }
            break;
    } 

    return (rc)? 1: 0;
}

int (*func[])(int, unsigned long) = {
    test_fixed_block_allocator,
    0
};

int main(int argc, char* argv[]) {
    test_driver(func, argc, argv);
    return EXIT_SUCCESS;
}

simple_allocator.h

#ifndef _FREDRIC_SIMPLE_ALLOCATOR_H_
#define _FREDRIC_SIMPLE_ALLOCATOR_H_

#include <limits>
#include <memory>

template <typename T>
class Allocator {
public:
    typedef T value_type;
    typedef T* pointer;
    typedef T const* const_pointer;
    typedef T& reference;
    typedef T const& const_reference;
    typedef std::size_t size_type;
    typedef std::ptrdiff_t difference_type;
public:
    template <typename U>
    struct rebind {
        typedef Allocator<U> other;
    };
public:
    Allocator() {}
    ~Allocator() {}
    Allocator(Allocator const&) {}
    template <typename U>
    explicit Allocator(Allocator<U> const&) {}

    const_pointer address(const_reference r) {
        return &r;
    }

    void construct(pointer p, T const& t) {
        new (p) T(t);
    }

    void destroy(pointer p) {
        p->~T();
    }

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

    bool operator==(Allocator const&)  {
        return true;
    }

    bool operator!=(Allocator const& a) {
        return !operator==(a);
    } 

    pointer allocate(
        size_type count, 
        typename std::allocator<void>::const_pointer = 0
    ) {
        return reinterpret_cast<pointer>(::operator new(count* sizeof(T)));
    }

    void deallocate(pointer p, size_type) {
        ::operator delete(p);
    }

};
#endif

old_block_allocator.h

#ifndef _FREDRIC_OLD_BLOCK_ALLOCATOR_H_
#define _FREDRIC_OLD_BLOCK_ALLOCATOR_H_
#include <limits>
#include <memory>

// 快速的不好的fixed block分配器
// 实现上在查找free block时,有一个O(n)的复杂度,不好

class block_o_memory {
public:
    static unsigned const size = 8;
    static size_t const blocksize = 512;
    static void* allocate(std::size_t size);
    static void deallocate(void* p);
    static unsigned count();

private:
    static char mem_[size][blocksize];
    static bool free_[size]; // free_指示每个块是否空闲
};

template <typename T, size_t n>
class block_allocator {

public:

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

    pointer address(reference r) {
        return &r;
    }

    const_pointer address(const_reference r) {
        return &r;
    }
    
    template <typename U>
    struct rebind {
        typedef block_allocator<U, n> other;
    };

public:
    block_allocator() {}
    ~block_allocator() {}
    block_allocator(block_allocator const&) {}

    template <typename U>
    explicit block_allocator(block_allocator<U, n> const&) {}

    void construct(pointer p, T const& t) {
        return new (p) T(t);
    }

    void destroy(pointer p) {
        p->~T();
    }

    size_type max_size() const {
        return block_o_memory::blocksize;
    }

    bool operator==(block_allocator const& other) const {
        return true;
    }

    bool operator!=(block_allocator const& a) const {
        return !operator==(a);
    }

    pointer allocate(size_type count,
        typename std::allocator<void>::const_pointer = 0
    ) {
        return reinterpret_cast<pointer>(block_o_memory::allocate(count * sizeof(T)));
    }

    void deallocate(pointer p, size_type) {
        block_o_memory::deallocate(p);
    }
};
#endif

old_block_allocator.cpp

#include "old_block_allocator.h"
#include <exception>

char block_o_memory::mem_[block_o_memory::size][block_o_memory::blocksize];
bool block_o_memory::free_[block_o_memory::size] = {true, true, true, true, true, true, true, true};

void* block_o_memory::allocate(std::size_t size) {
    if(size <= blocksize) {
        for(unsigned i=0; i<sizeof(free_)/sizeof(free_[0]); ++i) {
            if(free_[i]) {
                free_[i] = false;
                return mem_[i];
            }
        }
    }

    throw std::bad_alloc();
}

void block_o_memory::deallocate(void* p) {
    typedef char blocktype[512];
    if(p==0) {
        return;
    }

    if(p<(void*)mem_ || p>(void*)(mem_+size)) {
        throw std::bad_alloc();
    }

    unsigned i = static_cast<blocktype*>(p) - mem_;
    free_[i] = true;
    mem_[i][0] = 0;
}

// 计算分配了多少个block, debugging用
unsigned block_o_memory::count() {
    unsigned ct = 0;
    for(unsigned i=0; i<size; ++i) {
        if(!free_[i]) {
            ct += 1;
        }
    }
    return ct;
}

程序输出如下,
可以看出,在实验次数较少时,使用自定义分配器,可能有一定的效果。但是在实验次数较多时,特别是频繁的分配释放内存的场景时,改变allocator起不到多大效果。


image.png
上一篇下一篇

猜你喜欢

热点阅读