当前位置: 首页 > news >正文

【计组】理解Disruptor--《计算机组成原理》(十五)

Disruptor 的开发语言,并不是很多人心目中最容易做到性能极限的 C/C++,而是性能受限于 JVM 的 Java。其实只要通晓硬件层面的原理,即使是像 Java 这样的高级语言,也能够把 CPU 的性能发挥到极限。

一、Padding Cache Line,体验高速缓存的威力

Disruptor 里面一段神奇的代码。这段代码里,Disruptor 在 RingBufferPad 这个类里面定义了 p1,p2 一直到 p7 这样 7 个 long 类型的变量。这些变量没有实际意义,只是帮助我们进行缓存行填充(Padding Cache Line),使得我们能够尽可能地用上 CPU 高速缓存(CPU Cache)。

abstract class RingBufferPad
{protected long p1, p2, p3, p4, p5, p6, p7;
}

CPU Cache 装载内存里面的数据,不是一个一个字段加载的,而是加载一整个缓存行。举个例子,如果我们定义了一个长度为 64 的 long 类型的数组。那么数据从内存加载到 CPU Cache 里面的时候,会一次性加载固定长度的一个缓存行。

我们现在的 64 位 Intel CPU 的计算机,缓存行通常是 64 个字节(Bytes)。一个 long 类型的数据需要 8 个字节,所以一下子会加载 8 个 long 类型的数据。也就是说,一次加载数组里面连续的 8 个数值。这样的加载方式可以加快遍历数组元素时的速度。因为后面连续 7 次的数据访问都会命中缓存,不需要重新从内存里面去读取数据。

但是,不使用数组,而是使用单独的变量的时候,这里就会出现问题了。在 Disruptor 的 RingBuffer(环形缓冲区)的代码里面,定义了一个 RingBufferFields 类,里面有 indexMask 和其他几个变量,用来存放 RingBuffer 的内部状态信息。

CPU 在加载数据的时候,自然也会把这个数据从内存加载到高速缓存里面来。不过,这个时候,高速缓存里面除了这个数据,还会加载这个数据前后定义的其他变量。问题就来了,Disruptor 是一个多线程的服务器框架,在这个数据前后定义的其他变量,可能会被多个不同的线程去更新数据、读取数据。这些写入以及读取的请求,会来自于不同的 CPU Core。于是,为了保证数据的同步更新,我们不得不把 CPU Cache 里面的数据,重新写回到内存里面去或者重新从内存里面加载数据。

CPU Cache 的写回和加载,都不是以一个变量作为单位的。这些动作都是以整个 Cache Line 作为单位的。所以,当 INITIAL_CURSOR_VALUE 前后的那些变量被写回到内存的时候,这个字段自己也写回到了内存,这个常量的缓存也就失效了。要再次读取这个值的时候,还需重新从内存读取。这也就意味着,读取速度大大变慢了。

面临这样一个情况,Disruptor 里有一种神奇的代码技巧,就是缓存行填充。Disruptor 在 RingBufferFields 里面定义的变量的前后,分别定义了 7 个 long 类型的变量。前面的 7 个来自继承的 RingBufferPad 类,后面的 7 个则是直接定义在 RingBuffer 类里面。这 14 个变量没有任何实际的用途。我们既不会去读他们,也不会去写他们。

......abstract class RingBufferPad
{protected long p1, p2, p3, p4, p5, p6, p7;
}abstract class RingBufferFields<E> extends RingBufferPad
{......    private final long indexMask;private final Object[] entries;protected final int bufferSize;protected final Sequencer sequencer;......    
}public final class RingBuffer<E> extends RingBufferFields<E> implements Cursored, EventSequencer<E>, EventSink<E>
{......    protected long p1, p2, p3, p4, p5, p6, p7;......
}

 RingBufferFields 里面定义的这些变量都是 final 的,第一次写入之后不会再进行修改。所以,一旦它被加载到 CPU Cache 之后,只要被频繁地读取访问,就不会再被换出 Cache 了。这也就意味着,对于这个值的读取速度,会是一直是 CPU Cache 的访问速度,而不是内存的访问速度。

