《WebKit 技术内幕》学习之九(2): JavaScript引擎

2 V8引擎

2.1 基础

        V8是一个开源项目,也是一个JavaScript引擎的实现。它最开始是由一些语言方面的专家设计出来的,后被Google收购,成为了JavaScript引擎和众多相关技术的引领者。其目的很简单,就是为了提高性能。因为在当时之前的JavaScriptCore引擎和其他的JavaScript引擎的性能都不能令人非常满意。为了提高JavaScript代码的执行效率从而获得更好的网页浏览效果,它甚至采用直接将JavaScript编译成本地代码的方式。

        V8支持众多的操作系统,包括但是不限于Windows、Linux、Android、Mac OS X等。同时它也能够支持众多的硬件架构,例如IA32、X64、ARM、MIPS等。这么看来,它将主流软硬件平台一网打尽,由于它是一个开源项目,开发者可以自由使用它的强大能力,一个例子就是目前炙手可热的NodeJs项目,它就是基于V8项目研发的。开源的好处就是大家可以很方便地学习、贡献和使用,就让我们首先从它的代码结构开始。

2.1.1 代码结构

        V8的代码量超过50万行,应该也算一个中型的项目,但是它的代码结构其实非常的简单,如图9-9所示。对于想了解V8的读者来说,建议将目光主要放在两个主要目录“include”和“src”中,它们一个是包含了V8对外的接口,一个是包含了V8内部的实现,其他都是一些辅助的工具和与测试相关的设施。图9-9中只列出了一些主要目录和文件,以及它们的介绍,对于其他内容,读者可以自行参阅源代码加以理解。

                                        图9-9 V8项目的代码结构

2.1.2 应用程序编程接口(API)

        要了解V8的内部工作机制或者说原理,有必要先了解V8所提供的应用编程接口,它们在V8代码目录的include/V8.h中,通过接口中的某些类可以一窥V8的工作方式,其中一些主要的类如下。

  • 各种各样的基础类 :这里面包含对象引用类(如WeakReferenceCallbacks)、基本数据类型类(如Int32、Integer、Number、String、StringObject)和JavaScript对象(Object)。这些都是基础抽象类,没有包含实际的实现,真正的实现在“src”目录中的“objects.h/cc”中。
  • VaIue :所有JavaScript数据和对象的基类,如上面的Integer、Number、String等。
  • V8数据的句柄类 :以上数据类型的对象在V8中有不同的生命周期,需要使用句柄来描述它们的生命周期,以及垃圾回收器如何使用句柄来管理这些数据,句柄类包括Local、Persistent和Handle。
  • IsoIate :这个类表示的是一个V8引擎实例,包括相关状态信息、堆等,总之这是一个能够执行JavaScript代码的类,它不能被多个线程同时访问,所以,如果非要这么做的话,需要使用锁。V8使用者可以使用创建多个该类的实例,但是每个实例之间就像这个类的名字一样,都是孤立的。
  • Context :执行上下文,包含内置的对象和方法,如print方法等,还包括JavaScript内置的库,如math等。
  • Extension :V8的扩展类。用于扩展JavaScript接口,V8使用者基于该类来实现相应接口,被V8引擎调用。
  • Handle :句柄类,主要用来管理基础数据和对象,以便被垃圾回收器操作。主要有两个类型,一个是Local(就是Local类,继承自Handle类),另一个是Persistent(Persistent类,继承自Handle类)。前者表示本地栈上的数据,所以量级比较轻,后者表示函数间的数据和对象访问。
  • Script :用于表示被编译过的JavaScript源代码,V8的内部表示。
  • HandleScope :包含一组Handle的容器类,帮助一次性删除这些Handle,避免重复调用。
  • FunctionTemplate :绑定C++函数到JavaScript,函数模板的一个例子就是将JavaScript接口的C++实现绑定到JavaScript引擎。
  • ObjectTemplate :绑定C++对象到JavaScript,对象模板的典型应用是Chromium中将DOM节点通过该模板包装成JavaScript对象。

        读者看到这里,可能对一些类的描述不是很理解,这是因为缺少对V8中一些基本概念的认识,希望后面的解释能够帮助到你。下面通过一个例子来了解V8使用者是如何调用这些接口以使用V8引擎来执行JavaScript代码的。

