【介绍】

本文完整的描述了C++语言的性能优化方法,从编译器、算法、语言特性、硬件、Linux等多个角度去考虑问题,文章技术含量很高,值得一看。


来源:http://www.whysearch.org/a/zh_CN/date/20110824

作者:冲出宇宙

【目录】

第一章 性能优化原理

第二章 善用编译器

第三章 算法为王

第四章 c++语言特性

第五章 理解硬件

第六章 linux系统



1、性能优化原理

在谈论性能优化技术之前,有几点大家一定要明确。第一点是必须有编写良好的代码,编写的很混乱的代码(如注释缺乏、命名模糊),很难进行优化。第二点是良好的构架设计,性能优化只能优化单个程序,并不能够优化蹩脚的构架。不过,网络如此发达,只要不是自己乱想的构架,只要去积极分析别人的成功构架,大家几乎不会遇到蹩脚的构架。

 

1.1、计算函数、代码段调用次数和耗时

函数的调用次数比较好说,用一个简单的计数器即可。一个更加通用的框架可能是维护一个全局计数,每次进入函数或者代码段的时候,给存储的对应计数增加1

为了精确的计算一段代码的耗时,我们需要极高精度的时间函数。gettimeofday是其中一个不错的选择,它的精度在1us,每秒可以调用几十万次。注意到现代cpu每秒能够处理上G的指令,所以1uscpu可以处理几千甚至上万条指令。对于代码长度少于百行的函数来说,其单次执行时间很可能小于1us。目前最精确的计时方式是cpu自己提供的指令:rdtsc。它可以精确到一个时钟周期(1条指令需要消耗cpu几个时钟周期)。


我们注意到,系统在调度程序的时候,可能会把程序放到不同的cpu核心上面运行,而每个cpu核心上面运行的周期不同,从而导致了采用rdtsc时,计算的结果不正确。解决方案是调用linux系统的sched_setaffinity来强制进程只在固定的cpu核心上运行。

有关耗时计算的参考代码:

// 通常计算代码耗时

uint64_t preTime = GetTime();

//代码段

uint64_t timeUsed = GetTime() - preTime;

// 改进的计算方式

struct TimeHelper{

uint64_t preTime;

TimeHelper():preTime(GetTime())

{}

~TimeHelper(){

g_timeUsed = GetTime() - preTime;

}

};

// 调用

{

TimeHelper th;

// 代码段

}

// g_timeUsed保存了耗时

// 得到cputick countcpuid(重整时钟周期)消耗约300周期(如果不需要特别精确的精度,可以不执行cpuid

inline uint64_t GetTickCPU()

{

uint32_t op;  // input:  eax

uint32_t eax; // output: eax

asm volatile(   

"pushl %%ebx   \n\t"

"cpuid         \n\t" 

"popl %%ebx    \n\t" 

: "=a"(eax)   : "a"(op)  : "cc" );

uint64_t ret;

asm volatile ("rdtsc" : "=A" (ret));

return ret;

}

// 得到cpu的主频本函数第一次调用会耗时0.01秒钟

inline uint64_t GetCpuTickPerSecond()

{

static uint64_t ret = 0;

if(ret == 0)

{

const uint64_t gap = 1000000 / 100;

uint64_t endTime = GetTimeUS() + gap;

uint64_t curTime = 0;

uint64_t tickStart = GetTickCPU();

do{

curTime = GetTimeUS();

}while(curTime < endTime);

uint64_t tickCount = GetTickCPU() - tickStart;

ret = tickCount * 1000000L / (curTime - endTime + gap);

}

return ret;

}

1.2、其他策略

除了基本的计算执行次数和时间外,还有如下几种分析性能的策略:

a、基于概率

通过不断的中断程序,查看程序中断的位置所在的函数,出现次数最多的函数即为耗时最严重的函数。

b、基于事件

当发生一次cpu硬件事件的时候,某些cup会通知进程。如果事件包括L1失效多少次这种,我们就能知道程序跑的慢的原因。

c、避免干扰

性能测试最忌讳外界干扰。比如,内存不足,读内存变成了磁盘操作。

1.3、性能分析工具-callgrind

valgrind系列工具因为免费,所以在linux系统上面最常见。callgrindvalgrind工具里面的一员,它的主要功能是模拟cpucache,能够计算多级cache的有效、失效次数,以及每个函数的调用耗时统计。

callgrind的实现机理(基于外部中断)决定了它有不少缺点。比如,会导致程序严重变慢、不支持高度优化的程序、耗时统计的结果误差较大等等。

我们编写了一个简单的测试程序,用它来测试常见性能分析工具。代码如下:

// 计算最大公约数

inline int gcd(int m, int n)

{

PERFOMANCE("gcd"); // 全局计算耗时的define

int d = 0;

do{

d = m % n;

m = n;

n = d;

 }while(d > 0);

return m;

}

// 主函数

int main(){

int g = 0;

uint64_t pretime = GetTickCPU();

for(int idx = 1; idx < 1000000;idx ++)

g += gcd(1234134,idx);

uint64_t time = GetTickCPU() - pretime;

printf("%d,%lld\n", g, time);

return 0;

}

callgrind运行的结果如下:

我们把输出的结果在windows下用callgrind的工具分析,得到如下结果:

1.4g++性能分析

gprofg++自带的性能分析工具(gnu profile)。它通过内嵌代码到各个函数里面来计算函数耗时。按理说它应该对高度优化代码很有效,但实际上它对-O2的代码并不友好,这个可能和它的实现位置有关系(在代码优化之后)。gprof的原理决定了它对程序影响较小。

下图是同样的程序,用gprof检查的结果:


 

我们可以看到,这个结果比callgrind计算的要精确很多。








在前一章,我们对分析代码和函数性能的策略进行了介绍。本章将介绍算法在程序性能方面的作用。

