【Flutter入门到进阶】Dart进阶篇---DartVM单线程设计原理

news2024/11/15 13:56:53

1 虚拟机的指令执行设计

1.1 虚拟机的分类

        基于栈的虚拟机,比如JVM虚拟机

        基于寄存器的虚拟机,比如Dalvik虚拟机

1.2 虚拟机的概念

        首先问一个基本的问题,作为一个虚拟机,它最基本的要实现哪些功能?

他应该能够模拟物理CPU对操作数的移进移出,理想状态下,它应该包含如下概念:

(1)将源码编译成VM指定的字节码。

(2)包含指令和操作数的数据结构(指令用于处理操作数作何种运算)。

(3)一个为所有函数操作的调用栈。

(4)一个“指令指针(Instruction Point —IP)”:用于指向下一条将要执行的指令。

(5)一个虚拟的“CPU”–指令的派发者:

        1)取指:获取下一条指令(通过IP获取)

        2)译码:对指令进行翻译,将要作何种操作。

        3)执行:执行指令。

        以上是CPU的三级流水线操作,实际上五级流水线还包括回写,即把执行后生成的结果回写进存储器。

有两种基本的方法实现虚拟机:

        基于Stack的和基于Register的,比如基于Stack的虚拟机有JVM、.net的CLR、还有最近热门的以太坊EVM,这种基于Stack实现虚拟机是一种广泛的实现方法。而基于Register的虚拟机有Lua VM(是Lau编程语言的虚拟机)和Dalvik VM。

        这两种虚拟机实现的不同主要在于操作数和结果的存储和检索机制不一样。

        注意Dalvik因为是基于寄存器的虚拟机,其编译结果文件的格式和class文件是完全不同的

1.3 核心概念

1.3.1 最终的指令的执行的表现形式是

        ADD P1 P2--- P1 P2为操作数(操作数的本质是地址)

1.3.2 核心需要理解的是,不同芯片,他们的线路设计规格不一致(含单片机)

        1.常规CPU Intel  ARM AMD等:32、64

        2.单片机:8、16

1.4 Stack-Based虚拟机(最大特征就是一堆pop push)

        Stack-Based虚拟机(最大特征就是一堆pop push)

        如下就是一个典型的计算20+7在栈中的计算过程:

        指令执行过程为
           1、POP 20
           2、POP 7
           3、ADD   // 此时单条指令就可以了,不需要再跟 20, 7, result
           4、PUSH result

1.5 Register-Based虚拟机(最大特征就是较多操作数)

        基于寄存器的虚拟机,它们的操作数是存放在CPU的寄存器的。没有入栈和出栈的操作和概念。但是执行的指令就需要包含操作数的地址了,也就是说,指令必须明确的包含操作数的地址,这不像栈可以用栈指针去操作。比如如下的加法操作:

1.5.1 指令执行过程

        ADD R1, R2, R3 ;就一条指令搞定了。

1.6 总结

1.6.1 因为计算机芯片规格不一致

        像常规Intel ARM AMD这些芯片能够支持比较多的操作,因为一般他们的线路设计都是32以上

        像单片机这种,最便宜的单片机可能在线路上的设计是8

1.6.2 因此考虑到设备上的兼容

        JAVA的方案

                栈---兼容最小8的指令执行(能兼容单片机等设备)

        Android的方案

                寄存器---无需兼容,android设备一般依赖于高性能芯片

1.7 思考?

        基于上篇了解指令执行过程中的问题,那么需要考虑线程资源

        什么是线程资源?

                参考:C++ chapter 11  STL之线程管理

                        https://www.processon.com/view/link/62ab388e5653bb5256dae2fd

                PS:最好去把这节课看了

        线程资源本质

                1.指令的最终执行由OS分配

                2.虚拟机需要做的事情是将指令编译完成扔给OS的内核

                3.完成指令提交通过OS的系统接口完成

                4.内核在处理代码时会对代码进行组织,即task_struct对象

                5.在内核角度,一个task_struct其实就是一个进程|线程

                        对于linux而言其实进程与线程没什么太大的区别, 就是信息上有一点差异

                6.在内核角度,提供pthread_create这种形式让我们动态生成一个线程资源,本质就是通过该函数告诉linux,哥们开一个新的task_struct,代码就是{自己指定}这一段

