分类 其它 下的文章

JavaScript 中栈的运用

在 JavaScript 中,栈(Stack)是一种非常有用的数据结构,它遵循后进先出(Last In First Out,LIFO)的原则。在本文中,我们将深入探讨栈的概念以及在 JavaScript 中的实际运用。

一、栈的概念

栈是一种线性数据结构,它只能在一端进行插入(称为入栈或压栈,push)和删除(称为出栈或弹栈,pop)操作。想象一下一摞盘子,你只能从最上面拿盘子(出栈)或者把盘子放在最上面(入栈)。

栈通常具有以下几个基本操作:

  1. push(element)
    :将一个元素压入栈顶。
  2. pop()
    :弹出栈顶元素并返回它。
  3. peek()
    :查看栈顶元素,但不弹出它。
  4. isEmpty()
    :判断栈是否为空。

二、在 JavaScript 中实现栈

以下是用 JavaScript 实现一个简单栈的代码:

class Stack {
    constructor() {
        this.items = [];
    }

    push(element) {
        this.items.push(element);
    }

    pop() {
        if (this.isEmpty()) {
            return "Underflow";
        }
        return this.items.pop();
    }

    peek() {
        if (this.isEmpty()) {
            return null;
        }
        return this.items[this.items.length - 1];
    }

    isEmpty() {
        return this.items.length === 0;
    }

    size() {
        return this.items.length;
    }
}

三、栈的实际运用

(一)表达式求值

  1. 中缀表达式转后缀表达式
    在计算机科学中,将中缀表达式转换为后缀表达式是栈的一个重要应用。中缀表达式是我们通常使用的算术表达式形式,如
    (2 + 3) * 4
    。后缀表达式则是将运算符放在操作数之后,例如
    2 3 + 4 *

算法步骤如下:

  • 初始化一个空栈用于存储运算符。
  • 从左到右遍历中缀表达式。
  • 如果遇到操作数,直接输出。
  • 如果遇到左括号,将其压入栈。
  • 如果遇到右括号,弹出栈中的运算符并输出,直到遇到左括号,然后丢弃左括号。
  • 如果遇到运算符,根据其优先级进行处理。如果栈顶运算符的优先级高于或等于当前运算符,则弹出栈顶运算符并输出;否则,将当前运算符压入栈。
  • 遍历结束后,将栈中的剩余运算符依次弹出并输出。

以下是用 JavaScript 实现中缀表达式转后缀表达式的代码:

function infixToPostfix(expression) {
    const stack = new Stack();
    let postfix = "";
    const precedence = {
        '+': 1,
        '-': 1,
        '*': 2,
        '/': 2
    };

    for (let char of expression) {
        if (/[0-9]/.test(char)) {
            postfix += char;
        } else if (char === '(') {
            stack.push(char);
        } else if (char === ')') {
            while (!stack.isEmpty() && stack.peek()!== '(') {
                postfix += stack.pop();
            }
            stack.pop(); // 弹出左括号
        } else {
            while (!stack.isEmpty() && precedence[stack.peek()] >= precedence[char]) {
                postfix += stack.pop();
            }
            stack.push(char);
        }
    }

    while (!stack.isEmpty()) {
        postfix += stack.pop();
    }

    return postfix;
}
  1. 后缀表达式求值
    一旦将中缀表达式转换为后缀表达式,就可以很容易地对后缀表达式进行求值。

算法步骤如下:

  • 从左到右遍历后缀表达式。
  • 如果遇到操作数,将其压入栈。
  • 如果遇到运算符,弹出栈中的两个操作数,进行相应的运算,然后将结果压回栈。
  • 遍历结束后,栈中唯一的元素就是表达式的结果。

以下是用 JavaScript 实现后缀表达式求值的代码:

function evaluatePostfix(postfix) {
    const stack = new Stack();
    for (let char of postfix) {
        if (/[0-9]/.test(char)) {
            stack.push(parseInt(char));
        } else {
            const operand2 = stack.pop();
            const operand1 = stack.pop();
            switch (char) {
                case '+':
                    stack.push(operand1 + operand2);
                    break;
                case '-':
                    stack.push(operand1 - operand2);
                    break;
                case '*':
                    stack.push(operand1 * operand2);
                    break;
                case '/':
                    stack.push(operand1 / operand2);
                    break;
            }
        }
    }
    return stack.pop();
}