如果没有看过第一章的兄弟,在这里查看:第一章 性能分析原理。

算法为王

算法是程序的核心,一个程序的好坏,大部分是看起算法的好坏。对于一般的程序员来讲,我们无法改变系统和库的调用,只能根据规则来使用它们,我们可以改变的是我们自己核心代码的算法。

算法能够十倍甚至百倍的提高程序性能。如快排和冒泡排序,在上千万规模的时候,后者比前者慢几千倍。

通常情况下,有如下一些领域的算法:

A)常见数据结构和算法

B)输入输出

C)内存操作

D)字符串操作

E)加密解密、压缩解压

F)数学函数

本文不是讲解算法和数据结构,所以,我们不展开。

2.1 选择算法

程序里面使用最多的是检索和排序。

map是一种很通用的结构(如c++里面的std::map或者javaTreeMap),一般的语言都是用红黑树来实现。红黑树是一种读写性能比较均衡的平衡二叉树。

对于排序来说,std::sort采用的是改进的quicksort算法,即intro sort。这种算法在递归层次较深的时候,改用堆排序,从而避免了快排进入“陷阱”(即O(N)复杂度)。Introsort是公认的最好的快速排序算法。

平常的排序用introsort即可,但在遇到大规模字符串排序的时候,更好的一个策略是采用基数排序。笔者的经验是,千万量级时,基数排序在字符串领域比introsort快几十倍。有很多研究论文探讨基数排序在字符串领域的应用,大家可以去看看,如:Efficient Trie-Based Sorting of Large Sets Of Strings

在某些情况下,如果数组基本有序的话,可能希尔排序也是一个好选择。希尔排序最重要的是其每次选择的数据间隔,这个方面有专门的研究可以参考。

至于其他的特殊算法,如多个有序数组归并等等,大家可以在实际情况中灵活应变。

2.2 算法应用优化策略

在实际应用中,有一些基本的优化策略可以借鉴。如:

A)数组化

这条策略的逻辑很简单:访问数组比访问其他结构(如指针)更快。基于这种考虑,我们可以把树结构变成数组结构。数组平衡树,它把一个通常的平衡树修改为数组的形式,但编程比较复杂。双数组Trie树,用2个或者多个数组来描述Trie树,因为trie树是一个多叉树,变成数组后,性能可以提高10多倍。数组hashhash表用数组描述,最方面最有名的结构是bloom filtercuckoo hash

参考:双数组Trie


参考:bloom-filter


B)大节点化

如果一个节点(树或者链表等)长度太小,那么单个数据命中cpu cache的概率就很低。考虑到cpu cache line的长度(如64字节),我们需要尽量把一个节点存放更多的数据。B树就是这样的一种结构,它一个节点保存了大量连续数据,能有效利用cacheJudy Array也是通过谨慎安排树节点的长度来利用cache。列表结构,一个节点存放多个数据,也能提高cache命中率。

2.3 内存管理算法

常见的内存管理算法有很多,如First-FitBest-FitBuddy-systemHal-Fit。每个程序根据自己的特点会采用不同的算法,没有绝对好的算法。比如,内核可能采用Buddy-System。有1个比较经典的算法大家需要清楚,即c语言的内存分配malloc算法。我们目前在各种系统中看到的算法,比如memcachednginx等,都是这种算法的简单变形。

参考:malloc


malloc算法根据空闲内存块大小进行分段,每个段有一个字节范围,在这个范围内的空闲内存块都挂在对应链表上面。分配内存的时候,先找到对应的段,然后取链表的第一个内存块分配即可。

TLSF算法是号称最好的内存分配算法。它也是malloc算法的一种变形。

参考:tlsf


2.4 库的选择

毫无疑问,首选glibc/stl库,因为他们被论证多年,并且,同样的算法,很难写出更好更快的代码。

第二可以考虑boost库,但建议只用那些最常见的功能。

ACMLMKL也是一种高性能的库,他们对向量计算很友好。

对于各种开源库,如glib/apr/ace/gsl/crypto++等等,必须考虑它们开源的协议,避免使用商业收费的协议。对于安装服务器比例不高的库,也尽量不要使用,因为开源库代码都不加什么注释,出错很难查。








在前一章,我们对常见算法的选择做了些简单的说明。本章将介绍g++编译器在性能优化中的重要作用。

如果没有看过第二章的兄弟,在这里查看:第二章 算法为王。

善用编译器

算法能够十倍、几十倍的提高程序性能,但当算法已经很难改进时,还有一种简单的办法提高程序性能,那就是微调编译器。利用编译器提供的各种功能,你能够轻松的提高几倍的程序性能。

大家要记住的是,编译器绝对比想象的要强大的多。编写编译器的人大都是十年、几十年代码编写经验的科学家!你能简单想到的,他们都已经想到过了。普通的编译器,可以支持大部分已知的优化策略以及多媒体指令。

至于哪个编译器更好?大部分人的观点是:intelIntel毕竟是最优秀的cpu提供者,他们的编译器考虑了很多cpu的特性,跑的更快。但目前intel编译器有一些比较弱智的地方,即它只识别自己的cpu,不是自己的cpu,就认为是最差的i386-i686机器,从而不能在amd等平台上面支持sse功能。我们在linux上面写代码,一般更加喜欢流行的编译器,比如gcc

Gcc的优点是它更新快,开源,bug修改迅速。正因为他更新快,所以它能够支持部分C03的规范。

3.1 gcc支持的优化技术

1) 函数内联

函数调用的过程是:压入参数到堆栈、保护现场、调用函数代码、恢复现场。当一个函数被大量调用的时候,函数调用的开销特别巨大。函数内联是指把这些开销都去除,而直接调用代码。函数内联的不好之处是难以调试,因为函数实际上已经不存在了。

2) 常量预先计算

a=b+1000*16