2 回顾:GC的实现方案与思路

2.1 虚拟机内存管理思路

2.1.1 说明

        虚拟机进行内存管理,本质上是自己提前申请一块内存,然后自己来维护这一块内存,具体案例如下

2.1.2 示例

#include <stdio.h> #include <malloc.h> 
#define EDEN_SIZE 1024 * 1024 * 16 int main() {     
    //堆区初始化大小16M,实际开辟 16 * 3 48M     
    //其中16M为EDEN 32M为堆区     
    char* head = malloc(EDEN_SIZE * 3);     
    printf("申请空间位置%x ~ %x\n",&head,(&head + (EDEN_SIZE * 3)));     
    //年轻代设计分配     
    int yong_begin = &head ;     
    int yong_end = &head + EDEN_SIZE;     
    int eden_begin = &yong_begin;     
    int eden_end = &yong_begin + (int)(EDEN_SIZE * 0.8-1);     
    //survivor区分配     
    int survivor1_begin =&yong_begin + (int)(EDEN_SIZE * 0.8);     int survivor1_end = &yong_begin + (int)(EDEN_SIZE * 0.9 - 1);     int survivor2_begin =&yong_begin + (int)(EDEN_SIZE * 0.9);     int survivor2_end =&yong_begin + (int)(EDEN_SIZE);     //老年代设计     int old_begin = &head + EDEN_SIZE;     
    int old_end = old_begin + EDEN_SIZE * 3;     
    printf("eden空间分配区间%x ~ %x\n",eden_begin,eden_end);     
    printf("survivor1空间分配区间%x ~ %x\n",survivor1_begin,survivor1_end);     printf("survivor2空间分配区间%x ~ %x\n",survivor2_begin,survivor2_end);     printf("eden空间分配区间%x ~ %x\n",old_begin,old_end);     
    //java代码:int i = 10;     
    //指针标记     
    char* index = &head;     
    printf("当前地址%x\n",index);     
    int i = 20;    
    //length 4 value 20     
    char* val = 20;     
    int length = 4;     
    char* v1Index = index;     
    *index = val;     
    index += length;     
    printf("1当前分配后位置%x\n",index);     
    char* v2Index = index;     
    char* val2 = 30;     
    int length2 = 4;     
    *index = val2;     
    index += length2;     
    printf("2当前分配后位置%x\n",index);     
    int value = *v1Index;     
    printf("value:%d\n",value);     
    int value2 = *v2Index;     
    printf("value2:%d\n",value2);     
    return 0; 
}

2.1.3 运行结果

2.2 内存碎片处理方案

        2.2.1 这种自己来写,首先需要考虑的是内存碎片的问题

        2.2.2 传统处理方案一般是依赖于两个阶段

                1.标记要处理的数据对象

                2.清空要处理的数据对象(PS:但是对于内存数据管理来讲不进行情况,只进行覆盖)

       2.2.3  业内处理算法方案

         1、垃圾确认算法--标记阶段算法

                1)引用计数算法 

                2)GCRoot可达性分析算法

        2、清除垃圾算法--清除阶段算法

1)清除算法:直接删

        有碎片问题

2)复制算法:牺牲空间,换碎片

无碎片问题

3)压缩/整理算法:牺牲时间,换碎片

无碎片问题

        3、三种算法的性能指标对比

        效率上来说,复制算法最快,但是内存浪费最多而为了尽量兼顾上面三个指标,标记整理算法相对平滑一些,但是效率上不仅如此任意,他比复制算法多了一个标记阶段,比清除多了一个整理内存阶段

2.3 分代设计的思路

难道没有一种最优算法吗?