(二)函数调用栈

在 JavaScript 中,当一个函数调用另一个函数时,会在内存中创建一个称为调用栈(Call Stack)的结构。调用栈是一种栈数据结构,它用于跟踪函数的调用顺序。

例如:

function functionA() {
    console.log("Inside functionA");
    functionB();
}

function functionB() {
    console.log("Inside functionB");
}

functionA();


functionA
被调用时,它的执行上下文被压入调用栈。当
functionA
调用
functionB
时,
functionB
的执行上下文也被压入调用栈。当
functionB
执行完毕后,它的执行上下文从调用栈中弹出。然后,
functionA
继续执行,直到它也执行完毕,其执行上下文也从调用栈中弹出。

这种机制确保了函数的正确执行顺序和变量的作用域管理。

(三)深度优先搜索(DFS)

深度优先搜索是一种图遍历算法,它可以使用栈来实现。

以下是用 JavaScript 实现深度优先搜索的代码:

class Graph {
    constructor() {
        this.adjacencyList = {};
    }

    addVertex(vertex) {
        if (!this.adjacencyList[vertex]) {
            this.adjacencyList[vertex] = [];
        }
    }

    addEdge(vertex1, vertex2) {
        this.adjacencyList[vertex1].push(vertex2);
        this.adjacencyList[vertex2].push(vertex1);
    }

    dfs(startVertex) {
        const stack = new Stack();
        const visited = {};
        stack.push(startVertex);
        visited[startVertex] = true;

        while (!stack.isEmpty()) {
            const currentVertex = stack.pop();
            console.log(currentVertex);

            for (let neighbor of this.adjacencyList[currentVertex]) {
                if (!visited[neighbor]) {
                    stack.push(neighbor);
                    visited[neighbor] = true;
                }
            }
        }
    }
}

可以使用以下方式调用:

const graph = new Graph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');

graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('B', 'D');
graph.addEdge('C', 'E');

graph.dfs('A');

在这个例子中,深度优先搜索从给定的起始顶点开始,使用栈来存储待访问的顶点。每次从栈中弹出一个顶点,访问它,并将其未访问过的邻居顶点压入栈。

(四)括号匹配

检查一个字符串中的括号是否匹配是栈的另一个常见应用。

算法步骤如下:

  • 初始化一个空栈。
  • 遍历字符串中的每个字符。
  • 如果遇到左括号,将其压入栈。
  • 如果遇到右括号,检查栈是否为空。如果为空,说明右括号没有匹配的左括号,返回 false。如果栈不为空,弹出栈顶元素,检查弹出的左括号是否与当前右括号匹配。如果不匹配,返回 false。
  • 遍历结束后,如果栈为空,说明所有括号都匹配,返回 true;否则,返回 false。

以下是用 JavaScript 实现括号匹配的代码:

function isBalanced(str) {
    const stack = new Stack();
    for (let char of str) {
        if (char === '(' || char === '[' || char === '{') {
            stack.push(char);
        } else if (char === ')' || char === ']' || char === '}') {
            if (stack.isEmpty()) {
                return false;
            }
            const top = stack.pop();
            if ((char === ')' && top!== '(') || (char === ']' && top!== '[') || (char === '}' && top!== '{')) {
                return false;
            }
        }
    }
    return stack.isEmpty();
}

四、总结

栈是一种强大的数据结构,在 JavaScript 中有许多实际应用。从表达式求值到函数调用栈,从图的遍历到括号匹配,栈都发挥了重要作用。理解栈的概念和操作,以及如何在 JavaScript 中实现和应用栈,对于编写高效的代码和解决各种编程问题非常有帮助。

前言

深入理解相机视口,摸索相机视口旋转功能,背景透明或者不透明。
本篇,实现了一个左下角旋转HUD且背景透明的相机视口。


