目录
- 源码推荐
- 编程小点
- 注意事项
- 0.如何查找BUG
- 1.取余过滤
- 2.向上取整
- 3.数组元素删除
- 4.节省内存
- 5.map和数组
- 6.浮点数取绝对值
- 7. 动态数组初始化
- 8. 无符号数和有符号数运算
- *9. 关于vector的size()问题
- 10. while循环
- 11. 多线程写数据冲突
- 12. 模拟线程池,达到负载均衡
- 13. vector频繁插入优化
- 14. vector遍历
- *16.float的整数范围
- 17.不提倡使用`vector`
- *18. float比double运算慢?
- 19.switch和if-else性能比较
- 20.简单求和是否有必要用opm并行
- 21.没有冲突时原子操作加法和普通加发性能对比
- 22.二维数组的行遍历和列遍历
- 23. 多线程相关的bug
- 24.C/C++ 中利用debug宏定义打开/关闭调试输出
- 25. \__attribute__((packed))详解
- 26. 浮点数不要直接用=比较是否相等!!!
- 27. 函数调用与内联函数
- 28.unordered_map.emplace
- 29.二维vector的内存分配
- 30.C++枚举类型enum与enum class的使用方法是什么
- 16个字节的原子操作
- 31.dobule和float的误差
源码推荐
1.Rabbit Order
基于顶点重排序的提前数据布局优化是图分析中用于改善内存访问局域性的一种广泛应用的技术。虽然重新排序的图具有更好的分析性能,但现有的重新排序算法使用大量的计算时间来提供有效的顶点排序;因此,它们无法减少端到端处理时间。本文提出了一种实时并行重排序算法——Rabbit Order。它通过两种方法同时实现高局部性和快速的重新排序,从而减少端到端运行时间。第一种方法是分层的基于社区的排序,它利用了现实世界图中分层社区结构的局部性。我们的排序通过将分层社区映射到分层缓存中,充分利用了低延迟缓存级别。第二种方法是并行增量聚合,它通过减少要处理的顶点数量来提高重新排序的运行时效率。此外,这种方法利用轻量级原子操作进行并发控制,以避免锁定开销并实现高可伸缩性。我们的实验表明,Rabbit Order显著优于最先进的排序算法。
paper
code
技术点:
- tcmalloc
编程小点
注意事项
- 为了尽量避免问题,每次commit前确保提交的代码是正确的(测试一下结果!!);
- 使用If-else时,必须要清楚else所包含的所有情况,如果不要求性能的时候可以考虑用else-if 代替else,而将else作为一种错误提示。
0.如何查找BUG
- 测试(用数据集测试,尽量用既可以检验过程有简单且小的数据集)。如果只在大数据集中才报错,考虑数据集的特点,能否将该报错数据集切割成小数据集,进而复现bug。
- 对于运行时间过程的代码,测试将会是否耗时,可以根据不同情况想办法减少调试过程运行时间,例如多无关地方多开线程、部分固定内容写入序列化文件以后直接加载.
- 直接看代码
- debug
1.取余过滤
如果需要遍历一个数组并且需要跳过某个元素时,可以用取余,也可以用if.
{
int id = 3; // 1,2,3,4,5,6,7,8,9 从1开始
int Max = 9;
for(int i = 1; i <= Max; i++){
if(i == id) continue;
cout << i << ", ";
}
}
cout << endl;
{
int id = 3; // 1,2,3,4,5,6,7,8,9 从1开始
int Max = 9;
for(int i = id; i < id + Max - 1; i++){
int v = i % Max + 1;
cout << v << ", ";
}
}
cout << endl;
{
int id = 3; // 0,1,2,3,4,5,6,7,8 从0开始
int Max = 8;
for(int i = id; i < id + Max - 1; i++){
int v = (i + 1) % Max; // -
cout << v << ", ";
}
}
cout << endl;
{
int id = 3; // 0,1,2,3,4,5,6,7,8 从0开始
int Max = 8;
for(int i = id; i < id + Max - 1; i++){
int v = (i - 1 + Max) % Max; // 1
cout << v << ", ";
}
}
out:
1, 2, 4, 5, 6, 7, 8, 9,
4, 5, 6, 7, 8, 9, 1, 2,
4, 5, 6, 7, 0, 1, 2,
2.向上取整
int main(){
int a = 7;
cout << a / 3 << endl;
cout << (a + (3 -1)) / 3 << endl;
return 0;
}
3.数组元素删除
数组一般不支持删除操作,即使支持erase()操作的对于效率是也是很低的,所以如果条件允许的情况下可以考虑隐藏删除,即将要删除的元素与数组尾部的元素交换,当然此时数组的长度应该用一个变量标记并减一。
void remove_adj(std::vector<int> &u_adjlist, int v){
std::vector<int>::iterator it = find(u_adjlist.begin(), u_adjlist.end(), v);
if(it == u_adjlist.end()){
std::cout << "error: " << v << std::endl;
exit(0);
}
std::swap(*it, u_adjlist.back());
u_adjlist.pop_back();
}
4.节省内存
对于大型数据结构或者数据的参数传递,可以考虑使用引用。
例如
Summarization(unordered_map<K, Point<K>> &G, string outdir_, float s_threshold_){
for(auto &p : G){
vertex_[p.second.id] = p.second;
father_vertex_[p.second.id] = p.second.id;
}
}
5.map和数组
有时候为了方便会使用map来作映射,试图取代数组,然而在除了某些非用不可的情况下,我们还是应该尽量使用数组,因为c++中map底层时红黑色,需要时间,unorder_map依然会在hash上消耗时间,而数组则是连续存放,效率会高很多。对于下标不连续的情况可以使用unorder_map来记录真实下标,对原下标重新编号,这样就可以使用数组了。
例如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unordered_map>
#include <map>
#include <ctime>
#include <iostream>
using namespace std;
#define NUM 10000000
struct student
{
int no = 1;
int ranking = 2;
};
int main(void)
{
student *p;
p = new student[NUM];
std::unordered_map<int, student> q;
std::map<int, student> w;
// 数组
auto p_time = clock();
for(int i = 0; i < NUM; i++){
// printf("%d %d\n", p[i].no, p[i].ranking);
p[i].no++;
p[i].ranking++;
}
cout << "p_time=" << (clock() - p_time) << endl;
// q的索引遍历
auto q_time = clock();
for(int i = 0; i < NUM; i++){
q[i].no++;
q[i].ranking++;
}
cout << "q_time_1=" << (clock() - q_time) << endl;
// q的索引遍历
q_time = clock();
for(auto& temp : q){
temp.second.no++;
temp.second.ranking++;
}
cout << "q_time_2=" << (clock() - q_time) << endl;
// w的索引遍历
auto w_time = clock();
for(int i = 0; i < NUM; i++){
w[i].no++;
w[i].ranking++;
}
cout << "w_time_1=" << (clock() - w_time) << endl;
// w的索引遍历
w_time = clock();
for(auto& temp : w){
temp.second.no++;
temp.second.ranking++;
}
cout << "w_time_2=" << (clock() - w_time) << endl;
return 0;
}
运行结果:
p_time=55679
q_time_1=2722967
q_time_2=143026
w_time_1=11604879
w_time_2=475039
所以对于这种场景,无论时map还是unorder_map,以及无论时那种遍历方式,都会比数组慢很多。
6.浮点数取绝对值
- abs(): #include <cmath>
- fabs():
7. 动态数组初始化
c++使用new分配一个数组,分为数组初始化和未初始化两种情形:
bool* array = new bool[10]; //未初始化
bool* array = new bool[10](); //初始化为0
或者自己初始化:
bool数组初始化
8. 无符号数和有符号数运算
思考下面代码的输出结果:
#include <vector>
#include <iostream>
using namespace std;
int main(){
int max = -1;
vector<int> ve;
ve.emplace_back(0);
ve.emplace_back(1);
ve.emplace_back(2);
if(ve.size() > max){
cout << "true: " << ve.size() << " > " << max << ": " << (ve.size() > max) << endl;
}
else{
cout << "false: " << ve.size() << " > " << max << ": " << (ve.size() > max) << endl;
}
size_t a = -1;
cout << "size_t a=" << a << endl;
return 0;
}
输出结果如下:
false: 3 > -1: 0
size_t a=18446744073709551615
解释:首先可以查看vector.size()函数的源码,可以发现其返回值类型是size_t
,size_t
是一些C/C++标准在stddef.h中定义的。这个类型足以用来表示对象的大小。size_t
的真实类型与操作系统有关,在32位架构中被普遍定义为:typedef unsigned int size_t
; 而在64位架构中被定义为:typedef unsigned long size_t
; 也就是说上面的比较大小是有符号和无符号数字在比较大小,而在一个算数表达式中既有无符号数又有有符号数,那个有符号数(本例中是 int )就会变成无符号数
。所以例子中的max
会由-1
转为无符号数18446744073709551615
。所以比较大小会出问题,故在比较大小时一定保证两个数同是无符号数或者同是有符号数。
可以将代码修改为如下:
#include <vector>
#include <iostream>
using namespace std;
int main(){
int max = -1;
vector<int> ve;
ve.emplace_back(0);
ve.emplace_back(1);
ve.emplace_back(2);
if(ve.size() > max){
cout << "true: " << ve.size() << " > " << max << ": " << (ve.size() > max) << endl;
}
else{
cout << "false: " << ve.size() << " > " << max << ": " << (ve.size() > max) << endl;
}
size_t a = -1;
cout << "size_t a=" << a << endl;
int size = ve.size();
if(size > max){
cout << "true: " << size << " > " << max << ": " << (size > max) << endl;
}
else{
cout << "false: " << size << " > " << max << ": " << (size > max) << endl;
}
return 0;
}
输出结果如下:
false: 3 > -1: 0
size_t a=18446744073709551615
true: 3 > -1: 1
*9. 关于vector的size()问题
摘抄自:https://bbs.csdn.net/topics/390939818
int start=kxdata.size()-1>1?kxdata.size()-1:1;
return(start);
kxdata是个vector,按理说返回的start应该不小于1才对,但实际调用的时候发现第一次返回的start竟然是-1,但如果把上面一句改成:
int start=kxdata.size()>2?kxdata.size()-1:1;
则一切正常,这是怎么个情况?还请高手解答,谢谢.
10. while循环
对于while循环一定要清楚结束条件,如果对于结束条件没有十足把握时,最好加一个计数或者其它辅助条件来结束循环,不然陷入死循环确没有任何提示信息。
11. 多线程写数据冲突
对于多个线程同时写同一个变量时,如果没有处理冲突,那么不仅会使最后结果不正确,还使得运行起来比单线程慢。
下面给一个多线程求和的例子:
#include <iostream>
#include <vector>
#include<ctime>
#include "omp.h"
using namespace std;
int main(){
double start; //程序计时
double endtime;
int ans=0;
int num = 1e8;
cout << "测试加同一个数..." << endl;
start=clock();
for(int i=1;i<=num;i++)
ans++;
endtime=(double)(clock()-start)/CLOCKS_PER_SEC;
cout<<"Total time:"<< endtime <<endl; //s为单位
// 有冲突用时:
start=clock();
#pragma omp parallel for
for(int i=1;i<=num;i++)
ans++;
endtime=(double)(clock()-start)/CLOCKS_PER_SEC;
cout<<"有冲突时,Total time:"<< endtime <<endl; //s为单位
cout << "ans=" << ans << endl << endl;
// 无冲突
start=clock();
ans = 0;
#pragma omp parallel for reduction(+:ans)
for(int i=1;i<=num;i++)
ans += 1;
endtime=(double)(clock()-start)/CLOCKS_PER_SEC;
cout<<"无冲突时,Total time:"<< endtime <<endl; //s为单位
cout << "ans=" << ans << endl << endl;;
cout << "测试加不同的数..." << endl;
vector<int> array;
array.resize(num);
start=clock();
for(int i=1;i<=num;i++)
array[i] = 1;
endtime=(double)(clock()-start)/CLOCKS_PER_SEC;
cout<<"Total time:"<< endtime <<endl; //s为单位
start=clock();
#pragma omp parallel for
for(int i=1;i<=num;i++)
array[i] = 1;
endtime=(double)(clock()-start)/CLOCKS_PER_SEC;
cout<<"Total time:"<< endtime <<endl; //s为单位
return 0;
}
运行命令:
g++ test2.cc -fopenmp && ./a.out
运行结果:
测试加同一个数...
单线程,Total time:0.175922
多线程有冲突时,Total time:1.58442
ans=119152234
多线程无冲突时,Total time:0.210734
ans=100000000
测试加不同的数...
Total time:0.309844
Total time:0.388737
12. 模拟线程池,达到负载均衡
需求是,我们有一个数组A
,每个数组里面的元素需要处理,数组很大,所以希望用多线程来处理,那么如何分配任务和线程呢?
我首先想到的是开16个线程,每个线程负责一个id区间内的元素,区间大小为 A.size()/16
。但是结果是,0号线程和1号线程分别执行了15s, 其余线程只执行了0.1s, 这就表明了严重的负载不均衡。
为了改进,自然想到了用线程池,然而我并不会,开了下,感觉还点麻烦,而且我的需求相对简单,于是我想到了如下方案,进行模拟线程池:
线程池的本质就是为了动态分配任务,让各个线程尽量负载均衡。
故:我在线程外定义了一个公共遍历spnode_id=0
, 每个线程里面都去累加spnode_id
,如果累加成功,将累加前的spnode_id
的值拿去进行处理,这样,当一个线程在处理数组的一个元素时,另外的线程则可以去处理剩余的其它点,每个线程结束后,最多相差一个元素处理的时间,相对负载均衡。
int i = 0, cnt = 0;
while(i < A.size){
i = __sync_fetch_and_add(&spnode_id, 1);
if(i < A.size){
deal_element(i);
cnt++;
}
}
13. vector频繁插入优化
需求:一个二维的vector
,需要第二维大小不确定(即每列多少不确定,但是知道最大值),然后频繁插入vector
。
假设第一个维度我们可以知道大小,可以直接resize()
,提前申请好,那么第二个维度则不能提前确定,但是既然我们可以知道最大维度的话,我们可以申请最大空间,一种方法是使用reserve(n)
:分配至少能容纳n
个元素的内存空间,reserve
并不改变容器中元素的数量,它仅影响vector
预先分配多大的内存空间,即改变的是capacity
。
#include <iostream>
#include "omp.h"
#include <stdint.h>
#include <vector>
#include<ctime>
using namespace std;
int main(int argc, char **argv) {
const int size = 100;
const int size2 = 1000000;
vector<vector<std::pair<int, float>> > edge;
bool flag = 1; // 0: 不提前申请,1:提前申请
clock_t start, end;
long long cnt = 1;
cout << "flag=" << flag << endl;
if(flag){
edge.resize(size2);
start=clock();
// edge.resize(size2);
cout << "size=" << edge.size() << endl;
cout << "capacity=" << edge.capacity() << endl;
for(int j = 0; j < size2; j++){
// edge[j].resize(size);
// edge[j].clear();
edge[j].reserve(size);
for(int i = 0; i < size; i++){
edge[j].emplace_back(std::pair<int, float>(1, 1.02));
cnt++;
}
}
end=clock(); //程序结束用时
double endtime=(double)(end-start)/CLOCKS_PER_SEC;
cout<<"Total time:"<< endtime << endl; //s为单位
}
else{
start=clock();
edge.resize(size2);
cout << "size=" << edge.size() << endl;
cout << "capacity=" << edge.capacity() << endl;
for(int j = 0; j < size2; j++){
for(int i = 0; i < size; i++){
edge[j].emplace_back(std::pair<int, float>(1, 1.02));
cnt++;
}
}
end=clock(); //程序结束用时
double endtime=(double)(end-start)/CLOCKS_PER_SEC;
cout<<"Total time:"<< endtime <<endl; //s为单位
}
cout << "cnt=" << cnt << endl;
return 0;
}
运行命令及结果:
xx@xx-desktop:~/code/test/c++_test_code$ g++ test.cc -fopenmp && ./a.out
flag=0
size=1000000
capacity=1000000
Total time:4.36478
cnt=100000001
xx@xx-desktop:~/code/test/c++_test_code$ g++ test.cc -fopenmp && ./a.out
flag=1
size=1000000
capacity=1000000
Total time:1.67359
cnt=100000001
14. vector遍历
C++ Vector遍历的几种方式及性能对比
vector按下标遍历和利用迭代器遍历时间对比,显示按下标更快。测试代码如下:
#include <time.h>
#include <cstdio>
#include <cstdlib>
#include <unordered_map>
#include <unordered_set>
#include <map>
#include <set>
#include <algorithm>
#include <vector>
using namespace std;
#define MAXN 10000000
#define MOD 1000000000
unordered_set<int> uset;
unordered_map<int, int> umap;
map<int, int> mp;
set<int> stt;
int in[MAXN];
int main()
{
srand(0);
long long sum = 0;
int t = 3;
uset.clear();
umap.clear();
mp.clear();
stt.clear();
vector<int> vec;
for (int i = 0; i < MAXN; i++)
{
int r = rand() % MOD;
vec.emplace_back(r);
}
long long sum_1 = 0;
while (t--)
{
long long st = clock();
for (int i = 0; i < MAXN; i++){
sum_1 += vec[i];
}
long long ed = clock();
sum += ed - st;
}
printf("time1=%lld\n", sum / 5);
sum = 0;
sum_1 =0;
t = 3;
while (t--)
{
long long st = clock();
for(auto v : vec){
sum_1 += v;
}
long long ed = clock();
sum += ed - st;
}
printf("time2=%lld\n", sum / 5);
return 0;
}
运行结果如下:
time1=11612
time2=36033
*16.float的整数范围
float与double的范围和精度
float虽然能够表示很大的数,但是当一个数很大的整数时,他不能够正确表示;
例如,把一个超过8位的整数赋值给float变量,会发现可能已经不相等了。
示例如下:
#include <time.h>
#include <cstdio>
#include <cstdlib>
#include <unordered_map>
#include <unordered_set>
#include <map>
#include <set>
#include <algorithm>
#include <vector>
#include <iostream>
#include <iostream> // std::cout, std::fixed
#include <iomanip> // std::setprecision
using namespace std;
#define MAXN 10000000
#define MOD 1000000000
unordered_set<int> uset;
unordered_map<int, int> umap;
map<int, int> mp;
set<int> stt;
int in[MAXN];
int main()
{
float f = 0;
// uint32_t ui = 20506657;
uint32_t ui = 1e8+2;
std::cout << f << " " << ui << std::endl;
f = ui;
std::cout << std::setprecision(9) << f << " " << ui << std::endl;
return 0;
}
输出:
0 100000002
100000000 100000002
17.不提倡使用vector<bool>
https://www.zhihu.com/question/23367698/answer/32961598
避免使用vector<bool>
简单的说就是它并未实际保存一个bool, 而是用位域的概念进行了封装.
所以你在实际应用的时候可能会发生一些你意料之外的问题.
个人遇到过相关问题,当时查找了好久想到可能是由于vector<bool>
引起的, 我当时用不同的线程同时修改不同位置的vector<bool>
元素,然后总是会和与其结果不一致。
切忌,勿用!
*18. float比double运算慢?
多年以前,有人告诉我,float运算比double要快
c++ primer,其中有一页写道,很多现代机器运算double数据的速度并不比float要慢,而float由于精度不高,因此在可以的情况下,尽量使用double数据类型。实验:
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
int main()
{
clock_t time1, time2;
double a = 3;
time1 = clock();
for (unsigned int i = 0; i < 10000000u; i++)
{
a += 1e-3;
a += 1e-3;
a += 1e-3;
a += 1e-3;
}
time2 = clock();
cout << a << endl;
cout << time2 << endl;
cout << time1-time2 << endl;
return 0;
}
实验证明,同等情况下,在本人机器上(core i5-4200)上,float类型和double类型运算所消耗的时钟周期相同。值得一提是,如果将a的声明改为float a = 3,那么运算时间会增加几乎一倍,主要原因在于,在循环体内部的运算存在类型从double到float的转换,如果将1e-3写为1e-3f则会省去类型转换。
因此,在此澄清一下多年来的谬论,很多时候,double比float的精度更高,避免了可能的类型转换,而这种类型转换可能反而拖慢了程序运行的速度。
————————————————
19.switch和if-else性能比较
【2018.04.27 C与C++基础】关于switch-case及if-else的效率问题
在分支比较多时,switch-case的效率肯定比if-else的要高许多,其原理类似于我们在优化某些程序时使用查表来代替算法计算一样。
如果想进一步深究的话,可以查看GCC编译之后的汇编代码。
这里有两篇Blog可以参考一下,致谢两位作者。
- switch与ifelse的效率问题
https://blog.csdn.net/kehui123/article/details/5298337
- if else和switch的效率
https://blog.csdn.net/spy19881201/article/details/5851792
先记录下这两篇博文中的结论:
-
switch和if-else相比,由于使用了BinaryTree算法,绝大部分情况下switch会快一点,除非是if-else的第一个条件就为true.
-
编译器编译switch与编译if…else…不同,对switch-case会生成一个跳表,不管有多少case,都直接跳转,不需要逐个比较查询,注意这一部分内容其实在深入理解计算机系统这本书上有提及的。
-
由于swtich-case在编译处理时要生成跳表,会占用较多的代码空间,当case常量分布范围较大但实际的有效值又比较少的时候,switch-case的空间利用率会降低。
-
switch-case仅限于处理分支条件结果为常量的情况,对于较复杂的条件需要多做程序上的处理,在灵活性上不如if-else。
测试程序:
void test_switch_if(){
int num = 1e9;
double s_time = clock();
float sum = 0;
for(int i = 0; i < num; i++){
if(i % 10 == 0){
sum += 1;
}
else if(i % 10 == 1){
sum += 2;
}
else if(i % 10 == 2){
sum += 3;
}
// else if(i % 10 == 3){
// sum += 4;
// }
// else if(i % 10 == 4){
// sum += 5;
// }
// else if(i % 10 == 5){
// sum += 6;
// }
}
cout << "sum=" << sum << endl;
cout << "time1=" << (clock()-s_time)/1e6 << endl;
s_time = clock();
sum = 0;
for(int i = 0; i < num; i++){
int x = i % 4;
switch (x)
{
case 0:
sum += 1;
break;
case 1:
sum += 2;
break;
case 2:
sum += 3;
break;
// case 3:
// sum += 4;
// break;
// case 4:
// sum += 5;
// break;
// case 5:
// sum += 6;
// break;
}
}
cout << "sum=" << sum << endl;
cout << "time1=" << (clock()-s_time)/1e6 << endl;
}
实验结果:
sum=6.71089e+07
time1=3.48076
sum=6.71089e+07
time1=2.13096
20.简单求和是否有必要用opm并行
示例:
#include <iostream>
#include "omp.h"
#include <stdint.h>
#include<ctime>
#include <time.h>
#include <cstdio>
#include <cstdlib>
#include <unordered_map>
#include <unordered_set>
#include <map>
#include <set>
#include <algorithm>
#include <vector>
#include <iostream>
#include "Queue.h"
using namespace std;
void test_parallel_for_add(){
//设置线程数,一般设置的线程数不超过CPU核心数,这里开4个线程执行并行代码段
omp_set_num_threads(4);
long long num = 1e9;
long long sum = 0;
double s_time = clock();
for(int i = 0; i < num; i++){
sum += i;
}
cout << "sum1=" << sum << endl;
cout << "time1=" << (clock()-s_time)/1e6 << endl;
sum = 0;
s_time = clock();
#pragma omp parallel for
for(int i = 0; i < num; i++){
__sync_fetch_and_add(&sum, i);
}
cout << "sum2=" << sum << endl;
cout << "time2=" << (clock()-s_time)/1e6 << endl;
sum = 0;
s_time = clock();
#pragma omp parallel for reduction(+:sum)
for(int i = 0; i < num; i++){
sum += i;
}
cout << "sum3=" << sum << endl;
cout << "time3=" << (clock()-s_time)/1e6 << endl;
sum = 0;
s_time = clock();
vector<long long> temp_sum(omp_get_num_threads(), 0);
#pragma omp parallel for reduction(+:sum)
for(int i = 0; i < num; i++){
temp_sum[omp_get_thread_num()] += i;
}
for(int i = 0; i < omp_get_num_threads(); i++){
sum += temp_sum[i];
}
cout << "sum4=" << sum << endl;
cout << "time4=" << (clock()-s_time)/1e6 << endl;
}
int main(int argc, char **argv) {
test_parallel_for_add();
std::cout << "\ntest finish..\n";
return 0;
}
输出结果:
g++ test1.cc -fopenmp && ./a.out
sum1=499999999500000000
time1=0.913899
sum2=499999999500000000
time2=32.5441
sum3=499999999500000000
time3=2.11578
sum4=31249999875000000
time4=9.47504
test finish..
从上面的例子看出,非并行的最快,然后用__sync_fetch_and_add()原子操作的速度最慢.
21.没有冲突时原子操作加法和普通加发性能对比
#include <iostream>
#include "omp.h"
#include <stdint.h>
#include<ctime>
#include <time.h>
#include <cstdio>
#include <cstdlib>
#include <unordered_map>
#include <unordered_set>
#include <map>
#include <set>
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
inline bool atomic_compare_and_swap(float& val, float old_val, float new_val) {
uint32_t* ptr = reinterpret_cast<uint32_t*>(&val);
const uint32_t* old_val_ptr = reinterpret_cast<const uint32_t*>(&old_val);
const uint32_t* new_val_ptr = reinterpret_cast<const uint32_t*>(&new_val);
return __sync_bool_compare_and_swap(ptr, *old_val_ptr, *new_val_ptr);
}
inline void atomic_add(float& a, float b) {
volatile float new_a, old_a;
// std::cout << a << ", " << b << std::endl;
do {
old_a = a;
new_a = old_a + b;
} while (!atomic_compare_and_swap(a, old_a, new_a));
}
void test_atomic_add(){
float sum = 0;
int num = 1e9;
double s_time = clock();
for(int i = 0; i < num; i++){
sum += 1.0f;
}
cout << "sum1=" << sum << endl;
cout << "time1=" << (clock()-s_time)/1e6 << endl;
s_time = clock();
sum = 0;
for(int i = 0; i < num; i++){
atomic_add(sum, 1.0f);
}
cout << "sum1=" << sum << endl;
cout << "time1=" << (clock()-s_time)/1e6 << endl;
}
int main(int argc, char **argv) {
test_atomic_add();
std::cout << "\ntest finish..\n";
return 0;
}
结果:
sum1=1.67772e+07
time1=2.75672
sum1=1.67772e+07
time1=9.20504
22.二维数组的行遍历和列遍历
void test_visit_array(){
int a[10000][10000]; // 太大,移到外面,成全局变量
int num = 10000;
int sum = 0;
for(int i = 0; i < num; i++){
for(int j = 0; j < num; j++){
a[i][j] = 1;
}
}
double s_time = clock();
for(int i = 0; i < num; i++){
for(int j = 0; j < num; j++){
sum += a[i][j];
}
}
cout << "sum1=" << sum << endl;
cout << "time1=" << (clock()-s_time)/1e6 << endl;
s_time = clock();
sum = 0;
for(int i = 0; i < num; i++){
for(int j = 0; j < num; j++){
sum += a[j][i];
}
}
cout << "sum2=" << sum << endl;
cout << "time2=" << (clock()-s_time)/1e6 << endl;
}
输出结果:
sum1=100000000
time1=0.171745
sum2=100000000
time2=0.466071
23. 多线程相关的bug
开了32个线程跑一个代码,每个线程执行的任务中又有for循环,我将这个for用了cilk里面的并行循环来处理,但是我cilk只开了1个线程。于是,我的32个线程运行起来之后并没有完全占满cpu(即3200%)。将cilk的并行for改为普通for就解决了。原因可能是用了cilk循环之后,大家都用cilk开的线程去执行任务了,然而它只开了一个,所以会轮着使用,到是32个外面线程不能真正并行。
24.C/C++ 中利用debug宏定义打开/关闭调试输出
void test_debug(){
#define openDebug // 是否开始调试
#ifdef openDebug
#define DEBUG(expr) expr // 开启时,执行该语句
#else
#define DEBUG(expr) // 否则不执行
#endif
int a = 0;
DEBUG(cout << "debug, " << 2 << endl;)
DEBUG(a++;)
cout << "a=" << a << endl;
}
随便贴一个可以看汇编的网站: https://godbolt.org/
如下图,我们打开第5行openDebug时,会发现左边代码第12行,会产生如右边第11行到19行对应的汇编代码,即会执行
cout << "debug, " << 2 << endl;
左边第13行,会产生有右边第20行对应的汇编代码, 即会执行
a++;
相反,我们注释调第5行,会发现,左边第12,13行不会产生汇编代码.如下图,第11行对应右边第8行,左边第14行,对应右边第9-19行,左边的12,13没有产生汇报代码.
25. _attribute_((packed))详解
attribute((packed))详解
- attribute ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。这个功能是跟操作系统没关系,跟编译器有关,gcc编译器不是紧凑模式的,我在windows下,用vc的编译器也不是紧凑的,用tc的编译器就是紧凑的。例如:
在TC下:struct my{ char ch; int a;} sizeof(int)=2;sizeof(my)=3;(紧凑模式)
在GCC下:struct my{ char ch; int a;} sizeof(int)=4;sizeof(my)=8;(非紧凑模式)
在GCC下:struct my{ char ch; int a;}attrubte ((packed)) sizeof(int)=4;sizeof(my)=5
- attribute__关键字主要是用来在函数或数据声明中设置其属性。给函数赋给属性的主要目的在于让编译器进行优化。函数声明中的__attribute((noreturn)),就是告诉编译器这个函数不会返回给调用者,以便编译器在优化时去掉不必要的函数返回代码。
GNU C的一大特色就是__attribute__机制。__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。
26. 浮点数不要直接用=比较是否相等!!!
float
和double
数据类型的变量在比较大小时一定不能用等于号,不然会有BUG!!!
应该用做差,然后差值小于一个阈值则认为相等!!!
27. 函数调用与内联函数
函数的调用会有一定的开销.
相关内容推荐: 被知乎大佬嘲讽后的一个月,我重新研究了一下内联函数
另外一位博主对各类函数调用的性能做了一个测试: C++ 函数调用时间开销
此外, 个人主要想说下内联函数调用的情况. 最近在开发系统的过程中发现一处代码比别人的系统中类似代码速度慢了将近一倍, 于是具体测试了一下,发现是函数调用的原因.
起初, 我以为是函数调用与非函数调用的导致的差距, 因为别人的系统是没有调用函数, 于是我将函数调用换成直接的计算逻辑,发现确实快了. 那么函数调用影响有这么大吗? 于是我写了代码测试, 发现普通函数调用开销不至于那么大. 我发现主要原因在内联函数上, 因为我用的是重些的虚函数所以内联每用.
参考博客: C++虚函数(10) - 虚函数能否为inline
bool f(float& cur, float a) {
cur += a;
return true;
}
inline bool f_inline(float& cur, float a) {
cur += a;
return true;
}
class Base {
public:
virtual void who() { std::cout << "I am Base" << std::endl; }
// virtual bool f(float& cur, float a) {
// cur += a;
// return true;
// }
virtual bool f(float& cur, float a) = 0;
// bool f(float& cur, float a) {}
bool f_f_1(float& cur, float a){
f(cur, a);
}
void test(){}
void test_2(){}
};
class Derived : public Base {
public:
void who() { std::cout << "I am Derived" << std::endl; }
bool f(float& cur, float a) {
cur += a;
return true;
}
bool f_2(float& cur, float a) {
cur += a;
return true;
}
void test() {
long long num = 2e9;
float cur = 0.f;
float a = 1.f;
for(int i = 0; i < num; i++){
// f(cur, a);
f_f_1(cur, a);
}
}
void test_2() {
long long num = 2e9;
float cur = 0.f;
float a = 1.f;
for(int i = 0; i < num; i++){
f_2(cur, a);
}
}
};
void test_fun_call(){
long long num = 1e8;
double test_time = 0;
{
std::cout << "非函数调用:" << std::endl;
test_time = GetCurrentTime();
float cur = 0;
float a = 1;
for(int i = 0; i < num; i++){
cur += a;
}
test_time = GetCurrentTime() - test_time;
// test_time = GetCurrentTime() - test_time;
std::cout << "test_time=" << test_time << std::endl;
}
{
std::cout << "普通函数f调用:" << std::endl;
test_time = GetCurrentTime();
float cur = 0;
float a = 1;
for(int i = 0; i < num; i++){
f(cur, a);
}
test_time = GetCurrentTime() - test_time;
// test_time = GetCurrentTime() - test_time;
std::cout << "test_time=" << test_time << std::endl;
}
{
std::cout << "内联函数f_inline调用:" << std::endl;
test_time = GetCurrentTime();
float cur = 0;
float a = 1;
for(int i = 0; i < num; i++){
f_inline(cur, a);
}
test_time = GetCurrentTime() - test_time;
// test_time = GetCurrentTime() - test_time;
std::cout << "test_time=" << test_time << std::endl;
}
{
std::cout << "类对象的虚函数:" << std::endl;
test_time = GetCurrentTime();
float cur = 0;
float a = 1;
Derived b;
b.who();
for(int i = 0; i < num; i++){
b.f(cur, a);
}
test_time = GetCurrentTime() - test_time;
// test_time = GetCurrentTime() - test_time;
std::cout << "test_time=" << test_time << std::endl;
}
{
std::cout << "类指针的虚函数:" << std::endl;
test_time = GetCurrentTime();
float cur = 0;
float a = 1;
Base* ptr = new Derived();
ptr->who();
for(int i = 0; i < num; i++){
// f(cur, a);
// cur += a;
ptr->f(cur, a);
}
test_time = GetCurrentTime() - test_time;
// test_time = GetCurrentTime() - test_time;
std::cout << "test_time=" << test_time << std::endl;
}
{
std::cout << "类成员函数: test" << std::endl;
test_time = GetCurrentTime();
float cur = 0;
float a = 1;
Derived b;
b.who();
b.test();
test_time = GetCurrentTime() - test_time;
// test_time = GetCurrentTime() - test_time;
std::cout << "test_time=" << test_time << std::endl;
}
{
std::cout << "类成员函数:test_2" << std::endl;
test_time = GetCurrentTime();
float cur = 0;
float a = 1;
Derived b;
b.who();
b.test_2();
test_time = GetCurrentTime() - test_time;
// test_time = GetCurrentTime() - test_time;
std::cout << "test_time=" << test_time << std::endl;
}
{
std::cout << "类成员函数1:test" << std::endl;
test_time = GetCurrentTime();
float cur = 0;
float a = 1;
Derived* ptr = new Derived();
ptr->who();
ptr->test();
test_time = GetCurrentTime() - test_time;
// test_time = GetCurrentTime() - test_time;
std::cout << "test_time=" << test_time << std::endl;
}
{
std::cout << "类成员函数2:test_2" << std::endl;
test_time = GetCurrentTime();
float cur = 0;
float a = 1;
Derived* ptr = new Derived();
ptr->who();
ptr->test_2();
test_time = GetCurrentTime() - test_time;
// test_time = GetCurrentTime() - test_time;
std::cout << "test_time=" << test_time << std::endl;
}
}
28.unordered_map.emplace
std::unordered_map<Key,T,Hash,KeyEqual,Allocator>::emplace
template < class ... args >
std:: pair < iterator, bool > emplace ( Args && ... args ) ;
- return
返回一个由一个迭代器组成的对插入的元素,如果没有插入,则返回已经存在的元素,以及一个 布尔值 表示插入是否发生(真的 如果插入发生, 错误的 如果没有)。
如果容器中没有带有键的元素,则将新元素插入到使用给定参数就地构造的容器中。 小心使用 emplace 允许构造新元素,同时避免不必要的复制或移动操作。 新元素的构造函数(即 std::pair<const Key, T>)使用与提供给 emplace 的参数完全相同的参数调用,通过 std::forward(args)… 元素可以 即使容器中已经有一个带有键的元素,也会被构造,在这种情况下,新构造的元素将被立即销毁。 如果由于插入而发生重新散列,则所有迭代器都将失效。 否则迭代器不受影响。 引用不会失效。 仅当新的元素数大于 max_load_factor()*bucket_count() 时才会发生重新散列。
注意: 对于已经存在的key将不会被重新插入,即不会覆盖old键值对。
测试代码:
void test_unordered_map() {
std::unordered_map<int, int> m;
auto rt = m.emplace(1, 1);
std::cout << m[1] << " - " << rt.second << std::endl;
rt = m.emplace(1, 2);
std::cout << m[1] << " - " << rt.second << std::endl;
m[1] = 3;
std::cout << m[1] << std::endl;
}
输出:
1 - 1
1 - 0
3
29.二维vector的内存分配
参考
在内存中,二维vector的每个子vector内部是连续的,但是子vector之间是非连续的。且vector[0]和vector[0][0]地址都是不同的。
30.C++枚举类型enum与enum class的使用方法是什么
https://www.yisu.com/zixun/311515.html
- 什么是枚举类型?
答:如果一个变量只有几种可能的值,那么就可以定义为枚举类型,比如:性别只有男和女,那么就可以将性别定义为一种枚举类型,其中男和女就是性别所包含的变量。所谓”枚举”是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。在C++中,枚举类型分为不限定作用域(enum)和限定作用域(enum class)。
- enum与enum class的区别?(为什么需要限定作用域?)
答:枚举作用域是指枚举类型成员名字的作用域,起自其声明之处,终止枚举定义结束之处。enum与class enum区别在于是否限定其作用域。C语言规定,枚举类型(enum)的成员的可见范围被提升至该枚举类型所在的作用域内。这被认为有可能污染了外部的作用域,为此,C++11引入了枚举类(enum class)解决此问题。枚举定义将被限制在枚举作用域内,并且不能隐式转换为整数类型,但是可以显式转化为整数类型。
enum class Sex
{
Girl,
Boy
};
int main(int argc, char *argv[])
{
Sex a=Sex::Gril;
int d1 =a; // 错误,无法从“Girl”隐式转换为“int”。
int d2 = int(a); // 正确,显示将enum class转换为整数
return 0;
}
16个字节的原子操作
C++ undefined reference to `__atomic_load_16’
参考:
https://stackoverflow.com/questions/42799293/undefined-reference-to-atomic-store-16
https://stackoverflow.com/questions/37613415/c-undefined-reference-to-atomic-load-16
#include <atomic>
struct MyStruct{
long x; long y;
};
struct X{
std::atomic<MyStruct> myStruct;
};
int main(){
X x;
MyStruct s = atomic_load(&x.myStruct);
}
运行命令:
g++ test_atomic.cc -o test -std=c++11 -g3 -latomic
31.dobule和float的误差
发现float除完在累和,已经损失了
long long size = 41291318;
float sum_f = 0;
double sum_d = 0;
for (int i = 0; i < size; i++) {
sum_f += (1 - 0.85) / size;
sum_d += (1 - 0.85) / size;
}
std::cout << "sum_f=" << sum_f << std::endl; // sum_f=0.0625
std::cout << "sum_d=" << sum_d << std::endl; // sum_d=0.15