对于这段代码,程序会预先计算1000*16,从而变成:

a=b+16000

3) 相同子串提取

a=(b+1)*(b+1)

这里,b+1需要计算2次,可以只用计算一次:

tmp=b+1

a=tmp*tmp

4) 生存周期分析

这是一个比较高级的技术。假设有代码:

a=b+1

c=a+1

在执行的时候,因为第二句依赖第一句,所以2句是线性执行。

但编译器其实可以知道,c就是等于b+2,所以代码变成:

a=b+1

c=b+2

这样,这2句就没有任何关系来了,执行的时候,cpu可以并行执行它们。

5) 清除跳转

看如下代码:

int func()

{

int ret = 0;

if(xxx)

ret=5;

else if(yyy)

ret=6;

return ret;

}

当条件xxx满足的时候,程序还会跳到下面执行,但其实是没有必要的。编译器会把它变成:

int func()

{

if(xxx)

return 5;

else if(yyy)

return 6;

}

6) 循环展开

循环由几个部分组成:计数器赋值、计算器比较、跳转。每次循环,后面2步都是必须的消耗。把循环内的代码拷贝多份,可以大大减少循环的次数,节约后面2步的耗时。参考:

for(int counter=0;counter<4;count++)

xxx;

可以变成:

xxx;

xxx;

xxx;

xxx;

编译器不仅仅可以展开普通循环,它还能展开递归函数。原理是一样的,递归其实是一个不定长的借用了堆栈的循环。

7) 循环内常量移除

for(int idx=0;idx<100;idx++)

a[idx]=a[idx]*b*b;

因为b*b在循环体内的值固定(常量),所以代码可以变成:

tmp=b*b;

for(int idx=0;idx<100;idx++)

a[idx]=a[idx]*tmp;

8) 并行计算

大家都知道,现代cpu支持超流水线技术,同时可以执行多条语句。多条语句能否同时执行的限制是不能互相依赖。编译器会自动帮我们把看起来单线程执行的代码,变成并行计算,参考:

d=a+b;

e=a+d+f;

可以变成:

tmp=a+f;

d=a+b;

e=d+tmp;

9) 表达式简化

当年笔者在学习《离散数学》和《数字电路》的时候,总被眼花缭乱的布尔运算简化题目难倒。gcc终于让我松了一口气。参考:

!a && !b

这句需要3步执行,但变成:

!(a || b)

只需要2步执行。

3.2 gcc重要优化选项

1) 内联

Ø -finline-small-functions

内联比较小的函数。-O2选项可以打开。

Ø -findirect-inlining

间接内联,可以内联多层次的函数调用。-O2选项可以打开。

Ø -finline-functions

内联所有可以内联的函数。-O3选项可以打开。

Ø -finline-limit=N

可以进行内联的函数的最小代码长度。注意,这里是伪代码,不是真实代码长度。伪代码是编译器经过处理后的代码。带inline等标志的函数,默认300行代码即可内联,不带的默认50行代码。和这个相关的选项是max-inline-insns-singlemax-inline-insns-auto

Ø max-inline-insns-recursive-auto

内联递归函数时,函数的最大代码长度。

Ø large-function-insnslarge-function-growthlarge-unit-insns

函数内联的副作用是它导致代码变多,程序变长。这里的几个参数可以控制代码的总长度,避免编译后出现巨大的程序,影响性能和浪费资源。

2) -fomit-frame-pointer

不采用标准的ebp来记录堆栈指针,节省了一个寄存器,并且代码会更短。但据说在某些机器上面会导致debug模式出错。实际测试表明,在gcc4.2.4以下,O2O3都无法打开这个选项。

3) -fwhole-program

把代码当做一个最终交付的程序来编译,即明确指定了不是编译库。这个时候,编译器可以使用更多的static变量,来加快程序速度。

4) mmx/ssex/avx

多媒体指令,主要支持向量计算。一般来说,-march=i686-mmx-msse-msse2是目前机器都支持的指令。

除了基本的多媒体支持外,gcc编译器还支持-ftree-vectorize,这个选项告诉编译器自动进行向量化,也是-O3支持的选项。

多说几句。在平常的使用中,多媒体指令不是很常见(除非游戏)。如果你有几个位表(bitset),它们需要进行各种位操作的话,多媒体指令还是挺有效果滴。

3.3 gcc大杀器-profile driven optimize

这是比较晚才出现的技术。其基本原理是:根据实际运行情况,缩短hot路径的长度。编译器通过加入各种计数器来监控程序的运行,然后根据计算出来的实际访问路径情况,来分析hot路径,并且缩短其长度。根据gcc开发者的说法,这种技术可以提高20-30%的运行效率。

其使用方式为:

Ø 编译代码,加上-fprofile-generate选项

Ø 到正式环境一段时间

Ø 当程序退出后,会产生一个分析文件

Ø 利用这个分析文件,加上-fprofile-use,重新编译一次程序

举个例子来说:

a=b*5;

如果编译发现b经常等于10,那么它可以把代码变成:

a=50;

if(b != 10)

a=b*5;

从而在大多数情况下,避免了乘法消耗。