Demo

请添加图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


HUD相机的坐标

抬头HUD就是通过投影矩阵来实现,具体可参看《OSG开发笔记(二十):OSG使用HUD显示文字》

  • Hud要单独创建一个新相机
  • 注意关闭光照,不受光照影响,所以内容以同一亮度显示
  • 关闭深度测试
  • 渲染顺序设置为POST,否则可能会被场景中的其他图形所覆盖。
  • 设置参考贴为绝对型:setReferenceFrame(osg::Transform:ABSOLUTE_RF)
  • 使其不受父节点变换的影响:setMatrix(osg::Matrix::identity())
  • 投影矩阵通常会设置为屏幕尺寸大小


相机(Camera)

相机(osg::Camera)和视口(Viewport)是两个核心概念,对于理解OSG中的三维场景渲染至关重要。
相机在OSG中用于模拟真实世界中的摄影机,它负责捕捉和渲染三维场景。相机类(osg::Camera)继承自osg::Transform和osg::CullSetting类,用来管理OSG中的模型——视图矩阵。相机的管理主要是通过各种变换实现的,这些变换包括:

  • 视点变换:设置视点的方向和位置。默认情况下,视点定位为坐标原点,指向Y正方向。可以通过调整视点的位置和参考点的位置来改变相机的观察方向和角度。
  • 投影变换:由于显示器只能用二维图像显示三维物体,因此要靠投影来降低维数。投影变换的目的是定义一个视景体,使视景体外多余的部分被裁减掉,最终进入图像的只是视景体内的有关部分。OSG支持两种投影方式:透视投影(Perspective Projection)和正视投影(Orthographic Projection)。透视投影能够模拟人眼的视觉效果,使远处的物体看起来更小,而正视投影则保持物体的大小不变,不受距离影响。
  • 视口变换:将视景体内投影的物体显示在二维的视口平面上。即将经过几何变换、投影变换和裁剪变换后的物体显示于屏幕窗口内指定的区域内,这个区域通常为矩形,称为视口。


视口(ViewPort)

具体来说,视口变换涉及以下几个参数:

  • 屏幕左下角的坐标:定义了视口在屏幕上的左下角位置。
  • 屏幕宽度和高度:定义了视口的宽度和高度,即相机捕捉的场景在屏幕上显示的区域大小。
    在OSG中,可以通过调用相机的setViewport方法来设置视口。例如:
pCamera->setViewport(new osg::Viewport(0, 0, width, height));

这行代码创建了一个新的视口,并将其设置为相机的当前视口。其中,0和0是屏幕左下角的坐标,width和height是视口的宽度和高度。


相机与视口的关系

相机和视口在OSG中紧密相连,共同决定了三维场景的渲染效果。相机负责捕捉和渲染场景,而视口则定义了相机捕捉的场景在屏幕上的显示位置和大小。通过调整相机的各种变换和设置视口的大小和位置,可以实现丰富的三维视觉效果和交互体验。


设置相机观察函数

void setViewMatrixAsLookAt(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up);
  • eye:表示相机的位置。这是一个三维向量,指定了相机在世界坐标系中的位置。
  • center:表示相机观察的中心点。这也是一个三维向量,指定了相机应该对准的物体或场景的中心位置。
  • up:表示哪个方向是正方向。这同样是一个三维向量,通常用于指定相机的上方方向(例如,通常设置为 (0,0,1) 表示Y轴正方向为上方)。
    设置相机位置和方向:通过指定 eye、center 和 up 三个参数,你可以精确地控制相机的位置和姿态。eye 和 center 之间的向量表示相机的观察方向,而 up 向量则用于确定相机的上方方向。
    关闭漫游器:在使用 setViewMatrixAsLookAt 函数之前,通常需要关闭相机的漫游器(Camera Manipulator)。这是因为漫游器会自动更新相机的观察矩阵,从而覆盖你通过 setViewMatrixAsLookAt 设置的参数。可以通过调用 viewer->setCameraManipulator(NULL) 来关闭漫游器。
    坐标系:OSG 使用右手坐标系,其中 X 轴向右,Y 轴向上,Z 轴向前。因此,在设置 eye、center 和 up 参数时,需要确保它们符合右手坐标系的规则。
    视图矩阵:setViewMatrixAsLookAt 函数实际上是通过设置相机的视图矩阵来实现相机位置和姿态的调整。视图矩阵是一个 4x4 的矩阵,用于将相机坐标系中的点转换到世界坐标系中。
    setViewMatrixAsLookAt 是一个强大的函数,它允许你以直观的方式设置相机的位置和姿态。通过合理地使用这个函数,你可以创建出各种复杂而逼真的三维场景和视觉效果。