2.1.3 接口使用示例

        通过调用这些编程接口和对应的内存管理方式,希望读者能够初步理解V8的工作方式。图9-10是来自V8项目官方网站上的图片,主要描述使用V8的基本流程和这些接口对应的内存管理方式。

                图9-10 调用V8编程接口的例子和对应的内存管理方式

        图中没有描述如何创建一个Isolate对象,此对象可以通过Isolate::GetCurrent()来获取,它会创建一个V8引擎示例,后面的操作都是在它提供的环境中来进行的。

        图中第一条语句表示建立一个域,前面也介绍了,用于包含一组Handle对象,便于释放它们。

        图中第二条语句根据Isolate对象来获取一个Context对象,使用Handle来管理。Handle对象本身存放在栈上,而实际的Context对象保存在堆中。

        图中第三条语句根据两个对象Isolate和Context来创建一个函数间使用的对象,所以使用Persistent类来管理,这里展示的是它的用处和含义,在本例中不是必需的。读者可以看到它的句柄和数据都单独存储在另外的地方。

        图中第四条表示为Context对象创建一个基于栈的域,下面的执行步骤都是在该域中对应的上下文中来进行的。

        图中第五条是从命令行参数读入JavaScript代码,也就是一段字符串。

        图中第六条将代码字符串编译成V8的内部表示,并保存成一个Script对象。

        图中第七条是执行编译后的内部表示,然后获得生成的结果。

        一个典型的使用V8编程接口的例子就是V8项目提供的D8工具。它通过V8的接口来实现一个可执行程序,因为V8本身只是一个C++库而已。该可执行程序能够帮助V8的使用者做各种基础测试和分析,能够读入JavaScript文件并输出结果,以及提供调试JavaScript的基础能力。

2.2 工作原理

2.2.1 数据表示

        大家知道在JavaScript语言中,只有基本数据类型Boolean、Number、String、Null和Undefined,其他数据都是对象,V8使用一种特殊的方式来表示它们。

        在V8中,数据的表示分成两个部分,第一部分是数据的实际内容,它们是变长的,而且内容的类型也不一样,如String、对象等。第二个部分是数据的句柄,句柄的大小是固定的,句柄中包含指向数据的指针。为什么会是这种设计呢?主要是因为V8需要进行垃圾回收,并需要移动这些数据内容,如果直接使用指针的话就会出问题或者需要比较大的开销,使用句柄的话就不存在这些问题,只需要将句柄中的指针修改即可,使用者使用的还是句柄,它本身没有发生变化。

        除了极少数的数据例如整形数据,其他的内容都是从堆中申请内存来存储它们,这是因为Handle本身能够存储整形,同时也为了快速访问。而对于其他类型,受限于Handle的大小和变长等原因,都存储在堆中。

        下面我们来看一看句柄是如何区分这些类型的。图9-11描述了句柄在32位和64位机器上的表示方式。

                        图9-11 Handle类的定义,小整数和其他类型表示

        由上面Handle的定义可以看出,一个Handle对象的大小是4字节(32位机器)或者8字节(64位机器),这一点不同于JavaScriptCore引擎,后者是使用8个字节来表示数据的句柄。整数(小整数,因为只有31位能表示)直接从value_中获取值,而无须从堆中分配,然后使用一个指针指向它,这可以减少内存的使用并增加数据的访问速度。而对于其他类型,则使用一个指针来指向它在堆中的数据。

        因为堆中存放的对象都是4字节对齐的,所以指向它们的指针的最后两位都是00,所以这两位其实是不需要的。在V8中,它们被用来表示句柄中包含数据的类型。

        JavaScript对象的实现在V8中包含3个成员,正如图9-12中所描述的那样,第一个是隐藏类的指针,这是V8为JavaScript对象创建的隐藏类。第二个指向这个对象包含的属性值。第三个指向这个对象包含的元素。

                                图9-12 JavaScript对象内部表示