2.3.1 分代收集算法

        为了满足垃圾回收的效率最优性,所以分代收集算法应运而生

        分代收集算法基于一个事实:不同的对象生命周期是不一样的,因此,不同生命周期的对象可以采取不同的手机方式,以便于提高回收效率。一般是把堆分为新生代和老年代,这样就可以根据各个年代的特点使用不同回收算法,相对提高效率

        在系统运行过程汇总,会产生大量对象,其中有些对象是业务信息相关,如HTTP请求的Session、线程、Socket连接等对象,这类对象跟业务挂钩,因此生命周期长,还有一部分是运行过程汇总生成的临时变量,这些对象生命周期短,比如:String,这些对象甚至只使用一次即可回收

2.3.2 分代算法衍生后

1)eden作为生产地,运用清除算法

2)但是可能存在需要存活的对象数据,所以预留一块数据进行保存存活数据(old)

3)为了保证不产生碎片,且这块数据的体量实际不大,所以这里采用复制算法
 

4)指针碰撞!

5)处理方案:

​        维护一个空闲列表,解决碎片化

​            算法:清除、复制、整理算法

​            如何自己来设计这个方案

​                    70-99% - 一次性  年轻人

​                    1-30% 长久       老年人

​                    建议新老年代占比:90% 10%

​            年青代

​                eden(生产)-survivor(缓冲)

​            老年代

​                eden-清除算法  all clear

​                survivor - 复制  缓冲

​                old - 清理

2.4 内存回收方案

2.4.1 分区

2.4.2 增量

3 Dart 内存结构设计

3.1 堆栈区设计思路

3.1.1 JAVA

3.1.2 Dart

3.2.3 说明

1.JAVA采取的方案

        栈区独立内存开销,维持指令运行

        堆区数据共享

        优点

                无额外数据开销,线程间共享数据直接找共享堆区

        缺点

                因为共享,会出现临界区问题,需要引入锁机制处理

2.Dart采取的方案

        GC隔离

        如上图展示,在内存管理设计角度,将栈,堆,等数据进行完全隔离设计,包括GC线程也进行隔离

2.2 线程间数据传递问题

2.2.1 JAVA

        直接共享堆,但是需要设计锁业务

2.2.2 DART

        独立堆栈,但是需要设计隔离区的通信机制

4 事件轮询机制

4.1 事件轮训机制建立的目的

        单线程内部建立异步体系

        这种方案一般适用于有事件驱动下的业务体系(一般指UI体系)

        服务体系一般是多线程

        这里设计的目的请注意:

        1.dart出现的目的是为了去对标JS做所谓的跨平台

        2.后续切入到移动端其实他的本质也不变,因为是为了做UI的跨平台

        3.既然是UI的开发那么整个业务运载的模型肯定是事件驱动模型

        4.在android当中,是运用handler来支撑事件驱动模型

4.2 程序运行设计模型

4.2.1 单线程模型

        单线程同步模型中,任务依次执行,如果某个任务因为I/O阻塞了,其他任务也只能等待,直到前面的任务执行完成后才能执行。这样3个没有依赖关系的任务,需要互相等待顺序执行,大大降低了执行效率。

说明

        这种是一根线程的状态,很多单片机是这种,有耗时操作后续就等着

4.2.2 多线程模型

        多线程模型中,任务分别在独立的线程中执行,不需要互相等待。在多处理器系统上可以并行处理,在单处理器系统上可以交替执行。这种方式更有效率,但是开发者需要保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙且令人痛不欲生的bug。

        典型的多线程状态,如果存在分支功能,让另外一个线程去进行维护

4.2.3 事件驱动模型

        事件驱动模型中,3个任务交替执行,但仍然在一个单独控制的线程中。当处理一个I/O或其他昂贵的操作时,注册一个回调到事件队列中,当I/O操作完成后继续执行。回调描述了如何处理某个事件。事件队列轮询事件,事件发生后将事件分派给事件处理器进行处理。这种方式让程序尽可能执行而不需要额外的线程。使用事件驱动模型开发者不需要担心线程安全的问题。

        单线程下的无等待形式运行

        1.传统单线程如果需要等待事件的响应去处理某个业务,那么只能等待一个事件,其他需要等这一个事件处理完成后才响应