Demo关键源码


创建Hud相机

    // 步骤一:创建HUD摄像机
// osg::ref_ptr<osg::Camera> pCamera = new osg::Camera;
osg::ref_ptr<HudRotateCamera> pCamera = new HudRotateCamera;
pCamera->setMasterCamera(_pViewer->getCamera());
// 步骤二:设置投影矩阵
// pCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1280, 0, 800));
// 步骤三:设置视图矩阵,同时确保不被场景中其他图形位置变换影响, 使用绝对帧引用
pCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
pCamera->setViewMatrix(osg::Matrix::identity());
// 步骤四:清除深度缓存
pCamera->setClearMask(GL_DEPTH_BUFFER_BIT);
// 步骤五:设置POST渲染顺序(最后渲染)
// pCamera->setRenderOrder(osg::Camera::PRE_RENDER); // 渲染不显示
// pCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
pCamera->setRenderOrder(osg::Camera::POST_RENDER);
// 步骤六:设置为不接收事件,始终得不到焦点
pCamera->setAllowEventFocus(false);

// osg::ref_ptr<osg::Geode> pGeode = new osg::Geode();
// pGeode = new osg::Geode();
osg::ref_ptr<osg::StateSet> pStateSet = pGeode->getOrCreateStateSet();
// 步骤七:关闭光照
pStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
// 步骤九:关闭深度测试
pStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);

// pGeode->addDrawable(pGeometry.get());

pCamera->addChild(pGeode.get());

pGroup->addChild(pCamera.get());


HudRotateCamera.h

#ifndef HUDROTATECAMERA_H
#define HUDROTATECAMERA_H

#include "osg/Camera"
#include "osg/CopyOp"

class HudRotateCamera : public osg::Camera
{
public:
HudRotateCamera();

HudRotateCamera(const HudRotateCamera& copy, const osg::CopyOp &copyOp = osg::CopyOp::SHALLOW_COPY);

META_Node(osg, HudRotateCamera);

public:
void setMasterCamera(Camera* camera);

public:
virtual void traverse(osg::NodeVisitor& nodeVisitor);

protected:
virtual ~HudRotateCamera();

protected:
osg::observer_ptr<Camera> _pMasterCamera; // 新增了相机,主要是用来获取举证的
};

#endif // HUDROTATECAMERA_H


HudRotateCamera.cpp

#include "HudRotateCamera.h"

HudRotateCamera::HudRotateCamera(): Camera()
{

}

HudRotateCamera::HudRotateCamera(const HudRotateCamera & copy, const osg::CopyOp & copyOp)
: Camera(copy, copyOp),
_pMasterCamera(copy._pMasterCamera)
{

}

HudRotateCamera::~HudRotateCamera()
{

}

void HudRotateCamera::setMasterCamera(osg::Camera *camera)
{
_pMasterCamera = camera;
}