3.4 gcc支持的优化属性(__attribute__

Ø aligned

可以设置对齐到64字节,和cpucache line看齐

Ø fastcall

如果函数调用的前面2个参数是整数类型的话,这个选项可以用寄存器来传递参数,而不是用常规的堆栈

Ø pure

函数是纯粹的函数,任何时刻,同样的输入,都会有同样的输出。可以很方便依据概率来优化它。

3.5 gcc其他优化技术

Ø #pragma pack()

对齐到一个字节,节省内存

Ø __builtin_expect

直接告诉编译器表达式最可能的结果,方便优化

Ø 编译带debug信息的小文件

以下代码能够大大减少编译后程序大小,同时保留debug信息。其原理是外链一个带debug的版本。

g++ tst.cpp -g -O2 -pipe

copy a.out a.gdb

strip --strip-debug a.out

objcopy --add-gnu-debuglink=a.gdb a.out








在前一章,我们对gcc编译器的性能优化策略进行了简单描述。本章将介绍和c++语言相关的性能优化技术。

如果没有看过第二章的兄弟,在这里查看:第二章 善用编译器。

4 C++代码优化


C++语言博大精深,作为一个不到10年的使用者,笔者并没有多少经验,只能通过学习,看源码来形成一些自己的想法。

4.1 变量存储

1、数据区

可执行文件包含多个区域,有代码区,数据区等。一般的c++编译器,会把全局变量、static变量、float/double/string常量、switch跳表、初始化变量列表、虚函数表等存放到数据区域。int变量一般会存储在代码区,和指令放到一起。

略解释一下初始化变量列表:int d[]={1,2,3};

2、堆栈区

堆栈区域保存函数调用、上下文、局部变量。因为局部变量存储在堆栈区,所以访问局部变量很可能会命中cpucache,其速度很快。

3、堆

申请的内存(如通过new)。

4.2 变量优化

1、使用成员初始化和构造初始化列表

它们都可以避免2次赋值(即初始化后再赋值)。如:

pubilc C(): x(10)

{}

std::string str("java");

避免使用:

std::string str="java";

2、堆栈最快

上面已经说过,因为cache的原因,堆栈变量访问速度很快。

Ø 缩短变量周期

让变量更快速的结束,有2个好处:占用的位置可以给下面的变量使用、编译器甚至可以用寄存器来存储变量。

Ø 延期申请

变量距离上一个使用过的变量近,被cache概率高。

3、参数传递

为了降低函数调用的开销,当有多个参数时,最好把参数组合成一个结构。

4、返回变量

Ø 返回构造形式

避免2次拷贝。如:

return string("java");

要比

return "java";

更快。

Ø 用引用代替返回

避免构造对象。在函数调用的时候,把需要返回的对象都用引用传递进来。如:

void func(Object& retObj);

5、变量紧密定义

关联度很高的变量可以定义在一起。举例来说:

int a[N],b[N];

for(int idx=0;idx<N;idx++)

a[idx] = b[idx];

修改成:

struct {

int a,b;

} d[N];

for(int idx=0;idx<N;idx++)

d[idx].a=d[idx].b;

后者因为a,b紧密定义在一起,其访问对cache更友好。

6、类/结构成员顺序

因为默认对齐的原因,成员变量的顺序对对象的空间占用有一定影响。一般把变量按照字节大小从前往后放。比如:

struct{

double d;

int i;

short s;

bool b;

}

size16字节。但:

struct{

bool b;

double d;

short s;

int i;

}

size20字节。

例外的是数组成员。一般认为数组成员应该往后放。这是因为访问其他变量的时候,相对偏移(结构的初始位置)比较小,代码更短。如:

mov eax, [ebp+10h] 显然比 mov eax, [ebp+256h] 实际代码要短。

4.3 函数内联

函数内联作为编译器最大最好的优化选项,无论在哪里都值得探讨一番。函数内联的好处是节省了保护现场和返回值的开销。编译器并不是万能的,有些函数很容易进行内联,有些函数则很难进行内联。

对编译器友好的函数,一般代码比较短,函数没有递归逻辑。对编译器不友好的函数,显然就是指:函数指针调用、深度递归、虚函数。函数指针调用,会让编译器不知道真实的函数在哪里,既然都不知道函数在哪里,自然无法内联了。虚函数也是一样的问题,编译器不清楚调用的方法在哪里。

有一种策略可以把虚函数变成可以内联的函数,下面在重点讨论这个策略。假设我们的程序如下:

struct CParent{

    virtual int f(){

        return 0;

}

};

struct CChild1: CParent{

int f(){

return 1;

}

};

struct CChild2: CParent{

int f(){

return 2;

}

};

调用语句如下:

int count=0;

vector<CParent*> ds;

vector<CParent*>::iterator iter=ds.begin();

while( iter !=ds.end())

{

count += (*iter)->f();

iter ++;

}

毫无疑问,程序在编译的时候,不可能知道f函数到底是CChild1::f还是CChild2::f。我们通过加一个内置的type,来明确告诉编译器到底是f是哪个真正的函数:

struct CParent{

    int type;

    virtual int f(){

        return 0;

}

};

struct CChild1: CParent{

    CChild1(){

type=1;

    }

int f(){

return 1;

}

};

struct CChild2: CParent{

    CChild2(){

    type=2;

}

    int f(){

return 2;

}

};

inline int f(CParent* p){

switch(p->type){

case 1:

return ((CChild1*)p)->CChild1::f();

case 2:

return ((CChild2*)p)->CChild2::f();

}

return 0;

}

// 调用代码

int count=0;

vector<CParent*> ds;

vector<CParent*>::iterator iter=ds.begin();

while( iter !=ds.end())

{

count += f(*iter);

iter ++;

}

我们仅用一个switch语句就节约了函数调用的各种开销,很值得。事实证明,这种策略可以极大的提高程序效率。

4.4 switch优化

switch3种替换模式:

Ø if

if来替换switch。当switch的判断值数量少时,这种策略比较高效。

Ø Jump table,跳表

用一个数组存储case包含的代码,而直接用case的值来跳转到代码位置。如:

switch(d){

case 0:

xxx;

break;

case 1:

yyy;

break;

...

}

变成:

addr={addr_xxx, addr_yyy};

jump addr[d];

但这个策略只适合判断的值比较连续的情况,这是因为数组下标连续。

Ø Lookup table,查找表

查找表适合简单的逻辑,可以预先计算结果,然后直接根据某种逻辑返回结果。但一般需要编程者自己完成设计。比如:

ret ={"a","b","c"}

return ret[d];

最后大家看一下switch语句的独特用法:

4.5 最大概率路径最短化

这次策略的思想我们多次提到。有几种常见的方式来达到这个目的:

Ø switch/if/?x:y

把最常见的情况放在前面,减少其比较次数。

Ø 布尔表达式

&&表达式时,把最不容易为true的放在前面。在||表达式时,把最容易为true的放在前面。

Ø 函数内cache

可以用一个变量存储最常见的返回结果。如:

static int comm_input, comm_output;

if(input == comm_input)

return comm_output;

xxx;

考虑到计算最常见的输入需要很多额外操作,故我们可以只存储最上一次的结果。

4.6 异常

C++的异常有不少诟病,比如没有finally,出错时难以释放资源。它还需要大量额外的资源,因为需要保存那些变量没有被析构等信息。我们的建议是不使用异常,尽量通过自定义log以及assert的方式来处理程序异常。










在前一章,我们对c++语言和性能有关的部分进行了简单描述。本章将介绍和和cpu硬件相关的性能优化技术。


理解硬件


    Intelcpu外部结构由著名的CPU、南北桥组成。北桥连接的都是快速设备,如内存和显卡。南桥则是低速设备,如磁盘、声卡等等。

图 支持intel的主板

 

intel cpu基本构架图

 

 

AMDcpu'外部结构有所不同。其socket的接口为AM3,其内存控制器在cpu里面,可以直接连接内存。

图 支持AMD的主板

 

 

图 amd K10构架

 

5.1 理解内存

我们现在使用的一般是DDR3代内存条,台式机的内存一般有240针,但内存的实际数据位数只有64位,即8个字节。

 

图 台式机内存脚的说明

 

一般来说,内存的速度比cpu的速度慢。为了提高内存的数据传输速度,有人设计了多通道模式。在这种模式下,内存的传输速度可以加倍。多通道有双通道和三通道之说,后者仅最新的Intel cpu支持。多通道又分为GangedUnganged模式。前者每次提供128位数据,后者每次提供64位数据,但容许同时读取。实际结果表明,后者对多核多线程的服务器更加有效。(注:cpuz会把unganged模式识别为单通道+

 

图 多通道

 

 

 

5.2 理解cpu cache

现代cpu包含多级cache,一般为L1-L33级。下图是笔者对cpu cache的分析图:

 

图 cpu cache分析

 

从图中我们可以看到,内存控制器(IMC)通过多个通道和内存(memory stick)连接。每个通道的位宽是64。内存控制器和L3 cache的位宽是96。为啥是94位呢?这是因为在双通道模式下,内存控制器的进入位宽是128,而IMC的频率一般为内存频率的1.5倍(实际数据),如果出位宽是96,则IMC的频率差不多刚好够用(需要为内存频率的1.33倍)。

 

5.3 cache line的解释

有几点大家要明白:

内存位宽是64

Cpu Lx cache line64字节;

Cache每次消耗8个时钟读取一个数据(从内存中)。

在双通道ganged模式下,只需要4个时钟的读取时间。

Lx cache N-way的概念

内存中的数据,只能固定存储在Lx cache中的某些位置。对于32waycache来说,每块内存中的数据(即把内存按照cache line的长度切分)只能在32个位置中出现。这样的目的是加快cache的查找。

 

5.4 内存速度

内存的速度很难计算,毕竟不是所有的指令都能让cpu和内存之间的通道全速工作。好在mov/movq指令可以比较全面的利用cpu流水线,能够测试出大概的内存速度。

对于DDR3 1333的内存和北桥(或者IMC)速度为1995Mcpu,我们大致可以计算如下:

Ø DDR3 1333, unganged dual channel

理论带宽:1333*8*2=21G/s,访问延迟:几十个ns

Ø L3 cache, NB-speed(1995M), on-die, 48-way

理论带宽:1995*8=16G/s,访问延迟:10ns

Ø L2 cache, full-speed, on-die

理论带宽2800*8=22G/s,访问延迟:几ns

Ø L1 cache

理论带宽未知,访问延迟<1ns

 

下面的图是EVEREST的测试结果:

 

 

5.5 几个简单应用

第一个应用:快速2分查找。2分查找的主要问题是访问随机,对cache很不友好。我们根据查找树把元素重新排列,增加每个节点的数据个数。从而提高了cache的命中率。

 

图 快速2分查找

 

第二个应用:最快速的内存拷贝。主要是3个步骤:1)预先读取指令,把数据读取到cpu cache里面;2)采用mmx缓存器,有效利用cpu流水线;3movn指令,直接写内存,不存放在Lx cache里面。

 