4.3 事件轮训机制详解

4.3.1 Dart实现过程

图例

说明

        一个Dart应用有一个消息循环和两个消息队列-- event队列和microtask队列。

        event队列包含所有外来的事件:I/O,mouse events,drawing events,timers,isolate之间的message等。

        microtask 队列在Dart中是必要的,因为有时候事件处理想要在稍后完成一些任务但又希望是在执行下一个事件消息之前。

        event队列包含Dart和来自系统其它位置的事件。但microtask队列只包含来自当前isolate的内部代码。

        正如下面的流程图,当main方法退出后,event循环就开始它的工作。首先它会以FIFO的顺序执行micro task,当所有micro task执行完后它会从event 队列中取事件并执行。如此反复,直到两个队列都为空。

        当一个Dart应用开始的标志是它的main isolate执行了main方法。当main方法退出后,main isolate的线程就会去逐一处理消息队列中的消息。

        正如下面的流程图,当main方法退出后,event循环就开始它的工作。首先它会以FIFO的顺序执行micro task,当所有micro task执行完后它会从event 队列中取事件并执行。如此反复,直到两个队列都为空。

        当一个Dart应用开始的标志是它的main isolate执行了main方法。当main方法退出后,main isolate的线程就会去逐一处理消息队列中的消息

4.3.2 JAVA模拟示例

Task

package com.kerwin.event; 
public abstract class Task {     
    public abstract void run(); 
}

ScheduleMicrotask

package com.kerwin.event; 
public class ScheduleMicrotask extends Task {     
    @Override     
    public void run() {} 
}

Timer

package com.kerwin.event; 
public class Timer extends Task {     
    @Override     
    public void run() {} 
}

Main

package com.kerwin.event; 
import java.util.Queue; 
import java.util.concurrent.LinkedBlockingQueue; 

public class Main {     
    //微任务队列-优先级高
    private static Queue<ScheduleMicrotask> scheduleMicrotasks 
= new LinkedBlockingQueue<>();         
    //事件队列-优先级低     
    private static Queue<Timer> timers = new LinkedBlockingQueue<>();     
    public static void processAsync(){
        while(!scheduleMicrotasks.isEmpty() || !timers.isEmpty()){
            Task task = null;             
            if((task = scheduleMicrotasks.poll()) != null){
            }else if((task = timers.poll()) != null){}             
            task.run();         
        }     
    }   
  
    public static void main(String[] args){         
        System.out.println("main start!");         
        timers.offer(new Timer(){            
            @Override             
            public void run() {                 
                System.out.println("timer - event - A");                 
                scheduleMicrotasks.offer(new ScheduleMicrotask(){                     
                    @Override                     
                    public void run() {                         
                        System.out.println("ScheduleMicrotask - A - in Timer A");      
                    }                 
                });                 

                scheduleMicrotasks.offer(new ScheduleMicrotask(){                     
                    @Override                     
                    public void run() {                         
                        System.out.println("ScheduleMicrotask - B - in Timer A");
                    }                 
                });             
            }         
        });       
 
        scheduleMicrotasks.offer(new ScheduleMicrotask(){             
            @Override             
            public void run() {                 
                System.out.println("ScheduleMicrotask - C - in MAIN ");                 
                timers.offer(new Timer(){                     
                    @Override                     
                    public void run() {                         
                        System.out.println("timer - event - B - in ScheduleMicrotask - C ");                     
                    }                 
                });             
            }         
        });         
        System.out.println("main end!");         
        processAsync();     
    } 
}

4.4 示例

import 'dart:async';

void main(){
  print("main begin");

  Timer.run(() {
    print("timer - event - A");

    scheduleMicrotask(() {
      print("ScheduleMicrotask - A - in Timer A");
    });

    scheduleMicrotask(() {
      print("ScheduleMicrotask - B - in Timer A");
    });

  });

  scheduleMicrotask(() {
    print("ScheduleMicrotask - C - in MAIN ");

    Timer.run(() {
      print("timer - event - B - in ScheduleMicrotask - C ");
    });
  });

  print("main end");
}

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/356478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用uni-app框架中uni.chooseAddress()接口,获取不到用户收货地址