void HudRotateCamera::traverse(osg::NodeVisitor &nodeVisitor)
{
double fovy, aspectRatio, vNear, vFar;
_pMasterCamera->getProjectionMatrixAsPerspective(fovy, aspectRatio, vNear, vFar);

// 设置投影矩阵,使缩放不起效果, 改为正投影,正投影不会随相机的拉近拉远而放大、缩小,这样就没有缩放效果,
// 放大缩小是根据左右,上下距离,越大就物体越小,越小就物体越大
this->setProjectionMatrixAsOrtho(-50 * aspectRatio,
50 * aspectRatio,
-50,
50,
vNear,
vFar);
// 让坐标轴模型位于窗体左下角
osg::Vec3 vec3(-40, -40, 0);
if (_pMasterCamera.valid())
{
// 改变视图矩阵, 让移动位置固定
osg::Matrix matrix = _pMasterCamera->getViewMatrix();

// 让移动固定, 即始终位于窗体右下角,否则鼠标左键按住模型可以拖动或按空格键时模型会动
matrix.setTrans(vec3);
this->setViewMatrix(matrix);
}
osg::Camera::traverse(nodeVisitor);
}


工程模板v1.35.0

在这里插入图片描述


入坑


入坑一:没有按照预期的方式全屏显示在正中间


问题

想一直显示在中间,且能旋转,移动中心,但是实际效果如下,方格100x100,间距1.0,测试:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


尝试

将面方格缩小为10x10,线放小,测试:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

缩小正交投影:
在这里插入图片描述
在这里插入图片描述

可能跟相机查看位置有关,新增相机位置和方向等信息:
在这里插入图片描述

没什么影响:
在这里插入图片描述

这里可能理解有问题,我们需要区域投影到视口,那么一个是投影的区域三维区域的大小,一个是投影到桌面2D他的大小,这里其实类似于HUD,通过HUD的方式,添加了几行代码设置投影矩阵:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

经过测试,可以跳过相机调整视口、中心改变后也会移动,所以他一种在其位置区域。
然后回到前面,发现也可以,再次摸索,发现如下特点:
在这里插入图片描述

所以此时,纵横都是10,所以窗口大小要是符合1:1(10:10=1:1)的比例,改为400,400测试,还是一样:
在这里插入图片描述

但是调整为800x800就好了:
在这里插入图片描述

放最大也不会截取少了:
在这里插入图片描述

所以这个有点搞不明白了,总之是解决了,且投影矩阵和正交矩阵都可以解决,测试投影矩阵和正交举证都收视口大小影响,但是影响具体不知,就好像800x800是最小一样(其他的没测了,只测了400x400、600x600不行,看比例800x800是最小正好满窗口了)。
在这里插入图片描述

又怀疑投过去的区域小了,将区域放大,其位置反倒缩小,所以跟理解不一样:

  • 一种是理解直接投射过去,投射过去区域变大所以变大(不是的);
  • 一种是投射过去区域不变,那么区域变大视图区域可见空间范围变大(实际是这样,但是视口对屏幕的大小未变);
    综合以上,又测试了加大视口,也正常,所以怀疑有可能是qt和osg结合的时候这个地方设置了一个最小值,而可能吧,欢迎探讨,这里深究暂时也没结果,且解决了,所以不继续深究了。


解决

修改相机视口大小为最小800x800。
在这里插入图片描述


后续补充

后续查看做的这个qtosg兼容类,做的时候,自己设置的800x800,就是这个原因了:
在这里插入图片描述


入坑二:相机视口区域不透明


问题

当作最前面的文本hud,是可以透明,但是这里进行调整之后,无法透明。
在这里插入图片描述


尝试

修改了语句,可以透明了部分,但是没了。
在这里插入图片描述

在这里插入图片描述


解决

相机是一个投影矩阵,没有透明,但是文字hud为什么透明呢?。


入坑三:内置几何体关闭光照后纯白色


问题

关闭光照后,几何体白色
在这里插入图片描述


原理

光照关闭要设置颜色,不想设置颜色,就单独给体开放关照。


解决

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


入坑四:文本看不到


问题

文本看不到,旋转后发现是太大了。
在这里插入图片描述

位置较大。


尝试

测试下是把整个坐标区域的展示范围扩大,那么实际看起来就是缩小。


解决

在这里插入图片描述

在这里插入图片描述


入坑五:hud旋转中心不对


问题

Hud旋转中心不对
在这里插入图片描述

这时旋转中心还不对,可能需要调整旋转中心
在这里插入图片描述


原理

