⑴ SET I1=100 指令的含义是将100的值赋给()
摘要 重排序后, a 的两次操作被放到一起,指令执行情况变为 Load a、Set to 100、Set to 110、 Store a。下面和 b 相关的指令不变,仍对应 Load b、 Set to 5、Store b。
理论上的就不说了,你自己搜也能搜到很多。
举个例子,你从一个方法a调用了另一个方法b。
我们知道,在a和b之中是可以创建相同名称的变量的,比如都有int i = 0;这句话。这种现象的根本原因在于,方法的调用会产生中断,中断产生后,cpu会做现场保护,包括把变量等进行压栈操作,即把方法a的相关资源进行了压栈,而方法b的相关资源放在栈顶,只有栈顶资源可以与cpu交互(就把方法a中的变量i保护起来),当方法b结束后出栈,a就又回到了栈顶,并获取了方法b运行的结果,然后继续运行。
哎,有些啰嗦了。方法的调用、中断、压栈出栈等等这些操作你说一点不消耗资源吧,那是不可能的,多少都会消耗一些,虽然很非常十分微不足道。那么编译器的优化过程,我知道的其作用之一,就是会把这些做一个优化。原本方法a一共10句话,你偏要只写1句,然后第2句写成方法b,第3句写成方法c。。。。。,然后依次嵌套调用。这样的源代码,编译器优化后,就跟你直接写10句是一个结果,即做了一定程度上的优化。
⑶ java指令重排序,happens-before的问题
不会的。
java代码肯定是执行t.i = 1这个后,
再执行new Thread(t).start();这个
所以不会出现你说的情况
⑷ 编译器的编译器优化
应用程序之所以复杂, 是由于它们具有处理多种问题以及相关数据集的能力。实际上, 一个复杂的应用程序就象许多不同功能的应用程序“ 粘贴” 在一起。源文件中大部分复杂性来自于处理初始化和问题设置代码。这些文件虽然通常占源文件的很大一部分, 具有很大难度, 但基本上不花费C PU 执行周期。
尽管存在上述情况, 大多数Makefile文件只有一套编译器选项来编译项目中所有的文件。因此, 标准的优化方法只是简单地提升优化选项的强度, 一般从O 2 到O 3。这样一来, 就需要投人大量 精力来调试, 以确定哪些文件不能被优化, 并为这些文件建立特殊的make规则。
一个更简单但更有效的方法是通过一个性能分析器, 来运行最初的代码, 为那些占用了85 一95 % CPU 的源文件生成一个列表。通常情况下, 这些文件大约只占所有文件的1%。如果开发人员立刻为每一个列表中的文件建立其各自的规则, 则会处于更灵活有效的位置。这样一来改变优化只会引起一小部分文件被重新编译。进而,由于时间不会浪费在优化不费时的函数上, 重编译全部文件将会大大地加快。
⑸ DSP编译器如何进行优化
优化是一个很大的学问,c6000的编程工具指南(清华出版)大部分讲的是这个,可以看看,貌似这本书不好买
⑹ C语言中Valatile关键字有什么用
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。下面举例说明。在DSP开发中,经常需要等待某个事件的触发,所以经常会写出这样的程序:
short flag;
void test()
{
do1();
while(flag==0);
do2();
}
这段程序等待内存变量flag的值变为1(怀疑此处是0,有点疑问,)之后才运行do2()。变量flag的值由别的程序更改,这个程序可能是某个硬件中断服务程序。例如:如果某个按钮按下的话,就会对DSP产生中断,在按键中断程序中修改flag为1,这样上面的程序就能够得以继续运行。但是,编译器并不知道flag的值会被别的程序修改,因此在它进行优化的时候,可能会把flag的值先读入某个寄存器,然后等待那个寄存器变为1。如果不幸进行了这样的优化,那么while循环就变成了死循环,因为寄存器的内容不可能被中断服务程序修改。为了让程序每次都读取真正flag变量的值,就需要定义为如下形式:
volatile short flag;
需要注意的是,没有volatile也可能能正常运行,但是可能修改了编译器的优化级别之后就又不能正常运行了。因此经常会出现debug版本正常,但是release版本却不能正常的问题。所以为了安全起见,只要是等待别的程序修改某个变量的话,就加上volatile关键字。
volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int i=0;
int main(void)
{
...
while (1)
{
if (i) do_something();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用do_something函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致do_something永远也不会被调用。如果变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
二、volatile 的含义
volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的,它有下面的作用:
1 不会在两个操作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器自己无法知道,volatile就是告诉编译器这种情况。
2 不做常量合并、常量传播等优化,所以像下面的代码:
volatile int i = 1;
if (i > 0) ...
if的条件不会当作无条件真。
3 对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值操作,然而对Memory Mapped IO的处理是不能这样优化的。
前面有人说volatile可以保证对内存操作的原子性,这种说法不大准确,其一,x86需要LOCK前缀才能在SMP下保证原子性,其二,RISC根本不能对内存直接运算,要保证原子性得用别的方法,如atomic_inc。
对于jiffies,它已经声明为volatile变量,我认为直接用jiffies++就可以了,没必要用那种复杂的形式,因为那样也不能保证原子性。
你可能不知道在Pentium及后续CPU中,下面两组指令
inc jiffies
;;
mov jiffies, %eax
inc %eax
mov %eax, jiffies
作用相同,但一条指令反而不如三条指令快。
三、编译器优化 → C关键字volatile → memory破坏描述符zz
“memory”比较特殊,可能是内嵌汇编中最难懂部分。为解释清楚它,先介绍一下编译器的优化知识,再看C关键字volatile。最后去看该描述符。
1、编译器优化介绍
内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序执行的操作之间设置内存屏障(memory barrier),linux 提供了一个宏解决编译器的执行顺序问题。
void Barrier(void)
这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。
2、C语言关键字volatile
C语言关键字volatile(注意它是用来修饰变量而不是上面介绍的__volatile__)表明某个变量的值可能在外部被改变,因此对这些变量的存取不能缓存到寄存器,每次使用时需要重新存取。该关键字在多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程,例如:
DWORD __stdcall threadFunc(LPVOID signal)
{
int* intSignal=reinterpret_cast<int*>(signal);
*intSignal=2;
while(*intSignal!=1)
sleep(1000);
return 0;
}
该线程启动时将intSignal 置为2,然后循环等待直到intSignal 为1 时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程却不会退出,即使在外部将它的值改为1,看一下对应的伪汇编代码就明白了:
mov ax,signal
label:
if(ax!=1)
goto label
对于C编译器来说,它并不知道这个值会被其他线程修改。自然就把它cache在寄存器里面。记住,C 编译器是没有线程概念的!这时候就需要用到volatile。volatile 的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在threadFunc中的intSignal前面加上volatile关键字,这时候,编译器知道该变量的值会在外部改变,因此每次访问该变量时会重新读取,所作的循环变为如下面伪码所示:
label:
mov ax,signal
if(ax!=1)
goto label
3、Memory
有了上面的知识就不难理解Memory修改描述符了,Memory描述符告知GCC:
1)不要将该段内嵌汇编指令与前面的指令重新排序;也就是在执行内嵌汇编代码之前,它前面的指令都执行完毕
2)不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变量会以不可预知的方式发生改变,因此GCC插入必要的代码先将缓存到寄存器的变量值写回内存,如果后面又访问这些变量,需要重新访问内存。
如果汇编指令修改了内存,但是GCC 本身却察觉不到,因为在输出部分没有描述,此时就需要在修改描述部分增加“memory”,告诉GCC 内存已经被修改,GCC 得知这个信息后,就会在这段指令之前,插入必要的指令将前面因为优化Cache 到寄存器中的变量值先写回内存,如果以后又要使用这些变量再重新读取。
使用“volatile”也可以达到这个目的,但是我们在每个变量前增加该关键字,不如使用“memory”方便。
⑺ C++编译器(Dev-C)是否会自动内联函数 对于什么样的函数即使标记inline也会拒绝内联
G++编译器是否会自动进行内联函数?
G++编译器是很先进的,编译的时候如果开启优化,G++会代码进行各种优化,如:对合适的函数进行内联(即便是没有添加inline关键字),对某些函数直接对其进行求值,除此之外G++编译器还可以对代码进行重排序 等等。编译器比你更了解硬件,所以只要允许它优化,他会尽量进行优化。你使用的Dev C++集成开发环境使用的c++编译器就是G++。
什么样的函数即使标记inline也无法内联?
比如函数体太大、太复杂的话(比如包含多重循环、包含递归调用),对其进行内联得不偿失,这时编译器就会忽略inline关键字,VC++编译器提供了强制内联函数的关键字,除非你非常了解硬件,不然最好让编译器来处。编译不对那些函数进行内联要看具体的编译器实现了。
inline关键字的有哪些作用?
inline关键字可以提示编译器对某个函数进行内联,并且强制函数使用内部链接。比如说你在头文件定义了某个函数,为了防止多重定义,你可以添加inline关键字来防止多重定义错误。
如果对硬件不是很了解,底层的代码优化还是留给编译器来处理。
看看下面的几个编译器优化函数的例子:
1.编译器直接对函数求值:
解释一下:
第一条和第二天指令分别将b和a的地址加载到寄存器rdx和rcx中
第三条指令将b的值加载到eax寄存器中
第四条指令将34存入b中
第五条指令将eax的值加1(eax保存了之前b的值)
第六条指令将eax的值存入a中
可以看出编译器将函数的两条语句换了位置,这种优化主要是优化代码的执行速度,有的CPU内存读写操作的的开销不一样,所以重新排序一下某些代码能够提高程序执行速度。
⑻ 编译器 优化
编译是从源代码(通常为高阶语言)到能直接被计算机或虚拟机执行的目标代码(通常为低阶语言或机器语言)的翻译过程。然而,也存在从低阶语言到高阶语言的编译器,这类编译器中用来从由高阶语言生成的低阶语言代码重新生成高阶语言代码的又被叫做反编译器。也有从一种高阶语言生成另一种高阶语言的编译器,或者生成一种需要进一步处理的的中间代码的编译器(又叫级联)。
典型的编译器输出是由包含入口点的名字和地址, 以及外部调用(到不在这个目标文件中的函数调用)的机器代码所组成的目标文件。一组目标文件,不必是同一编译器产生,但使用的编译器必需采用同样的输出格式,可以链接在一起并生成可以由用户直接执行的可执行程序。
从他的原理我们就好优化了,但是方法很多的
⑼ java中虚拟机的内存到底分为几类呢,网上说法挺多,能不能给个专业的
Java内存模型
主内存与工作内存
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。
Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面将的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系如下图所示
这里的主内存、工作内存与Java内存区域的Java堆、栈、方法区不是同一层次内存划分。
内存间交互操作
关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:
· lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
· unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
· read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
· load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
· use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
· assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
· store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
· write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间是可以插入其他指令的,如对主内存中的变量a、b进行访问时,可能的顺序是read a,read b,load b, load a。Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:
· 不允许read和load、store和write操作之一单独出现
· 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
· 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
· 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
· 一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现
· 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
· 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
· 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。
重排序
在执行程序时为了提高性能,编译器和处理器经常会对指令进行重排序。重排序分成三种类型:
编译器优化的重排序。编译器在不改变单线程程序语义放入前提下,可以重新安排语句的执行顺序。
指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
内存系统的重排序。由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
从Java源代码到最终实际执行的指令序列,会经过下面三种重排序:
为了保证内存的可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。Java内存模型把内存屏障分为LoadLoad、LoadStore、StoreLoad和StoreStore四种:
同步机制
介绍volatile、synchronized和final
原子性、可见性与有序性
Java内存模型JMM解决了可见性和有序性的问题,而锁解决了原子性的问题。
可见性
指的是一个线程对变量的写操作对其他线程后续的读操作可见。由于现代CPU都有多级缓存,CPU的操作都是基于高速缓存的,而线程通信是基于内存的,这中间有一个Gap,可见性的关键还是在对变量的写操作之后能够在某个时间点显示地写回到主内存,这样其他线程就能从主内存中看到最新的写的值。volatile,synchronized(隐式锁), 显式锁,原子变量这些同步手段都可以保证可见性。
可见性底层的实现是通过加内存屏障实现的:
1. 写变量后加写屏障,保证CPU写缓冲区的值强制刷新回主内存
2. 读变量之前加读屏障,使缓存失效,从而强制从主内存读取变量最新值
写volatile变量 = 进入锁
读volatile变量 = 释放锁
有序性
指的是数据不相关的变量在并发的情况下,实际执行的结果和单线程的执行结果是一样的,不会因为重排序的问题导致结果不可预知。volatile, final, synchronized,显式锁都可以保证有序性。
有序性的语意有几层,
1. 最常见的就是保证多线程执行的串行顺序
2. 防止重排序引起的问题
3. 程序执行的先后顺序,比如JMM定义的一些Happens-before规则
重排序
的问题是一个单独的主题,常见的重排序有3个层面:
1. 编译级别的重排序,比如编译器的优化
2. 指令级重排序,比如CPU指令执行的重排序
3. 内存系统的重排序,比如缓存和读写缓冲区导致的重排序
原子性
是指某个(些)操作在语意上是原子的。比如读操作,写操作,CAS(compareand set)操作在机器指令级别是原子的,又比如一些复合操作在语义上也是原子的,如先检查后操作if(xxx== null){}
有个专有名词竞态条件来描述原子性的问题。
竞态条件(racing condition)是指某个操作由于不同的执行时序而出现不同的结果,比如先检查后操作。
volatile变量只保证了可见性,不保证原子性,比如a++这种操作在编译后实际是多条语句,比如先读a的值,再加1操作,再写操作,执行了3个原子操作,如果并发情况下,另外一个线程很有可能读到了中间状态,从而导致程序语意上的不正确。所以a++实际是一个复合操作。
加锁可以保证复合语句的原子性,sychronized可以保证多条语句在synchronized块中语意上是原子的。
显式锁保证临界区的原子性。
原子变量也封装了对变量的原子操作。
非阻塞容器也提供了原子操作的接口,比如putIfAbsent。