错误描述 在我们使用uni-app框架或微信原生开发微信小程序时&#xff0c;使用到uni.chooseAddress(OBJECT)接口获取用户收货地址时&#xff0c;无法跳转到收货地址页面获取。 打印接口返回信息&#xff0c;显示 "chooseAddress:fail the api need to be declared in the …

LeetCode-17. 电话号码的字母组合

题目来源 17. 电话号码的字母组合 题目思路 从示例上来说&#xff0c;输入"23"&#xff0c;最直接的想法就是两层for循环遍历了吧&#xff0c;正好把组合的情况都输出了。 如果输入"233"呢&#xff0c;那么就三层for循环&#xff0c;如果"2333"…

接口测试(Fiddler工具)

目录 1.Fiddler是什么&#xff1f; 2.Fiddler的原理 3.Fiddler安装 4.Fiddler界面 4.1.常用工具 4.2 会话列表 4.3 状态栏 4.4 内容显示区 1.Fiddler是什么&#xff1f; Fiddler是客户端与服务器之间的HTTP代理&#xff0c;是当前最常用的HTTP协议抓包工具。 主要功能&a…

NSDT可编程3D场景【兼容Three.js】

NSDT编辑器简化了WebGL 3D应用的开发&#xff0c;完全兼容Three.JS生态。本文介绍如何在自己的应用中嵌入使用NSDT编辑器搭建的3D场景&#xff0c;并通过JS API与场景进行交互。 在自己的应用中嵌入3D场景只需要三个步骤&#xff1a; 在NSDT编辑器中搭建3D场景在自己的前端应…

Nonebot2官网插件nonebot-plugin-chatgpt让自己的QQ聊天机器人不再呆头呆脑

前言 如果你会使用Nonebot2搭建QQ聊天机器人&#xff0c;那么你一定会使用Nonebot官网上插件商店发布的插件&#xff0c;今天这篇博客记录一下使用插件时遇到的错误&#xff0c;最终如何解决的错误。在开始之前先看一下效果图吧&#xff01; 瞬间我们的QQ机器人就高大上了起…

Java serialVersionUID 作用和自动生成设置

一、由来 最近在做一个军工的项目&#xff0c;代码提交后&#xff0c;军方用代码安全扫描工具&#xff0c;对代码进行全局扫描&#xff0c;提示一个漏洞&#xff0c;导致原因是实体类实现了Serializable接口&#xff0c;未对serialVersionUID手动赋值&#xff0c;java机制里&am…

Zero-shot(零次学习)简介

zero-shot基本概念 首先通过一个例子来引入zero-shot的概念。假设我们已知驴子和马的形态特征&#xff0c;又已知老虎和鬣狗都是又相间条纹的动物&#xff0c;熊猫和企鹅是黑白相间的动物&#xff0c;再次的基础上&#xff0c;我们定义斑马是黑白条纹相间的马科动物。不看任何斑…

枚举类的使用方法

一、理解枚举类型 枚举类型是Java 5中新增特性的一部分&#xff0c;它是一种特殊的数据类型&#xff0c;之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束&#xff0c;但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。下面先来看看如何写…

如何用一句话感动测试工程师?产品和技术都这么说!

测试工程师在公司里的地位一言难尽&#xff0c;产品挥斥苍穹&#xff0c;指引产品前路&#xff1b;开发编写代码实现功能&#xff0c;给产品带来瞩目成就。两者&#xff0c;一个是领航员&#xff0c;一个是开拓者&#xff0c;都是聚光灯照耀的对象&#xff0c;唯独团队中的保障…

换脸方法大汇总:生成对抗网络GAN、扩散模型等

1、One-Shot Face Video Re-enactment using Hybrid Latent Spaces of StyleGAN2StyleGAN的高保真人像生成&#xff0c;已逐渐克服了单样本面部视频驱动重现的低分辨率限制&#xff0c;但这些方法至少依赖于以下其中之一&#xff1a;明确的2D/3D先验&#xff0c;基于光流作为运…