开始去修改矩阵,发现都不对,其实中间点一直是0,0,0,其就是中心,那么我们设置文本的显示点不是从0,0开始即可。
下面将四边形的角点改为0,0,0来标识,然后修改文本的中心点:
在这里插入图片描述


解决

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


入坑六:hud旋转反向了


问题

旋转是反的,然后光照也反了,变换矩阵有问题
在这里插入图片描述


原理

数据几何变换算来算去很费劲,直接测试的结论:
在这里插入图片描述

代码写反了,让上下反向了,应该是-50~50


解决

在这里插入图片描述

在这里插入图片描述

还剩下光照问题,这个不好咋弄了,反正是关闭光照,或者是自己手动添加光源,用系统的可能有点问题。
在这里插入图片描述

在这里插入图片描述

这个暂时没解决,实际使用就是用一个,可以从长度单独给这个相机设置一个光源,这里因为需求本身不需要投影,不做测试了。

前言

推荐一款集成了超过100款控件的流行 XAML 控件库,同时提供了一系列常用的 .NET 帮助类-CookPopularUI。它可以简化开发流程,让我们能够更加专注于核心业务逻辑的实现。

让我们一起学习如何使用 CookPopularUI,并详细了解其提供的丰富控件内容。

项目介绍

CookPopularUI
不仅提供了丰富的控件选择,包括但不限于数据网格、图表、导航菜单、对话框等,还特别注重于提升开发效率。通过内置的帮助类,可以轻松处理诸如数据绑定、异步操作、文件操作等常见任务,而无需从头开始编写大量代码。

另外,该控件库对多个版本的 .NET 提供了良好的支持,无论是 .NET Framework 还是 .NET Core/.NET 5+,都能确保应用的兼容性和稳定性。

项目特点

  • 丰富的控件库:CookPopularUI 包含了多种类型的控件,如数据网格、图表、导航菜单、对话框等,满足不同应用场景的需求。
  • 常用 .NET 帮助类:内置了大量 .NET 帮助类,简化了数据绑定、异步操作、文件处理等常见开发任务。
  • 高度可定制化:所有控件都支持高度自定义,您可以根据自己的需求调整样式和行为。
  • 良好的文档和示例:提供了详细的文档和丰富的示例代码,帮助您快速上手并高效开发。
  • 多版本支持:支持多个版本的 .NET,无论是 .NET Framework 还是 .NET Core/.NET 5+,都能确保兼容性和稳定性。

项目使用

1、添加Nuget包引用

<PackageReferenceInclude="CookPopularUI.WPF"Version="1.0.1-preview2" />

2、添加如下代码即可全部引用(两种方式皆可)

<Application.Resources>
    <ResourceDictionary>
          <ResourceDictionary.MergedDictionaries>
              <!--<ResourceDictionary Source="pack://application:,,,/CookPopularUI.WPF;component/Themes/DefaultPopularColor.xaml" />-->
              <!--<ResourceDictionary Source="pack://application:,,,/CookPopularUI.WPF;component/Themes/DefaultPopularControl.xaml" />-->
              <ui:PopularThemeLanguage="English"Theme="Light" />
          </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

项目控件

1、Border

2、DataGrid

3、Message

项目地址

Gitee:
https://gitee.com/CookCSharp/CookPopularUI

总结

本文展示了部分功能和内容,如有需求访问案例地址获取详细信息。希望本文能在WPF控件开发方面为各位提供有益的帮助。期待大家在评论区留言交流,分享您的宝贵经验和建议。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号
[DotNet技术匠]
社区,与其他热爱技术的同行一起交流心得,共同成长!

一、问题

假设存在这样的时钟控制模型:

CLK1、CLK2以及系统时钟的频率与相位均不一致,我们希望在clk_sel=1时,输出CLK1,反之输出CLK2,CLK_SEL可以由系统时钟驱动,也可以由组合逻辑驱动。那么在这种情况下就会出现以下的“毛刺”问题:

可以看到,在CLK_SEL的交界处,非常容易出现CLK_OUT时钟出现毛刺的现象,从而影响系统的正常工作。