2.2.2 V8工作过程

         根据前面的介绍,我们对于V8工作的整个过程应该有了一个大概的理解,该过程包括两个阶段,第一是编译,第二是运行。只不过鉴于JavaScript语言的工作方式,它们都是在用户使用它们的时候发生。同时,V8中还有一个非常重要的特点就是延迟(deferred)思想,这使得很多JavaScript代码的编译直到运行的时候被调用到才会发生,这样可以减少时间开销。

        首先来看编译阶段。读者应该了解JavaScript引擎是如何将源代码解释执行或者转化为本地代码的。同JavaScriptCore引擎比较,V8引擎有自己特殊的地方,如图9-13所示为从源代码到最后本地代码的过程。

                                图9-13 V8引擎处理源代码到本地代码的过程

        从图中可以看出,首先它也是将源代码转变成抽象语法树的,这一点同JavaScriptCore引擎一样,之后两个引擎开始分道扬镳。不同于JavaScriptCore引擎,V8引擎并不将抽象语法树转变成字节码或者其他中间表示,而是通过JIT编译器的全代码生成器(full code generator)从抽象语法树直接生成本地代码,所以没有像Java一样的虚拟机或者字节码解释器。这样做的原因,主要是因为减少抽象语法树到字节码的转换时间,这一切都在网页加载时完成,虽然可以提高优化的可能,但是这些分析可能带来巨大的时间浪费。当然,缺点也很明显,至少包括两点:第一是在某些JavaScript使用场景其实使用解释器更为合适,因为没有必要生成本地代码;第二是没有中间表示会减少优化的机会,因为缺少一个中间表示层。至于有些文章说的丧失了移植性,个人觉得对于JavaScript这种语言来说不是问题,因为并没有将JavaScript代码先编译然后再运行的明显两个阶段分开的用法,例如像Java语言那样。但是,针对V8设计思想来说,笔者认为它的理念比较先进,做法虽然比较激进,但是确实给JavaScript引擎设计者们带来很多新思路。

        下面来看一看V8引擎编译JavaScript生成本地代码(也称为JIT编译)使用了哪些主要的类和过程。图9-14给出了主要的类,下面逐一来分析它们。

  • Script :表示是JavaScript代码,既包含源代码,又包含编译之后生成的本地代码,所以它既是编译入口,又是运行入口。
  • Compiler :编译器类,辅助Script类来编译生成代码,它主要起一个协调者的作用,会调用解释器(Parser)来生成抽象语法树和全代码生成器,来为抽象语法树生成本地代码。
  • Parser :将源代码解释并构建成抽象语法树,使用AstNodeFactory类来创建它们,并使用Zone类来分配内存,这个在后面内存管理中介绍。
  • AstNode :抽象语法树节点类,是其他所有节点的基类,它包含非常多的子类,后面会针对不同的子类生成不同的本地代码。
  • AstVisitor :抽象语法树的访问者类,基于著名的设计模式Visitor来设计,主要用来遍历异构的抽象语法树。
  • FullCodeGenerator :AstVisitor类的子类,通过遍历抽象语法树来为JavaScript生成本地可执行的代码。

                                        图9-14 V8编译器涉及的主要类

        根据上面类的描述,我们大致可以描绘出这样一个编译JavaScript代码的过程:Script类调用Compiler类的Compile函数为其生成本地代码。在该函数中,第一,它使用Parser类来生成抽象语法树;第二,使用FullCodeGenerator类来生成本地代码。根据前面描述的延迟编译的思想,事实上,JavaScript中的很多函数是没有被编译生成本地代码的。因为JavaScript代码编译之前需要构建一个运行环境,所以实际上在编译之前,V8引擎会构建众多全局对象并加载一些内置的库,如math库等。

        对于编译器的全代码生成器来说,因为本地代码跟具体的硬件平台密切相关,所以它使用多个后端来生成实际的代码,如图9-15所示的过程。V8引擎至少包含四个跟平台相关的后端,用于生成不同平台上的本地汇编代码。

                                        图9-15 V8代码生成器生成本地代码

        代码生成器在不同的平台上有不同的实现。例如在IA32平台,读者会发现代码生成器中的函数是根据该平台的需求而实现的。对于ARM平台,同样有自己的实现。

        当代码生成器遍历AST树的时候,FullCodeGenerator会为每个节点生成相应的汇编代码,不过没有了全局的视图,因此没有为节点之间考虑可能的优化。在不同的平台上,FullCodeGenerator的很多函数有不同的实现,它们在full-codegen-ia32.cc、full-codegen-x64.cc、full-codegen-arm.cc和full-codegen-mips.cc文件中分别作了不同的实现。

        图9-13中,V8在生成本地代码之后,为了性能考虑,通过数据分析器(Profiler)来采集一些信息,以帮助决策哪些本地代码需要优化,以生成效率更高的本地代码,这是一个逐步改进的过程。同时,V8中还有一种机制,也就是当发现优化后的代码性能其实并没有提高甚至还有所降低,那么V8能够退回到原来的代码,这些都是在运行阶段涉及到的技术。

        下面来看一下代码的运行阶段。首先依然是运行阶段的主要类,图9-16描述了V8支持JavaScript代码运行的主要类。

                              图9-16 V8引擎运行JavaScript代码的主要类

  • Script :这个前面已经介绍过,包含编译之后生成的本地代码,运行代码的入口。
  • Execution :运行代码的辅助类包含一些重要的函数,例如“Call”函数,它辅助进入和执行Script中的本地代码。
  • JSFunction :需要执行的JavaScript函数表示类。
  • Runtime :运行这些本地代码的辅助类,它的功能主要是提供运行时各种各样的辅助函数,包括但是不限于属性访问、类型转换、编译、算术、位操作、比较、正则表达式等。
  • Heap :运行本地代码需要使用内存堆,堆的内部构成和结构相当复杂,这个在后面的内存管理中会介绍。
  • MarkCompactCollector :垃圾回收机制的主要实现类,用来标记(Mark)、清除(Sweep)和整理(Compact)等基本的垃圾回收过程。
  • SweeperThread :负责垃圾回收的线程。

        结合这些类,V8引擎是按照图9-17中描述的过程来执行的。当然实际上的过程更为复杂,而且还有垃圾回收等处理,下面主要描述了几个基本的可能会被调用的函数。

        调用发生在图中的三个子阶段。第一就是延迟编译,也就是“CompileLazy”这个函数的调用,根据需要编译和生成这些本地代码的时候,实际上也是在使用编译阶段那些类和操作。这一思想同样被广泛应用在WebKit和Chromium项目中。在V8中,函数是一个基本单位。当某个JavaScript函数被调用的时候,属于该函数的本地代码就会生成。具体工作的方式是V8查找该函数是否已经生成本地代码,如果已经生成,那么直接调用该函数。否则,V8引擎会触发生成本地代码,目的当然是节约时间,减少去处理那些使用不到的代码的时间。第二就是图9-17中的1.2.3,这时执行编译后的代码就是为JavaScript构建JS对象,这需要Runtime类来辅助创建对象,并需要从Heap类分配内存。第三就是图9-17中的1.2.4,此阶段需要借助Runtime类中的辅助函数来完成一些功能,如属性访问、类型转换等。

                                图9-17 V8引擎中的代码执行过程

        因为V8是基于抽象语法树直接生成本地代码,没有中间表示层(字节码),所以很多时候代码没有经过很好的优化。关于JavaScript引擎的性能之争非常激烈,没有经过优化的代码导致该引擎在性能上没有特别大的突破,而其他引擎都在进步。有鉴于此,在2010年,V8引入了新的编译器,这就是Crankshaft编译器,它主要针对那些热点函数进行优化。该编译器基于JavaScript源代码开始分析,而不是本地代码,同时构建Hydrogen图并基于此来进行优化分析,Hydrogen图包括超过132条指令。鉴于它的复杂性,这里不再详细介绍,有兴趣的读者请自行探索。