图 最快的memory copy

 

 

5.5 多核的问题

图 代码

 

 

图 问题

 

 

在多核的情况下,cpu需要经常同步他们之间的cache。而每次同步都是以一个cache line为单位。当多个核心cache了同样的line时,如果你不小心修改了line中的一个字节,也会导致整个line被同步。这就导致了本来和其他线程无关的数据,也会经常的被同步给它们。所以,我们建议在定义变量的时候,每个线程只写入位置超过一个line长度的变量。

5.6 cpu指令集介绍

386:支持少量寄存器;

x6464位,多了几个寄存器;

mmxsseX:多媒体指令、适合向量计算;

avx256YMM寄存器,支持浮点(但只被新系统支持);

3dnow!:多媒体指令









在前一章,我们对cpu及其周边部分进行了简单描述。本章将介绍和和linux系统有关的一些通用性能优化技术。


6 linux系统

系统对程序性能的影响很大,因为程序大量使用系统提供的核心库。从程序角度来看,耗时严重的地方主要是在IO处理,包括基本的磁盘读写、网络读写外,还包括系统对页的管理(会导致磁盘IO)。

6.1 更快的磁盘IO

通常使用标准c/c++函数读取文件时,库和系统都会进行很多处理,如把数据在各自的buffer里面进行传递。

