我怎样才能避免std :: vector<>初始化所有元素?

时间:2011-05-11 02:26:07

标签: c++ optimization gcc default-value

编辑:我将问题及其标题编辑得更精确。

考虑以下源代码:

#include <vector>
struct xyz {
    xyz() { } // empty constructor, but the compiler doesn't care
    xyz(const xyz& o): v(o.v) { } 
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v; // <will be initialized to int(), which means 0
};

std::vector<xyz> test() {
    return std::vector<xyz>(1024); // will do a memset() :-(
}

...如何避免向量&lt;&gt;分配的内存?用它的第一个元素的副本进行初始化,这是一个O(n)操作,我宁愿为了速度而跳过,因为我的默认构造函数什么都不做?

如果不存在通用的解决方案,那么g ++特定的解决方案就可以了(但我找不到任何属性)。

编辑:生成的代码如下(命令行:arm-elf-g ++ - 4.5 -O3 -S -fno-verbose-asm -o-test.cpp | arm-elf-c ++ filt | grep -vE'^ [[:space:]] + [。@]。* $')

test():
    mov r3, #0
    stmfd   sp!, {r4, lr}
    mov r4, r0
    str r3, [r0, #0]
    str r3, [r0, #4]
    str r3, [r0, #8]
    mov r0, #4096
    bl  operator new(unsigned long)
    add r1, r0, #4096
    add r2, r0, #4080
    str r0, [r4, #0]
    stmib   r4, {r0, r1}
    add r2, r2, #12
    b       .L4          @
.L8:                     @
    add     r0, r0, #4   @
.L4:                     @
    cmp     r0, #0       @  fill the memory
    movne   r3, #0       @
    strne   r3, [r0, #0] @
    cmp     r0, r2       @
    bne     .L8          @
    str r1, [r4, #4]
    mov r0, r4
    ldmfd   sp!, {r4, pc}

编辑:为了完整起见,这里是x86_64的程序集:

.globl test()
test():
LFB450:
    pushq   %rbp
LCFI0:
    movq    %rsp, %rbp
LCFI1:
    pushq   %rbx
LCFI2:
    movq    %rdi, %rbx
    subq    $8, %rsp
LCFI3:
    movq    $0, (%rdi)
    movq    $0, 8(%rdi)
    movq    $0, 16(%rdi)
    movl    $4096, %edi
    call    operator new(unsigned long)
    leaq    4096(%rax), %rcx
    movq    %rax, (%rbx)
    movq    %rax, 8(%rbx)
    leaq    4092(%rax), %rdx
    movq    %rcx, 16(%rbx)
    jmp     L4          @
L8:                     @
    addq    $4, %rax    @
L4:                     @
    testq   %rax, %rax  @ memory-filling loop
    je      L2          @
    movl    $0, (%rax)  @
L2:                     @
    cmpq    %rdx, %rax  @
    jne     L8          @
    movq    %rcx, 8(%rbx)
    movq    %rbx, %rax
    addq    $8, %rsp
    popq    %rbx
    leave
LCFI4:
    ret
LFE450:
EH_frame1:
LSCIE1:
LECIE1:
LSFDE1:
LASFDE1:
LEFDE1:

编辑:我认为当你想避免不必要的初始化时,结论是不使用std::vector<>。我最终展开了我自己的模板化容器,它表现得更好(并且有专门用于neon和armv7的版本)。

9 个答案:

答案 0 :(得分:12)

分配的元素的初始化由Allocator模板参数控制,如果需要自定义,则自定义它。但请记住,在脏黑客的情况下,这很容易结束,因此请谨慎使用。例如,这是一个非常脏的解决方案。它将避免初始化,但它很可能会在性能上更差,但出于演示的缘故(因为人们已经说过这是不可能的!......不可能不是C ++程序员的词汇!):

template <typename T>
class switch_init_allocator : public std::allocator< T > {
  private:
    bool* should_init;
  public:
    template <typename U>
    struct rebind {
      typedef switch_init_allocator<U> other;
    };

    //provide the required no-throw constructors / destructors:
    switch_init_allocator(bool* aShouldInit = NULL) throw() : std::allocator<T>(), should_init(aShouldInit) { };
    switch_init_allocator(const switch_init_allocator<T>& rhs) throw() : std::allocator<T>(rhs), should_init(rhs.should_init) { };
    template <typename U>
    switch_init_allocator(const switch_init_allocator<U>& rhs, bool* aShouldInit = NULL) throw() : std::allocator<T>(rhs), should_init(aShouldInit) { };
    ~switch_init_allocator() throw() { };

    //import the required typedefs:
    typedef typename std::allocator<T>::value_type value_type;
    typedef typename std::allocator<T>::pointer pointer;
    typedef typename std::allocator<T>::reference reference;
    typedef typename std::allocator<T>::const_pointer const_pointer;
    typedef typename std::allocator<T>::const_reference const_reference;
    typedef typename std::allocator<T>::size_type size_type;
    typedef typename std::allocator<T>::difference_type difference_type;

    //redefine the construct function (hiding the base-class version):
    void construct( pointer p, const_reference cr) {
      if((should_init) && (*should_init))
        new ((void*)p) T ( cr );
      //else, do nothing.
    };
};

template <typename T>
class my_vector : public std::vector<T, switch_init_allocator<T> > {
  public:
    typedef std::vector<T, switch_init_allocator<T> > base_type;
    typedef switch_init_allocator<T> allocator_type;
    typedef std::vector<T, allocator_type > vector_type;
    typedef typename base_type::size_type size_type;
  private:
    bool switch_flag; //the order here is very important!!
    vector_type vec;
  public:  
    my_vector(size_type aCount) : switch_flag(false), vec(aCount, allocator_type(&switch_flag)) { };
    //... and the rest of this wrapper class...
    vector_type& get_vector() { return vec; };
    const vector_type& get_vector() const { return vec; };
    void set_switch(bool value) { switch_flag = value; };
};

class xyz{};

int main(){
  my_vector<xyz> v(1024); //this won't initialize the memory at all.
  v.set_switch(true); //set back to true to turn initialization back on (needed for resizing and such)
}

当然,上面的内容很笨拙而且不推荐,当然不会比实际让内存充满第一个元素的副本更好(特别是因为使用这个标志检查会阻碍每个元素-施工)。但是,当想要优化STL容器中元素的分配和初始化时,这是一个探索的途径,所以我想展示它。关键是,你可以注入代码的唯一地方是阻止std :: vector容器调用copy-constructor来初始化你的元素,这是在vector的allocator对象的construct函数中。

此外,您可以取消“切换”并简单地执行“no-init-allocator”,但是,您还可以关闭在调整大小期间复制数据所需的复制构造(这会使矢量类更不用了)。

答案 1 :(得分:9)

这是vector的一个奇怪的角落。问题是您的元素正在初始化值...这是第一个原型元素中的随机内容被复制到向量中的所有其他元素。 (此行为随C ++ 11而改变,该值初始化每个元素。)

这是(/ was)有充分理由:考虑一些引用计数对象...如果你构造一个vector要求1000个元素初始化为这样的对象,你显然想要一个带有引用的对象数量为1000,而不是拥有1000个独立的“克隆”。我说“显然”是因为首先计算了对象引用意味着非常需要它。

无论如何,你几乎没有运气。实际上,vector确保所有元素都是相同的,即使它同步的内容恰好是未初始化的垃圾。


在非标准g ++特定的快乐黑客攻击的范围内,我们可以利用vector接口中的任何公开模板化成员函数作为后门来简单地通过专门化一些新类型的模板来更改私有成员数据

警告:不仅仅是针对此“解决方案”,而是为了避免默认构建的整个过程... 对于具有重要不变量的类型不执行此操作 - 您破解封装并且可以轻松地使vector本身或您尝试调用operator=(),复制构造函数和/或析构函数的某些操作,其中*this /左和/或右侧参数不要不尊重那些不变量。例如,避免使用您希望为NULL的指针的值类型或有效对象,引用计数器,资源句柄等。

#include <iostream>
#include <vector>

struct Uninitialised_Resize
{
    explicit Uninitialised_Resize(int n) : n_(n) { }
    explicit Uninitialised_Resize() { }
    int n_;
};

namespace std
{
    template <>
    template <>
    void vector<int>::assign(Uninitialised_Resize ur, Uninitialised_Resize)
    {
        this->_M_impl._M_finish = this->_M_impl._M_start + ur.n_;

        // note: a simpler alternative (doesn't need "n_") is to set...
        //   this->_M_impl._M_finish = this->_M_impl._M_end_of_storage;
        // ...which means size() will become capacity(), which may be more
        // you reserved() (due to rounding; good) or have data for
        // (bad if you have to track in-use elements elsewhere,
        //  which makes the situation equivalent to just reserve()),
        // but if you can somehow use the extra elements then all's good.
    }
}

int main()
{
    {
        // try to get some non-0 values on heap ready for recycling...
        std::vector<int> x(10000);
        for (int i = 0; i < x.size(); ++i)
            x[i] = i;
    }

    std::vector<int> x;
    x.reserve(10000);
    for (int i = 1; i < x.capacity(); ++i)
        if (x[0] != x[i])
        {
            std::cout << "lucky\n";
            break;
        }
    x.assign(Uninitialised_Resize(1000), Uninitialised_Resize());

    for (int i = 1; i < x.size(); ++i)
        if (x[0] != x[i])
        {
            std::cout << "success [0] " << x[0] << " != [" << i << "] "
                << x[i] << '\n';
            break;
        }
}

我的输出:

lucky
success [0] 0 != [1] 1

这表明新的vector被重新分配给第一个向量在超出范围时释放的堆,并显示值不被赋值所破坏。当然,如果不仔细检查vector来源,就无法知道其他一些重要的类不变量是否已失效,私人成员的确切名称/导入可能随时变化....

答案 2 :(得分:3)

将所有基元包装在结构中:

struct IntStruct
{
    IntStruct();

    int myInt;
}

将IntStruct()定义为空构造函数。因此,您将v声明为IntStruct v;,因此当vector xyzs所有值初始化时,他们所做的只是值初始化v,这是一个无操作。

编辑:我误解了这个问题。如果您有vector原始类型,那么应该这样做,因为vector被定义为在通过resize()方法创建元素时进行值初始化。在构造时,结构不需要对其成员进行值初始化,尽管这些“未初始化”的值仍然可以通过其他东西设置为0 - 嘿,它们可以是任何东西。

答案 3 :(得分:1)

我没有看到内存初始化。默认的int()构造函数不执行任何操作,就像在C中一样。

程序:

#include <iostream>
#include <vector>

struct xyz {
    xyz() {}
    xyz(const xyz& o): v(o.v) {} 
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v;
};

std::vector<xyz> test() {
    return std::vector<xyz>(1024);
}

int main()
{
    std::vector<xyz> foo = test();
    for(int i = 0; i < 10; ++i)
    {
        std::cout << i << ": " << foo[i].v << std::endl;
    }
    return 0;
}

输出:

$ g++ -o foo foo.cc
$ ./foo 
0: 1606418432
1: 1606418432
2: 1606418432
3: 1606418432
4: 1606418432
5: 1606418432
6: 1606418432
7: 1606418432
8: 1606418432
9: 1606418432

修改

如果您只是尝试将向量初始化为一些重要的东西,并且不想浪费时间默认构造其内容,您可能想尝试创建自定义迭代器并将其传递给向量的构造函数。 / p>

修改示例:

#include <iostream>
#include <vector>
#include <iterator>

struct xyz {
    xyz() {}
    xyz(int init): v(init) {}
    xyz(const xyz& o): v(o.v) {} 
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v;
};

class XYZInitIterator: public std::iterator<std::input_iterator_tag, xyz>
{
public:
                        XYZInitIterator(int init): count(init) {}
                        XYZInitIterator(const XYZInitIterator& iter)
                        : count(iter.count) {}
    XYZInitIterator&    operator=(const XYZInitIterator& iter)
                        { count = iter.count; return *this; }
    value_type          operator*() const { return xyz(count); }
    bool                operator==(const XYZInitIterator& other) const 
                        { return count == other.count; }
    bool                operator!=(const XYZInitIterator& other) const 
                        { return count != other.count; }
    value_type          operator++() { return xyz(++count); }
    value_type          operator++(int) { return xyz(count++); }
private:
    int count;
};

std::vector<xyz> test() {
    XYZInitIterator start(0), end(1024);
    return std::vector<xyz>(start, end);
}

int main()
{
    std::vector<xyz> foo = test();
    for(int i = 0; i < 10; ++i)
    {
        std::cout << std::dec << i << ": " << std::hex << foo[i].v << std::endl;
    }
    return 0;
}

输出:

$ g++ -o foo foo.cc
$ ./foo 
0: 0
1: 1
2: 2
3: 3
4: 4
5: 5
6: 6
7: 7
8: 8
9: 9

答案 4 :(得分:1)

作为参考,以下代码可以在g ++中实现最佳组装: 我不是说我会用它,我不鼓励你。它不适合C ++!这是一个非常非常脏的黑客!我想它甚至可能依赖于g ++版本,所以,真的,不要使用它。如果我看到它在某处使用,我会呕吐。

#include <vector>

template<typename T>
static T create_uninitialized(size_t size, size_t capacity) {
    T v;
#if defined(__GNUC__)
    // Don't say it. I know -_-;
    // Oddly, _M_impl is public in _Vector_base !?
    typedef typename T::value_type     value_type;
    typedef typename T::allocator_type allocator_type;
    typedef std::_Vector_base<value_type, allocator_type> base_type;
    base_type& xb(reinterpret_cast<base_type&>(v));
    value_type* p(new value_type[capacity]);
#if !defined(__EXCEPTIONS)
    size=p?size:0;         // size=0 if p is null
    capacity=p?capacity:0; // capacity=0 if p is null
#endif
    capacity=std::max(size, capacity); // ensure size<=capacity
    xb._M_impl._M_start = p;
    xb._M_impl._M_finish = p+size;
    xb._M_impl._M_end_of_storage = p+capacity;
#else
    // Fallback, for the other compilers
    capacity=std::max(size, capacity);
    v.reserve(capacity);
    v.resize(size);
#endif
    return v;
}

struct xyz {
    // empty default constructor
    xyz() { }
    xyz(const xyz& o): v(o.v) { }
    xyz& operator=(const xyz& o) { v=o.v; return *this; }
    int v;
    typedef std::vector<xyz> vector;
};

// test functions for assembly dump
extern xyz::vector xyz_create() {
    // Create an uninitialized vector of 12 elements, with
    // a capacity to hold 256 elements.
    return create_uninitialized<xyz::vector>(12,256);
}

extern void xyz_fill(xyz::vector& x) {
    // Assign some values for testing
    for (int i(0); i<x.size(); ++i) x[i].v = i;
}

// test
#include <iostream>
int main() {
    xyz::vector x(xyz_create());
    xyz_fill(x);
    // Dump the vector
    for (int i(0); i<x.size(); ++i) std::cerr << x[i].v << "\n";
    return 0;
}

编辑:已实现_Vector_impl是公开的,这简化了事情。

编辑:这里是为xyz_create()生成的ARM程序集,使用-fno-exceptions(使用c ++ filt进行解码)编译并且没有任何内存初始化循环:

xyz_create():
    mov r3, #0
    stmfd   sp!, {r4, lr}
    mov r4, r0
    str r3, [r0, #0]
    str r3, [r0, #4]
    str r3, [r0, #8]
    mov r0, #1024
    bl  operator new[](unsigned long)(PLT)
    cmp r0, #0
    moveq   r3, r0
    movne   r3, #1024
    moveq   r2, r0
    movne   r2, #48
    add r2, r0, r2
    add r3, r0, r3
    stmia   r4, {r0, r2, r3}    @ phole stm
    mov r0, r4
    ldmfd   sp!, {r4, pc}

..这里是x86_64:

xyz_create():
    pushq   %rbp
    movq    %rsp, %rbp
    pushq   %rbx
    movq    %rdi, %rbx
    subq    $8, %rsp
    movq    $0, (%rdi)
    movq    $0, 8(%rdi)
    movq    $0, 16(%rdi)
    movl    $1024, %edi
    call    operator new[](unsigned long)
    cmpq    $1, %rax
    movq    %rax, (%rbx)
    sbbq    %rdx, %rdx
    notq    %rdx
    andl    $1024, %edx
    cmpq    $1, %rax
    sbbq    %rcx, %rcx
    leaq    (%rax,%rdx), %rdx
    notq    %rcx
    andl    $48, %ecx
    movq    %rdx, 16(%rbx)
    leaq    (%rax,%rcx), %rcx
    movq    %rbx, %rax
    movq    %rcx, 8(%rbx)
    addq    $8, %rsp
    popq    %rbx
    leave
    ret

答案 5 :(得分:1)

你无法避免std :: vector的元素初始化。

出于这个原因,我使用std :: vector派生类。  此示例中实现了resize()。您也必须实现构造函数。

虽然这不是标准的C ++而是编译器实现: - (

#include <vector>

template<typename _Tp, typename _Alloc = std::allocator<_Tp>>
class uvector : public std::vector<_Tp, _Alloc>
{
    typedef std::vector<_Tp, _Alloc> parent;
    using parent::_M_impl;

public:
    using parent::capacity;
    using parent::reserve;
    using parent::size;
    using typename parent::size_type;

    void resize(size_type sz)
    {
        if (sz <= size())
            parent::resize(sz);
        else
        {
            if (sz > capacity()) reserve(sz);
            _M_impl._M_finish = _M_impl._M_start + sz;
        }
    }
};

答案 6 :(得分:0)

我也很好奇。你是否只想将内存随机初始化?

矢量元素存储在连续的存储单元中,因此可以进行随机初始化。

答案 7 :(得分:-1)

如果您想要一个仅保留内存但没有初始化元素的向量,请使用reserve而不是构造函数:

std::vector<xyz> v;
v.reserve(1024);
assert(v.capacity() >= 1024);
assert(v.size() == 0);

答案 8 :(得分:-1)

使用此时声明struct的方式,没有机制可以默认初始化结构的int成员,因此您将获得默认的C行为,这是一个不确定的初始化。为了使用默认初始化值初始化int成员变量,您必须将其添加到结构构造函数的初始化列表中。例如,

struct xyz {
    xyz(): v() { } //initialization list sets the value of int v to 0
    int v;
};

其中-作为

struct xyz {
    xyz(): { } //no initialization list, therefore 'v' remains uninitialized
    int v;
};