C++(STL)容器采用迭代器访问及下标访问的效率详细对比,附详细代码及测试结果。
C++(STL)容器处理大量数据时的遍历操作,代码效率情况如何,今天做一个系统的效能测试,对比写不同的代码书写方法对效率的影响。值得注意的是,容器遍历这种底层操作,往往对上层高度封装后的代码效率在几何累计效应之下会产生巨大影响,不可忽视。我们选用最简单的vector容器作为测试目标。先看下测试代码:void main() {vector<byte> tagv;...
C++(STL)容器处理大量数据时的遍历操作,代码效率情况如何,今天做一个系统的效能测试,对比不同的遍历访问方法对效率的影响。值得注意的是,容器遍历这种底层操作,往往对上层高度封装后的代码效率产生巨大影响,不可忽视。
我们选用最简单的std::vector容器作为测试目标。先看下测试代码:
void main() {
std::vector<byte> tagv; //为节约内存选用一个byte数据类型,类型的不同对测试结果并无影响。
size_t sit = 10000 * 10000 * 1; //单个类型的测试总数,执行1亿次遍历操作。
tagv.resize(sit,1);
size_t aaa = 0;
CLTick tk; //CLTick 是一个超高精度的计时器,用于记录遍历操作所用时间。
//方式1:下标访问,多次计算上限。以下简称:[] 方式。
tk.timingStart();
for (size_t i = 0; i < tagv.size(); ++i)
aaa += tagv[i]; //为避免在Release模式下将无效用的变量优化掉,采用一个简单的+操作。
double ss0 = tk.getSpendTime();
//方式2:下标访问,单次计算上限。以下简称:[](Single) 方式。
tk.timingStart();
for (size_t i = 0,ie = tagv.size(); i < ie; ++i)
aaa += tagv[i];
double ss1 = tk.getSpendTime();
//方式3:迭代器访问,前向自加,多次计算上限。以下简称:++i 方式。
tk.timingStart();
for (auto i = tagv.begin(); i != tagv.end(); ++i)
aaa += *i;
double ss2 = tk.getSpendTime();
//方式4:迭代器访问,前向自加,单次计算上限。以下简称:++i(Single) 方式。
tk.timingStart();
for (auto i = tagv.begin(),ie = tagv.end(); i != ie; ++i)
aaa += *i;
double ss3 = tk.getSpendTime();
//方式5:迭代器访问,后向自加,多次计算上限。以下简称:i++ 方式。
tk.timingStart();
for (auto i = tagv.begin(); i != tagv.end(); i++)
aaa += *i;
double ss4 = tk.getSpendTime();
//方式6:迭代器访问,后向自加,单次计算上限。以下简称:i++(Single) 方式。
tk.timingStart();
for (auto i = tagv.begin(), ie = tagv.end(); i != ie; i++)
aaa += *i;
double ss5 = tk.getSpendTime();
//打印6种遍历方式所用的时间结果。
printf("\n\n\
Test result:\n\n\
[] = %.9fs;\n\
[](Single) = %.9fs;\n\
++i = %.9fs;\n\
++i(Single) = %.9fs;\n\
i++ = %.9fs;\n\
i++(Single) = %.9fs; %lld\n\n",ss0, ss1, ss2, ss3, ss4,ss5,aaa);
return;
}
【注:代码测试环境选用Windows10(x64)平台下vs2019community开发环境】
我们采用在Release版本下"无优化"和"有优化"两种情况分别测试,且为消除系统波动影响,每种模式下分别执行4次测试观察结果。我们采用一个高精度的计时器以保证统计准确性。6种不同方式,每种遍历访问1亿次,耗时结果输出,分别记录后统计到后附图,大家可以详细对比下。
在此,我先说下结论:
1、在忽略测试环境、优化等因素影响的情况下,单从6种不同代码写法的逻辑效能上来说,[ ] (Single)下标访问方式无疑是最快的,比 i++ 迭代器后向自加访问方式快了近6倍,比++i(Single) 迭代器前向自加访问方式快4倍,这是一个很惊人的差距。在代码深层封装后,可能对上层代码效率产生巨大影响。
2、我们应该在for循环中尽量避免习惯性的书写类似:i < tagv.size();
i != tagv.end();
i++
,等耗时重复的界限判断等操作和迭代器后向自加操作;
3、在Release版本下,编译器对代码的优化选项将会大大提升代码效率,使得以上不同代码写法效率趋同,但总体来说 [ ] (Single)下标访问方式仍然是最快的,效率是其他几种方式的1.5~1.8倍,执行效率上的提升程度非常可观。
【本测试中Microsoft vs 2019 c++编译器打开优化选项,相较于无优化情况,效率提升了近10倍,首先感概微软不愧是大厂,在代码优化方案上的功底非常深厚,并且猜测微软编译器应该也是把代码中的迭代器遍历方式替换成了类似下标访问方式,从而达到效率提升的。】
Release(无优化)的4次结果------------------------------------------------------------
Test result1:
[] = 0.396070005s;
[] (Single) = 0.263895732s;
++i = 1.607403243s;
++i(Single) = 0.990342251s;
i++ = 1.663455849s;
i++(Single) = 1.047853357s; 600000000
Test result2:
[] = 0.393777273s;
[] (Single) = 0.261934670s;
++i = 1.606728663s;
++i(Single) = 0.989997653s;
i++ = 1.655665284s;
i++(Single) = 1.061884357s; 600000000
Test result3:
[] = 0.394304852s;
[] (Single) = 0.261815494s;
++i = 1.607436410s;
++i(Single) = 0.990355181s;
i++ = 1.661023704s;
i++(Single) = 1.048095363s; 600000000
Test result4:
[] = 0.394131147s;
[] (Single) = 0.262404909s;
++i = 1.606184501s;
++i(Single) = 0.990225886s;
i++ = 1.653217399s;
i++(Single) = 1.048682249s; 600000000
Release(优化)的4次结果------------------------------------------------------------
Test result1:
[] = 0.040111137s;
[] (Single) = 0.023506612s;
++i = 0.039167286s;
++i(Single) = 0.039219285s;
i++ = 0.039153232s;
i++(Single) = 0.039206637s; 600000000
Test result2:
[] = 0.039322721s;
[] (Single) = 0.023916419s;
++i = 0.039501766s;
++i(Single) = 0.039347174s;
i++ = 0.039111633s;
i++(Single) = 0.039233339s; 600000000
Test result3:
[] = 0.039414914s;
[] (Single) = 0.024027725s;
++i = 0.039333121s;
++i(Single) = 0.039118941s;
i++ = 0.039101515s;
i++(Single) = 0.039087461s; 600000000
Test result4:
[] = 0.039605202s;
[] (Single) = 0.024055833s;
++i = 0.039387930s;
++i(Single) = 0.039707794s;
i++ = 0.039304170s;
i++(Single) = 0.039539149s; 600000000
更多推荐
所有评论(0)