图 文件读取示意图

Linux系统提供了底层open函数,加上设置O_DIRECT,可以跳过中间的cache,直接从磁盘读取(当然,磁盘设备自身的cache还会有)。有人测试表明,这种策略几乎不能提高(提高1-2%)顺序读取文件的速度。这种策略的最大好处其实不是跳过程序中间cache,而是跳过linux系统全局文件块缓存。全局文件块缓存会额外的消耗不少cpu和内存,考虑这点,加上减少的中间层cpu消耗,本策略能够大大降低cpu的消耗。从别人的测试结果中能够看到,在多线程同时进行磁盘IO时,本方法能够较大的提高性能。使用的时候,请注意它的对齐约束。

mmap策略是另外一个更好的办法,简单易用,并且没有open函数的系统调用开销。异步ioaio)是另外一个不错的减少等待的方式,不会阻塞当前线程。

6.2 更快的网络IO

第一步,用epoll替换select

select每次等待都会遍历所有的socket,而epoll每次都只用处理有事件的socket。在大量并发socket的情况下,它们的速度相差很远。

第二步,加快连接速度。

使用基本的connect函数,在连不上时(如服务器端口未监听),需要很久很久才会返回错误。一种解决方案是采用异步connect,不阻塞在当前线程;另外一种解决方案是设置IPTOS_LOWDELAY标志,让对方马上响应。参考代码:

bool SetLowDelay(int32_t sd){

#  ifdef IPTOS_LOWDELAY

    const int tos = IPTOS_LOWDELAY;

#  else

    const int tos = 0x10;

#  endif

    return setsockopt (sd, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof (tos)) == 0;

}

第三步,禁止Nagle算法。

这个算法会缓存数据,等数据达到一定大小后再一起发送。某些情况下,它会导致包发送速度变慢。通过设置TCP_NODELAY来禁止它。有人测试说,禁用Nagle算法会导致apache服务器吞吐变慢。参考代码:

bool SetDisableNagle(int32_t sd){

    int bool_true = 1;

    return setsockopt( sd, IPPROTO_TCP, TCP_NODELAY, (char *)&bool_true, sizeof(bool_true)) == 0;

}

第四步,把socket缓存设置为最大。

如果有权限,可以先修改系统支持的最大缓冲大小,参考:

/sbin/sysctl -w net.core.rmem_max

/sbin/sysctl -w net.core.wmem_max

/sbin/sysctl -w net.ipv4.tcp_mem

至于在程序中,可以通过SO_SNDBUF标志来修改socket的实际缓冲大小。有一种策略是用2分法来找到可以设置成功的最大缓冲值。

第五步,使用accept4替换accept

通常在accept得到一个socket id之后,都会对socket进行一些基本的设置,这就产生了2次系统调用。而accept4函数可以直接设置socketflag,减少了一次系统调用。

第六步,使用合适的网络层构架。

图 网络层构架1

正如图中所示,本结构把socket交给单个线程管理,每个线程管理多个socket。这种结构的好处是耦合少,不用进行线程同步;额外开销少,适合单任务处理速度很快的情况。不好之处是在socket比较少时,难以使用所有线程的处理能力;二次发送数据间隔较长,导致实际处理缓慢。典型的系统见memcachednginx

另外一种网络结构统一管理socket,通过分发器来均匀分发任务,确保每个线程都繁忙。参考图:

这种结构统一管理任务,发送数据时异步缓存,保证线程一次发送成功。能够极限利用cpu的处理能力。其缺点是结构复杂,额外有不少消耗。目前电商搜索后台一般采用这种逻辑。

6.3 少用swap

swap的工作原理是:系统自动的把它认为不重要的页从内存置换到磁盘,以提供内存空间给其他人使用。虽然其设计者认为在linux下应该把swappiness设置为100%,但在很多情况下,swap都会导致严重问题。

swap会导致机器死机。某些情况下,当某个程序的swap占用过大时,如果这个时候程序要退出或者不需要那么多页,系统会尝试把swap的数据置换到内存,以便进行下一步操作,如果这个时候内存不足以存放这些数据,就会导致死机。注:swap导致死机的问题,不知道大家有没有更好的解释?

一般来说,数据库和能够自带缓存的程序,都不希望系统把内存页置换出去。最简单的“阻止”swap的策略是修改系统参数,参考:

sysctl -w vm.swappiness=0

echo 1>/proc/sys/vm/drop_caches

第一条语句是建议系统不要使用swap,第二条语句是让系统清理cache,以便释放更多内存。但第一条并不能够绝对阻止swap(因为只是建议)。

mlockall是程序比较常用的锁定内存的函数。它能够防止内存页被置换到swap

6.4 避免多线程写磁盘

多线程大量写入磁盘文件,如果是同一个文件有多个写入者,会导致同步的麻烦;如果是不同的文件,也会因为大量IO导致机器假死(如不接收网络连接)。通过调低vm.dirty_ratiovm.dirty_background_ratio参数来降低系统文件写缓存能力,减少写入时大量的cpu耗用。

题外话,减少squidcoss文件的使用,因为coss文件消耗cpu也很严重。

6.5 锁理论

多线程环境下,给资源加锁是很有技巧的事情。最简单的策略是尽量减少线程进入锁的时间。从理论上面讲,CAS是锁的基础CASCompare&Swap,其原理很简单,就是一个基本的操作:如果n等于m,则把nq交换。为啥CAS能够能够完美的支持锁呢?原理很复杂,参考论文《Wait-Free Synchronization》。汇编语言的CMPXCHG就可以支持CAS。而G++自带的__sync_val_compare_and_swap/__sync_bool_compare_and_swap函数(支持CAS),仅在编译的时候用-march=i486以上时才生效。考虑到现在的intelamd的都支持i686以上指令,可以直接换成-march=i686

