febird.dataio 优化技术
优化技术主要有两点:
1. 优化的inline
a) 频繁调用的函数都使用inline,但是值得注意的是,在inline的时候,只inline最频繁的分支,很少走到的分支使用非inline函数,例如:
1 2 3 4 5 6 7 8 9 |
void InputBuffer::ensureRead(void* vbuf, size_t length) { // 为了效率,这么实现可以让编译器更好地inline这个函数 // inline 后的函数体并尽可能小 if (m_cur+length <= m_end) { memcpy(vbuf, m_cur, length); m_cur += length; } else fill_and_ensureRead(vbuf, length); } |
一般情况下,如果length是个不大的常数值,编译器会把memcpy优化成赋值语句。至少在VC2008中我观察到了这个优化。
但是这里仍有一种不太优化的情况,在理想的情况下,编译器应该把m_cur/m_end都放在寄存器中,只有在寄存器 Spill Out 的时候,才把它们的值从寄存器拷到对象,并调用fill_and_ensureRead。但实际上编译器没有这么做,每次都存内存读取m_cur/m_end。这可能是编译器观察到InputBuffer有点大,并且有虚函数。
b) MinMemIO/MemIO/AutoGrowMemIO
这个几个效率更高,但只能在内存中操作,编译器的极端优化,在这里得到了体现:在Buffer类中,编译器没有做到我想要的优化,但是在这里,编译器做到了,他吧MinMemIO放到了寄存器中。
2. 抛弃标准C++ stream,使用简单、直接的Stream/Buffer
a) 可以对各种流进行快速缓冲的StreamBuffer,包括
i. 效率高、最常用的:InputBuffer/OutputBuffer
ii. 效率高、不常用的:SeekableInputBuffer/SeekableOutputBuffer
iii. 效率稍差、不常用的:SeekableBuffer,可读也可写,共享一个位置指针
iv. 这几个Buffer结构简单,操作直接,结合编译器inline可以达到很高的效率,同时可以和实际Stream互操作。
3. 使用typetraits识别可以memcpy的类,进一步优化
a) 基本类型不用说,都可以进行memcpy,并且这个memcpy实际上被优化成了赋值
b) 对稍微复杂的类型,有两种方法:
i. 直接dump,不管它的格式
实现简单,只管dump就行,boost::archive::binary_xxx实现了这种优化,但是它只能对基本类型和用户声明为可直接dump的类优化。并且如果febird也使用这种优化,将不能对Portable格式优化。
ii. 直接dump,再转化格式
就比较复杂,需要一些技巧,febird做到了一点,不管对Native还是Portable格式,都做到了优化。因为序列化使用宏来进行声明,因此,应用代码不用改变,只要认真优化这个宏,就可以做到。febird使用了这样的技巧:
DATA_IO_LOAD_SAVE(MyData1, &a&b&c&d&e&f&g&h)
在这个宏调用中第二个参数&a&b&c&d&e&f&g&h被使用了多次,其中有一次展开后将是是这样的:
DataIO_load_vector_impl(dio, *this, /
DataIO_is_realdump<DataIO,0,true>()&a&b&c&d&e&f&g&h, /
bswap)
其中高亮部分DataIO_is_realdump<DataIO,0,true>()&a&b&c&d&e&f&g&h将推导出一个类DataIO_is_realdump<DataIO, Size, IsDumpable>,其中Size是abcdefgh的尺寸之和,IsDumpable是abcdefgh的IsDumpable的and结果,DataIO_load_vector_impl以这个类为参数,进行函数调用的自动分派,如果Size==sizeof(MyData1)就说明MyData中没有编译器为对齐成员自动产生的Padding,如果IsDumpable同时为true,那么这个类就可以被dump。但是这里仍然有一个潜在的危险:如果&a&b&c&d&e&f&g&h的顺序和它们在类定义中出现的顺序不同,那么这个优化产生的行为将违背调用者的真实意图。关于这一点,无法进行自动检查,因此使用者需要特别注意。如果要测试是否出现了这种错误,可以先禁用这种优化,产生数据,然后使用优化,来读取数据,如果数据格式不同,就说明出了错。
4. 使用了这么多优化,达到的效果,平均情况下,如果是基本类型vector,比boost快不了太多,但是对复杂类型,比boost要快20~50倍,如果数据已经过验证,不用担心越界,读取时可以使用NativeDataInput<MinMemIO>,此时速度更加惊人:比boost快1600倍!