精华内容
下载资源
问答
  • 编译器设计-代码优化Compiler Design - Code Optimization优化是一种程序转换技术,它试图通过使代码消耗更少的资源(如CPU、内存)来改进代码,并提供高速。 优化中,高级通用编程结构被非常高效的低级编程代码所...

    编译器设计-代码优化

    Compiler Design - Code Optimization

    优化是一种程序转换技术,它试图通过使代码消耗更少的资源(如CPU、内存)来改进代码,并提供高速。

    在优化中,高级通用编程结构被非常高效的低级编程代码所代替。代码优化过程必须遵循以下三条规则:

    输出代码无论如何不能改变程序的含义。

    优化应该提高程序的速度,如果可能的话,程序应该需要更少的资源。

    优化本身应该是快速的,不应该延迟整个编译过程。

    优化代码的工作可以在编译过程的不同级别进行。

    在开始时,用户可以更改/重新排列代码或使用更好的算法来编写代码。

    生成中间代码后,编译器可以通过地址计算和改进循环来修改中间代码。

    在生成目标机器代码时,编译器可以使用内存层次结构和CPU寄存器。

    优化可以大致分为两类:独立于机器的优化和依赖于机器的优化。

    机器独立优化Machine-independent Optimization

    在这种优化中,编译器接受中间代码,并转换不涉及任何CPU寄存器和/或绝对内存位置的部分代码。例如:

    do

    {

    item = 10;

    value = value + item;

    } while(value<100);

    此代码涉及重复分配标识符项,一旦:

    Item = 10;
    do
    {
       value = value + item; 
    } while(value<100);

    不仅可以节省CPU周期,而且可以在任何处理器上使用。

    机器相关优化Machine-dependent Optimization

    在生成目标代码之后,以及根据目标机器体系结构转换代码时,执行与机器相关的优化。它涉及CPU寄存器,可能有绝对内存引用,而不是相对引用。依赖于机器的优化器努力最大限度地利用内存层次结构。

    基本块Basic Blocks

    源代码通常有许多指令,这些指令总是按顺序执行,并被视为代码的基本块。这些基本块之间没有任何跳转语句,即当执行第一条指令时,同一基本块中的所有指令将按其出现顺序执行,而不会失去程序的流控制。

    程序可以有各种各样的结构作为基本块,如IF-THEN-ELSE、SWITCH-CASE条件语句和循环(如DO-WHILE、FOR和REPEAT-UNTIL等)。

    基本块识别Basic block identification

    我们可以使用以下算法来查找程序中的基本块:

    从基本块开始的所有基本块的搜索头语句:

    程序的第一个语句。

    任何分支的目标语句(条件/无条件)。

    任何分支语句之后的语句。

    Header语句及其后面的语句构成一个基本块。

    基本块不包括任何其他基本块的任何头语句。

    从代码生成和优化的角度来看,基本块都是重要的概念。

    c58430ed736b568bb8a99c5c5916a631.png

    基本块在识别变量方面起着重要作用,这些变量在一个基本块中被多次使用。如果某个变量被多次使用,则分配给该变量的寄存器内存不需要清空,除非块完成执行。 控制流图Control Flow Graph

    程序中的基本块可以用控制流图来表示。控制流图描述程序控制如何在块之间传递。它是一个有用的工具,通过帮助定位程序中任何不需要的循环来帮助优化。

    d60553154b2959ba554faa1dc8973053.png

    回路优化Loop Optimization

    大多数程序在系统中作为循环运行。为了节省CPU周期和内存,有必要对循环进行优化。循环可以通过以下技术进行优化:

    不变代码Invariant code:驻留在循环中并在每次迭代时计算相同值的代码片段称为循环不变代码。通过将代码保存为只计算一次而不是每次迭代,可以将此代码移出循环。

    归纳分析Induction analysis:如果一个变量的值在循环中被循环不变的值改变,则称之为归纳变量。

    强度降低Strength reduction:有些表达式消耗更多的CPU周期、时间和内存。在不影响表达式输出的情况下,这些表达式应替换为更便宜的表达式。例如,乘法(x*2)在CPU周期方面比(x<1)昂贵,并且产生相同的结果。

    死码消除Dead-code Elimination

    死代码是一个或多个代码语句,它们是:

    要么从未执行,要么无法实现,

    或者如果执行了,它们的输出就永远不会被使用。

    因此,死代码在任何程序操作中都不起作用,因此可以简单地消除它。

    部分死代码Partially dead code

    有些代码语句的计算值仅在某些情况下使用,即有时使用值,有时不使用。这种代码称为部分死代码。

    a3e95cb17cd83d72f00b98c17e50a39d.png

    上面的控制流图描述了一个程序块,其中变量“a”用于分配表达式“x*y”的输出。假设分配给“a”的值从未在立即循环在控件离开循环后,“a”被赋给变量“z”的值,该值稍后将在程序中使用。我们在此得出结论,“a”的赋值代码从未在任何地方使用过,因此它有资格被删除。

    047fcdde4e595f6f4e97a44ff8bdc584.png

    同样,上面的图片描述了条件语句总是false,这意味着以true case编写的代码永远不会执行,因此可以删除它。

    部分冗余Partial Redundancy

    在并行路径中多次计算冗余表达式,而不改变操作数在路径中多次计算部分冗余表达式,操作数不做任何更改。例如,

    e73b035c106171f63c77b4448500fc99.png

    循环不变代码是部分冗余的,可以通过使用代码运动技术来消除。

    部分冗余代码的另一个示例可以是:

    if (condition)
    {
       a = y OP z;
    }
    else
    {
       ...
    }
    c = y OP z;

    我们假设操作数(y和z)的值不会从变量a的赋值更改为变量c。这里,如果条件语句为true,则y OP z计算两次,否则为一次。代码操作可以用来消除这种冗余,如下所示:

    if (condition)
    {
       ...
       tmp = y OP z;
       a = tmp;
       ...
    }
    else
    {
       ...
       tmp = y OP z;
    }
    c = tmp;

    这里,条件是真是假;y OP z只应计算一次。

    展开全文
  • java用什么编译器_JavaJava编译

    千次阅读 2020-06-25 22:35:52
    在上一篇文章中,我写了关于如何在运行时生成代理的内容,我们已经了解到生成Java代码的程度。 但是,要使用该类,必须对其进行编译,并将生成的字节码加载到内存中。 那是“编译”时间。 幸运的是,从Java 1.6...

    java用什么编译器

    在上一篇文章中,我写了关于如何在运行时生成代理的内容,我们已经了解到生成Java源代码的程度。 但是,要使用该类,必须对其进行编译,并将生成的字节码加载到内存中。 那是“编译”时间。 幸运的是,从Java 1.6开始,我们可以在运行时访问Java编译器,因此可以将编译时与运行时混淆。 尽管在这种非常特殊的情况下,这可能会导致过多的麻烦事情,通常导致无法维护的自我修改代码,但它可能还是有用的:我们可以编译运行时生成的代理。

    Java编译器API

    Java编译器读取源文件并生成类文件。 (将它们组装到JAR,WAR,EAR和其他软件包中是另一种工具的责任。)源文件和类文件不一定是驻留在磁盘,SSD或内存驱动器中的真实操作系统文件。 毕竟,当涉及到运行时API时,Java通常对于抽象是很好的,现在就是这种情况。 这些文件是一些“抽象”文件,您必须通过API提供访问这些文件,这些文件可以是磁盘文件,但同时几乎可以是任何其他文件。 将源代码保存到磁盘上只是为了让编译器在同一进程中运行以将其读回并在类文件准备好后对其进行相同操作,通常会浪费资源。

    Java编译器作为运行时可用的API,要求您提供一些简单的API(或您喜欢的SPI)来访问源代码并发送生成的字节码。 如果我们在内存中有代码,则可以有以下代码( 来自此文件 ):

    public Class<?> compile(String sourceCode, String canonicalClassName)
    			throws Exception {
    		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    		List<JavaSourceFromString> sources = new LinkedList<>();
    		String className = calculateSimpleClassName(canonicalClassName);
    		sources.add(new JavaSourceFromString(className, sourceCode));
    
    		StringWriter sw = new StringWriter();
    		MemoryJavaFileManager fm = new MemoryJavaFileManager(
    				compiler.getStandardFileManager(null, null, null));
    		JavaCompiler.CompilationTask task = compiler.getTask(sw, fm, null,
    				null, null, sources);
    
    		Boolean compilationWasSuccessful = task.call();
    		if (compilationWasSuccessful) {
    			ByteClassLoader byteClassLoader = new ByteClassLoader(new URL[0],
    					classLoader, classesByteArraysMap(fm));
    
    			Class<?> klass = byteClassLoader.loadClass(canonicalClassName);
    			byteClassLoader.close();
    			return klass;
    		} else {
    			compilerErrorOutput = sw.toString();
    			return null;
    		}
    	}

    编译器实例可通过ToolProvider并且要创建编译任务,我们必须调用getTask() 该代码通过字符串编写器将错误写入字符串。 文件管理器( fm )是在同一程序包中实现的,它只是将文件作为字节数组存储在映射中,其中的键是“文件名”。 这是类加载器稍后在加载类时将获取字节的位置。 该代码未提供任何可诊断的侦听器(请参见RT中Java编译器的文档),编译器选项或注释处理器要处理的类。 这些都是空值。 最后一个参数是要编译的源代码列表。 我们仅在此工具中编译一个类,但是由于编译器API是通用的并且需要可迭代的源,因此我们提供了一个列表。 由于存在另一个抽象级别,因此此列表包含JavaSourceFromString

    要开始编译,必须“调用”创建的任务,如果编译成功,则从生成的一个或多个字节数组中加载类。 请注意,如果在我们编译的顶级类中有嵌套类或内部类,则编译器将创建几个类。 这就是为什么即使只编译一个源类,我们也必须维护类的整个映射,而不是单个字节数组。 如果编译不成功,则错误输出将存储在一个字段中并可以查询。

    该类的使用非常简单,您可以在单元测试中找到示例:

    private String loadJavaSource(String name) throws IOException {
    		InputStream is = this.getClass().getResourceAsStream(name);
    		byte[] buf = new byte[3000];
    		int len = is.read(buf);
    		is.close();
    		return new String(buf, 0, len, "utf-8");
    	}
    ...
    	@Test
    	public void given_PerfectSourceCodeWithSubClasses_when_CallingCompiler_then_ProperClassIsReturned()
    			throws Exception {
    		final String source = loadJavaSource("Test3.java");
    		Compiler compiler = new Compiler();
    		Class<?> newClass = compiler.compile(source, "com.javax0.jscc.Test3");
    		Object object = newClass.newInstance();
    		Method f = newClass.getMethod("method");
    		int i = (int) f.invoke(object, null);
    		Assert.assertEquals(1, i);
    	}

    请注意,以这种方式创建的类仅在运行时可用于代码。 例如,您可以创建对象的不可变版本。 如果要在编译时使用可用的类,则应使用scriapt之类的注释处理器。

    翻译自: https://www.javacodegeeks.com/2016/03/java-compile-java.html

    java用什么编译器

    展开全文
  • 前言这几天重新研究了一下内存模型、内存屏障,学习内存屏障的时候,了解了lock前缀指令,为了编译出lock前缀指令,于是去学了一下字节码指令。...Java代码如何运行我们写的Java代码是高级语言...

    6cfd4e0f14a153227a33dab8180c9a9a.png

    前言

    这几天重新研究了一下内存模型、内存屏障,在学习内存屏障的时候,了解了lock前缀指令,为了编译出lock前缀指令,于是去学了一下字节码指令。因为还要添加一些运行参数,于是今天又看了一下午JVM的编译问题。知识一环套一环,现在内存屏障这一块还没搞完。这可能也正是自学乐趣之所在,知识无限延展,层层连贯,于是晚上就整理了一下关于java编译方面总结。

    Java代码如何运行

    我们写的Java代码是高级语言,机器肯定是读不懂的。所以我们需要将它转换成机器能读懂的机器语言(机器码)。
    转换工作主要分为以下几个步骤:

    前端编译器

    javac就是前端编译器,可以将java文件编译成字节码组成的class文件。
    java代码如下:

    public class Info {
        public static void main(String[] args) {
            int a = 1;
            System.out.println(a);
        }
    }

    执行javac Info.java生成Info.class文件,再使用javap -c Info.class来查看其中的字节码。
    class中字节码内容如下:

    59657523c71636f1501d4c8c2f76bb36.png

    解释器和即时编译器

    我们通过javac将java文件编译成class文件,当jvm启动加载class,需要逐条执行字节码指令来完成程序功能。但是程序的执行还是得在机器上,但是机器是不认识字节码的,所以我们需要将字节码转换成机器码,这样才能让机器执行程序。什么是机器码?
    机器码就是用二进制代码表示的计算机能直接识别和执行的一种机器指令的集合。
    而解释器和即时编译器(Just In Time Compiler,JIT)就是JVM中将字节码转化为机器码的工具。

    解释器

    解释器是一行一行地将字节码解析成机器码,解释到哪就执行到哪,狭义地说,就是for循环100次,你就要将循环体中的代码逐行解释执行100次。当程序需要迅速启动和执行时,解释器可以首先发挥作用,省去编译的时间,立即执行。

    即时编译器(JIT)

    即时编译器按照我的理解就是:以方法为单位,将热点代码的字节码一次性转为机器码,并在本地缓存起来的工具。避免了部分代码被解释器逐行解释执行的效率问题。
    即时编译器分为两种,Client Compiler(C1编译器)和Server Compiler(C2),默认使用的是C2,因其运行性能更高。什么是热点代码?
    被多次调用的方法和循环体被认定为热点代码。热点代码的判断方法有两种,一是基于采样的热点探测:周期检查每个线程栈顶,统计哪个方法出现次数多,但是不准确;二是基于计数器的热点探测:目前在用,为每个方法建立计数器,统计方法的调用次数。计数器分为方法调用计数器(默认阈值C1是1500次,C2是1w,到达阈值则触发即时编译)和回边计数器(统计一个方法中循环体的执行次数)。
    下图为方法调用计数器的执行过程:

    eb2529cf618f071ad09ac95929037ab5.png


    目前主流的HotSpot虚拟机中默认是采用解释器与其中一个编译器(C2编译器)直接配合的方式将字节码转换成机器码

    运行参数

    在执行java程序的时候,以下参数是和编译方面的运行及调试参数。

    2a217e357daab65b93351f95bb4f10ea.png

    结语

    主要讲述编译器和解释器一些概念性的东西,如果觉得有些地方解释的不够透彻,可以空余时间研究一下《深入理解Java虚拟机规范》。

    7f497fe7d092fbefc9ada813d1a14115.png
    展开全文
  • 由于堆栈的初始状态以及每个指令对其的影响是众所周知的,因此您可以精确地预测任何时候操作数堆栈上会出现哪种项:[ ] // initially empty[ I ] 0: iload_1[ ] 1: ifne 6[ I ] 4: iconst_1[ ] 5: ireturn[ I...

    由于堆栈的初始状态以及每个指令对其的影响是众所周知的,因此您可以精确地预测在任何时候操作数堆栈上会出现哪种项:

    [ ] // initially empty

    [ I ] 0: iload_1

    [ ] 1: ifne 6

    [ I ] 4: iconst_1

    [ ] 5: ireturn

    [ I ] 6: iload_1

    [ I O ] 7: aload_0

    [ I O I ] 8: iload_1

    [ I O I I ] 9: iconst_1

    [ I O I ] 10: isub

    [ I I ] 11: invokevirtual #2 // Method factorial:(I)I

    [ I ] 14: imul

    [ ] 15: ireturn

    jvm_的验证器将精确地做到这一点,预测每个指令后堆栈的内容,以检查它是否适合作为后续指令的输入。但是在这里有一个声明的最大大小是有帮助的,因此验证器不需要为理论上可能的64K堆栈条目维护动态增长的数据结构或预分配内存。使用声明的最大大小,它可以在遇到推多的指令时停止,因此它从不需要比声明的内存更多的内存。

    如您所见,声明的最大堆栈大小在

    iconst_1

    索引9的说明。

    然而,这并不意味着编译器必须执行这样的指令分析。编译器具有从源代码派生的更高级别的代码模型,称为

    Abstract syntax tree

    .

    这个结构将被用来生成所得到的字节码,并且它可能已经能够预测该级别上所需的堆栈大小。但是编译器实际上是如何做到的,这取决于实现。

    展开全文
  • 我试图理解Java代码如何执行的,我对JVM内部的JIT编译器实际上是什么感到困惑.首先,让我告诉您我是如何理解从Java代码计算机上执行机器代码的过程.也许,我误解了导致混乱的过程中的某些事情.步骤:>源...
  • 下面是一个很简单的例子,关于Java中的多态:方法重载和方法覆盖; 多态指的是方法不同的时刻表现出不同的形式;编译期间,这被叫做方法重载;方法重载允许相关的方法被同一个方法名字调用,这有时候被叫做ad-...
  • Java作为高级语言,高度抽象,无法直接运行机器上,这样就必须设计一个面向Java语言特征的虚拟机,并通过编译器Java程序转化成虚拟机所能识别的指令序列,也成Java字节码。Java 虚拟机将运行时内存区域划分为五...
  • 如何让我们页面编辑器里粘贴上的代码以原编译器中那样彩色的样式显示呢?百度了下,发现很简单。 当我们往页面编辑器里粘贴代码时会发现它只不过是选中的代码块区域前后加上了 ~ ~ ~ ,三个符号。这时候我们根据...
  • 2.C++无需额外的运行时,通常编译后的代码可以让机器直接读取,即机器码问题一:Java为什么要虚拟机运行?1.一次编译,到处运行。这里涉及到一个概念:字节码。Java字节码指的是设计一个面向Java特性的虚拟机,...
  • 本节,我们研究如何把函数声明和函数调用转换成可执行的java 字节码,完成本节代码后,我们的编译器能把下面代码编译成可被java 虚拟机执行的字节码,示例代码如下: void f() { printf("execute function f()")...
  • 本文介绍了java虚拟机所能运行的基础指令,同时讲解了虚拟机是如何基于堆栈和队列配合相关基础指令,...最后我们给出了一段C语言代码,并详细讲解了我们的编译器如何代码编译成能在java虚拟机上执行的java汇编语言
  • 想像C语言一样直接运行在硬件上显然是不可能的,所以在Java程序运行之前,需要将Java程序通过编译器转换成虚拟机所能识别的Java字节码,Java字节码都是固定的一个字节,因此只要将Java程序转换成虚拟机所能识别的...
  • 前不久代码的时候,我不小心踩到一个可变长参数的坑。你或许已经猜到了,它正是可变长参数方法的重载造成的。(注:官方文档建议避免重载可变长参数方法,见 [1] 的最后一段。)我把踩坑的过程放在了文稿里,你...
  • Java代码执行步骤编译Java文件通过JVM的编译器编译成字节码文件,有了字节码,JVM的类加载器就开始加载字节码文件。解释器解释器会将字节码转换成汇编指令,然后转换成CPU可以识别的机器指令(下图是汇编指令转成...
  • 这可能是一件好事,因为查看字节码可能有助于理解如何Java代码编译成Java字节码.此外,它可能会给出一些关于编译器将执行何种优化的想法,以及编译器可以执行的优化量的一些限制.例如,如果执行字符串连接,javac将...
  • Java注释是不由编译器和解释器执行的语句。注释可用于提供有关变量,方法,类或任何语句的信息。它也可以用于隐藏特定时间的程序代码Java注释的类型Java中有三种类型的注释。单行注释多行评论文档注释单行注释单行...
  • 本文从C语言的层面介绍协助编译器代码优化的一些建议,其他语言比如C++、Java亦可借鉴。 示例代码: typedef struct{ long len; data_t* data; }vec_rec, *vec_ptr; //实际程序中,数据类型data_t可以被声明为int...
  • Java代码如何运行?

    2019-12-29 16:30:36
    编译器Java程序转换成Java字节码 Java 虚拟机具体是怎样运行 Java 字节码的 一次编写,到处运行 Java虚拟机可以由硬件实现,也可以各个现有平台提供软件实现。一旦一个程序被转换成 Java 字节码,那么它便可以...
  • 自顶向下的语法解析中,可以通过属性传递的方式去...但是,就如上节我们看到的,LALR语法解析过程是通过栈而不是函数调用的方式来实现的,这节,我们注重研究如何在LALR的解析过程中实现属性传递,从而实现代码生成。
  • http://study.163.com/course/courseMain.htm?courseId=1002830012大家好,欢迎大家来到coding迪斯尼,在上一节,我们讨论了如何对简单的正则表达式构造其对于的NFA状态机,通过代码,我们理解了如何对形如[abcd], ...
  • 我们知道一个Java类要想被Java虚拟机加载,必须先生成相应的二进制文件,即class文件,而一个方法或一段代码在运行期要么被解释执行,要么被编译器编译成汇编码,编译执行,那如何去查看这些转换后的代码呢?...
  • 我们日常编程中,异常处理是必不可少的,异常处理是否得当关系到程序的健壮性和后续维护成本。试想一下,如果一个项目从头到尾没有考虑过异常处理,当程序出错从哪里寻找出错的根源?但是如果一个项目异常处理设计...
  • 简介:先看一张Javc编译成class文件的时候流程图image.png至于什么是token流,语法树相关可以参考我之前的两篇帖子。...JavaC JavaParser用途:把Java源码转换成 JavaParser定义的Statement对象,...
  • JavaPoet如何生成.java代码时间:2017-10-11来源:华清远见Java培训中心谈到自动生成源代码,你应该感到很兴奋吧,很多学Java的朋友对这个知识点是相当的感兴趣,JavaPoet可用于生成.java代码,当我们处理注解...
  • java开发编译器:中间语言格式

    千次阅读 2016-08-17 15:49:02
    通常情况下,编译器会将目标语言转换成某种中间语言格式,而...不少C语言编译器,都会将代码编译成汇编语言,然后再通过汇编编译器将汇编代码转换成目标机器可执行的二进制代,这一节我们讨论编译器如何生成中间代码
  • 由于Java是一门纯面向对象的编程语言,在Java项目中,会用到大量的类。项目大了,难免会产生类的名称是相同的情况。例如,在"圆柱体"工程和"球体"工程中,它们可能都有相同的类名——体积类,而对于Java编译器来说,在相同...
  • 2.C++无需额外的运行时,通常编译后的代码可以让机器直接读取,即机器码问题一:Java为什么要虚拟机运行? 1.一次编译,到处运行。 这里涉及到一个概念:字节码。 Java字节码指的是设计一个面向Java特性的虚拟机...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,192
精华内容 476
关键字:

在java代码如何编译器

java 订阅