pthread_mutex是常用的锁模式。它的开销也不算大,但在大量调用的时候,还是对性能有影响。有人测试说,开100个线程,每个调用10w次,需要25秒。

atomic原子操作也很常用。因为它主要是对int类型进行处理,故经常在引用计数的地方看到它。

在高性能编程中,我们更有兴趣的是non-blocking算法(也叫lock free)。这类算法能够不用加锁来进行多线程同步。常见的有以下两种策略:

1circular buffer(也叫ring) fifo

首尾相连的队列结构,一般用数组表示。用开始和结束指针来表示数据位置。参考:

这种结构适合一个线程写一个线程读的情况。

2RCUread-copy-update

读取拷贝更新,意思就是说,在要进行update的时候,先保留旧数据,然后替换新数据;再等待旧数据无人使用后,删除旧数据。从它的思想可以看到它写入数据的开销比较大,因为要等待所有读取线程都不在使用旧数据为止。这种结构比较适合用在很多线程读,但只有少量写的地方。

Linux核心提供了基于RCU的锁函数。但在用户态下,需要自己调用其他库,如liburcu

下图简单的描述了在实时索引技术里面使用的一种免锁机制:

图中是内存结构,每个色块代表一个内存数据,而白色的表示空闲内存。当来了新的数据,和橘色的块合并为一个新的块,把这个块存放到空闲内存的最底部。右边灰色的表示旧橘色数据的位置。


(完)


【附录】

文章相关链接:

http://hi.baidu.com/algorithms/blog/calendar/201108


http://www.whysearch.org/a/zh_CN/date/20110824
http://www.whysearch.org/a/zh_CN/date/20110826
http://www.whysearch.org/a/zh_CN/date/20110901
http://www.whysearch.org/a/zh_CN/date/20110902
http://www.whysearch.org/a/zh_CN/date/20110905
http://www.whysearch.org/a/zh_CN/date/20110922






