• [技术干货] Java中容易混淆的基础知识[转载]
    面向对象三大特性:继承,封装,多态封装3中修饰符:public,private,protected,给位于同一个或不同包中的对象赋予了不同的访问权限封装的一些好处通过隐藏对象的属性来保护对象内部的状态提高代码的可用性,可维护性提高模块化继承给对象提供从基类获取字段和方法的能力,基础提高代码的重用性,可以在不修改类的情况下添加新的特性多态多态就是同一函数在不同类中有不同的实现;面向对象的多态性,即“一个接口,多个方法”。多态性体现在基类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。多态性允许一个接口被多个同类使用,弥补了单继承的不足。final,finally,finalize的区别finalfinal可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个 常量不能被重新赋值。finallyfinally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码放入finally代码块中,表示不管是 否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。finalizefinalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调 用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的最后判断。int和integer的区别intint是基本数据类型integerinteger是其包装类,是一个类为了在各种类型中转换,通过各种方法调用int a = 0; String result = Integer.toString(a); //将int转换为String重载与重写的区别override重写overload重载抽象类和接口的区别接口时公开的,不能有私有的方法和变量,抽象类可以有私有的方法或私有的变量抽象类和接口的区别反射的用途以及实现反射机制所提供的功能在运行时创造一个类的对象;判断一个类所具有的成员变量和方法调用一个对象的方法生成动态代理Java反射的主要功能:确定一个对象的类取出类的modifiers,数据成员,方法,构造类,超类在运行时刻调用动态对象的方法.创建数组,数组大小和类型反射机制的理解和应用自定义注解的场景及实现登陆、权限拦截、日志处理,以及各种Java 框架,如Spring,Hibernate,JUnit 提到注解就不能不说反射,Java自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志 注解,就进行日志记录。http请求的get和post方式的区别原理区别一般我们在浏览器输入一个网址访问网站都是GET请求;再FORM表单中,可以通过设置Method指定提交方式为GET或者POST提交方式,默认为GET提交方式。HTTP定义了与服务器交互的不同方法,其中最基本的四种:GET,POST,PUT,DELETE,HEAD,其中GET和HEAD被称为安全方法,因为使用GET和HEAD的HTTP请求不会产生什么动作。不会产生动作意味着GET和HEAD的HTTP请求不会在服务器上产生任何结果。但是安全方法并不是什么动作都不产生,这里的安全方法仅仅指不会修改信息。根据HTTP规范,POST可能会修改服务器上的资源的请求。比如CSDN的博客,用户提交一篇文章或者一个读者提交评论是通过POST请求来实现的,因为再提交文章或者评论提交后资源(即某个页面)不同了,或者说资源被修改了,这些便是“不安全方法”。请求的区别GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在 客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。参考:get和post请求方式的区别seesion与cookie的区别cookie数据存放在客户的浏览器上,session数据放在服务器上.cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。设置cookie时间可以使cookie过期。但是使用session-destory(),我们将会销毁会话。session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。(Session对象没有对存储的数据量的限制,其中可以保存更为复杂的数据类型)JDBC流程加载JDBC驱动程序:在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机), 这通过java.lang.Class类的静态方法forName(String className)实现。例如:try{ //加载MySql的驱动类 Class.forName("com.mysql.jdbc.Driver") ; }catch(ClassNotFoundException e){ System.out.println("找不到驱动程序类 ,加载驱动失败!"); e.printStackTrace() ; 成功加载后,会将Driver类的实例注册到DriverManager类中。提供JDBC连接的URL连接URL定义了连接数据库时的协议、子协议、数据源标识。书写形式:协议:子协议:数据源标识协议:在JDBC中总是以jdbc开始 子协议:是桥连接的驱动程序或是数据库管理系统名称。数据源标识:标记找到数据库来源的地址与连接端口。例如:jdbc:mysql://localhost:3306/test? useUnicode=true&characterEncoding=gbk;useUnicode=true;(MySql的连接URL)表示使用Unicode字符集。如果characterEncoding设置为 gb2312或GBK,本参数必须设置为true 。characterEncoding=gbk:字符编码方式。创建数据库的连接java.sql.DriverManager Connection代表一个数据库的连接。使用DriverManager的getConnectin(String url , String username , String password )方法传入指定的欲连接的数据库的路径、数据库的用户名和 密码来获得。例如: //连接MySql数据库,用户名和密码都是rootString url = "jdbc:mysql://localhost:3306/test" ; String username = "root" ; String password = "root" ; try{ Connection con = DriverManager.getConnection(url , username , password ) ; }catch(SQLException se){ System.out.println("数据库连接失败!"); se.printStackTrace() ; • 要执行SQL语句,必须获得java.sql.Statement实例,Statement实例分为以下3 种类型:1、执行静态SQL语句。通常通过Statement实例实现。2、执行动态SQL语句。通常通过PreparedStatement实例实现。3、执行数据库存储过程。通常通过CallableStatement实例实现。具体的实现方式:Statement stmt = con.createStatement() ; PreparedStatement pstmt = con.prepareStatement(sql) ; CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}") ;执行SQL语句Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate 和execute1、ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句 ,返回一个结果集(ResultSet)对象。2、int executeUpdate(String sqlString):用于执行INSERT、UPDATE或 DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等3、execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的 语句。 具体实现的代码:ResultSet rs = stmt.executeQuery(“SELECT * FROM …”) ; int rows =stmt.executeUpdate(“INSERT INTO …”) ; boolean flag = stmt.execute(String sql) ;处理结果两种情况:1、执行更新返回的是本次操作影响到的记录数。2、执行查询返回的结果是一个ResultSet对象。• ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法提供了对这些 行中数据的访问。• 使用结果集(ResultSet)对象的访问方法获取数据:while(rs.next()){String name = rs.getString(“name”) ;String pass = rs.getString(1) ; // 此方法比较高效}(列是从左到右编号的,并且从列1开始)关闭JDBC对象操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声 明顺序相反:1、关闭记录集2、关闭声明3、关闭连接对象 if(rs!=null){ // 关闭记录集 try{ rs.close(); }catch(SQLException e){e.printStackTrace(); } }try{ stmt.close(); } }catch(SQLException e){e.printStackTrace(); } if(conn!=null){ // 关闭连接对象 try{ conn.close(); }catch(SQLException e){ e.printStackTrace();20} }MVC思想M:Model 模型V:View 视图C:Controller控制器模型就是封装业务逻辑和数据的一个一个的模块,控制器就是调用这些模块的(java中通常是 用Servlet来实现,框架的话很多是用Struts2来实现这一层),视图就主要是你看到的,比如JSP 等.当用户发出请求的时候,控制器根据请求来选择要处理的业务逻辑和要选择的数据,再返回去 把结果输出到视图层,这里可能是进行重定向或转发等.equals 与 == 的区别值类型(int,char,long,boolean等)都是用==判断相等性。对象引用的话,判断引用所指的对象 是否是同一个。equals是Object的成员函数,有些类会覆盖(override)这个方法,用于判 断对象的等价性。例如String类,两个引用所指向的String都是"abc",但可能出现他们实际对应的对象并不是同一个(和jvm实现方式有关),因此用 ==判断他们可能不相等,但用equals判断一定是相等的。线程创建线程的方法及实现Java中创建线程主要有三种方式:一、继承Thread类创建线程类(1) 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。(2) 创建Thread子类的实例,即创建了线程对象。(3) 调用线程对象的start()方法来启动该线程。package com.thread; public class FirstThreadTest extends Thread{ int i = 0; //重写run方法,run方法的方法体就是现场执行体public void run() { for(;i<100;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args) { for(int i = 0;i< 100;i++) { System.out.println(Thread.currentThread().getName()+" "+i): if(i==20) { new FirstThreadTest().start(); new FirstThreadTest().start(); } } }二、通过Runnable接口创建线程类(1) 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。(2) 创建 Runnable实现类的实例的target来创建Thread对象,该Thread对象才是真正的线程对象。(3)调用线程对象的start()方法来启动该线程package com.thread; public class RunnableThreadTest implements Runnable { private int i; public void run() { for(i = 0;i <100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); } } public static void main(String[] args) { for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); if(i==20) { RunnableThreadTest rtt = new RunnableThreadTest(); new Thread(rtt,"新线程1").start(); new Thread(rtt,"新线程2").start(); } } } } 三、通过Callable和Future创建线程(1) 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。(2) 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(3) 使用FutureTask对象作为Thread对象的target创建并启动新线程。(4) 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值package com.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); 13 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " 的循 环变量i的值" + i); if (i == 20) { new Thread(ft, "有返回值的线程").start(); } } try { System.out.println("子线程的返回值:" + ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception 34 { int i = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } return i; } }采用实现Runnable、Callable接口的方式创见多线程时,优势是:线程类只是实现了 接口或 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同 一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面 向对象的思想。劣势是:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。使用继承Thread类的方式创建多线程时优势是:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this 即可获得当前线程。劣势是:线程类已经继承了Thread类,所以不能再继承其他父类。sleep() 、join()、yield()的区别一、sleep()在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度 程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如 果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一 个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级 的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的 线程有执行的机会。二、join()Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前, B不能工作。保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有 存活,则当前线程不需要停止。三、yield()yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方 法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态 后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也 和sleep()方法不同。再给大家推荐一本书:Java并发编程想要电子版也可以私信我线程池的几种方式newFixedThreadPool(int nThreads)创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数 量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程newCachedThreadPool()创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当 需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制newSingleThreadExecutor()这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会 创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行newScheduledThreadPool(int corePoolSize)创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。private static final Executor exec=Executors.newFixedThreadPool(50); Runnable runnable=new Runnable(){ public void run(){ ... } } exec.execute(runnable); Callable<Object> callable=new Callable<Object>() { public Object call() throws Exception { return null; } } Future future=executorService.submit(callable); future.get(); // 等待计算完成后,获取结果 future.isDone(); // 如果任务已完成,则返回 true future.isCancelled(); // 如果在任务正常完成前将其取消,则返回 true future.cancel(true); // 试图取消对此任务的执行,true中断运行的任务,false 允许正在运行的任务运行完成线程的生命周期新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态(1)生命周期的五种状态新建(new Thread)当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。例如:Thread t1=new Thread();就绪(runnable)线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队 等候得到CPU资源。例如:t1.start();运行(running)线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有 优先级更高的线程进入,线程将一直运行到结束。死亡(dead)当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态 等待执行。自然终止:正常运行run()方法后终止异常终止:调用**stop()**方法让一个线程终止运行堵塞(blocked)由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。正在等待:调用wait()方法。(调用motify()方法回到就绪状态)被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)锁机制线程安全线程安全是指要控制多个线程对某个资源的有序访问或修改,而在这些线程之间没有产生冲 突。在Java里,线程安全一般体现在两个方面:1、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关 键字synchronized。如ArrayList和Vector,HashMap和Hashtable(后者每个方法前都有synchronized关键字)。如果你在interator一个List对象时,其它线程remove一个element, 问题就出现了。2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。volatle实现原理深入分析 Volatile 的实现原理悲观锁,乐观锁是一种思想。可以用在很多方面。比如数据库方面。悲观锁就是for update(锁定查询的行)乐观锁就是 version字段(比较跟上一次的版本号,如果一样则更新,如果失败则要重复读­比较­写的操作。)JDK方面:悲观锁就是sync乐观锁就是原子类(内部使用CAS实现)本质来说,就是悲观锁认为总会有人抢我的。乐观锁就认为,基本没人抢。乐观锁是一种思想,即认为读多写少,遇到并发写的可能性比较低,所以采取在写时先读出 当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重 复读­比较­写的操作。CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。CAS顶多算是乐观锁写那一步操作的一种实现方式罢了,不用CAS自己加锁也是可以的。乐观锁的业务场景及实现方式每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁, 但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不 进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁, 期间该数据可以被其他线程进行读写操作。乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可 能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量 的查询操作,降低了系统的吞吐量。最后都看到这了,给孩子一个三连支持一下吧,Java对初学者很友好;Java资源丰富,因为它可以解决不同的问题;Java有一个庞大而友好的社区;Java无处不在,因此更容易找到第一份工作;Java开发人员缺口大,薪水很高。只要你具备能力,薪资待遇你敢要公司就敢给。链接:https://bbs.huaweicloud.com/blogs/336625
  • [HPC] QE 6.8 移植指南(CentOS 8.2)
    1.1 介绍QUANTUM ESPRESSO是一种用于电子结构计算和材料建模的abinitio量子化学方法的软件套件,在GNU通用公共许可证下免费分发。它基于密度泛函理论,平面波基组和赝势(包括范数守恒和超软)。该程序主要在Fortran-90中编写,其中一些部分在C或Fortran-77中,是根据不同的独立开发的核心软件包的合并和重新设计以及一组旨在互操作的软件包构建的。基本软件包包括Pwscf,它解决了自洽的Kohn和Sham方程,获得了周期性实体,CP实现了Car-Parrinello分子动力学,PostProc实现了数据分析和绘图。关于附加封装,值得注意的是指出赝势生成的原子,PHonon封装,它实现了密度泛函扰动理论(DFPT),用于计算能量相对于原子位移和NEB的二阶和三阶导数。关于QUANTUM ESPRESSO的更多信息请访问QUANTUM ESPRESSO官网。语言:C++/Fortran。一句话描述:可扩展的计算化学工具。开源协议:GPL 2.0。建议的版本建议使用的版本为“QE 6.8”。1.2 环境要求硬件要求硬件要求如表2-1所示。硬件要求项目说明CPU鲲鹏920处理器。GPUNVIDIA Tesla V100。 软件要求软件要求如表2-2所示。软件要求项目版本下载地址QUANTUM ESPRESSO6.8https://github.com/QEF/q-e/archive/refs/tags/qe-6.8.tar.gzOpenBLAS0.3.6https://github.com/xianyi/OpenBLAS/archive/refs/tags/v0.3.6.tar.gzHyper MPI1.1.1https://support.huaweicloud.com/usermanual-kunpenghpcs/userg_huaweimpi_0010.htmlNVIDIA CUDA11.4https://developer.download.nvidia.com/compute/cuda/11.4.0/local_installers/cuda_11.4.0_470.42.01_linux_sbsa.runNVIDIA HPC SDK21.9https://developer.download.nvidia.com/hpc-sdk/21.9/nvhpc_2021_219_Linux_aarch64_cuda_11.4.tar.gz 操作系统要求操作系统要求如表2-3所示。操作系统要求项目版本下载地址CentOS8.2https://vault.centos.org/8.2.2004/isos/aarch64/CentOS-8.2.2004-aarch64-dvd1.isoKernel4.18.0-193https://vault.centos.org/8.2.2004/BaseOS/Source/SPackages/kernel-4.18.0-193.el8.src.rpm 1.3 移植规划数据本章节给出QE软件在移植过程中涉及到的相关软件安装规划路径的用途及详细说明。移植规划数据序号软件安装规划路径用途说明1-基础环境搭建中的各安装包安装路径。参考《HPC解决方案 基础环境搭建指导书》中“安装规划数据”章 节。2/path/to/qeQUANTUM ESPRESSO的安装规划路径。这里的安装规划路径只是一个举例说明,建议部署在共享路径中。现网需要根据实际情况调整,后续章节凡是遇到安装路径的命令,都以现网实际规划的安装路径为准进行替换,不再单独说明。3/usr/local/cuda-11.4NVIDIA CUDA的安装规划路径。4/path/to/openblasOpenBLAS的安装规划路径。5/opt/nvidia/hpc_sdkNVIDIA HPC SDK的安装规划路径。6/path/to/CASEQUANTUM ESPRESSO的算例文件存放规划路径。 1.4 配置编译环境前提条件使用SFTP工具将各安装包上传至服务器对应目录下。配置流程配置流程序号配置项说明1基础环境搭建参考《HPC解决方案 基础环境搭建指导书》中“集群场景环境搭建”章节。2禁用nouveau驱动参考2.4.1 禁用nouveau驱动。3安装NVIDIA CUDA参考2.4.2 安装NVIDIA CUDA组件。4安装Hyper MPI参考2.4.3 安装Hyper MPI。5安装NVIDIA HPC SDK参考2.4.4 安装NVIDIA HPC SDK。6安装OpenBLAS参考2.4.5 安装OpenBLAS。 1.4.1 禁用nouveau驱动操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令查看nouveau驱动是否已禁用。                    lsmod |grep nouveau若有回显信息,表示nouveau驱动未禁用,则执行步骤3。若无回显信息,表示nouveau驱动已禁用,则结束操作。步骤 3 执行以下命令禁用nouveau驱动。新建文件“disable-nouveau.conf”。                    vi /etc/modprobe.d/disable-nouveau.conf按“i”进入编辑模式,添加如下内容。                    blacklist nouveau                    options nouveau modeset=0按“Esc”键,输入:wq!,按“Enter”保存并退出编辑。步骤 4 执行以下命令备份并生成新的“initramfs”文件                    cp /boot/initramfs-$(uname -r).img /boot/initramfs-$(uname -r)-nouveau.img                    dracut -f /boot/initramfs-$(uname -r).img $(uname -r)步骤 5 执行以下命令重启机器。                    reboot----结束1.4.2 安装NVIDIA CUDA组件操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令确认nouveau驱动是否已禁用。                    lsmod |grep nouveau若有回显信息,表示nouveau驱动未禁用,则执行步骤3~步骤5。若无回显信息,表示nouveau驱动已禁用,则结束操作。步骤 3 执行以下命令安装NVIDIA CUDA组件。                    wget https://developer.download.nvidia.com/compute/cuda/11.4.0/local_installers/cuda_11.4.0_470.42.01_linux_sbsa.run                    sudo shcuda_11.4.0_470.42.01_linux_sbsa.run步骤 4 执行以下命令配置环境变量。                    export PATH=/usr/local/cuda-11.4/bin:$PATH                    export LD_LIBRARY_PATH=/usr/local/cuda-11.4/lib64:$LD_LIBRARY_PATH步骤 5 执行以下命令进行验证。                    nvcc --version若返回结果已包含NVIDIA CUDA组件版本信息,说明安装成功。----结束1.4.3 安装Hyper MPI操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令安装HUCX源码包和XUCG源码包。                    wget -c https://github.com/kunpengcompute/hucx/archive/refs/heads/huawei.zip -O hucx-huawei.zip                    wget -c https://github.com/kunpengcompute/xucg/archive/refs/heads/huawei.zip -O xucg-huawei.zip步骤 3 执行以下命令解压HUCX源码包和XUCG源码包。                    unzip hucx-huawei.zip                    unzip xucg-huawei.zip步骤 4 执行以下命令将XUCG源码包中的内容复制到HUCX源码包中的“src/ucg”目录下。                    cp -r xucg-huawei/* hucx-huawei/src/ucg/步骤 5 执行以下命令进入hucx-huawei目录。                    cd hucx-huawei步骤 6 执行以下命令设置环境变量。                    export SEC_CFLAGS="-Wno-error=deprecated-declarations -DHAVE___CLEAR_CACHE=1 -Wno-error=asm-operand-widths -Wno-error=implicit-int-float-conversion -Wno-error=format -Wno-error=tautological-pointer-compare"步骤 7 执行以下命令创建HUCX安装目录。                    mkdir -p /path/to/ucx步骤 8 执行以下命令安装HUCX。                    ./autogen.sh                    ./contrib/configure-opt --prefix=/path/to/ucx CFLAGS="$SEC_CFLAGS" CXXFLAGS="$SEC_CFLAGS"                    make -j32                    make install步骤 9 执行以下命令获取Hyper MPI源码包。                    wget -c https://github.com/kunpengcompute/hmpi/archive/refs/heads/huawei.zip -O hmpi-huawei.zip步骤 10 执行以下命令解压Hyper MPI源码包。                    unzip hmpi-huawei.zip步骤 11 执行以下命令进入hmpi-huawei目录。                    cd hmpi-huawei步骤 12 执行以下命令安装Hyper MPI。                    ./autogen.pl                    ./configure --prefix=/path/to/ompi --with-platform=contrib/platform/mellanox/optimized --enable-mpi1-compatibility --with-ucx=/path/to/ucx                    make -j16                    make install步骤 13 执行以下命令配置环境变量。                    export PATH=/path/to/ompi/bin:/path/to/ucx/bin:$PATH                    export LD_LIBRARY_PATH=/path/to/ompi/lib:/path/to/ucx/lib:$LD_LIBRARY_PATH                    export OPAL_PREFIX=/path/to/ompi/                    export INCLUDE=/path/to/ompi:$INCLUDE----结束1.4.4 安装NVIDIA HPC SDK操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令获取毕昇编译器软件包。                    wget https://developer.download.nvidia.com/hpc-sdk/21.9/nvhpc_2021_219_Linux_aarch64_cuda_11.4.tar.gz步骤 3 执行以下命令创建毕昇编译器安装目录(这里以/opt/compiler为例)。                    mkdir -p /opt/compiler步骤 4 执行以下命令将毕昇编译器压缩包拷贝到安装目录下。                    cp –r nvhpc_2021_219_Linux_aarch64_cuda_11.4 /opt/compiler步骤 5 执行以下命令进入压缩包目录。                    cd /opt/compiler步骤 6 执行以下命令解压缩软件包。                    tar -zxvf nvhpc_2021_219_Linux_aarch64_cuda_11.4.tar.gz步骤 7 执行以下命令解压目录。                    cd nvhpc_2021_219_Linux_aarch64_cuda_11.4/步骤 8 执行以下命令进行安装。                    ./install按“Enter”键确认安装位置,默认为“/opt/nvidia/hpc_sdk”,再按“1”选择单机安装,安装程序会自行找到之前安装的“cuda toolkit”,安装完成后按“Enter”键结束。步骤 9 执行以下命令配置NVIDIA HPC SDK的环境变量。                    module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/21.9步骤 10 执行以下命令进行验证。                    pgcc --version若返回结果已包含“PGI Compilers and Tools”字样,说明安装成功。----结束1.4.5 安装OpenBLAS操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令解压OpenBLAS安装包。                    tar -zxvf OpenBLAS-0.3.6.tar.gz步骤 3 执行以下命令进入解压后的目录。                    cd OpenBLAS-0.3.6步骤 4 执行以下命令编译安装OpenBLAS。                    make                    make PREFIX=/path/to/OPENBLAS install步骤 5 执行以下命令设置OpenBLAS的环境变量。                    export LD_LIBRARY_PATH=/path/to/OPENBLAS/lib:$LD_LIBRARY_PATH----结束1.5 获取源码操作步骤步骤 1 下载QUANTUM ESPRESSO安装包。下载地址:https://github.com/QEF/q-e/releases/tag/qe-6.8步骤 2 使用SFTP工具将QUANTUM ESPRESSO安装包上传至服务器“/path/to/qe”目录。----结束1.6 编译和安装操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令进入主程序安装目录。                    cd /path/to/qe步骤 3 执行以下命令解压安装包。                    tar -zxvf q-e-qe-6.8.tar步骤 4 执行以下命令进入解压后路径。                    cd q-e-qe-6.8/步骤 5 执行以下命令开始编译。                    ./configure --with-cuda=yes --with-cuda-runtime=11.4 --with-cuda-cc=80  --enable-openmp --with-scalapack=no“--with-cuda-cc=80”请根据当前机器的显卡架构进行修改,此处测试机器显卡是v100。                    make -j32 pwall若编译机器无法联网,需下载“devicexlib-master.tar.gz”包,并上传至编译机器,编译完成后执行make pp -j32,具体命令如下:                    mv devicexlib-master.tar.gz /path/to/qe-gpu-6.7/external devicexlib.tar.gz                    tar xzf devicexlib.tar.gz --strip-components=1                    export F90FLAGS="-FAST -mCACHE_align -Mpreprocess -Mlarge_arrays"                    ./configure FC=pgf90 CC=pgcc --with-cuda=no --with-cuda-cc= --with-cuda-runtime= --disable-parallel --enable-cuda-env-check=no                    make pp -j32若出现类似如下错误:是由于链接库配置不对,需要更改“inc”文件。   打开“inc”文件。                    vi make.inc按“i”进入编辑模式,在如下位置增加“LD_LIBS = -lcurand”。按“Esc”键,输入:wq!,按“Enter”保存并退出编辑。步骤 6 执行以下命令查看是否生成可执行文件。                    ll /path/to/qe-gpu-6.7/bin/pw.x回显信息中包含“pw.x”文件,表示已生成可执行文件。步骤 7 执行以下命令设置环境变量。                    export PATH=/path/to/qe/bin/:$PATH----结束1.7 运行和验证操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令进入测试目录。                    cd /path/to/CASE步骤 3 执行以下命令下载测试算例,使用SFTP工具将测试算例上传至服务器“/path/to/CASE”目录。                    wget https://github.com/QEF/benchmarks/archive/refs/tags/bench0.0.tar.gz步骤 4 执行以下命令解压测试算例包,并将需要的文件拷贝到“/path/to/CASE”目录。                    tar xvf benchmarks-bench0.0.tar.gz                    cp benchmarks-master/AUSURF112/* ./步骤 5 执行以下命令运行QE程序。                    mpirun --allow-run-as-root -np 2 -x OMP_NUM_THREADS=1 --mca btl ^openib pw.x -input ./ausurf.in 2>&1 | tee -a qe.log“-np”表示测试使用的总进程数,这里写2是测试机器有2张显卡。“-x OMP_NUM_THREADS=1”表示指定每个进程的线程数为1。需要查看“qe.log”日志中的“WALL”数值,单位是“s”,数值越高性能越低,输出的结果如下图所示。----结束
  • [HPC] fastp 移植指南(CentOS 7.6)
    1 介绍fastp是一种工具,旨在为FastQ文件提供快速的多合一预处理。该工具是用C ++开发的,支持多线程以提供高性能。关于fastp的更多信息请访问GitHub相关页面。语言:C/C++一句话描述:超快速的多合一FASTQ预处理器。开源协议:MIT License建议的版本建议使用版本为“fastp 0.20.1”。2 环境要求硬件要求硬件要求如表2-1所示。表2-1 硬件要求项目说明CPUKunpeng 920 软件要求软件要求如表2-2所示。表2-2 软件要求项目版本下载地址fastp0.20.1https://github.com/OpenGene/fastp/archive/v0.20.1.tar.gz测试算例1ERR1044518_1.fastqftp://ftp.sra.ebi.ac.uk/vol1/fastq/ERR104/008/ERR1044518/ERR1044518_1.fastq.gz测试算例2ERR1044518_2.fastqftp://ftp.sra.ebi.ac.uk/vol1/fastq/ERR104/008/ERR1044518/ERR1044518_2.fastq.gz 操作系统要求操作系统要求如表2-3所示。表2-3 操作系统要求项目版本下载地址CentOS7.6https://www.centos.org/download/Kernel4.14.0-115.el7a.0.1https://www.centos.org/download/3 移植规划数据本章节给出fastp软件在移植过程中涉及到的相关软件安装规划路径的用途及详细说明。表3-1 移植规划数据序号软件安装规划路径用途说明1-基础环境搭建中的各安装包安装路径。参考《HPC解决方案 基础环境搭建指南》中“安装规划数据”章节。2/path/to/FASTPfastp的安装规划路径。这里的安装规划路径只是一个举例说明,建议部署在共享路径中。现网需要根据实际情况调整,后续章节凡是遇到安装路径的命令,都以现网实际规划的安装路径为准进行替换,不再单独说明。4 配置编译环境前提条件使用SFTP工具将各安装包上传至服务器对应目录下。配置流程表4-1 配置流程序号配置项说明1基础环境搭建参考《HPC解决方案 基础环境搭建指南》中“集群场景环境搭建”章节。5 获取源码操作步骤步骤 1    下载fastp源码包“fastp-0.20.1.tar.gz”。下载地址:https://github.com/OpenGene/fastp/archive/v0.20.1.tar.gz步骤 2    使用SFTP工具将下载好的软件包上传至服务器“/path/to/FASTP”目录。----结束6 编译和安装操作步骤步骤 1    使用PuTTY工具,以root用户登录服务器。步骤 2    执行以下命令,解压fastp安装包。tar -xvf fastp-0.20.1.tar.gz步骤 3    执行以下命令,进入解压后的目录。cd fastp-0.20.1步骤 4    执行以下命令,编译安装。make -j 16 步骤 5    执行以下命令,设置环境变量。export PATH=/path/to/FASTP/fastp-0.20.1:$PATH----结束7 运行和验证操作步骤步骤 1    使用PuTTY工具,以root用户登录服务器。步骤 2    执行以下命令进入算例目录。cd /path/to/FASTP/testdata步骤 3    执行以下命令解压算例文件。gzip -d ERR1044518_1.fastq.gzgzip -d ERR1044518_2.fastq.gz步骤 4    执行以下命令,运行算例。{ time fastp -i ERR1044518_1.fastq -I ERR1044518_2.fastq -w 16 ; } 2>&1 |tee fastp.log运行完成后,将回显以下信息:    Duplication rate: 0.581538%    Insert size peak (evaluated by paired-end reads): 169    JSON report: fastp.json    HTML report: fastp.html说明:    fastp支持多线程运行,-w指定线程数,但最多使用16个线程。运行完成后会生成fast.json和fast.html两份报告,可以通过-j和-h参数分别指定两个文件的路径和名称。更多参数信息可以使用fastp --help命令查看。----结束8 更多资源获取更多资源,请访问fastp的GitHub页面:https://github.com/OpenGene/fastp。
  • [交流吐槽] 常见的对比
    Runnable VS CallableCallable仅在 Java 1.5 中引入,目的就是为了来处理Runnable不支持的用例。Callable 接口可以返回结果或抛出检查异常Runnable 接口不会返回结果或抛出检查异常,如果任务不需要返回结果或抛出异常推荐使用 Runnable接口,这样代码看起来会更加简洁工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))shutdown() VS shutdownNow()shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。shutdownNow() :关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。 shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终isTerminated() VS isShutdown()isShutDown 当调用 shutdown() 方法后返回为 true。isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true
  • [数据库] Oracle并行查询计划分析
    Oracle并行查询计划分析Oracle 的并行执行计划是由一个或者几个Data Flow Operator(DFO)组成,同一棵DFO树中,最多只有两组PX进程(生产者和消费者)。每个生产者进程都存在一个和每个消费者进程的连接,每个PX进程和QC都存在一个连接。两组PX进程之间,同一时间只存在一个活跃的数据分发,如果执行路径很长,数据需要多次分发,两组PX进程会变换生产者消费者角色,相互协作,完成所有并行操作PX进程之间或者与QC的连接至少存在一个(单节点下至多三个,RAC环境下至多四个)消息缓冲区用于进程间数据交互,该消息缓冲区默认在Largepool中分配(如果没有配置Largepool则在Sharedpool中分配)。多个缓冲区是为了实现异步通信,提高性能 Oracle并行执行计划树红色为数据消费者,蓝色为数据生产者数据消费者和数据生产之间的数据分发数据生产者和消费者之间数据分发有四种方式:send hashsend broadcastSend replicate(12c特性)adaptive(12 c新特性) send hash:生产者PX进程会根据join key,生成一个hash值,然后根据这个hash值把这行数据发送给一个消费者px进程, Hash分发的本质是把hashjoin的左边和右边(两个数据源),通过同样hash函数重新分发,切 分为N个工作单元(假设DoP=N),再进行join ,目的是减少PX进程进行join 操作时需要连接的数据量,如果hash join有一边在连接键上做hash分区,那么优化器可以选择对分区表不分发,因为hash分区已经对数据完成切分,这只需要hash分发hash join的其中一边,这就是partial partition wise join如果hash join的两边都在连接键上做了hash join分区,那么每个PX进程可以独立的处理对等的hash分区, 没有数据需要分发,这是full partition wise join send broadcast:广播分发是数据生产者将数据广播给所有的数据消费者,也就是说每个消费者都有一份数据,广播分发适合扫描后结果集相对比较小的表,广播分发是,数据结果行数*PX进程数据(消费者),相当于每个PX消费者进程都有一份相同的数据,小表驱动大表,建议做广播分发,如果分发的是大表,分发会消耗大量的CPU和内存Send replicate(12c特性):复制分发其实没有数据分发,每个PX进程重复扫描维度表(inner table),并行扫描事实表(outer table),在进行hash join时,每个PX进程重复扫描hashj oin 的左边的表,buffer cache 被用来缓存hashj oin 左边的小表,减少重复扫描所需的物理读,replicate 仅限于hashj oin 左边是表的情况,如果 hashj oin 的左边的结果集来自其他操作,比如join 或者视图,那么此时无法使用replicateadaptive(12 c新特性):通常, replicate 或者 broadcast 分发不受数据倾斜的影响.对于 hash 分发, hash join 两边连接键的最热门数据, 会被分发到同一 PX 进程进行 join 操作, 容易造成明显的并行执行倾斜.12c 引入 adaptive 分发, 可以解决 hash 分发时并行执行倾斜的问题.如果执行计划显示px send hybrid hash,说明该执行计划启用了adaptive分发特性。对hash join左边分发之前, 会插入一个STATISTICS COLLECTOR操作, 用于运行时确定hash join左边数据集的大小. 如果hash join左边的数据量小于并行度的两倍, 那么对于hash join左边的分发会切换为broadcast方式, 对hash join右边的分发为round-robin. 如果hash join左边的数据大于等于并行度的两倍, 对于hash join两边的分发方式都为hash, 和传统的hash分发一样如果存在柱状图信息, 表明hash join右边连接键上存在数据倾斜, 大部分数据为少数热门的值. 硬解析时, 会对hash join右边的表进行动态采样, 确认热点数据(重复度高的数据),Hash join的左边,的热点的数据会被广播到每个接收者,非热点数据被hash分发;Hash join右边的热点数据通过round-robin的方式发送, 非热点数据被hash分发. 布隆过滤:Oracle的布隆过滤器就是在执行join之前,过滤掉大部分的数据,其实就是包hash join的部分连接操作提前了,对 hash join 右边扫描时, 就第一时间把不符合 join条件的大部分数据过滤掉. 大大降低后续数据分发和 hash join 操作的成本. 不同的分布方式, 布隆过滤的生成和使用方式稍有不同:对于 broadcast 分发和 replicate, 每个 PX 进程持有 hash join 左边的完整数据, 对连接键生成 一个完整的布隆过滤, 扫描 hash join 右边时使用. 如果 sql 涉及多个维度表, 维度表全部使用broadcast 分发, 优化器可能对不同的维度表数据生成多个的布隆过滤, 在扫描事实表时同时使用.对于hash 分发,作为消费者的进程接收了hash  join左边的数据之后,每个PX进程分别对各自的数据集生成布隆过滤,在广播给作为生产者的每个PX进程,扫描右边数据使用。Oracle数据库会根据统计信息和SQL的过滤条件自动选择是否布隆过滤。大部分场景,使用布隆过滤都会提升性能,但在某些场景下,不建议使用布隆过滤,例如:hash join左边的结果集比较大,几百万或者几千万的数据,且连接键的重复数据不多,这种情况下使用布隆过滤,生成额布隆过滤会非常大,无法在CPU cache中完整缓存数据,没事使用布隆过滤都需要到内存里读取数据进行判断,会导致性能下降
  • [技术干货] Redis使用单线程却快到飞起的原因,全在这里了[转载]
    Redis想必大家都或多或少听过吧,我们在工作学习中通常用它来作为缓存使用,既然是作为缓存,大家的第一反应肯定是:这家伙很快!实际上它确实也很快 : ),但Redis底层却是单线程的!有同学可能就要有疑问了,为什么单线程的Redis却能够快到飞起?别急,我尽量用通俗易懂的语言来给各位说道说道~~Redis是单线程,主要是指Redis的网络IO和读写是由一个线程来完成的,但Redis的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。这不是本文讨论的重点,有个印象即可Redis为什么用单线程?多线程的开销通常情况下,在采用多线程后,如果没有良好的系统设计,其实是左图所展示的那样(注意纵坐标)。刚开始增加线程数时,系统吞吐率会增加,再进一步增加线程时,系统吞吐率就增长迟缓了,甚至还会出现下降的情况。上面两张图的标题手误被我标反了,源码还删了=_=关键瓶颈在于: 系统中通常会存在会被多线程同时访问的共享资源,为了保证共享资源的正确性,就需要有额外的机制保证线程安全性,例如加锁,这会带来额外的开销。比如拿最常用的List类型来举例吧,假设Redis采用多线程设计,有两个线程A和B分别对List做LPUSH和LPUSH操作,为了使得每次执行都是相同的结果,即【B线程取出A线程放入的数据】就需要让这两个过程串行执行。这就是多线程编程模式面临的共享资源的并发访问控制问题。并发访问控制一直是多线程开发中的一个难点问题:如果只是简单地采用一个互斥锁,就会出现即使增加了线程,大部分线程也在等待获取互斥锁,并行变串行,系统吞吐率并没有随着线程的增加而增加。同时加入并发访问控制后也会降低系统代码的可读性和可维护性,所以Redis干脆直接采用了单线程模式。Redis使用单线程为什么还这么快?之所以使用单线程是Redis设计者多方面衡量的结果。Redis的大部分操作在内存上完成采用了高效的数据结构,例如哈希表和跳表采用了多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率既然Redis使用单线程进行IO,如果线程被阻塞了就无法进行多路复用了,所以不难想象,Redis肯定还针对网络和IO操作的潜在阻塞点进行了设计。网络与IO操作的潜在阻塞点在网络通信里,服务器为了处理一个Get请求,需要监听客户端请求(bind/listen),和客户端建立连接(accept),从socket中读取请求(recv),解析客户端发送请求(parse),最后给客户端返回结果(send)。最基本的一种单线程实现是依次执行上面的操作。上面标红的accept和recv操作都是潜在的阻塞点:当Redis监听到有连接请求,但却一直不能成功建立起连接时,就会阻塞在accept()函数这里,其他客户端此时也无法和Redis建立连接当Redis通过recv()从一个客户端读取数据时,如果数据一直没有到达,也会一直阻塞基于多路复用的高性能IO模型为了解决IO中的阻塞问题,Redis采用了Linux的IO多路复用机制,该机制允许内核中,同时存在多个监听套接字和已连接套接字(select/epoll)。内核会一直监听这些套接字上的连接或数据请求。一旦有请求到达,就会交给Redis处理,这就实现了一个Redis线程处理多个IO流的效果。此时,Redis线程就不会阻塞在某一个特定的客户端请求处理上,所以它可以同时和多个客户端连接并处理请求。回调机制select/epoll一旦监测到FD上有请求到达时,就会触发相应的事件被放进一个队列里,Redis线程对该事件队列不断进行处理,所以就实现了基于事件的回调。例如,Redis会对Accept和Read事件注册accept和get回调函数。当Linux内核监听到有连接请求或读数据请求时,就会触发Accept事件和Read事件,此时,内核就会回调Redis相应的accept和get函数进行处理。Redis的性能瓶颈点经过上面的分析,虽然通过多路复用机制可以同时监听多个客户端的请求,但Redis仍然有一些性能瓶颈点,这也是我们平时编程需要极力避免的情况。1. 耗时操作任意一个请求在Redis中一旦耗时较久,都会影响整个server的性能。后面的请求都要等前面这个耗时请求处理完成,自己才能被处理到。这一点需要我们在设计业务场景时去规避;Redis的lazy-free机制也把释放内存的耗时操作放在了异步线程中去执行了。2. 高并发场景并发量非常大时,单线程读写客户端IO数据存在性能瓶颈,虽然采用IO多路复用机制,但还是只能单线程依次读取客户端的数据,无法利用到CPU多核。Redis在6.0可以利用CPU多核多线程读写客户端数据,但只是针对客户端的读写是并行的,每个命令的真正操作还是单线程。其他Redis相关的有趣问题借此机会也提几个和redis相关的有意思的问题。1. 为什么要用Redis,直接访问内存不好吗?这一条其实并没有很明确的界定,对于一些不经常变动的数据,可以直接放到内存里,不一定要放到Redis里,可以放到内存里。一致性问题:如果一个数据被修改了,数据在本地内存里的话,可能只有一台服务器上的数据被修改了。如果用Redis里面的话,我们访问Redis服务器,可以解决一致性问题。2. 数据太多内存放不下怎么办?比如我要缓存100G的数据,怎么办?这里也要打一个广告Tair是淘宝开源的分布式KV缓存系统,它从Redis继承了丰富的操作,理论上总数据量无限制,针对可用性、可扩展性、可靠性也进行了升级,感兴趣的小伙伴们可以了解一下~原文链接:https://blog.csdn.net/HNU_Csee_wjw/article/details/122567260
  • [技术干货] synchronize偏向锁底层实现原理[转载]
    链接:https://bbs.huaweicloud.com/blogs/3359231 偏向锁的意义无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。偏向锁主要用来优化同一线程多次申请同一个锁的竞争,即当对象被当做同步锁并有一个线程抢到了锁时,则在Mark Word设置该线程的线程ID、是否偏向锁设置1、锁标志位设置01等信息,此时的Mark Word 存储的就是偏向锁状态信息。在:创建一个线程并在线程中执行循环监听的场景下或单线程操作一个线程安全集合时同一线程每次都需获取和释放锁,每次操作都会发生用户态与内核态的切换。获取偏向锁的场景:在自己的线程栈生成一条Lock Record,然后Object Reference指向对象头,此时Lock Record与对象头就建立了联系:① : 先判断Mard Word的Thread ID是否有值没有,则表示当前资源没有被其他线程占用,把当前线程ID等信息记录到Mark Word(这需CAS,可能多条线程修改Mark Word,需要保证原子性)有,则表示当前资源被线程占用,需要判断该线程是不是自己该线程ID是自己的,则表示可重入,直接获取(此时在自己的线程栈中继续生成一条新的Lock Record)该线程ID不是自己的,说明出现其他线程竞争,当前持有偏向锁的线程就需要撤销了,即当其他线程尝试获取偏向锁才释放锁轻量级锁的获取及释放依赖多次的CAS操作,而偏向锁只依赖一次CAS置换ThreadID。一旦出现多个线程竞争时必须撤销偏向锁,所以:撤销偏向锁消耗的性能必须 < 之前节省下来的CAS原子操作的性能消耗不然得不偿失!JDK6默认开启偏向锁,可通过-XX:-UseBiasedLocking禁用偏向锁。2 偏向锁的获取偏向锁的入口,synchronizer.cpp 文件的ObjectSynchronizer::fast_enter由BiasedLocking::revoke_and_rebias实现2.1 markOop mark = obj->mark()获取对象的markOop数据mark,即对象头的Mark Word2.2 判断mark是否为可偏向状态mark的偏向锁的锁标志位为 012.3 判断mark中JavaThread的状态若指向当前线程,则执行同步代码块若为空,则走4若指向其它线程,则走52.4 执行CAS原子指令设置mark中JavaThread为当前线程ID。若CAS成功,则执行同步代码块,否则走5。2.5 执行CAS失败说明当前存在多个线程竞争锁,当达到全局安全点(safepoint),获得偏向锁的线程就会被挂起,撤销偏向锁,并升级为轻量级锁。升级完成后被阻塞在安全点的线程继续执行同步代码块。BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) { assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint"); // We can revoke the biases of anonymously-biased objects // efficiently enough that we should not cause these revocations to // update the heuristics because doing so may cause unwanted bulk // revocations (which are expensive) to occur. // step1 markOop mark = obj->mark(); if (mark->is_biased_anonymously() && !attempt_rebias) { // We are probably trying to revoke the bias of this object due to // an identity hash code computation. Try to revoke the bias // without a safepoint. This is possible if we can successfully // compare-and-exchange an unbiased header into the mark word of // the object, meaning that no other thread has raced to acquire // the bias of the object. markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } else if (mark->has_bias_pattern()) { Klass* k = obj->klass(); markOop prototype_header = k->prototype_header(); if (!prototype_header->has_bias_pattern()) { // This object has a stale bias from before the bulk revocation // for this data type occurred. It's pointless to update the // heuristics at this point so simply update the header with a // CAS. If we fail this race, the object's bias has been revoked // by another thread so we simply return and let the caller deal // with it. markOop biased_value = mark; markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark); assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked"); return BIAS_REVOKED; } else if (prototype_header->bias_epoch() != mark->bias_epoch()) { // The epoch of this biasing has expired indicating that the // object is effectively unbiased. Depending on whether we need // to rebias or revoke the bias of this object we can do it // efficiently enough with a CAS that we shouldn't update the // heuristics. This is normally done in the assembly code but we // can reach this point due to various points in the runtime // needing to revoke biases. if (attempt_rebias) { assert(THREAD->is_Java_thread(), ""); markOop biased_value = mark; markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED_AND_REBIASED; } } else { markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } } } HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias); if (heuristics == HR_NOT_BIASED) { return NOT_BIASED; } else if (heuristics == HR_SINGLE_REVOKE) { Klass *k = obj->klass(); markOop prototype_header = k->prototype_header(); if (mark->biased_locker() == THREAD && prototype_header->bias_epoch() == mark->bias_epoch()) { // A thread is trying to revoke the bias of an object biased // toward it, again likely due to an identity hash code // computation. We can again avoid a safepoint in this case // since we are only going to walk our own stack. There are no // races with revocations occurring in other threads because we // reach no safepoints in the revocation path. // Also check the epoch because even if threads match, another thread // can come in with a CAS to steal the bias of an object that has a // stale epoch. ResourceMark rm; if (TraceBiasedLocking) { tty->print_cr("Revoking bias by walking my own stack:"); } EventBiasedLockSelfRevocation event; BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD, NULL); ((JavaThread*) THREAD)->set_cached_monitor_info(NULL); assert(cond == BIAS_REVOKED, "why not?"); if (event.should_commit()) { event.set_lockClass(k); event.commit(); } return cond; } else { EventBiasedLockRevocation event; VM_RevokeBias revoke(&obj, (JavaThread*) THREAD); VMThread::execute(&revoke); if (event.should_commit() && (revoke.status_code() != NOT_BIASED)) { event.set_lockClass(k); // Subtract 1 to match the id of events committed inside the safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.set_previousOwner(revoke.biased_locker()); event.commit(); } return revoke.status_code(); } } assert((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?"); EventBiasedLockClassRevocation event; VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD, (heuristics == HR_BULK_REBIAS), attempt_rebias); VMThread::execute(&bulk_revoke); if (event.should_commit()) { event.set_revokedClass(obj->klass()); event.set_disableBiasing((heuristics != HR_BULK_REBIAS)); // Subtract 1 to match the id of events committed inside the safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.commit(); } return bulk_revoke.status_code(); }3 偏向锁的撤销只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销由BiasedLocking::revoke_at_safepoint实现:void BiasedLocking::revoke_at_safepoint(Handle h_obj) { assert(SafepointSynchronize::is_at_safepoint(), "must only be called at safepoint"); oop obj = h_obj(); HeuristicsResult heuristics = update_heuristics(obj, false); if (heuristics == HR_SINGLE_REVOKE) { revoke_bias(obj, false, false, NULL, NULL); } else if ((heuristics == HR_BULK_REBIAS) || (heuristics == HR_BULK_REVOKE)) { bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL); } clean_up_cached_monitor_info(); }偏向锁的撤销动作必须等待全局安全点(safepoint,GC时会让所有线程阻塞的停顿点)暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态撤销偏向锁,恢复到无锁(标志位 01)或轻量级锁(标志位 00)状态偏向锁在Java 1.6后默认启用,但在应用程序启动几s后才激活,可关闭延迟:-XX:BiasedLockingStartupDelay=0若确定应用程序中所有锁通常情况下处于竞争状态,可关闭偏向锁:XX:-UseBiasedLocking=false(默认打开)偏向锁的释放遍历线程栈的所有Lock Record,把ObjectReference切断,即ObjectReference = null.把ObjectReference置null,但锁对象的对象头的Mark Word还是没改变,依然偏向之前的线程,那还是没释放锁的嘛,的确是,线程退出临界区时候,并没有释放偏向锁,这么做是为 : 当再次需要获取锁时,只需要简单判断是否是重入,即可快速获取锁,而不用每次都CAS,这也是偏向锁在只有一个线程访问锁的情景下高效的核心。总结当出现锁资源访问的时候,都会在当前线程栈生成一条Lock Record,并且ObjectReference将指向锁对象的对象头 的Mark Word,该设置可能出现多线程,需CAS操作多线程情况下竞争同一个锁资源,偏向锁的撤销会影响效率偏向锁的重入计数依靠线程栈里Lock Record个数偏向锁撤销失败,最终会升级为轻量级锁偏向锁退出时并没有修改Mark Word,也就是没有释放锁偏向锁相对轻量级锁来说,当同一线程去再次获取锁的时候,不用进行CAS操作,提高了性能.(轻量级锁在同一线程情况下每次去获取锁,在无锁的状态下,每次都要进行一次CAS操作)偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁偏向锁的撤销是很复杂,成为理解代码的障碍,也阻碍了对同步系统重构,而且现如今基本都是多核系统,偏向锁的劣势越来越明显,所以在Java 15废弃了偏向锁】
  • [技术干货] 【故障注入第十七期】Linux OS相关故障-进程
    今天周五啦,掐指一算,好久没更新帖子了,今天继续我们上一期的话题,使用DemonCAT,如何注入Linux OS进程方面的故障呢,我们一起来看下吧!进程异常退出进程/线程死循环 进程/线程D状态进程挂起线程挂起进程Z状态线程数过多进程句柄数耗尽线程异常退出进程数过多进程页表数据被踩进程内存泄漏(分配)内核线程R死锁内核进程死锁进程内存数据被踩停止进程定时器进程内存泄漏(malloc)DemonCAT关于Linux OS 注入进程故障方面的内容就先介绍到这,当然如果有关于任何章节感兴趣的伙伴,欢迎留言或联系我们,下一期,向大家介绍文件系统方面的故障内容,尽情期待~
  • [优秀实践] TrustZone 机密计算资源池化项目实践心得
      很荣幸地能参与到华为鲲鹏众智计划项目中,本次参与的项目主要是将远程客户端调用Kunpeng服务器(openEuler)中的TrustZone特性,满足了虚拟化或VM场景下更便利的使用TrustZone特性的安全需求。在本次项目中,我从开发到测试再到部署以及文档编写等环节中受益良多,感受到了本科阶段体验过的完整开发流程,与华为的合作也是让我体会到了一个优秀公司对于项目开发的严谨与求真。  在项目开始阶段通过与华为项目团队的沟通,华为方面详细的为我们介绍了这个项目的需求、目的以及验收标准,并给出了详细且完善的文档,为之后的开发做了充足的准备。在项目初期,主要由导师牵头为我们分配任务,每个人都有各自负责的模块,我负责的模块是服务器端的内部数据透传,将数据从一个进程传递到另一个进程,并对其中的进程与线程作排队处理。  通过该项目,让我对华为的Openeuler系统操作有了更深刻的认识,熟悉了在无图形界面的终端上进行操作,并且在对服务器操作的过程中,了解到了如何去维护服务器、服务器如何进行内网透传等操作,对企业级的开发有了更深刻的认识。在Openeuler上进行开发,没有windows开发的编译器,对于命令行操作的要求更为严格,通过不断地学习,我也对linux开发更加熟练并且产生了兴趣。在服务器上代码的调试与测试需要使用另一套软件,对其调试的过程中,更加加深了我对于内存、指针等本科未深入学习方向的认识。  在开发的过程中,我也掌握了如何使用开源代码针对所需功能进行编写与开发,在遇到困难时可以尝试多种不同的方法进行调试,如分配进程到内核,可以使用产生与时间有关的随机数进行随机分配,以保证得出的进程编号不会重复。在编译过程中,编译时需要自己编写makefile文件,该过程让我了解到了在windows上使用编译器时未曾学习到的,编译的基本逻辑,受益良多。  在测试过程中,针对每个接口进行单独测试、并发测试、容器测试等内容,让我了解到除了实现功能,实现的功能的效率也至关重要,对于企业级的项目开发也有了全新的认识。  在文档编写阶段,我了解到了如何去描述一个项目,并且清楚、简洁且直观地表述出来,如何将一件事情清晰的表达出来,也是对我个人能力的提升。  在本次与华为项目的合作中,我清晰地认识到了团队合作的重要性,开拓了视野,锻炼了我的实战能力,最后感谢华为提供了鲲鹏众智平台提供了项目实践的平台。希望在今后能与华为公司进一步合作,做出更好的成绩。武汉大学 - 余发江研究团队 - 杨一夫,指导老师:余发江
  • [技术干货] js单线程理解笔记-原创
    一、什么 JavaScript 是单线程?JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript 不能有多个线程呢 ?这样能提高效率啊。JavaScript 的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript 就是单线程,这已经成了这门语言的核心特征,将来也不会改变。二、JavaScript是单线程,怎样执行异步的代码?单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。消息队列:消息队列是一个先进先出的队列,它里面存放着各种消息。事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程。实际上,主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。三、事件循环机制上图大致描述就是:主线程运行时会产生执行栈,栈中的代码调用某些 api 时,它们会在事件队列中添加各种事件(当满足触发条件后,如 setTimeout 执行完毕)而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调如此循环注意,总是要等待栈中的代码执行完毕后才会去读取事件队列中的事件
  • [优秀实践] 浅谈TrustZone机密计算资源池化中共享内存有关的项目实践
      在跟余老师学习的过程中,有机会接触到华为TrustZone机密计算资源池化项目,从开始的一头雾水到后来熟练制作,每一遍的相似操作中总会有新的发现,整体做下来最大的收获就是对linux系统底层的运作又有了更加深刻的理解,相比技术性的提高更重要的是相关的专业素养得到增加,以及知识面得到拓广,并且和华为的合作使我受益匪浅。  在该项目中我们实现了远程客户端调用Kunpeng服务器(openEuler)中的TrustZone特性,满足了虚拟化或VM场景下更便利的使用TrustZone特性的安全需求。在其中开发出了进程池。其中我的主要学习了是Kunpeng服务器上工作环境的搭建与共享内存方面的知识。  共享内存指在多处理器的计算机系统中,可以被不同中央处理器访问的大容量内存。由于多个CPU需要快速访问存储器,这样就要对存储器进行缓存。任何一个缓存的数据被更新后,由于其他处理器也可能要存取,共享内存就需要立即更新,否则不同的处理器可能用到不同的数据。共享内存是 Unix下的多进程之间的通信方法 ,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。在调用远程服务器中的TrustZone时,通过共享内存使物理上隔离的机器之间能便捷快速的调用命令,传递信息。  本项目中的共享内存主要思路如下,与大家共同学习:  1 部署在容器或VM中的客户CA调用PARSECGP Client 连接TEE接口TEEC_InitializeContext,PARSEC GP Client将调用通过PARSEC Interface发到服务器端;;  2 服务侧的PARSEC Provider收到调用后,识别为GP初始连接接口,转换为本地GP client调用,通过本地的lso获得上下文context,之后通过返回通道将接口返回到客 户CA应用;  3 客户CA在已创建的context连接上创建会话,指定TA,获得session。这个过程类似于获得context的过程,涉及组件都是透传为主,不再赘述;注意以上过程中PARSEC GP Provider需要维护上下文以及会话关系。  4 根据客户CCA自己的设计,如果CCA是计划使用内存来传递参数的(此例是采用临时申请的内存),则需要使用TEECC_AllocateSharedMemory接口申请临时内存。这个申请需要在已存的Context下进行,接口调用时需要在TEEC_SharedMemory结构中填入大小,以及类型(输入还是输出)。  5 TEEC_SharedMemory调用在PARSECGP Client终结,并不会直接透传到服务器,PARSEC GP Client根据CA输入的大小和类型,创建buffer,将指针返回CA应用;  6 CA发起操作调用,在操作结构中,会指定参数的传递的方式(paramTypes),以及参数信息(params);PARSEC GP Client需要进行识别,选择不同的分支处理:   1)如果paramType==TEEC_VALUE_*类型,表示此次调用的参数采用直接传递的方式,PARSEC GP Client通过PARSEC直接透传调用,将调用透传到服务器PARSEC GP Provider,触发本地调用,并从服务器本地获得处理结果;   2)如果paramType== TEEC_MEMREF_*表示此调用的参数是通过内存共享传递的:     a. PARSECGP Client库会将GP操作发往provider,在TEECC_InvokeCommand中关键参数除了commandID,增加了参数buffer,将数据一起通过interface传至GP provider、GP Proxy、GP Worker。GP Worker根据不同的参数类型进行allocate。即现在Invoke命令中就可以直接传buffer,而不是走两个来回去传buffer。     b. GP Worker获取到了数据以及指令后,调用本地的标准的TEEC_InvokeCommand,并接收返回。  7 PARSEC GP Client库需要继续维护来自CCA的内存释放接口。注意释放接口是针对内存buffer精确的操作,即可能会存在多个buffer。收到调用后,PARSEC GP Client即刻将本地指定的buffer清零,之后再通过扩展的远程数据清除操作(0x0108),将buffer指针送到GP Proxy,GP Proxy传到GP Worker。GP Worker收到调用后,找到对应的内存清零,接口调用本地标准的TEEC_ReleaseSharedMemory,清除TEE侧对应的数据(注意此时需要使用真实的buffer指标)。CCA和GP Worker分别释放。  本次的项目进展顺利,与华为的合作非常高效,在遇到问题的时候,团队内及时配合,与华为的技术人员及时沟通,都是顺利推进的关键因素。技术性相关知识的学习,以及如何理解项目要求,学习并运用相关知识以完成项目,与同学老师的合作,都使我受益良多。武汉大学 - 余发江研究团队 - 毛煜灵,指导老师:余发江
  • [交流吐槽] 常见的对比
    Runnable VS CallableCallable仅在 Java 1.5 中引入,目的就是为了来处理Runnable不支持的用例。Callable 接口可以返回结果或抛出检查异常Runnable 接口不会返回结果或抛出检查异常,如果任务不需要返回结果或抛出异常推荐使用 Runnable接口,这样代码看起来会更加简洁工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))shutdown() VS shutdownNow()shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。shutdownNow() :关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。 shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终isTerminated() VS isShutdown()isShutDown 当调用 shutdown() 方法后返回为 true。isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true
  • [技术干货] Mysql架构与内部模块(一)
    一、一条查询SQL是如何执行的?1.客服端/服务器通信协议2.查询缓存,若缓存中有数据直接返回,若没有执行第三步3.解析器进行语法解析,解析树预处理给到新解析树,4.查询优化器5执行计划,查询计划引擎6.返回结果,并缓存结果。API接口查询到存储引擎1.通信协议 首先,MySQL 必须要运行一个服务,监听默认的3306端口MYSQL支持TCP/IP 协议,编程语言的连接模块都是用 TCP 协议连接到 MySQL 服务器的。MYSQL还支持Unix Socket。比如我们在 Linux 服务器,不用通过网络协议,也可以连接到 MySQL 的服务器。2.通信方式分为三种A(发送器)--->B(接收器)  单工,数据单向传输A(发送器,接收器)<--->B(发送器,接收器)  半双工,数据双向传输,但是不能同时传输A(发送器,接收器)<===>B(发送器,接收器)  双工,数据双向传输,同时传输MySQL 使用半双工的通信方式。 半双工意味着要么是客户端向服务端发送数据,要么是服务端向客户端发送数据,这两个动作不能 同时发生。 所以客户端发送 SQL 语句给服务端的时候,(在一次连接里面)数据是不能分成小块发送的,不管 你的 SQL 语句有多大,都是一次性发送。 如果发送给服务器的数据包过大,我们必须要调整 MySQL 服务器配置 max_allowed_packet 参数的值 (默认是 4M)另一方面,对于服务端来说,也是一次性发送所有的数据,不能因为你已经取到了想要的数据就中 断操作。 所以,我们一定要在程序里面避免不带 limit 的这种操作。连接方式8小时不活动的连接,MySQL 服务器会断开。MySQL 默认的最大连接数是 151 个(5.7 版本)Sleep 线程正在等待客户端,以向它发送一个新语句 Query 线程正在执行查询或往客户端发送数据 Locked 该查询被其它查询锁定 Copying to tmp table 临时结果集合大于 tmp_table_size。线程把临时表从存储器内部格式改变为磁 盘模式,以节约存储器 Sending data 线程正在为 SELECT 语句处理行,同时正在向客户端发送数据 Sorting for group 线程正在进行分类,以满足 GROUP BY 要求 Sorting for order 线程正在进行分类,以满足 ORDER BY 要求 查询缓存MySQL 内部自带了一个缓存模块。默认是关闭的。主要是因为 MySQL 自带的缓存的应用场景有 限,第一个是它要求 SQL 语句必须一模一样。第二个是表里面任何一条数据发生变化的时候,这张表所 有缓存都会失效。语法解析和预处理(Parser & Preprocessor) 假如随便执行一个字符串 fkdljasklf ,服务器报了一个 1064 的错: [Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'fkdljasklf' at line 1 服务器是怎么知道我输入的内容是错误的? 或者,当我输入了一个语法完全正确的 SQL,但是表名不存在,它是怎么发现的? 这个就是 MySQL 的 Parser 解析器和 Preprocessor 预处理模块。 这一步主要做的事情是对 SQL 语句进行词法和语法分析和语义的解析词法解析 词法分析就是把一个完整的 SQL 语句打碎成一个个的单词。 比如一个简单的 SQL 语句: select name from user where id = 1; 它会打碎成 8 个符号,记录每个符号是什么类型,从哪里开始到哪里结束。语法解析 第二步就是语法分析,语法分析会对 SQL 做一些语法检查,比如单引号有没有闭合,然后根据 MySQL 定义的语法规则,根据 SQL 语句生成一个数据结构。这个数据结构我们把它叫做解析树预处理器(Preprocessor) 如果表名错误,会在预处理器处理时报错。 它会检查生成的解析树,解决解析器无法解析的语义。比如,它会检查表和列名是否存在,检查名 字和别名,保证没有歧义。 4、查询优化(Query Optimizer)与查询执行计划 什么优化器? 问题:一条 SQL 语句是不是只有一种执行方式?或者说数据库最终执行的 SQL 是不是就是我们发 送的 SQL? 这个答案是否定的。一条 SQL 语句是可以有很多种执行方式的。但是如果有这么多种执行方式,这 些执行方式怎么得到的?最终选择哪一种去执行?根据什么判断标准去选择? 这个就是 MySQL 的查询优化器的模块(Optimizer)。 查询优化器的目的就是根据解析树生成不同的执行计划,然后选择一种最优的执行计划,MySQL 里 面使用的是基于开销(cost)的优化器,那种执行计划开销最小,就用哪种。 使用如下命令查看查询的开销: show status like 'Last_query_cost'SHOW VARIABLES LIKE 'optimizer_trace'; set optimizer_trace="enabled=on"; select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid; select * from information_schema.optimizer_trace\G
  • [技术干货] OpenMP API的使用(一)
    ## 导语 OpenMP(Open Multi-Processing)作为一种共享内存的并行编程模型[1],是目前国际上最主流的异构混合并行编程架构之一,自1997年开始推出至今已升级5个主要版本,最新的标准规范为OpenMP API 5.2 [2]。OpenMP API定义了一个可移植、易扩展的编程模型,提供了一系列简单而又灵活的接口(指导语句)用于指导用户简化应用代码和提升性能。OpenMP普遍应用于科学计算领域,如理论物理、化学、材料、气象等领域的数值模拟计算。 毕昇编译器作为一款支持异构并行计算的高性能编译器,支持C/C++/Fortran语言的OpenMP API。目前毕昇编译器基本支持C/C++ OpenMP API 5.0 [3]和Fortran OpenMP API 4.5 [4],并持续提升支持最新标准规范。本文主要讲解毕昇编译器如何使用OpenMP API以及比较常用的指导语句。 ## 使用OpenMP API范例 这里给出一个简单的Fortran代码(test.f90),开启4个线程,并行打印每个线程的信息。 ```fortran program main use omp_lib integer :: id, num_threads call omp_set_num_threads(4) !设置默认创建线程数为4 !$omp parallel private(id) !创建4个线程 id = omp_get_thread_num() !获取当前线程ID num_threads = omp_get_num_threads() !获取当前创建的线程总数 print *, "Hello World Id ", id, " of ", num_threads, " num_threads" !$omp end parallel !结束parallel指导语句,退出并行区域 end ``` 使用毕昇编译器进行编译和运行,结果如下所示: ```shell $ flang test.f90 -fopenmp -o a.out $ ./a.out Hello World Id 3 of 4 num_threads Hello World Id 0 of 4 num_threads Hello World Id 1 of 4 num_threads Hello World Id 2 of 4 num_threads ``` 从结果可以看出,该用例创建了4个线程,并行执行7-9行代码,故print打印语句执行了4次,每个线程打印对应线程的ID。 ## OpenMP常用指导语句 ### parallel指令 parallel指令是最常用的OpenMP API之一,作用是创建一个线程组,parallel指令是进行并行控制的关键。根据OpenMP API标准规范,每个指导语句可以跟随一些子句,用于指导更加详细的功能,比如parallel指令可以跟随if,num_threads,private,firstprivate,shared,default,copyin,reduction,proc_bind,allocate子句。常用子句的详细使用指导如下: - if子句:`if ([parallel:] scalar-logical-expression)`,如果`scalar-logical-expression`的结果为真,则创建一个线程组,含多个线程;如果结果为假,则只创建一个线程。 - num_threads子句:`num_threads (scalar-integer-expression)`,在parallel并行区域内创建多个线程,线程数为`scalar-integer-expression`。 - private子句:`private(list)`,list为一系列变量,list所有变量在并行区域内为私有变量,即每个线程有一份私有拷贝,其它线程不可访问。 - firstprivate子句:`firstprivate(list)`,private子句的升级版,除了定义list所有变量在并行区域内为私有变量,还将进入parallel并行区域前的变量的值赋值给每个线程对应的私有变量。 - shared子句:`shared(list)`,list所有变量共享内存,所有线程均可访问,并不进行拷贝,与并行区域外的同名变量为同一个变量。 > 这里需要注意在并行区域结束处有一个隐含的同步操作,即所有线程都到达并行区域结束处,主线程才会继续执行并行区域外的代码。 ### worksharing-loop指令 worksharing-loop指令是最常用的OpenMP API之一,它的作用是对循环结构进行并行处理。C/C++的指导语句为`#pragma omp for [子句]`,Fortran的指导语句为`!$omp do`。worksharing-loop指令指导接下来的循环被并行执行,子句指导如何被并行执行。worksharing-loop指令可以跟随private,firstprivate,lastprivate,schedule,collapse,nowait,linear,reduction,ordered,allocate。常用子句的详细使用指导如下: - private子句:`private(list)`,用法同parallel的private子句,区别为list变量的范围为循环区域。 - firstprivate子句:`firstprivate(list)`,用法同parallel的private子句,区别为list变量的范围为循环区域。 - lastprivate子句:`lastprivate([lastprivate-modifier:] list)`,private子句的升级版,除了定义list所有变量在并行区域内为私有变量,还将循环最后一次迭代的子线程的私有变量的值赋值给并行区域外同名的原始变量。 - schedule子句:`schedule([modifier[, modifier]:]kind[, chunk_size])`,指导循环的迭代如何调度到创建的线程上,具体调度算法或将在后续文章中详述。 - collapse子句:`collapse(n)`,将指令下的前n层循环进行合并后展开为一个更大的循环,增加可以调度的循环总数。 - nowait子句:`nowait`,在指令结束处不生成隐含的同步操作。如果没有该子句,在指令结束处将生成隐含的同步操作。 ## parallel和worksharing-loop指令的使用范例 ```fortran program main use omp_lib integer, parameter :: N = 10 integer :: i, tid, a(N), b(N) do i = 1, N a(i) = -1 b(i) = -1 enddo !$omp parallel num_threads(N) !$omp do private(tid) firstprivate(a, b) lastprivate(b) do i = 1, N tid = omp_get_thread_num() a(i) = a(i) + i + tid b(i) = b(i) + i + tid if (i == 1 .or. i == N) print *, i, tid, a(i), b(i) enddo !$omp end do !$omp end parallel print *, a print *, b end ``` 该用例与下面的用例等价: ```fortran program main use omp_lib integer, parameter :: N = 10 integer :: i, tid, a(N), b(N) do i = 1, N a(i) = -1 b(i) = -1 enddo !$omp parallel do private(tid) firstprivate(a, b) lastprivate(b) num_threads(N) do i = 1, N tid = omp_get_thread_num() a(i) = a(i) + i + tid b(i) = b(i) + i + tid if (i == 1 .or. i == N) print *, i, tid, a(i), b(i) enddo !$omp end parallel do print *, a print *, b end ``` 使用毕昇编译器进行编译和运行,结果如下所示: ```shell $ flang test.f90 -fopenmp -o a.out $ ./a.out 1 0 0 0 10 9 18 18 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 18 ``` OpenMP指导语句使得该程序将a和b数组在循环内进行私有拷贝,并且将并行区域外的同名变量(a, b)的值(-1)拷贝进并行区域内,计算之后,最后一次迭代的b数组的值(b(10)=18)赋给并行区域外,除此之外,并未改变并行区域外的同名变量(a, b)的值。 ## 后记 毕昇编译器2.1.0版本已发布,进入[毕昇编译器网页](https://www.hikunpeng.com/developer/devkit/compiler/bisheng)下载毕昇编译器可以体验OpenMP并行编程,同时我们提供在线课程和沙箱实验供感兴趣的同学学习使用毕昇编译器。使用过程中遇到问题或者有经验分享,可以前往毕昇论坛或Compiler SIG微信群(添加如下微信小助手进Compiler SIG微信群)进行互动交流。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/26/1645856365210211009.jpg) ## 参考 [1] https://www.openmp.org/ [2] https://www.openmp.org/wp-content/uploads/OpenMP-API-Specification-5-2.pdf [3] https://www.openmp.org/spec-html/5.0/openmp.html [4] https://www.openmp.org/wp-content/uploads/openmp-4.5.pdf ------ 关注 **毕昇编译** 获取编译技术更多信息 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20222/26/1645856377169914674.jpg)
  • [知识分享] 今儿新学会一个写日志技能:双缓冲机制
    >摘要:通过交换指针的方式实现两个缓冲区的功能互换,十分巧妙,令人称赞。本文分享自华为云社区《[奇妙的双缓冲机制写日志(Java实现)](https://bbs.huaweicloud.com/blogs/324161?utm_source=csdn&utm_medium=bbs-ex&utm_campaign=other&utm_content=content)》,作者: 洛叶飘 。 # 写日志面临的问题 写日志在Web程序中是一个十分基础与常见的需求,其对性能的要求很高。主要需要处理以下问题: 1. 多线程并发,需要保证顺序性。 2. 高配IO操作,但IO操作相比其他指令耗时长,性能低。 即一方面需要面对程序端高配的日志写请求,一方面需要受限于系统磁盘相对缓慢写入文件,应该如何处理呢。 # 双缓冲区 因此,引入双缓冲区机制,一个缓冲区存储应用程序端发送的日志,按照时间顺序依次存储;另一个缓冲区负责向低层磁盘发送写文件请求。 写文件请求执行相对较慢,因此当写文件执行完毕后,通知管理程序,此时可以将另一个缓冲区内容写入磁盘了。 双缓冲区的奇妙之处就在于,两个缓冲区的交换,是通过交换指针来实现的,非常的高效。 部分实现代码如下(其他部分逻辑代码已省略)。 ## 双缓冲区代码,不使用Java现有的线程安全类,后续通过加锁保证数据安全。 // 负责接收应用程序发来的日志 LinkedList currentBuffer = new LinkedList(); // 负责将数据同步到磁盘 LinkedList syncBuffer = new LinkedList(); ## 第一个缓冲区,接收应用程序高速写日志请求 public void log(String content) { // 加锁保证第一个缓冲区 synchronized(this) { // 将log写入内存缓冲中,这里不会直接刷入磁盘文件 currentBuffer.add(content); } // 将缓冲区中的内容刷到磁盘 logSync(); } ## 第二缓冲区,向磁盘写日志,并在写入后交换缓冲区指针 private void logSync() { synchronized(this) { // 当前在刷内存缓冲到磁盘中去 if (isSyncRunning) { // 判断是否第二个缓冲区还在刷 while (isSyncRunning) { try { // 释放锁,即允许第一个缓冲区继续接收日志缓存, 然后等待被唤醒 wait(2000); } catch (Exception e) { e.printStackTrace(); } } // 此时没有人在写磁盘 } // 交换缓冲区指针 setReadyToSync(); // 设置当前正在同步到磁盘的标志位 isSyncRunning = true; } // 刷磁盘,性能最低,不能加锁 logBuffer.flush(); synchronized(this) { // 同步完磁盘之后,将标志位复位 isSyncRunning = false; // 唤醒其他等待刷磁盘的线程 notifyAll(); } } ## 交换缓冲区指针,功能变更 public void setReadyToSync() { LinkedList tmp = currentBuffer; currentBuffer = syncBuffer; syncBuffer = tmp; } # 奇妙之处 ## 两个缓冲区各自处理,互不干扰 两个缓冲区很好的解决了应用程序的“**快速、多线程**”与IO操作的“**缓慢,单线程**”的矛盾。应该说,引入双缓冲区是一个显而易见的方式。 ## 缓冲区交换 通过交换指针的方式实现两个缓冲区的功能互换,十分巧妙,令人称赞。 # 总结 你知道吗?电视机里也在用着双缓冲机制
总条数:813 到第 页
上滑加载中