C++性能提升

 

说句题外话,最近脑子一团浆糊,堆了数不清的任务又啥啥都不想干,只有总结整理这种简单却效果明显的事才能让心里舒服一点。

 

做笔试题的过程中,用到了以下一些提升C++算法性能的技巧,在此记录:

得到性能提升的设置均用橘黄色标出。

 

1.  加速输入输出: 

优化:

std::ios::sync_with_stdio(false);
std::cin.tie(0);

std::ios::sync_with_stdio(false) 的作用是取消缓冲区同步。

因为 printf()/scanf() 是C函数,而 cin/cout 是C++函数,这些函数需要用到各自的缓冲区,为了防止各自的缓冲区错位,C++默认将C函数和C++函数的缓冲区同步。当你设置成std::ios::sync_with_stdio(false)后C++就会取消同步,这会提高cin/cout的运行速度。
 

static bool sync_with_stdio( bool sync = true );

设置标准 C++ 流是否与标准 C 流在每次输入/输出操作后同步。这个函数是一个“是否兼容stdio”的开关,C++为了兼容C,保证程序在使用了std::printf和std::cout的时候不发生混乱,将输出流绑到了一起。

遇到cin TLE时可以用于取消cin同步, 取消之后不能和 scanf,sscanf, getchar, fgets 之类同用。


tie是将两个stream绑定的函数,空参数的话返回当前的输出流指针。

#include <iostream>
#include <fstream>
 

int main(int argc, char *argv[])
{
    std::ostream *prevstr;
    std::ofstream ofs;
    ofs.open("test.txt");
 
    std::cout << "tie example:\n";    // 直接输出到屏幕
 
    *std::cin.tie() << "This is inserted into cout\n";    // 空参数调用返回默认的output stream,也就是cout
    prevstr = std::cin.tie(&ofs);                        // cin绑定ofs,返回原来的output stream
    *std::cin.tie() << "This is inserted into the file\n";    // ofs,输出到文件
    std::cin.tie(prevstr);                                    // 恢复
 
    ofs.close();
    system("pause");
    return 0;
}

 

在ACM里,经常出现数据集超大造成 cin TLE的情况。这时候会有人认为这是cin的效率不及scanf的错,甚至还上升到C语言和C++语言的执行效率层面的无聊争论。其实这只是C++为了兼容而采取的保守措施。我们可以在IO之前将stdio解除绑定,这样做了之后要注意不要同时混用cout和printf之类。

在默认的情况下cin绑定的是cout,每次执行 << 操作符的时候都要调用flush,这样会增加IO负担。可以通过tie(0)(0表示NULL)来解除cin与cout的绑定,进一步加快执行效率。

 

2.  如果元素个数确定,使用reserve函数来提前为vector分配对象内存空间

std::vector<BigClass> vec3;

vec3.reserve(10);

如果在vector塞入元素之前能确定需要塞入元素的个数,那么调用reserve函数提前分配对象的内存空间。但是不要直接使用带元素数量的初始化方式来初始化vector或者是调用resize函数。如果不确定vector的元素个数,那么直接正常使用即可。

 

无论是使用带元素数量的初始化方式还是调用resize,都会调用存储类型的默认构造函数并改变size。一般情况下,这个通过默认构造函数生成的对象是没什么用处,最后都会在塞入数据的时候被覆盖掉。而且如果存储类型是个比较大的或者说是个构造函数比较复杂的类,那么这两种方式对于性能的浪费就很大了。

而reserve函数能够实现同resize函数一样的先分配指定个数元素的内存空间,但是不进行该对象的构造。即reserve只做纯粹的内存空间分配(只改变了它的capacity)。

而且我们提前使用reserve函数分配了内存空间也节省了系统为我们动态分配时所消耗的内存

所以,使用vector时尽量用reserver函数来提高性能。

 

 

3. 对于数据量比较大的vector,使用clear+shrink_to_fit函数来正确的释放内存

v.clear();

v.shrink_to_fit();

C++11的vector提供了shrink_to_fit函数来使容器降低其capacity和size匹配。对于已经存储了很大数据量的vector对象,我们可以使用clear+shrink_to_fit函数来替换原来的swap大法来正确的释放vector使用的内存。这种主动释放内存的操作对于程序的性能也是有一定提升的。

首先,我们知道当我们对一个vector调用clear函数的时候,实际上只是清理了vector中的元素,使得vector的size变成了0,但是它的capacity并没有变成0。也就是说vector所占用的内存并没有被释放掉,我们仍然可以通过某种方式获取到之前的元素。看下面代码。

#include<iostream>
#include<vector>

using namespace std;