Android 基础知识4-2.5View与VIewGroup的概念、关系与区别

1.概念&#xff1a; Android里的图形界面都是由View和ViewGroup以及他们的子类构成的&#xff1a; View&#xff1a;所有可视化控件的父类,提供组件描绘和时间处理方法 ViewGroup&#xff1a; View类的子类&#xff0c;可以拥有子控件,可以看作是容器 Android UI中的控件都是…

Java【七大算法】算法详细图解,一篇文章吃透

文章目录一、排序相关概念二、七大排序1&#xff0c;直接插入排序2&#xff0c;希尔排序3&#xff0c;选择排序4&#xff0c;堆排序5&#xff0c;冒泡排序5.1冒泡排序的优化6&#xff0c;快速排序6.1 快速排序的优化7&#xff0c;归并排序三、排序算法总体分析对比总结提示&…

K8s学习(一)从零开始搭建kubernetes集群环境(虚拟机/kubeadm方式)

文章目录1 Kubernetes简介&#xff08;k8s&#xff09;2 安装实战2.1 主机安装并初始化2.2 安装docker2.3 安装Kubernetes组件2.4 准备集群镜像2.5 集群初始化2.6 安装flannel网络插件3 部署nginx 测试3.1 创建一个nginx服务3.2 暴漏端口3.3 查看服务3.4 测试服务1 Kubernetes简…

centos7安装RabbitMQ

1、查看本机基本信息 查看Linux发行版本 uname -a # Linux VM-0-8-centos 3.10.0-1160.11.1.el7.x86_64 #1 SMP Fri Dec 18 16:34:56 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux cat /etc/redhat-release # CentOS Linux release 7.9.2009 (Core)2、创建创建工作目录 mkdir /…

下一代视频编码技术2023

下一代视频编码技术 下面将从这两个角度来介绍华为云视频在下一代视频编码技术上的一些工作。这些技术得益于华为2012 媒体技术院全力支持。 2.1 下一代视频编码标准技术 从上图可以看出&#xff0c;下一代的视频编码标准大概分为三个阵营或者三个类型&#xff1a; 国际标准…

卷积神经网络(CNN)经典模型分析(一)

CNN经典模型分析&#x1f42c; 目录: 一、CNN概论二、model分析LeNet5AlexNetVggNetGoogleNetResNet 三、参考资料 一、CNN概论 如图所示&#xff1a;人工智能最大&#xff0c;此概念也最先问世&#xff1b;然后是机器学习&#xff0c;出现的稍晚&#xff1b;最后才是深度学习…

Python编程自动化办公案例(2)

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.前期代码 二.实现批量读取 1.os库 2.实现思路 &#xff08;1&#…

VBA提高篇_ 22 事件处理

文章目录1.事件编程2.常用工作簿事件名称与对应处理过程名称示例3. 事件编程的步骤4&#xff0e;工作簿事件4.1 Open4.2 BeforeClose4.3 NewSheet5&#xff0e;工作表事件6&#xff0e;变量和过程函数的作用域1.事件编程 写在事件发生地(对应工作簿或工作表) 2.常用工作簿事…

22 pandas字符串操作

文章目录字符串对象方法cat和指定字符串进行拼接查看数据不指定参数&#xff0c;所有姓名拼接不指定参数&#xff0c;所有姓名拼接添加分隔符添加数据遇到空值时合并split按照指定字符串分隔partition 按照指定字符串分割get 获取指定位置的字符&#xff0c;只能获取1个slice 获…

LeetCode题目笔记——6362. 合并两个二维数组 - 求和法

文章目录题目描述题目链接题目难度——简单方法一&#xff1a;常规双指针遍历代码/Python方法二&#xff1a;字典\哈希表代码/Python总结题目描述 给你两个 二维 整数数组 nums1 和 nums2. nums1[i] [idi, vali] 表示编号为 idi 的数字对应的值等于 vali 。nums2[i] [idi, …