C++性能提升
说句题外话,最近脑子一团浆糊,堆了数不清的任务又啥啥都不想干,只有总结整理这种简单却效果明显的事才能让心里舒服一点。
做笔试题的过程中,用到了以下一些提升C++算法性能的技巧,在此记录:
得到性能提升的设置均用橘黄色标出。
1. 加速输入输出:
优化:
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::ios::sync_with_stdio(false) 的作用是取消缓冲区同步。
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
#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减少内存拷贝和移动
#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%!