int main() {
    vector<int> v {1,2,3,4,5};

    cout << "size:" << v.size() << endl;
    cout << "capacity:" << v.capacity() << endl;

    v.clear();
    cout << "after clear size:" << v.size() << endl;
    cout << "after clear capacity:" << v.capacity() << endl;

    cout << "v[0] = " << *v.data() << endl;

    system("pause");

    return 0;
}

 结果:

size:5
capacity:5
after clear size:0
after clear capacity:5
v[0] = 1

 

在C++11之前,我们一般使用swap大法来释放内存。用一个空的vector和需要清空的vector进行swap,这样原来的vector被空vector替换成空的,而之前的空vector被替换为原来vector中的内存,其作为临时变量在离开作用域后就会被自动析构,从而释放掉了原来vector中的内存。代码如下。
#include<iostream>
#include<vector>

using namespace std;

int main() {
    vector<int> v{ 1,2,3,4,5 };

    cout << "size:" << v.size() << endl;
    cout << "capacity:" << v.capacity() << endl;

    v.swap(vector<int>());
    cout << "after swap size:" << v.size() << endl;
    cout << "after swap capacity:" << v.capacity() << endl;

    system("pause");

    return 0;
}

结果:

size:5
capacity:5
after swap size:0
after swap capacity:0

 

C++11为我们带来了vector的新函数shrink_to_fit,这样我们可以使用clear+shrink_to_fit的方法来达到和swap大法一样的效果,而且语法很清晰。使用示例如下。

#include<iostream>
#include<vector>

using namespace std;

int main() {
    vector<int> v{ 1,2,3,4,5 };

    cout << "size:" << v.size() << endl;
    cout << "capacity:" << v.capacity() << endl;

    v.clear();
    v.shrink_to_fit();
    cout << "after clear+shrink_to_fit size:" << v.size() << endl;
    cout << "after clear+shrink_to_fit capacity:" << v.capacity() << endl;

    system("pause");

    return 0;
}

 

结果:

size:5
capacity:5
after clear+shrink_to_fit size:0
after clear+shrink_to_fit capacity:0

 

 

4. 使用emplace_back替代push_back减少内存拷贝和移动

C++11中对所有的标准容器(除了array)都增加了emplace、emplace_front、emplace_back等来替代原来的insert、push_front、push_back等插入元素的操作。进一步提升容器插入元素的性能。相比于原来的push_back,emplace_back能通过直接通过参数就地的在内存中构造元素对象,而不需要执行拷贝或者移动内存的操作。所以,我们在大部分情况下,都可以使用emplace系列的函数来替代insert或push系列的函数来提升程序的性能。
#include <vector>  
#include <string>  
#include <iostream>  
#include <map>

struct Person {
    std::string name;
    std::string country;
    int year;

    Person(std::string p_name, std::string p_country, int p_year)
        : name(std::move(p_name)), country(std::move(p_country)), year(p_year) {
        std::cout << "I am being constructed." << std::endl;
    }
    Person(Person&& other)
        : name(std::move(other.name)), country(std::move(other.country)), year(other.year) {
        std::cout << "I am being moved." << std::endl;
    }
};

int main() {
    std::map<int, Person> m;
    std::cout << "map insert..." << std::endl;
    m.insert(std::make_pair(23333, Person("kk", "china", 9021)));

    std::cout << "map emplace..." << std::endl;
    m.emplace(23333, Person("kk", "china", 9021));

    std::vector<Person> v;
    std::cout << "vector push_back..." << std::endl;
    v.push_back(Person("kk", "china", 9021));

    std::vector<Person> v1;
    std::cout << "vector emplace_back..." << std::endl;
    v1.emplace_back("kk", "china", 9021);

    system("pause");

    return 0;
}

 结果:

map insert...
I am being constructed.
I am being moved.
I am being moved.
map emplace...
I am being constructed.
I am being moved.
vector push_back...
I am being constructed.
I am being moved.
vector emplace_back...
I am being constructed.

从运行结果可以看出无论是map还是vector使用emplace系列的函数都少了一次内存移动的调用。而且emplace系列的函数还能直接通过参数就地构造(需要有对应的构造函数),这样代码也可以少写点。

 

5. 在没有排序需求时,使用无序容器(unordered container)替代有序容器

C++11提供了unordered_map、unordered_multimap、unordered_set和unordered_multiset四种无序容器。因为这些容器中的元素不需要排序,所以他们的插入等操作的效率更高一些。

无序容器内部使用哈希表来组织元素。通过哈希函数和关键字类型的==运算符来实现元素的快速操作。

对于基本类型,我们可以像使用有序容器一样使用无序容器。对于自定义类型的结构体,就需要提供哈希函数和重载==运算符。

 

参考:震惊!接受这4个小建议可以使你的C++程序性能提升80%!

 

posted @ 2020-03-24 16:55  山竹小果  阅读(961)  评论(0编辑  收藏  举报