二、使用 RingBuffer,利用缓存和分支预测

利用 CPU Cache 性能的思路,贯穿了整个 Disruptor。Disruptor 整个框架,其实就是一个高速的生产者 - 消费者模型(Producer-Consumer)下的队列。生产者不停地往队列里面生产新的需要处理的任务,而消费者不停地从队列里面处理掉这些任务。

实现一个队列,最合适的数据结构应该是链表。只要维护好链表的头和尾,就能很容易实现一个队列。生产者只要不断地往链表的尾部不断插入新的节点,而消费者只需要不断从头部取出最老的节点进行处理就好了。实际上,Java 自己的基础库里面就有 LinkedBlockingQueue 这样的队列库,可以直接用在生产者 - 消费者模式上。

不过,Disruptor 里面并没有用 LinkedBlockingQueue,而是使用了一个 RingBuffer 这样的数据结构,这个 RingBuffer 的底层实现是一个固定长度的数组。比起链表形式的实现,数组的数据在内存里面会存在空间局部性。

数组的连续多个元素会一并加载到 CPU Cache 里面来,所以访问遍历的速度会更快。而链表里面各个节点的数据,多半不会出现在相邻的内存空间,自然也就享受不到整个 Cache Line 加载后数据连续从高速缓存里面被访问到的优势。

除此之外,数据的遍历访问还有一个很大的优势,就是 CPU 层面的分支预测会很准确。可以更有效地利用 CPU 里面的多级流水线,使程序就会跑得更快。

三、无锁的 RingBuffer

(一)缓慢的锁

Disruptor 作为一个高性能的生产者 - 消费者队列系统,一个核心的设计就是通过 RingBuffer 实现一个无锁队列。

Java 里面的基础库里像 LinkedBlockingQueue 这样的队列库比起 Disruptor 里用的 RingBuffer 要慢上很多。慢的第一个原因我们说过,因为链表的数据在内存里面的布局对于高速缓存并不友好,而 RingBuffer 所使用的数组则不然。

另外一个重要的因素是 LinkedBlockingQueue 对锁的依赖较强。在生产者 - 消费者模式里,我们可能有多个消费者,同样也可能有多个生产者。多个生产者都要往队列的尾指针里面添加新的任务,就会产生多个线程的竞争。于是,在做这个事情的时候,生产者就需要拿到对于队列尾部的锁。同样地,在多个消费者去消费队列头的时候,也就产生竞争。同样消费者也要拿到锁。

就算只有一个生产者,一个消费者,也是需要锁的。一般来说,在生产者 - 消费者模式下,消费者要比生产者快。不然的话,队列会产生积压,队列里面的任务会越堆越多。任务不能及时完成,内存也会放不下。虽然生产者 - 消费者模型下,有一个队列来作为缓冲区,但是大部分情况下,这个缓冲区里面是空的。也就是说,即使只有一个生产者和一个消费者者,这个生产者指向的队列尾和消费者指向的队列头是同一个节点。因此,生产者和消费者之间一样会产生锁竞争。

在 LinkedBlockingQueue 上,这个锁机制是通过 Java 基础库 ReentrantLock 来实现的。这个锁是一个用 Java 在 JVM 上直接实现的加锁机制,锁机制需要由 JVM 来进行裁决。锁的争夺,会把没有拿到锁的线程挂起等待,也就需要经过一次上下文切换(Context Switch)。

上下文切换的过程,需要把当前执行线程的寄存器等的信息,保存到线程栈里面。而这个过程也必然意味着,已经加载到高速缓存里面的指令或者数据,又回到了主内存里面,会进一步拖慢性能。

加锁和不加锁自增到5亿性能对比:

package com.xuwenhao.perf.jmm;import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockBenchmark{public static void runIncrement(){long counter = 0;long max = 500000000L;long start = System.currentTimeMillis();while (counter < max) {counter++;}long end = System.currentTimeMillis();System.out.println("Time spent is " + (end-start) + "ms without lock");}public static void runIncrementWithLock(){Lock lock = new ReentrantLock();long counter = 0;long max = 500000000L;long start = System.currentTimeMillis();while (counter < max) {if (lock.tryLock()){counter++;lock.unlock();}}long end = System.currentTimeMillis();System.out.println("Time spent is " + (end-start) + "ms with lock");}public static void main(String[] args) {runIncrement();runIncrementWithLock();

Time spent is 207ms without lock
Time spent is 9603ms with lock

(二)无锁的 RingBuffer

加锁很慢,所以 Disruptor 的解决方案就是“无锁”。这个“无锁”指的是没有操作系统层面的锁。实际上,Disruptor 还是利用了一个 CPU 硬件支持的指令,称之为 CAS(Compare And Swap,比较和交换)。在 Intel CPU 里面,这个对应的指令就是 cmpxchg。

Disruptor 的 RingBuffer 是这么设计的:和直接在链表的头和尾加锁不同,Disruptor 的 RingBuffer 创建了一个 Sequence 对象,用来指向当前的 RingBuffer 的头和尾。这个头和尾的标识不是通过指针来实现的,而是通过一个序号。

在这个 RingBuffer 当中,进行生产者和消费者之间的资源协调,采用的是对比序号的方式。当生产者想要往队列里加入新数据的时候,它会把当前的生产者的 Sequence 的序号,加上需要加入的新数据的数量,然后和实际的消费者所在的位置进行对比,看看队列里是不是有足够的空间加入这些数据,而不会覆盖掉消费者还没有处理完的数据。

在 Sequence 的代码里面,就是通过 compareAndSet 这个方法,并且最终调用到了 UNSAFE.compareAndSwapLong,也就是直接使用了 CAS 指令。

 public boolean compareAndSet(final long expectedValue, final long newValue){return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue);}public long addAndGet(final long increment){long currentValue;long newValue;do{currentValue = get();newValue = currentValue + increment;}while (!compareAndSet(currentValue, newValue));return newValue;

这个 CAS 指令,也就是比较和交换的操作,并不是基础库里的一个函数。它也不是操作系统里面实现的一个系统调用,而是一个 CPU 硬件支持的机器指令。在我们服务器所使用的 Intel CPU 上,就是 cmpxchg 这个指令。

compxchg [ax] (隐式参数,EAX累加器), [bx] (源操作数地址), [cx] (目标操作数地址)

cmpxchg 指令,一共有三个操作数,第一个操作数不在指令里面出现,是一个隐式的操作数,也就是 EAX 累加寄存器里面的值。第二个操作数就是源操作数,并且指令会对比这个操作数和上面的累加寄存器里面的值。如果值是相同的,那一方面,CPU 会把 ZF(也就是条件码寄存器里面零标志位的值)设置为 1,然后再把第三个操作数(也就是目标操作数),设置到源操作数的地址上。如果不相等的话,就会把源操作数里面的值,设置到累加器寄存器里面。

单个指令是原子的,这也就意味着在使用 CAS 操作的时候,不再需要单独进行加锁,直接调用就可以了。没有了锁,CPU 这部高速跑车就像在赛道上行驶,不会遇到需要上下文切换这样的红灯而停下来。虽然会遇到像 CAS 这样复杂的机器指令,就好像赛道上会有 U 型弯一样,不过不用完全停下来等待, CPU 运行起来仍然会快很多。

那么,CAS 操作到底会有多快呢?我们还是用一段 Java 代码来看一下。

package com.xuwenhao.perf.jmm;import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockBenchmark {public static void runIncrementAtomic(){AtomicLong counter = new AtomicLong(0);long max = 500000000L;long start = System.currentTimeMillis();while (counter.incrementAndGet() < max) {}long end = System.currentTimeMillis();System.out.println("Time spent is " + (end-start) + "ms with cas");}public static void main(String[] args) {runIncrementAtomic();}

 和上面的 counter 自增一样,只不过这一次,自增采用了 AtomicLong 这个 Java 类。里面的 incrementAndGet 最终到了 CPU 指令层面,在实现的时候用的就是 CAS 操作。可以看到,它所花费的时间,虽然要比没有任何锁的操作慢上一个数量级,但是比起使用 ReentrantLock 这样的操作系统锁的机制,还是减少了一半以上的时间。

当想要追求最极致的性能的时候,我们会从应用层、贯穿到操作系统,乃至最后的 CPU 硬件,搞清楚从高级语言到系统调用,乃至最后的汇编指令,这整个过程是怎么执行代码的。而这个,也是学习组成原理的意义所在。

【推荐阅读】Disruptor 官方文档,里面不仅包含了怎么用好 Disruptor,也包含了整个 Disruptor 框架的设计思路,是一份很好的阅读学习材料。另外,Disruptor 的官方文档里,还有很多文章、演讲,详细介绍了这个框架,很值得深入去看一看。Disruptor 的源代码其实并不复杂,很适合用来学习怎么阅读开源框架代码。

课程链接:深入浅出计算机组成原理_组成原理_计算机基础-极客时间 

相关文章:

【计组】理解Disruptor--《计算机组成原理》(十五)

Disruptor 的开发语言&#xff0c;并不是很多人心目中最容易做到性能极限的 C/C&#xff0c;而是性能受限于 JVM 的 Java。其实只要通晓硬件层面的原理&#xff0c;即使是像 Java 这样的高级语言&#xff0c;也能够把 CPU 的性能发挥到极限。 一、Padding Cache Line&#xff…...

Windows11 安装Apache24全过程

Windows11 安装Apache24全过程 一、准备工作 1、apache-httpd-2.4.55-win64-VS17.zip - 蓝奏云 2、Visual Studio Code-x64-1.45.1.exe - 蓝奏云 二、实际操作 1、将下载好的zip文件解压放到指定好的文件夹。我的是D:\App\PHP下 个人习惯把版本号带上。方便检测错误。 2…...

1302机器翻译(队列)

目录 题目描述 提示 解题思路 代码部分 题目描述 小晨的电脑上安装了一个机器翻译软件&#xff0c;他经常用这个软件来翻译英语文章。 这个翻译软件的原理很简单&#xff0c;它只是从头到尾&#xff0c;依次将每个英文单词用对应的中文含义来替换。对于每个英文单词&#…...

AcWing、第 90 场周赛:4806. 首字母大写、4807. 找数字、4808. 构造字符串(C++)

目录 4806. 首字母大写 题目描述&#xff1a; 实现代码&#xff1a; 4807. 找数字 题目描述&#xff1a; 实现代码&#xff1a; 回溯&#xff08;超时&#xff09;&#xff1a; 原理思路&#xff1a; 贪心&#xff1a; 原理思路&#xff1a; 4808. 构造字符串 问题…...

跟同事杠上了,Apache Beanutils为什么被禁止使用?

收录于热门专栏Java基础教程系列&#xff08;进阶篇&#xff09; 在实际的项目开发中&#xff0c;对象间赋值普遍存在&#xff0c;随着双十一、秒杀等电商过程愈加复杂&#xff0c;数据量也在不断攀升&#xff0c;效率问题&#xff0c;浮出水面。 问&#xff1a;如果是你来写…...

Golang 模糊测试的使用

一 背景 在 Go 1.18 中,Go 语言新增模糊测试(Fuzzing)。Fuzzing,又叫fuzz testing,中文叫做模糊测试或随机测试。其本质上是一种自动化测试技术,更具体一点,它是一种基于随机输入的自动化测试技术,常被用于发现处理用户输入的代码中存在的bug和问题。模糊测试和常规的功能…...

RSA公钥加密机制跨语言应用实战

在公钥密码学中(也称为非对称密码学)&#xff0c;加密机制依赖于两个密钥&#xff1a;公钥和私钥。公钥用于加密消息&#xff0c;而只有私钥的所有者才能解密消息。实际应用中通常需要对公钥和私钥进行序列化&#xff0c;然后分发密钥实现在不同场景、不同语言环境中使用。本文…...

P7面试送命题

面试总结&#xff0c;对标市场P7。什么叫送命题&#xff0c;一道题回答不上来面试直接挂的题目。JVM 运行时数据区域内存回收机制GC root有哪些volatile原理synchronize原理JDK 集合家族介绍HashMap原理ConcurrentHashMap原理Thread生命周期ThreadPoolExecutor生命周期、实例化…...

零信任-微软零信任介绍(2)

微软零信任是什么&#xff1f; Microsoft Zero Trust 是一种安全架构&#xff0c;旨在在没有信任任何设备、用户或网络的情况下保护网络。这种架构使用多重验证和分段技术&#xff0c;以确保每个请求和资源的安全性。 零信任不假定任何内部用户或设备是安全的&#xff…...

C++中对象调用成员函数this指针的作用

C中对象调用成员函数this指针的作用 Sales_data total;//定义对象 total.isbn();//调用对象中的成员函数isbn成员函数isbn()通过一个名为this的额外隐式参数来访问调用它的对象total。当我们调用一个成员函数时&#xff0c;用请求该函数的对象地址初始化this。 例如&#xff0…...

JavaScript------数组

目录 一、简介 1、什么是数组&#xff1f; 2、创建数组 3、数组的数据类型 4、向数组中添加元素 5、读取数组中的元素 6、实例属性&#xff1a;length 二、遍历数组 方式一&#xff1a;for循环 方式二&#xff1a;for...of 三、数组方法&#xff08;常用&#xff09…...

迷宫《1》

一天蒜头君掉进了一个迷宫里面&#xff0c;蒜头君想逃出去&#xff0c;可怜的蒜头君连迷宫是否有能逃出去的路都不知道。看在蒜头君这么可怜的份上&#xff0c;就请聪明的你告诉蒜头君是否有可以逃出去的路。输入格式第一行输入两个整数 &#xfffd;n 和 &#xfffd;m&#x…...

剑指 Offer 20. 表示数值的字符串

剑指 Offer 20. 表示数值的字符串 请实现一个函数用来判断字符串是否表示数值&#xff08;包括整数和小数&#xff09;。 数值&#xff08;按顺序&#xff09;可以分成以下几个部分&#xff1a; 若干空格 一个 小数 或者 整数 &#xff08;可选&#xff09;一个 ‘e’ 或 ‘…...

阻抗匹配之反射波形测量

稍微接触过高速信号的朋友&#xff0c;一定对阻抗匹配和信号反射都有所了解&#xff0c;甚至可以按照公式&#xff0c;把反射波形一路推导出来。但是&#xff0c;纸上得来终绝浅&#xff0c;绝知此事要躬行。 今天&#xff0c;我们就来实测一下信号反射波形&#xff0c;测试环…...

微信小程序 java家校通Springboot中小学家校联系电子作业系统

小程序前端框架&#xff1a;uniapp 小程序运行软件&#xff1a;微信开发者 后端技术:javaSsm(SpringSpringMVCMyBatis)vue.js 后端开发环境:idea/eclipse 数据库:mysql 通过对各种资料的收集&#xff0c;了解到“校讯通”是联系社会的窗口&#xff0c;是实现家校联系工作和学校…...

Fluent Python 笔记 第 8 章 对象引用、可变性和垃圾回收

本章先以一个比喻说明 Python 的变量&#xff1a;变量是标注&#xff0c;而不是盒子。如果你不知道引用式变量是什么&#xff0c;可以像这样对别人解释别名。 然后&#xff0c;本章讨论对象标识、值和别名等概念。随后&#xff0c;本章会揭露元组的一个神奇特性&#xff1a;元…...

转义字符的分类

什们是转义字符 可显示字符在字符集中&#xff0c;有一类字符具有这样的特性&#xff1a;当从键盘上输入这个字符时&#xff0c;显示器上就可以显示这个字符&#xff0c;即输入什么就显示什么。这类字符称为可显示字符&#xff0c;如a、b、c、$、和空格符等都是可显示字符。 控…...

剑指 Offer 03. 数组中重复的数字

剑指 Offer 03. 数组中重复的数字 一、题目描述&#xff1a; 找出数组中重复的数字。 在一个长度为 n 的数组 nums 里的所有数字都在 0&#xff5e;n-1 的范围内。数组中某些数字是重复的&#xff0c;但不知道有几个数字重复了&#xff0c;也不知道每个数字重复了几次。请找出…...

飞速创新更新IPO招股书:计划募资约14亿元,向伟为实际控制人

近日&#xff0c;深圳市飞速创新技术股份有限公司&#xff08;下称“飞速创新”&#xff09;预披露更新招股书&#xff0c;准备在深圳证券交易所主板上市。本次冲刺上市&#xff0c;飞速创新计划募资13.54亿元&#xff0c;招商证券为其保荐机构。 据介绍&#xff0c;飞速创新专…...

JUC(java.util.concurrent) 的常见类

1.ReentrantLock 可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全. ReentrantLock 也是可重入锁. "Reentrant" 这个单词的原意就是 "可重入. ReentrantLock 的用法: lock(): 加锁, 如果获取不到锁就死等.trylock(超时时间):…...

Angular4 中 ckeditor5 插件的使用

Angular4 中 ckeditor5 插件的使用 0 环境、新建项目 环境&#xff1a; Windows10Angular/cli1.4.10&#xff08;安装 Angular 的过程略过&#xff0c;Angular4 版本比较古老&#xff0c;这也导致项目安装插件及其他操作比较麻烦&#xff09; 1. ckeditor5 官方用法 基础用…...

[python刷题模板] 前缀函数/next数组/kmp算法

[python刷题模板] 前缀函数/next数组/kmp算法 一、 算法&数据结构1. 描述2. 复杂度分析3. 常见应用4. 常用优化二、 模板代码1. 裸前缀函数2. 树上kmp3. 裸kmp三、其他四、更多例题五、参考链接一、 算法&数据结构 1. 描述 前缀函数和next数组基本上是一个东西&#…...

rust 程序设计语言入门(1)

本文是阅读《Rust程序设计语言》的学习记录&#xff0c;配合视频《Rust编程语言入门教程》食用更佳 环境搭建 windows下载rustup_init.exe&#xff0c;点击安装&#xff0c;默认选择msvc的toolchain&#xff0c;一路default即可 解决下载慢的问题&#xff0c;在powershell中修…...

基于蜣螂算法改进的LSTM预测算法-附代码

基于蜣螂算法改进的LSTM预测算法 文章目录基于蜣螂算法改进的LSTM预测算法1.数据2.LSTM模型3.基于蜣螂算法优化的LSTM4.测试结果5.Matlab代码摘要&#xff1a;为了提高LSTM数据的预测准确率&#xff0c;对LSTM中的参数利用蜣螂搜索算法进行优化。1.数据 采用正弦信号仿真数据&…...

Python安全开发——Scapy流量监控模块watchdog

目录 Python蓝队项目说明 (一)Python-蓝队项目-Scapy流量分析 0x01 Scapy参数介绍...

阶段二5_集合ArrayList

一.对象数组 1.对象数组使用案例 需求&#xff1a;将&#xff08;张三&#xff0c;23&#xff09;&#xff08;李四&#xff0c;24&#xff09;&#xff08;王五&#xff0c;25&#xff09; 封装为3个学生对象并存入数组 随后遍历数组&#xff0c;将学生信息输出在控制台 思路…...

十一、Python——匿名函数

1.匿名函数:简化函数定义 2.格式 lambda 参数1&#xff0c;参数2…&#xff1a;运算 3.匿名函数特点 不需要指明函数名定义只有一条语句函数体必须是一个表达式不能显示使用return 4.匿名函数实现求和 s lambda a,b:a b result s(1,2) print(result) # 35.匿名函数作…...

数组常使用的方法

1. join (原数组不受影响)该方法可以将数组里的元素,通过指定的分隔符,以字符串的形式连接起来。返回值:返回一个新的字符串const arr[1,3,4,2,5]console.log(arr.join(-)&#xff1b;//1-3-4-2-52. push该方法可以在数组的最后面,添加一个或者多个元素结构: arr.push(值)返回值…...

2023华为软件测试笔试面试真题,抓紧收藏不然就看不到了

一、选择题 1、对计算机软件和硬件资源进行管理和控制的软件是&#xff08;D&#xff09; A.文件管理程序 B.输入输出管理程序 C.命令出来程序 D.操作系统 2、在没有需求文档和产品说明书的情况下只有哪一种测试方法可以进行的&#xff08;A&#xff09; A.错误推测法测…...

洛谷2月普及组(月赛)

&#x1f33c;小宇&#xff08;治愈版&#xff09; - 刘大拿 - 单曲 - 网易云音乐 OI赛制且难度对标蓝桥杯省赛&#xff08;&#x1f625;真难&#xff0c;第三题做了几百年&#xff0c;第四题只敢骗骗分&#xff09; 花了10块钱&#x1f643; 买官网的思路&#xff0c;结果…...

网站基础功能介绍/做网站哪个公司最好

针对蒙蔽软件遮挡剔除的扩展&#xff0c;本文提供了一种灵活的解决方案&#xff0c;可以在不影响原始蒙蔽遮挡算法性能的情况下&#xff0c;为无法充分预分类的场景提高深度缓冲区的最终精度。该方法中的合并时间与分辨率呈线性关系&#xff0c;并且与场景中的几何复杂度无关。…...

企业网站的建设公司/市场调研报告总结

1、什么是command命令模式&#xff1f; command命令模式是“封装方法调用”的一个模式&#xff0c;通过封装方法调用&#xff0c;把运算块封装成形。那么调用此运算的对象就不必深究事情是如何进行的&#xff0c;只需要知道可以使用这个包装成型的方法来完成它就可以了。因为它…...

创新的中小型网站建设/百度新闻app

面试例题1&#xff1a;对象与实例有什么区别&#xff1f; 解析&#xff1a;在Java的内存分配中&#xff0c;对象和实例是不同的&#xff0c;前者分配在内存堆里&#xff0c;后者分配在堆栈里&#xff0c;至于为什么要这样分&#xff0c;参考一下其他的资料。对象是不变的东西&a…...

免费的购物网站源码/网站运营及推广方案

前言 前几天接到客户需求&#xff0c;做一个登记客户信息的表单&#xff0c;做好以后要求有一个配置文件可以修改全局接口&#xff0c;第一次做&#xff0c;查了网上各种资料&#xff0c;最终以失败告终&#xff0c;在朋友的帮助下最终成功&#xff01;&#xff01;&#xff0…...

邢台做网站邮箱/手机百度下载app

磁自旋(magnetic spin)神奇的地方在于&#xff0c;它不会取代原子&#xff0c;能让硬盘上壁垒式的磁畴(walled domains)在1与0之间切换&#xff0c;又没有最终会让快闪位元单元耗损的疲乏机制(fatigue mechanisms)。目前的固态非挥发性存储器如快闪存储器、铁电存储器(FRAM)&am…...

大型外贸商城网站建设/南宁seo排名优化

市场要闻 韩国釜山发行地区性数字货币&#xff0c;力促自由贸易区发展 2019年4月&#xff0c;韩国选择釜山市为优先自由贸易特区&#xff0c;并于当地发行地域性数字货币。该决议将于7月末&#xff0c;进入“优先自由贸易特区委员会”的审查会议和讨论决议程序。7月1日&#x…...