2.2.3 优化回滚(Deoptimization)

        前面提到V8引擎为了性能上的优化,引入了更为高效的Crankshaft编译器。但是为了性能考虑,该编译器通常会做比较乐观和大胆的预测,那就是编译器认为这些代码比较稳定,变量类型不会发生改变,所以能够生成高效的本地代码。当然这是理想情况,现实是引擎会发现一些变量的类型已经发生变化。在这种情况下,V8使用一种机制来将它做的这些错误决定回滚到之前的一般情况,这个过程称为优化回滚。

        下面举个例子来说明为什么会出现这种情况吧。示例代码9-5介绍了其中一种情况,函数ABC被调用很多次之后,V8引擎可能会触发Crankshaft编译器来生成优化的代码,优化的代码认为示例代码的类型等信息都已经被获知了。但事实上,到目前为止,我们对于代码中的unknown变量的类型还一无所知,在这种情况下,V8只能将该段代码回滚到一个通用的状态。

示例代码9-5 会触发优化回滚的代码示例

     var counter = 0;function ABC(x, y) {counter++;if (counter < 10000000) {// do sthreturn 123;}var unknown = new Date();print(unknown);}

        优化回滚是一个很费时的操作,所以能够不回滚,肯定不要回滚,而且回滚会将之前优化的代码恢复到一个没有经过特别优化的代码,这是一个非常不高效的过程,写代码的时候要特别注意尽量不要触发这一过程。

2.2.4 隐藏类和内嵌缓存

        虽然JavaScript语言中没有类型的定义,那么借助于C++类的思想,是不是也能够为JavaScript的对象构建类型信息呢?当然可以,至少部分可以。V8使用类和偏移位置思想,将本来需要通过字符串匹配来查找属性值的算法改进为使用类似C++编译器的偏移位置的机制来实现,这就是隐藏类(Hidden Class)。隐藏类将对象划分成不同的组,对于相同的组,也就是该组内的对象拥有相同的属性名和属性值的情况,将这些属性名和对应的偏移位置保存在一个隐藏类中,组内的所有对象共享该信息。同时,也可以识别属性不同的对象。

        这听起来可能比较抽象,所以使用如图9-18这样的例子来加以说明。图中这一解释来自于V8的官方文档说明,下面将逐一解释它们。

                                图9-18 JavaScript对象归类和隐藏类

        因为JavaScript没有办法定义类型,所以图9-18中左半部分使用函数来定义。同时,创建了两个对象——a和b。这两个对象包含相同的属性名,在V8中,它们被归为同一个组,也就是隐藏类,这些属性在隐藏类中有相同的偏移值。这样,对象a和b可以共享这个类型信息,当访问这些对象属性的时候,根据隐藏类的偏移值就可以知道它们的位置并进行访问。因为JavaScript是动态类型语言,所以假如在上述代码之后,加入下面的代码:b.z = 2。那么,b所对应的将是一个新的隐藏类,这样a和b将属于不同的组。

        在理解了V8的隐藏类之后,下面了解一下代码是如何使用这些隐藏类来高效访问对象的属性的。以这段简单代码为例来说明:function add(a) { return a.x; }。首先看最基本的情况,访问对象属性的过程是这样的:首先获取隐藏类的地址,然后根据属性名查找偏移值,计算该属性的地址。不过,这一过程比较费时间。实际上的情况可能要好很多,因为很多情况下该函数中的参数a可能是同一种类型,那么是否能够使用缓存机制呢?

        是的,该缓存机制叫做内嵌缓存(Inline Cache),它可以避免方法和属性被存取的时候出现的因哈希表查找而带来的问题。该机制的基本思想是将使用之前查找的结果缓存起来,也就是说V8可以将之前查找的隐藏类和偏移值保存下来。当下次查找的时候,首先比较当前对象是否也是之前的隐藏类,如果是的话,可以直接使用之前缓存的偏移值,从而减少查找表的时间。

        当然,如果该函数中的对象a出现多个类型,那么缓存失误的机率就会高很多。当出现缓存失误的时候,V8可以按照上面说的,退回到之前的方式来查找哈希表。但是因为效率问题,V8会在缓存失败之后,通过对象a的隐藏类来查找该类有无一段代码,这段代码可以快速查找对象,其实就如示例代码9-6所示,这段代码就是保存在a对象的隐藏类对应的表中,所以如果该段代码已经生成,就同样可以较快地实现属性值的查找。

示例代码9-6 使用内嵌缓存机制的属性值访问代码示例

    if (a->hiddenClass() == cachedClass) {return a->properties[cachedOffset];} else {… //退回到原来的方法}
2.2.5 内存管理

        V8的内存管理部分主要讲两点,第一是V8内存的划分,第二是V8对于JavaScript代码的垃圾回收机制。

        对于内存的划分,首先看Zone类,它的特点主要是管理一系列的小块内存。如果用户想使用一系列的小内存,并且这些小内存的生命周期类似,这时可以使用一个Zone对象,这些小内存都是从Zone对象中申请的。Zone对象首先自己申请一块内存,然后管理和分配一些小内存。当一块小内存被分配之后,不能够被Zone回收,只能一次性回收Zone分配的所有小块内存。例如抽象语法树的内存分配和使用,在构建抽象语法树之后,会生成本地代码,然后抽象语法树的内存在这之后被一次性全部收回,效率非常高。但是,该机制有一个非常严重的缺陷,那就是假如这一个过程需要很多的内存,那么Zone就需要为系统分配大量的内存,但是又不能够释放,所以这会导致系统出现需要过多的内存而导致内存不够的情况。

        其次是堆。V8使用堆来管理JavaScript使用的数据,以及生成的代码、哈希表等,为了更方便地实现垃圾回收,同很多虚拟机一样,V8将堆分成三个部分,第一个是年轻分代,第二个是年老分代,其中还分成多个子部分,第三个是为大对象保留的空间。图9-19分别描述了这三个部分。

                                        图9-19 V8中堆的划分

        对于年轻分代,主要是为新创建的对象分配内存空间,因为年轻分代中的对象较容易被要求回收,为了方便垃圾回收,可以使用复制方式,将年轻分代分成两半,一半用来分配,另外一半在回收的时候负责将之前还需要保留的对象复制过来。对于年轻分代,经常需要进行垃圾回收。而对于年老分代,主要是根据需要将年老的对象、指针、代码等数据使用的内存较少地做垃圾回收。而对于大对象空间,主要是用来为那些需要使用较多内存的大对象分配内存,当然同样可能包含数据和代码等分配的内存,需要注意的是每个页面只分配一个对象。

        对于垃圾回收,因为使用了分代和大数据的内存分配,V8需要使用精简整理的算法,用来标记那些还被引用的对象,然后消除那些没有被标记的对象,最后整理和压缩(Compact)那些还需要保存的对象。在目前的虚拟机中,垃圾回收机制已经发展得越来越先进,我们有理由相信,V8将引入更多的垃圾回收优化算法,如并发机制等,以后可以使用并发标记、并发内存回收等。其中一些技术已经被实现,之后还会有更多技术被引入。

2.2.6 快照(Snapshot)

        前面介绍到,在V8引擎开始启动的时候,需要加载很多内置的全局对象,同时也要建立内置的函数,如Array、String、Math等。为了让引擎更加整洁,加载对象与建立函数等任务都是使用JS文件来实现的,V8引擎负责提供机制来支持,就是在编译和执行输入的JavaScript代码之前,先加载它们。

        根据前面的介绍,V8引擎需要编译和执行这些内置的JS代码,同时使用堆等来保存执行过程中创建的对象、代码等,这些都需要较多的时间。为此,V8引入了快照机制。

        快照机制就是将这些内置的对象和函数加载之后的内存保存并序列化。序列化之后的结果很容易被反序列化,经过快照机制的启动时间,可以缩减几毫秒。在编译的时候打开选项“snapshot=on”就可以让V8支持快照机制。在V8中,mksnapshot工具能够帮助生成快照。

        快照机制同样也能够将一些开发者认为需要的JS文件序列化,以减少以后处理的时间,不过快照机制有一个非常明显的缺点,那就是这些代码没有办法被CrankShaft这样的优化编译器优化,所以存在性能上的问题,原因读者可以仔细思考一下。

2.3 绑定和扩展

        很多时候,JavaScript引擎所提供的能力不能满足现实的需求,比如引擎本身没有HTML5的众多能力(如地理信息),这时,引擎使用者需要扩展它的能力。同很多其他的JavaScript引擎一样,V8可以提供扩展引擎的能力,如前面所述,当V8被使用在Chromium中时,它就使用V8的绑定机制来扩展DOM的实现。

        V8提供两种机制,第一是Extension机制,就是通过V8提供的基类Extension来达到扩展JavaScript能力的目的。第二是绑定,就是使用IDL文件或者接口文件来生成绑定文件,然后将这些文件同V8引擎的代码一起编译。这两种机制在第10章中会被详细介绍。

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

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

相关文章

【学习】focal loss 损失函数

focal loss用于解决正负样本的不均衡情况 通常我们需要预测的正样本要少于负样本&#xff0c;正负样本分布不均衡会带来什么影响&#xff1f;主要是两个方面。 样本不均衡的话&#xff0c;训练是低效不充分的。因为困难的正样本数量较少&#xff0c;大部分时间都在学习没有用…

216. 组合总和 III - 力扣(LeetCode)

题目描述 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次&#xff0c;组合可以以任何顺序返回。 输入示例 k 3, n 7输出示例 [[1,2,…

深入理解Kubernetes探针和.NET服务健康检查机制

前言 随着越来越多的软件采用云原生和微服务架构&#xff0c;我们面临着更多的技术挑战&#xff0c;比如&#xff1a; Kubernetes如何在容器服务异常终止、死锁等情况下&#xff0c;发现并自动重启服务&#xff1b;当服务依赖的关键服务&#xff08;例如数据库&#xff0c;Red…

Dockerfile-xxxx

1、Dockerfile-server FROM openjdk:8-jdk-alpine WORKDIR /app COPY . . CMD java -Xms1536M -Xmx1536M -XX:UseG1GC -jar -Dlog4j2.formatMsgNoLookupstrue -Dloader.pathresources,lib -Duser.timezoneGMT-05 /app/server-main-1.0.0.jar 2、Dockerfile-bgd #FROM openjdk…

8. UE5 RPG创建UI(上)

UI是显示角色的一部分属性玩家可以直接查看的界面&#xff0c;通过直观的形式在屏幕上显示角色的各种信息。如何使用一种可扩展&#xff0c;可维护的形式来制作&#xff0c;这不得不说到耳熟能详的MVC架构。 MVC&#xff08;Model-View-Controller&#xff09;是一种常见的软件…

如何在Linux上部署Nexus私服

如何在Linux上部署Nexus私服 Nexus 是一个强大的仓库管理解决方案&#xff0c;由Sonatype公司开发。它主要用于软件开发中各种依赖包和构件的存储、管理和分发。 1、为什么要部署nexus&#xff1f; 统一管理依赖&#xff1a;在软件开发过程中&#xff0c;项目通常会依赖大量的…

[Linux 杂货铺] —— 权限(文件权限和目录配置)

目录 &#x1f308;前言 &#x1f4c1; 文件的属性 &#x1f4c1; 权限的概念 &#x1f4c2;拥有者和所属组&#xff08;角色&#xff09;&#xff1a; &#x1f4c2;用户&#xff08;具体的人&#xff09;&#xff1a; &#x1f4c1; 权限的管理 &#x1f4c2;1. chmod…

如何让亚马逊,速卖通,美客多店铺排名和流量稳定爬升

一、关键词优化 关键词是亚马逊店铺排名的关键。通过合理的关键词优化&#xff0c;可以提高店铺的曝光率。卖家需要研究消费者的搜索习惯和行为&#xff0c;了解他们使用哪些关键词进行搜索&#xff0c;然后将这些关键词用于商品描述、标题和元数据中。此外&#xff0c;还可以…

Linux - 安装字体库解决乱码问题

文章目录 问题描述步骤资源 问题描述 该安装方法&#xff0c;不区分中文和英文字体 Java在linux上转word文档为pdf&#xff0c; linux的字体缺失&#xff0c;导致了转出的pdf为乱码。 ● Linux将word转为pdf后出现乱码&#xff1f; ● 在linux上将word转为pdf 是乱码 ● 在lin…

第一篇【传奇开心果短博文系列】Python的库OpenCV技术点案例示例:cv2常用功能和方法

传奇开心果短博文系列 短博文系列目录Python的库OpenCV技术点案例示例系列 短博文目录一、前言二、常用功能和方法示例三、归纳总结 短博文系列目录 Python的库OpenCV技术点案例示例系列 短博文目录 一、前言 cv2是Python中常用的第三方库&#xff0c;也称为OpenCV库&#…

在全志H616核桃派上实现USB摄像头的OpenCV颜色检测

在给核桃派开发板用OpenCV读取图像并显示到pyqt5的窗口上并加入颜色检测功能&#xff0c;尝试将图像中所有蓝色的东西都用一个框标记出来。 颜色检测核心api 按照惯例&#xff0c;先要介绍一下opencv中常用的hsv像素格式。颜色还是那个颜色&#xff0c;只是描述颜色用的参数变…

【C语言编程之旅 7】刷题篇-函数

第1题 解析 A&#xff1a;错误&#xff0c;一个函数只能返回一个结果 B&#xff1a;正确&#xff0c;将形参存在数组中&#xff0c;修改数组中内容&#xff0c;可以通过数组将修改结果带出去 C&#xff1a;正确&#xff0c;形参如果用指针&#xff0c;最终指向的是外部的实参…

SpringBoot3.1.7集成Kafka和Kafka安装

一、背景 我们在很多系统开发都需要用到消息中间件&#xff0c;目前来说Kafka凭借其优秀的性能&#xff0c;使得它的使用率已经是名列前茅了&#xff0c;所以今天我们将它应用到我们的系统 二、版本选择 在使用一个中间件一定要考虑版本的兼容性&#xff0c;否则后面会遇到很…

[足式机器人]Part2 Dr. CAN学习笔记- 最优控制Optimal Control Ch07-3 线性二次型调节器(LQR)

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - 最优控制Optimal Control Ch07-3 线性二次型调节器&#xff08;LQR&#xff09; 1. 数学推导2. 案例反洗与代码详解 1. 数学推导 2. 案例反洗与代码详解

Go 的 Http 请求系统指南

文章目录 快速体验请求方法URL参数响应信息BodyStatusCodeHeaderEncoding 图片下载定制请求头复杂的POST请求表单提交提交文件 CookieClient 上设置 Cookie请求上设置 Cookie 重定向和请求历史超时设置总超时连接超时读取超时 请求代理错误处理总结 前几天在 “知乎想法” 谈到…

【力扣】记录一下竞赛分上 Knight

记录一下力扣上 Knight 力扣的题还是相对来说比较简单的&#xff0c;前两个月写的题多一点&#xff0c;后面几乎都是只做了每日一题&#xff0c;感觉正常来说刷个两三个月的题水平就差不多够了&#xff0c;甚至在我才刷半个月的时候就可以做三题了&#xff0c;排名和现在差不多…

自然语言处理的崛起:从初步分析到深度理解

自然语言处理&#xff08;NLP&#xff09;是计算机科学、人工智能和语言学的交叉领域&#xff0c;旨在让计算机能够理解和生成人类语言。随着时间的推移&#xff0c;NLP 经历了一系列革命性的变化&#xff0c;从简单的规则和模式匹配到如今的深度学习模型&#xff0c;它们使计算…

宝塔面板SRS音视频TRC服务器启动失败

首先&#xff0c;查找原因 1.先看srs服务在哪 find / -type f -name srs 2>/dev/null运行结果&#xff1a; /var/lib/docker/overlay2/5347867cc0ffed43f1ae24eba609637bfa3cc7cf5f8c660976d2286fa6a88d2b/diff/usr/local/srs/objs/srs /var/lib/docker/overlay2/5347867…

STL——vector模拟实现

在学习过程种 &#xff0c;学习看源码是很有必要的&#xff0c;它可以让你更清楚的知道一些代码的细节&#xff0c;你也会发现源码将效率利用到了极致&#xff0c;不得不佩服写出源码的人。 下面我将配合源码来实现一个简单的vector&#xff0c;下面请看源码 看源码我们首先要…

React16源码: React中的completeUnitOfWork的源码实现

completeUnitOfWork 1 &#xff09;概述 各种不同类型组件的一个更新过程对应的是在执行 performUnitOfWork 里面的 beginWork 阶段它是去向下遍历一棵 fiber 树的一侧的子节点&#xff0c;然后遍历到叶子节点为止&#xff0c;以及 return 自己 child 的这种方式在 performUni…