二、解决方法

(以下方法出自B站UP:皮特派)

具体思路是:1.两个时钟互斥输出,即B4、B5两个非门作为最后的A2、A4的输入;

2.S3、S6寄存器采用下降沿驱动对sel进行同步,这样就可以在延迟一拍的情况下做到两个时钟无毛刺的输出。

分析如下:当sel为0时,ali_2初始值为0,ali_1为1,A1选通,随后在CLKA的驱动下对alo信号打两拍同步,随后S3在CLKA的下降沿对打拍同步的信号进行采样,这样在下降沿到后一个上升沿之前,由于CLKA为0,CLK-OUT始终拉低,而后CLKA为高,此时由于上个下降沿已经对ai2_2进行了同步,CLKA为1,且ai2_2为1,此时CLK_OUT输出为CLKA。
本质上是用延迟换准确性。

三、代码

根据上述的RTL,代码如下:

  1. RTL

// // =============================================================================
// File Name    : clk_gating_module.v
// Module       : clk_gating_module
// Function     : Burr free clock switching
// Type         : RTL
// Aythor       : Dongyang
// -----------------------------------------------------------------------------
// Update History :
// -----------------------------------------------------------------------------
`timescale 1 ns/1 ns
module  clk_gating_module(
        input            sys_clk    ,
        input            sys_rst_n  ,
  
        input            i_clka     ,
        input            i_clkb     ,
        input            i_clk_sel  ,
        output           o_clk_out  

);

//******************** siganl define ********************
wire         a1i_1        ;
wire         a1i_2        ;
wire         a3i_1        ;
wire         a1o          ;
wire         a3o          ;
wire         a3i_2        ;
wire         a2o          ;
wire         a4o          ;
reg          a4i_2    =  'b0;
reg          a2i_2    =  'b0;
reg  [1:0]   a1o_dly  =  'b0;
reg  [1:0]   a3o_dly  =  'b0;  

//******************** assign  *****************************
assign   a1i_1 =   ~i_clk_sel         ;
assign   a3i_1 =    i_clk_sel         ;
assign   a1i_2 =    ~a4i_2         ;
assign   a3i_2 =    ~a2i_2         ;
assign   a1o   =   a1i_1 & a1i_2 ;
assign   a3o   =   a3i_2 & a3i_1;
assign   a2o   =   i_clka & a2i_2;
assign   a4o   =   a4i_2 & i_clkb;
assign   o_clk_out = a2o | a4o   ;

//**********************always*********************************
// CLKA domain
always @(posedge i_clka) begin
    if(~sys_rst_n) begin
        a1o_dly <= 'b0;
    end
    else begin
        a1o_dly<= {a1o_dly[0],a1o};
    end
end

always @(negedge i_clka) begin
    if(~sys_rst_n) begin
        a2i_2 <= 'b0;
    end
    else  begin
        a2i_2 <= a1o_dly[1];
    end
end

//CLK B domain
always @(posedge i_clkb) begin
    if(~sys_rst_n) begin
        a3o_dly <= 'b0;
    end
    else begin
        a3o_dly<= {a3o_dly[0],a3o};
    end
end

always @(negedge i_clkb) begin
    if(~sys_rst_n) begin
        a4i_2 <= 'b0;
    end
    else  begin
        a4i_2 <= a3o_dly[1];
    end
end

endmodule
  1. TestBench

`timescale 1 ns/1 ns
module  tb_clk_gatting();

reg    clka   = 'b0;
reg    clkb   = 'b0;
reg    sys_clk = 'b0;
reg    sys_rst_n = 'b0;
reg    clk_sel  = 'b0 ;

initial begin
clka   = 'b0;
clkb   = 'b0;
sys_clk = 'b0;
sys_rst_n = 'b0;
clk_sel  = 'b0 ;
#6
clkb   = 'b1;
#100
sys_rst_n = 'b1;
#1000
clk_sel  = 1'b1;
#756
clk_sel  = 1'b1;
#1500
clk_sel  = 1'b0;
end