查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 成员函数指针

    成员函数指针函数指针中的函数都是全局作用域内的函数,而成员函数指针指向的函数往往都是某个类的非静态成员函数。使用如下代码,编译器会报非法转换的错误。报这个错有俩个原因首先类内的函数是在类作用域中的,而函数指针指向的函数是在全局作用域的。 成员函数有隐含的thi…...

    2024/4/18 18:45:30
  2. 将http访问强制指向https

    一个实用的.htaccess规则,将http访问强制指向https。使用只需将下面代码另存为.htaccess文件,传到网站根目录下即可。当然前提是你已经开通了.htaccess支持。如果不会的话,可以网上找找,很多这类教程的。 RewriteEngine on RewriteBase / RewriteCond %{SERVER_PORT} !^443…...

    2024/4/12 22:43:20
  3. C++之文件IO

    C++中,输入输出采用流来进行,例如iostream库中的 cin 和 cout 。对文件进行读写操作也使用流。可以将文件与流关联起来,然后对文件进行操作。要将流与文件关联起来,必须像声明变量那样声明流,流是一种特殊的变量,称为“对象”,要对流进行处理,也必须使用专门处理流的函…...

    2024/4/12 22:43:45
  4. android mediaplayer VideoPlayerManager 加载视频闪屏问题排查解决

    Android VideoPlayer 在滚动列表实现item视频播放(ListView控件和RecyclerView),在列表滚动时点击屏幕列表暂停,在item视频播放区域,视频播放时会出现闪屏问题。排查解决,VideoPlayerManager-》MediaPlayerWrapper.java->prepare():{ .prepareAsync().set(State.)(!= …...

    2024/4/18 23:01:31
  5. nginx解决vue刷新404问题

    在配置文件中加入以下配置 location / {if (!-e $request_filename){rewrite ^(.*)$ /index.html last; break;}}如果服务器是apache在根目录下新建.htaccess文件,然后加入以下配置<IfModule mod_rewrite.c>RewriteEngine OnRewriteBase /RewriteRule ^index\.html$ -…...

    2024/4/25 12:13:24
  6. C++获取类中成员函数的函数指针

    注意调用类中非静态成员函数的时候,使用的是类名::函数名;而不是实例名::函数名。class A { public:static void staticmember(){cout<<"static"<<endl;} //static membervoid nonstatic(){cout<<"nonstatic"<<endl;} …...

    2024/4/23 2:25:10
  7. Windows使用ROS机器人操作系统1&2记录

    文档稍后更新,在windows下直接使用ROS机器人操作系统1和2已经一年多,主要用于教学。为方便学生安装中快速解决问题,记录常见问题。需要分别需要catkin/colcon:ROS1:melodic choco upgrade ros-catkin-tools -y --execution-timeout=0ROS2:dashing choco upgrade ros-colc…...

    2024/4/20 7:32:12
  8. 20、 排序算法c语言描述---双向冒泡排序

    排序算法系列学习,主要描述冒泡排序,选择排序,直接插入排序,希尔排序,堆排序,归并排序,快速排序等排序进行分析。文章规划:一。通过自己对排序算法本身的理解,对每个方法写个小测试程序。 具体思路分析不展开描述。二。通过《大话数据结构》一书的截图,详细分析该算法…...

    2024/4/9 7:29:17
  9. iOSAPP之新手引导页

    1.在Main.storyboard中找到,ScrollView和PageControl。 2.在ScrollView中添加ImageView,新手引导页有几个图片就添加几个,然后设置ImageView的image,就是准备好的图片。 3.要设置好ScrollViewscroll View中的Left和View中的Width,使其等于图片的大小,还有就是图片大小的起始…...

    2024/4/12 15:48:31
  10. Android如何设置TextView的行间距、行高

    原文来自:你我学习网: 链接地址:http://www.niwoxuexi.com/blog/android/article/222.htmlAndroid系统中TextView默认显示中文时会比较紧凑,不是很美观。为了让每行保持一定的行间距,可以设置属性android:lineSpacingExtra或android:lineSpacingMultiplier。关于Android下…...

    2024/4/9 7:29:17
  11. 类成员函数指针和普通函数指针

    前言:先声明一下,普通函数指针和类成员函数指针有很大的区别!所以在绑定函数的时候也会发生很多的不同的情况,本文就函数指针可能出现的各种情况一一进行分析。测试目录:1.普通函数指针指向普通函数 2.普通函数指向非静态成员函数 3. 类外部的 类函数指针 指向普通函数 4. …...

    2024/4/13 11:07:15
  12. 301 替换 404

    apache写法:RewriteEngine On RewriteBase /RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ / [R=301,L]nginx写法:if (!-f $request_filename){set $rule_0 1$rule_0; } if (!-d $request_filename){set $rule_0 2$rule_0; }…...

    2024/4/18 3:49:57
  13. ROS 2 ardent apalone安装和使用说明

    ROS 2 Ardent Apalone是机器人操作系统(2代)第一个正式版。目前ROS 2和ROS 1 具体资料分别在如下两个网址: I . ROS 1(代表indigo/kinetic):http://wiki.ros.org/I I. ROS 2(代表ardent):https://github.com/ros2/ros2/wikiIII. ROS 1和ROS 2的一些资料:http://blog.…...

    2024/4/18 3:27:08
  14. C++之格式化输出

    每个输出流都有 precision 成员函数,一旦为某个输出流调用了 precision 函数,向该流输出带小数点的数字时候,要么总共保留两位有效数字,要么在小数点之后保留两位,具体实现方式由编译器决定。precision函数只对指定的流生效。 setf 成员函数是set flags的缩写,不是set f…...

    2024/4/12 22:43:30
  15. 辞职了,重温一下经典面试题。

    https://juejin.im/post/58a6c38861ff4b0062ae4c25 (Android面试题收集)Handler机制http://blog.csdn.net/lmj623565791/article/details/38377229/ (大神鸿洋的看法,讲解的清晰明了)事件分发 View事件分发:http://blog.csdn.net/lmj623565791/article/details/38960443…...

    2024/4/12 22:43:25
  16. ThinkPHP 本地正常 线上提示重定向的次数过多

    如果你确定不是你的程序问题,那一定是规则的问题或许,你的规则是这样的<IfModule mod_rewrite.c>RewriteEngine OnRewriteBase /RewriteRule ^index\.html$ - [L]RewriteCond %{REQUEST_FILENAME} !-fRewriteCond %{REQUEST_FILENAME} !-dRewriteRule . /index.html [L…...

    2024/4/16 9:58:07
  17. 类成员指针及成员函数指针

    如果我们要指定一个指向类内部元素的指针,那我们该怎么操作呢假设我们定义了一个类:里面有两个变量和两个函数struct Vector { public:int x;int y;public:Vector(int tX, int tY) :x{ tX }, y{ tY } {}int getX() const {return x;}int getY() const {return y;}假设我们要定义…...

    2024/4/19 14:08:57
  18. 数组-03. 冒泡法排序(20)

    时间限制400 ms内存限制65536 kB代码长度限制8000 B判题程序Standard作者徐镜春(浙江大学)将N个整数按从小到大排序的冒泡排序法是这样工作的:从头到尾比较相邻两个元素,如果前面的元素大于其紧随的后面元素,则交换它们。通过一遍扫描,则最后一个元素必定是最大的元素。然…...

    2024/4/15 3:00:20
  19. 什么是apache伪静态?apache伪静态怎么写?

    <IfModule mod_rewrite.c>RewriteEngine On RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /index.php?id=$1 </IfModule> 很多人问过RewriteCond %{REQUEST_FILENAME} 是什么意思 RewriteCond %{R…...

    2024/4/12 22:44:31
  20. iOS 9的 Universal Links 通用链接使用介绍

    一、 通用链接介绍 Apple 推出通用链接:一种能够方便的通过传统 HTTP 链接来启动 APP, 使 用相同的网址打开网站和 APP。 当你的应用支持通用链接,iOS9 之后 , 用户可以点击一个链接跳转到你的网站,并获得无缝重定向到您安装的应用程序,而无需通过 Safari 浏览器。如果你的应…...

    2024/4/19 13:45:51

最新文章

  1. 2014NOIP普及组真题 3. 螺旋矩阵

    线上OJ&#xff1a; 一本通&#xff1a;http://ybt.ssoier.cn:8088/problem_show.php?pid1967 背景知识&#xff1a; 螺旋矩阵可以采用模拟的方式生成。就是顺时针四个方向 第1步、是第 1 行&#xff0c;方向为从左到右&#xff0c;数值1。当向右遇到 边界n 或者 格子已填过数…...

    2024/4/25 20:57:14
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 磁盘管理与文件管理

    文章目录 一、磁盘结构二、MBR与磁盘分区分区的优势与缺点分区的方式文件系统分区工具挂载与解挂载 一、磁盘结构 1.硬盘结构 硬盘分类&#xff1a; 1.机械硬盘&#xff1a;靠磁头转动找数据 慢 便宜 2.固态硬盘&#xff1a;靠芯片去找数据 快 贵 硬盘的数据结构&#xff1a;…...

    2024/4/23 6:16:19
  4. 【项目新功能开发篇】开发编码

    作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…...

    2024/4/23 6:10:38
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/25 11:51:20
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/25 18:39:24
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/25 18:38:39
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/25 18:39:23
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/25 18:39:22
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/25 18:39:22
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/25 18:39:20
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/25 16:48:44
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/25 13:39:44
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/25 0:00:17
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/25 4:19:21
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/25 18:39:12
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/25 13:19:01
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/25 18:38:58
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/25 18:38:57
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57