always  # 10   sys_clk = ~sys_clk;    //sys_clk     50M
always  # 50   clka    = ~clka   ;    // CLKA       10M
always  # 30   clkb    = ~clkb   ;     // CLKB     16.6M
clk_gating_module  U_clk_gating_module(
        .sys_clk    (sys_clk),
        .sys_rst_n  (sys_rst_n),
        .i_clka     (clka),
        .i_clkb     (clkb), 
        .i_clk_sel  (clk_sel), 
        .o_clk_out  ()

);

endmodule

四、仿真波形

执行模型

执行模型(Processing Model)定义了数据库系统如何执行一个查询计划。

Iterator Model

基本思想:采用树形结构组织操作符,然后中序遍历执行整棵树,最终根结点的输出就是整个查询计划的结果。

每个操作符(Operator)实现如下函数:

  • Next()
    • 返回值:一个tuple或者EOF。
    • 执行流程:循环调用孩子结点的
      Next()
      函数。
  • Open()

    Close()
    :类似于构造和析构函数。

image-20241118105113714

输出从底部向顶部(Bottom-To-Top)汇聚,且支持流式操作,所以又称为Valcano Model,Pipeline Model。

Materialization Model

基本思想:操作符不是一次返回一个数据,暂存下所有数据,一次返回给父结点。

相比于Iterator Model,减少了函数调用开销,但是中间结果可能要暂存磁盘,IO开销大。

image-20241118105733041

可以向下传递一些暗示(hint),如
Limit
,避免扫描过多的数据。

更适用于OLTP而不是OLAP。

Vectoriazation Model

基本思想:操作符返回一批数据。

结合了Iterator Model和Materialization Model的优势,既减少了函数调用,中间结果又不至于过大。

可以采用SIMD指令加速批数据的处理。

image-20241118110928540

对比

特性 Iterator Model Materialization Model Vectorization Model
数据处理单位 单条记录(tuple-at-a-time) 整个中间结果(table-at-a-time) 批量记录(vector/batch-at-a-time)
性能 函数调用开销高,效率低 延迟高,内存/I/O 开销大 函数调用开销低,SIMD 加速性能优异
内存使用 内存需求低 内存需求高 中等
I/O 开销 中等
缓存利用率
复杂性 实现简单 中等 实现复杂
适用场景 小型数据集,流式处理 中间结果复用的复杂查询 大型数据集,需高性能计算的场景

数据访问方式

主要有三种数据访问方式:

  1. 全表扫描(Sequential Scan)
  2. 索引扫描(Index Scan)
  3. 多索引扫描(Multi-Index Scan)

Sequential Scan

全表扫描的优化手段:

image-20241118113337122

Data Skipping方法:

  1. 只需要大致结果:采样估计。
  2. 精确结果:Zone Map

image-20241118113508953

Zone Map基本思想:化整为零,提前对数据页进行聚合。

执行
Select * From table Where val > 600
时,下面的页可以直接跳过。

image-20241118113722074

Index Scan

如何确定使用哪个索引:数据分布。

image-20241118114047331

Multi-Index Scan

基本思想:根据每个索引上的谓词,独立找到满足条件的数据记录(Record),然后根据连接谓词进行操作(并集,交集,差集等)。

image-20241118114343292

Halloween Problem

对于UPDATE语句,需要追踪更新过的语句,否则会出现级联更新的问题。

image-20241118114850271

<999, Andy>执行更新,走索引扫描:

  1. 移除索引
  2. 更新Tuple,<1099, Andy>
  3. 插入索引
  4. (约束检查)

此时,如果不对<1099, Andy>进行标记,他满足Where子句,会被重新更新一次。

表达式求值

基本思想:采用树形结构,构建表达式树,用中序遍历方式执行所有求值动作,根结点的求值结果就是最终值。

image-20241118115507962

数据库中哪些地方采用了树结构:

  • B+树:存储。
  • 树形结构+中序遍历求值:查询计划,表达式求值。

优化手段:JIT Compilatoin。将热点表达式计算结点视为函数,编译为内联机器码,而不是每次都遍历结点。

image-20241118120356183