-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 574 KB
/
content.json
1
{"meta":{"title":"Luhao's Blog","subtitle":"luhao wiki","description":"Just for Learing","author":"Luhao","url":"http://luhao.wiki"},"pages":[{},{},{},{}],"posts":[{"title":"【Graphics-2022】Texture 纹理","date":"2023-12-10T13:54:10.000Z","path":"posts/2022-texture/","text":"定义 图形学中有两个与图片相关的概念,分别是 Image(贴图)和 Texture(纹理),可以这样区分它们: Image:内存意义上的图片,例如 tga, png, jpg Texture:GPU意义上的图片,例如 RGBA8 格式的一对像素组合 因此 Texture 一定是以 GPU像素格式来申明的,并且需要规定 Sampler 的规则。 Color Space 参考阅读: 【RealtimeRendering】5. Shading Basic > ColorSpace | Luhao’s Blog Gamma、Linear、sRGB 和Unity Color Space,你真懂了吗? - 知乎 Texture Mapping 将贴图的坐标映射到模型表面,通常采用一种 $u, v$ 坐标的形式,取值为 $[0, 1]$ 的浮点数。 贴图一般都是规规矩矩的方形,但模型的表面却差异很大,这套映射关系是如何选择的?下面展示三种基本的 texture mapping: Planar:只考虑两个维度的平面映射 Cubic:考虑三个维度的立方体映射 Cylindrical:柱状映射 此时,一个从贴图到模型的完整映射流程是: Texture Tiling 上面讨论的是 uv 映射 最完美的情况(即 texture Sampler 每个采样点与 Image 一一对应),然而实际应用中,这两者很难对上。 Q:当采样点落在多个 Image 像素之间怎么办? 如果粗暴地选取最近的一个像素,那么会导致严重的走样,不可取!称为 Nearest-neighbor Filtering 传统的方案是增加到四次采样,并在这四个像素之间作插值,称为 Bilinear interpolation 考虑到当图片与屏幕呈倾斜角度时,我们需要更多的采样点来铺满该区域!这是硬件层面实现的 Anisotropic Filtering。其中 4X AF 表示需要多采样4倍的pixel;16X AF 同理 参考阅读:Texture filtering - Wikipedia 越多的采样数往往意味着性能开销更大,下面是各种方案的采样数消耗: Filering Samples nearest-neighbor 1 bilinear 4 trilinear 8 AF 4X 32 AF 16X 128 Texture Tiling 当你翻出一张陈年老照片(分辨率很低),想将它作为 4k 显示屏的壁纸时,问题就出现了:待采样的贴图不足以铺满整个屏幕。 此时就需要 Texture Tiling,即考虑怎么将贴图放大、堆叠,传统的做法有如下几种: 下图忽略了 Repeat 模式 Interpolation 考虑下面这种情形,我们需要将半张图片渲染到 (下左图)经过透视变换的区域内。而 Vertex Shading 阶段只会接受三个顶点(及其UV属性),那么实际采样中应该如何确定区域内每个像素的 uv 取值呢? 显然这需要选取合适的插值方法!我们可以将这个问题规纳为一般情形(上右图): 已知三个顶点 $A, B, C$ 及其 $uv$ 属性 求三角形内任意顶点的 $uv$ 取值 考虑最简单的线性插值方案: $$ P(x, y) = \\alpha A + \\beta B + \\gamma C \\\\ \\alpha + \\beta + \\gamma = 1 $$ 通常会获得如下的效果(下图中),显然是错误的。 上图的原因用一句话概括为:ViewSpace 的线性变换不等价于 ScreenSpace的线性变换。 解决方案可阅读如下链接,暂时没理解公式: Conversion between View Space Linear and Screen Space Linear - Eric’s Blog comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf"},{"title":"【RealtimeRendering】5. Shading Basic","date":"2023-12-07T16:02:09.000Z","path":"posts/rtr-5/","text":"Pixel Shading 阶段会决定每个像素最终的颜色和透明度,而决定这些颜色的公式,就是所谓的 Shading Model 前言 这一部分其实更多是面对 TA 向的内容,掌握 Shading 基础,是各种 真实感渲染、卡通渲染… 的技术基础。虽然说大多商业引擎已经实现了非常少成熟的 PBR、NBR … 渲染框架,网上各种解决方案、优化技术也非常普遍,但是作为引擎(图形)开发程序,掌握这些基础的 Shading 知识仍然是非常必要的。 23年底时,曾面试 Garena 的图形引擎岗位,其中一个基础的问题没答完整:“请介绍 Phong Shading 的渲染公式”。这个经历提醒自己,一定要夯实基础,万丈高楼平地起,积沙成塔,只有底部足够扎实,才能爬得更高。 Shading Models RealTimeRendering 作者选择将这个章节放在 Textures 纹理之前,我觉得是有失偏颇的。应该是先有纹理贴图,继而发现朴素的贴图缺少光照,表现非常虚假、平淡,继而引入一系列逐渐复杂的 Shading Models,其目的是 优化纹理在不同光照、视角、及法线下的视觉表现,只不过通过一些 计算调参 的手段罢了。 我们从 第一性原理 出发,先考虑最简单的原则: 1. 将采样的 Texture 简单粗暴的渲染出来,即 Shading Model 是: $$C_{shading}=C_{tex}$$ 参考 LearnOpenGL的示例,得到的效果是明显不真实的: 2. 为什么这个最简单的 Shading Model 渲染出的结果具有强烈的不真实感?想象真实世界的物体,最主要的特征是(太阳)光线带来的强烈视觉感,而这个视觉感与远近没有明显的关系,影响最大的因素是与光线的夹角。 如果难以理解,想象一束光照射在镜子上: 当垂直入射时,是刺眼的白色(假设白光) 当(接近)水平入射时,是接近物体本身的颜色 根据这个从物理世界观察到的规律,我们进一步优化 Shading Model: $$ C_{Shading} = \\begin{cases} C_{Light} & if \\ angle \\geq N^\\circ \\\\ C_{Tex} & else\\\\ \\end{cases} $$ 实际计算可以作一些插值,以太阳光照射水面为例,效果大概如下所示: 3. Gooch Shading 上面都是自己的瞎扯,这里尝试进入正题。Gooch Shading 是一个足够简单但经典的着色模型,它将光照颜色分为两部分区域: 法线越接近光照:使用暖色调 法线越远离光照:使用冷色调 具体公式如下,其中 2(n*l)n - l 是用来计算 l 相对于法线的反射向量,在 shader 可以使用 reflect 函数代替: 如何区分 冷色调 和 暖色调? Cool:偏蓝色的 Warm:偏红色、橘色的 鉴于这个特征,Gooch Shading 又被称为 Cool to Warm Shading,实际效果如图所示: 4. Lambertian Shading 【GAMES101】Shading - Lambertian Shading | Luhao’s Blog 5. Phong Shading 【GAMES101】Shading - Phong Shading | Luhao’s Blog Light Sources 想要更好地描述光源对于物体表面渲染的影响,我们需要对光照这个行为进行定量地分析,这里列出 RTR 书中的分析思路: 1. 光 == 射线 先将 光照对表面的影响,可视化为 一组平行的射线,同时射线的密度代表光照的强度。 对于一个固定光源,不同射线的间距是固定的 $d$ 垂直入射时,到达表面的长度是 $d$ 倾斜入射时,到达表面的长度是 $d / cos\\theta$ 背面入射时($\\theta \\geq 90$),到达表面的长度是 $0$ 因为 $n \\cdot l = cos\\theta$,所以(单位)光照的影响长度为 $d / (n \\cdot l)$ 考虑到光照随面积的分布是均匀的,当单位光照影响的长度越大,其所受光的影响也就越弱。 假设 $d$ 是一个单位为1的值,那么(单位)光照的影响强度为 $max(0, n \\cdot l)$ 2. 有光 & 无光 PS:这里有一点困惑,假设自然界完全无光的情况下,那么物体表现应该也是纯黑色? 本章将物体表面区分为两种状态:无光、有光的环境,而最终的呈现是这两种结果的组合。 无光:$f_{unlit}$,阴影中死黑的部分 有光:$f_{lit}$,取决于光照公式的选取,如 Lambert、Phong... 光源颜色:$c_{lit}$,通过缩放还可以表示光照的强度 此时,对于一个光源的光照公式可以表示为: $$C_{shading} = f_{unlit} + c_{light} f_{lit}$$ 如果扩展到多个光源,那么有: $$C_{shading} = f_{unlit} + (\\sum_{i=1}^{n}c_{light} f_{lit})$$ 结合前面 Shading Model 介绍的 Gooch Shading 模型,我们可以为上面公式套上: $f_{unlit} = (0, 0, 0)$ $f_{lit} = f_{Gooch\\ Shading}$ 此时就得到一个完整的光照模型啦~ 但是考虑到自然界存在着无时无刻不发挥作用的间接光,这里将 $f_{unlit}$ 取为全死黑,显然是不科学的,后续可以继续改进。 2.1 方向光 Directional Light 是一个最简单的光源模型,象征自然界的太阳。它具有如下特征: 方向 $l$ 恒定,因此又称为 平行光 光源颜色(强度)$c_{light}$ 固定,不考虑任何衰减(伟大的太阳!) 没有位置的概念 因此在 shading 中可以考虑如下定义: struct DirectionalLight { vec3 direction; vec3 ambient; vec3 diffuse; vec3 specular; }; void main() { vec3 lightDir = normalize(-light.direction); [...] } 2.2 点光 Point Light 象征自然界的电灯泡,它的特征如下: 方向 $l$ 向所有方向均匀发射光线 强度 $c_{light}$ 随距离衰减 有明确的位置概念 RTR 书中使用下图解释 强度随距离衰减。考虑到单位光线影响的范围,随距离 $r$ 的增大而平方增长,因此:光线强度与 $1 / r^{2}$ 成正比。 注意,这个衰减并不是因为 能量随传播的衰减。 因此,对于距离为 $r$ 处的光源强度可以表示为:(这里选取了一个参考值 $c_{light_{0}}$,表示光源在距离为 $r_{0}$时的光照参数,你可以通过度量等方式定义它) $$c_{light_{r}} = c_{light_{0}} (\\frac{r_{0}}{r})^{2}$$ 这个公式称为 Inverse-square law - Wikipedia,即平方反比定律,物理学中存在非常多类似的例子,例如:万有引力定律、库仑定律… 在实际的工程使用中,它存在一个非常明显的问题,即当 $r$ 无穷趋近于0时(或者干脆取值为0),那么光源强度会是一个趋向无穷大的取值,这显然是无法接受的。对此,商业引擎有几种优化手段: Unreal:距离叠加一个极小的数,实际取值是 $1 cm$ $$c_{light_r}=C_{light_0}{\\frac{r_{0}^{2}}{r^{2}+\\epsilon}}.$$ CryEngine:限定 $r$ 的最小值 $$c_{light_{r}}=c_{light_{0}} (\\frac{r_{0}} {max(r,r_{m i n})})^{2}$$ 物理学解释 从物理学角度解释,CryEngine 的做法更科学。因为$r_{min}$在物理中表示发光物体的物理半径,比它还小的距离,对应着光源内部的着色表面,这在现实中是不可能发生的。 实际开发中,为了性能考虑,我们希望 光照强度在某个有限的距离处,能够乖乖地衰减到0,因此会引入一些距离衰减函数来实现这一目的,其中甚至包括指数衰减,这里不详细介绍。 另一方面,为了让点光的效果更贴近现实,OGRE Engine 引入一些复杂的衰减函数来实现点光,参考阅读: -Point Light Attenuation | Ogre Wiki 2.3 聚光灯 TODO: SpotLight 多种光源对比 从左到右依次为:平行光、点光(无衰减)、聚光灯(有衰减) Anti-Aliasing 抗锯齿的部分,看 GAMES101 时有作总结,这里不详细展开。不过有机会还是希望实践落地 SMAA、TAA … 【GAMES101】Anti-Aliasing | Luhao’s Blog [HPG 2017] MLAA from 2009 to 2017.pdf 半透明 TODO:这部分讨论的是,光线穿过半透明物体的效果。 显示编码 推荐阅读: Gamma、Linear、sRGB 和Unity Color Space,你真懂了吗? - 知乎 伽马空间与线性空间详解-腾讯游戏学堂 Gamma and Linear Space - What They Are and How They Differ Gamma Space Gamma Space 将颜色输出为 2.2 次幂,所谓伽马矫正是指如下公式: $$C_{gamma} = (C_{linear})^{2.2}$$ 为什么会引入伽马矫正?一般有两个原因: 传统 CRT 显示器的设计原因 人眼对暗部辨识度高于明部 以第2点为例,我们可以理解为:如果在 0% 和 50% 明暗处分别增加 10% 的亮度,那么人眼对前者的感知更加明显,这也意味着人眼对暗处更敏感。换言之,我们应该给暗处更大的存储、展示细节。 如下图,中间是标准的线性空间,右侧是 Gamma-2.2 空间,可以这么理解: 左一:给亮部更大精度 右一:给暗部更大精度 显然右一更符合人眼的观感,这正解释了为什么要将图像转化到 Gamma 空间!另外,我们购买的显示器一般也是 Gamma2.2 空间(又称为 sRGB)。 虽然 Gamma Space 更符合人眼的观感,但是它不利于 Shading 的计算,因此一个正常的渲染流程是: png/jpg:sRGB shading:linear display:sRGB(取决于硬件,一般是 sRGB 空间) sRGB sRGB 又称为 Gamma-0.45空间,即会给亮部更多的精度和细节。 当 sRGB 叠加一次 Gamma矫正 之后,就会得到一个正确的 Linear空间。 参考资料 Real-Time Rendering 4th Edition学习笔记(四) | Logiconsole Shading Basics - 就决定是你了 | Ciel’s Blog"},{"title":"【Graphics-2022】图形API","date":"2023-12-05T16:46:13.000Z","path":"posts/18QJ9Y1/","text":"推荐阅读: 2022图形引擎-内部资料 概要 为什么需要图形API? 暴露图形硬件的功能(GPU),并抽象出高->低维度的接口 用作 realtime rendering 图形API发展历史:(主要是15年左右诞生 Metal、DX12、Vulkan) 现代图形API的发展方向: 降低 CPU 性能瓶颈 多线程 优越的开发能力 从 High-Level -> Low-Level 角度来看: High-Level:意味着封装层次更高,性能较差 Low-Level:以为这封装较低,学习成本陡峭 整个完整的图形引擎调用栈: 图形API vs GPU 图形API = Resource Manager + Commnad Producer GPU = Commnad Consumer + Execute async 从 生产者消费者模型 理解: 图形API:从CPU端 创建资源 + 产生一系列 DrawCalls GPU:指令的消耗 和 异步执行。 GPU = Async Execute Engine 一个异步执行的引擎 CPU 永远领先 GPU 1~3 帧 Single Commnad 一个基本的 CPU 渲染指令,应该包含如下要素: Command ID 操作数(注意有大小限制,后面会说) CPU地址(GPU可访问的) GPU地址 问题 如何向 Commnad 传递一波数据? Method 1:直接传递操作数 (<64 Bytes),一般只有特定的API支持这么做,例如 vkCmdPushConstants Method 2:拷贝到显存(GPU可访问的),然后传地址进去。注意避免 CPU写 + GPU读 的情形发生 Method 3:在Method 2的基础上,通过 Blit 将数据拷到 GPU内存,然后传GPU地址 如果 Blit 一次,但是 Read 多次,那么收益比较高 API结构 前面说过,图形API分为两大类:Resource Manager + Command Producer: 以具体的API为例: API 一帧的调用 Create Resource Texture / VertexBuffer / IndexBuffer … Set RenderPass Set PipelineState Shader / BlendState / DepthState Bind Shader Resources Uniform / Buffer … DrawCall Present 1. Resources 第一步是资源管理,对应 Create Resources 的部分。根据资源的类型还可以细分如下: Resource Memory 上图框出来的部分,需要注意**内存的开销j( Buffer、Image)。对于图形API中的内存分配方式,一共分为两种: 自动分配:DX11/OpenGL/Metal 手动分配:DX12/Vulkan/Metal 对于手动分配的方式,有一个好处是 resouce aliasing,多个资源可以共用一块内存(真节省呀!),参考阅读: Vulkan Memory Allocator: Resource aliasing (overlap) Direct3D 12 Memory Allocator: Resource aliasing (overlap) 内存架构 对于PC端,CPU 和 GPU 都有独立的内存,有如下特点: GPU 内存传输快于 CPU (主要是带宽高,数据bus设计原因) GPU/CPU 之间传输很慢 对于移动端,CPU 和 GPU 共用一张内存,有如下特点: 考虑到低功耗(带宽变小),内存传输非常慢 GPU 部分有 Tiled Memory 的架构优化 Memory Types 图形API中有不同的内存类型,区分如下: Default:默认是 GPU 内存,不支持 CPU访问 大多数资源的选择:buffers、textures、rt Dynamic:指 CPU只写、GPU只读 的内存 需要CPU每帧更新的资源( todo:举个栗子) Readback:指 CPU只读、GPU只写 的内存(使用情况比较少) Memoryless:适用于 TBR 架构 2. Render Pass 对于 Render Pass 的定义:不切换 FrameBuffer 的连续 Drawcalls。 Render Pass 的性能需要关注两个操作: Load Action:注意 DontCare/Clear 是没有带宽开销的,只有 Load 需要注意 Store Action:将 FrameBuffer 写回到 主存,开销大头 3. Pipeline State 通俗说 Pipeline State 作用是控制渲染状态,现代API通常将所有状态打包为一个大的 PSO (Pipeline State Object)。 如下是 DX11 的示例,需要手动设置 Shaders、Blend、DS、Raster 等所有状态。 Shader Compilation 将 shader source 编译成 跨平台的 bytecode/glsl 将 bytecode/glsl 编译成 machine code(ISA) Patch the Shader (Todo):例如为 Binning Pass 产生仅包含 pos 的 vertex buffer 重要点 Pipeline State 创建非常慢 Shader 直到 Pipeline State 创建完,才能明确所有属性 尽可能早地创建 PSO 4. Shader Resource Binding 这阶段是为了给 shader 设置参数,以 DX11 为例: 每帧重复设置 逐个参数设置 反思一下,DX11 的设置方式太落后了,作为现代API,可以分配一块GPU内存专门用于 shader 参数传递(核心思想是 cache): 5. Draw Calls 核心是如下三个参数: Indexed:需要绘制的 vertex、index 的 下标 Instanced:一个 drawcall 绘制多个物体,CPU端 需要 buffer 存储不同的信息(如 vertex,pos) Indirect:GPU-driven rendering,同样是一个 drawcall 绘制多个物体,区别于前者地方在于,是 GPU端 填充 buffer 信息 6. Swapchain Swapchain 会持有如下资源,呈现最终的画面(Presentation)需要如下步骤: 从 Swanchain 取一张 image rendering 整个渲染流程 设置到 composition(因为有多个窗口) TODO SwapChain、Presentation 这几个部分没听懂 优化 应该同时对 GPU、CPU 进行 Profile,如果不是性能瓶颈,请不要 过度优化。 基本的优化手段: 降低 DrawCalls:Instanced、Indirect 规划绘制顺序:eg. 通过 state、distance 等排序 降低 带宽:合图、mipmap …"},{"title":"【Graphics-2022】渲染管线","date":"2023-12-04T17:31:13.000Z","path":"posts/2GFAABV/","text":"IMR、TBR、Pipeline… 推荐阅读: 2022图形引擎-内部资料 剖析虚幻渲染体系(12)- 移动端专题Part 2(GPU架构和机制) - 0向往0 - 博客园 Todo: 与 【硬件】GPU架构 | Luhao's Blog 这篇文章合并 硬件架构 如果仅从 CPU、GPU、Memory 三者的角度考虑硬件架构,那么可以分为如下两类: (左)分离式架构,CPU和GPU有各自独立的内存和 Cache,通过 PCI-e 总线通讯。其特点是:高带宽、高延迟,性能瓶颈是数据传输。主要应用于 PC 和 手机。 (右)耦合式架构,CPU和GPU共享内存和 Cache。主要应用于 PS4 等游戏主机。 IMR Immediate Mode Rendering,通常指 PC 端的GPU渲染架构,其特点是:所有渲染管线中的读写操作,都直接由 GPU(紫色) 和 显存(深灰色) 之间完成,如图所示: 其中 ↑ 表示读取显存,如 Vertex 阶段需要读取 几何(顶点)信息,而 ↓ 表示写回显存,如 Visibility 测试阶段需要写 Depth-Buffer。这些会带来巨大的带宽开销,IMR结构通过引入 L1、L2 Cache 之类的结构来尝试优化这部分带宽。 移动端 由于 高带宽 导致的 高功耗,IMR架构对 移动端 的 性能 是致命打击! 因此 移动端 大都选择 性能友好 的TBR架构 如何理解 GPU 的 高度并行化? 参考阅读: ##GPU 架构发展历史 假设 GPU 每个 core 一次只处理一个 vertex,(NVIDIA架构)每个 SM 中包含 32 个cores,那么就可以同时处理 32 个 vertex,这些统一称为一个 Warp 因此 Warp 的数量直接决定 GPU 的性能 问题:Warp 之间是可以并行执行的吗?为什么图示是并发执行呢? 光栅化阶段,最小单位是一个 Quard(即包含4个像素)。以下图为例,其中绿色表示通过光栅化,黄色表示因为 Quard 而被保留的像素。 之所以使用 Quard 的形式,是因为便于计算 ddx, ddy。这个在计算 mipmap 的时候有奇效。 TBR Tiled Based Rendering,一般用于移动端GPU(例如 Mali),通过引入 Tiled Memory 降低带宽的读写功耗。 优化点:先写 Tiled Meomry,再实现 Blend …,最后再写入 DDR 参考阅读:[译]The Mali GPU: An Abstract Machine, Part 2 - Tile-based Rendering | Litmin的笔记 参考阅读:剖析虚幻渲染体系(12)- 移动端专题Part 2(GPU架构和机制) - 0向往0 - 博客园 很多及计算机技术,都是通过引入一个中间件,TBR架构就是典型的例子。 面对一个巨大的汉堡,一口吞不下去(IMR带宽高) 尝试将汉堡咬成多个小口,慢慢吃下去(TBR低带宽) Index-Driven Vertex Shading (Mali) IDVS 是 Mali GPU 的优化技术,考虑到传统的 vertex shading,即使经过 50% backface culling(也可能是 frontface culling),顶点的写入数量也剩 50%: 优化关键点在于,既然有 50% 顶点是注定要被 Culling 掉的,那么为什么要写入内存呢? 因此 Mali GPU 将 Vertex Shading 细分为两个阶段,分别是: Position Shading:位置着色,发生在 Culling 之前,只转换顶点位置,因此输入只有 pos Varying Shading:可变着色,发生在 Culling 之后,只处理通过 Culling 的顶点的其他信息、操作 假设 顶点 Vertex 的分布是,按照 pos、npos 的比例为 1:1:(通常情况更为复杂,eg. position、uv、normal …) 缺陷是:需要CPU将这些信息分开存储 那么 IDVS 技术起码节省 50% 比例的 npos: 进一步分析,通常 Varing shading 开销一定是大于 Position Shading,所以 拆分两阶段,能够让 Varing shading 充分享受到 Culling 的收益。 Binning Pass (Adreno) 参照前文,既然某些 Vertex 压根不会参与最终的渲染,那么有没有可能,这些都不用写到内存呢?(节省掉 0.5 Vertex Write)。 Adreno 引入一个 Binning Pass 的技术,在 Vertex Shading 阶段(同样先是只处理 pos),将所有的 visibilty 写到内存中,避免了顶点信息的写入。 Adreno 基于 Binning Pass 的渲染架构如下图,继续悟一悟: Hidden Sureface Removal (PowerVR) HSR 可以直接理解为 隐藏表面的剔除,传统的 OverDraw 是通过 Early-Z 避免,而 HSR 可以无视绘制顺序避免 OverDraw。 如下由远及近绘制时,不会对遮挡的像素进行任何剔除,但是 PowerVR 做到了! HSR 实际做法是,在光栅化之后,写入一个 Depth-Buffer(降分辨率) 到 Tiled Memory,在后续的深度测试中也会依据它进行一些 Culling。 RenderPass 对于 RenderPass 的定义:对于渲染管线的一次完整执行(连续地往 FrameBuffer 绘制对象的一组行为),如下: RenderPass 的组织形式对于性能影响非常大,如下绘制一个Scene(电视机中是另一个Scene的画面): 下图展示了两种绘制方式: 左侧:3 Pass,先绘制音响,后从 FrameBuffer 切到另一个 RenderTarget,来绘制另一个Scene,接着再切回 FrameBuffer 绘制整个Scene 右侧:2 Pass,先绘制另一个Scene到某个RenderTarget上,然后切回 FrameBuffer,绘制音响和电视,并将之前的 RenderTarget 当做 Texture Load 过来即可 分析以上两者的绘制开销: 左侧:3 x store,2 x load 右侧:2 x store,1 x load 由于 Load/Store 操作依赖带宽的开销非常高,因此 2 Pass 的方案显然性能更加友好。 Frame Graph 为了优化 Render Pass 的绘制顺序,更好的配置 Load/Store 的关系,可以引入 Frame Graph 一种有向无环图: 有利于渲染并行、排序 有利于性能优化,降低 Load/Store 开销 Compute Pipeline Compute Pipeline 的意义: 在多线程组(thread group)之间共享内存。eg. 在一个 Quad 内计算 sum, agg ... 读写 Buffers(例如 FrameBuffer、顶点数组 …) 如下是一个 DX12 的渲染管线示例:"},{"title":"日志:2023年12月","date":"2023-12-04T17:24:09.000Z","path":"posts/2023-12/","text":"b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475def1af0b6bc61f7912497bd2791ef1396a9bcf0f6940df039a86f945fe75789ec31640a75d317a8abe2cb024b5dba86bfd34e4bd9e09040b251abc3b0833849a256141b075696212f5ac257b95a02cb70c1fb6c1dfa0046c0c4e50ab7d1f131976105380b96af668fb462122068b4e163024b111dfb4e1f0aa6d1310bb266a940e5f9b2c00815b2e6c6d5c94f661147bf2459a3e427b075205c997af090859dcc0f6a2bc00983015e3974124a37d5bb1246404d8c761a8886603c25b1334d4f7ea42576e8e59e547705dba29592e2cc07c9622468e190523cc899663124a6fee22d9435faa3710d6d951e354c26503744daa3dc9b0a026e22639fbc17e6a82f8312dc213906656a56642d95a9fdfc114c85c8540fab525816f8958fd5b6f53fc3ef8119c6b4d6d470437fd6854f6edf1e1fd2c0667d781a5ca4b2eaa5b59d08cf6f503e6c1c1716df8cddbaa7077355b9c35f46447967fa3afcb8c5e35b4e8be3e75565d1a79a4648dfb95e597a6438ca9be1f00c1d2cb89bf9e31e6636d0b2cbd3b93991a6287036cdd3191edd7d8c0a4a0ad5645174358886f2809699484f27ef51d5750abddb669777bc9c2551ae9e1f8f702d5c768c891ef0f863fee9f04a8bfa74057c9c4726dd0dc296eeac2ee9abb448bde504c647aa4a6aa58adb988e016da96fcb237dafb64428b08d8e555e3ffcaa58d922ade78131e10c0268c50acf1b088cf0fbbb1244cf55393cb140bc82719b916667da06ea72f4acc93d1158429630cfd3f55eeab3bcdfdc1f04ad2a3582fd8caf885efc77037ffc2881207a72f5ce14a84a89420d71f2faac7a4d5c5951eea7857660e838551e953814f74ba86c1af6a32f10faf1f664cb9e71bc8cc70ab2f1f27b225ab4b49c3239e553b52eab6cb83c52af2e426e3bef0cb3e40f889303d05b7395082ef77df9914fa9488908bf9c53de603ff1bf4fade763ffac122df68870941919379609bdbc9496fef29ccf6be4a4ce63bd0f5f3776155c10bbbcbcb2912fabe4fb11a0114033af102435c18d2c90e2ae7c1900332e2e1febea5073592e62393b6838dda6f1259a55e4b96d38f3177c567fd15fe49dc6101deabc8902f620ed3934afc4101ba3938cd2fac1d50ad93bb14e59c7291fea9d4093e0b53eee165a0e95805046078351f84adc55f717d3c45cb5f411a8530ad2aa3fac0e7e585810114c4026c7f80cc0ea864ff027fe2680e0c395613d41e37723981d361ff6ee94b5b557bc27694b8bdd685d4ac7da7c10d9313e4fe0b527225ac4dfaa297c60eca2d9cc219803e87a778e67d34ef1babff9b90f64b1a2f83a0dae204e054dc09fe8c315e0d99c83f381c852e232bc133afd86f4405b475ffd98161bd72d86142779ae8ce14b7c62cb4b3c7b630b641752173e5264ac8881eaf6454aaf711a10151842f176a7c78bafe7459aeb3bb3a4c4eebca83c2f4f89f72c174edc64c6d203422546275db1892f94c8127f800f4c3703aaab1870f9b15ddae3d991242919c22650f7a5db341194fcede17fdcb103e474bfd85adaca0c500848d478807cd8d2dd2c03a52d955bb1178aa5cfc0ce09f850e8490e5e3557866d33e2fa0d9adcf8cca0d34330cbb825c8f80b82b0c3367e0aa2b0a70ff43d0a36070819d0ebf25ee0923df066f97616de4914f9c553fa7deb7a5749a9fecb8d38c3875f1a02ce323637d8c52d0efa2648a4afdc329c597d0606b5ecb668b451f7af43d75ea1301cc41347ac427683e6e741daffab57a269c6f955e551a02910b6f01f12b0198bdace5564b14bd93666a3a990410bd6b915552dc60f214bbc76fa227ec168fd67b291c884e2f91c4ccc7f57d2985ce42b45acfda732ac8b545760dd69743c833ea224484d87eee20d032b8125917289cc3aa06941fd4e8a0d1c51ae69d26c2ab94b9f832eb282ba49f14d2407d5d9e9ec73683bd45af6447e521d5f37ab0e0311c972a9f55bb4f0f9f136691edaadbde4364628cdeb85484b79c64d7b78f2739e20bfd6fca0d85ef8c6a53fdc1e9f431e40a932d7d5bd69aec16615845273ea5e32b841e334ef57d425bb34c8c419b04c8c8f72e84a3a27d7bd43d2d5af942daef994d655b7e65b7332fa77abc971ee34db4b0bf180c26c64ac576808300712580c105966b456805c45262ae40f12566c448bf154af8401710033cb9b63c26f1a017cdeaf8658bd9def6e1ebc7ca65b1b7a2cbb60faafd4dfc195983ab2eeab90cd9c3fd8fa2f3a506fda37682bb846543df3dea39200e8ae30bda3c2834571dcd7b2fbd1e7d3791f0aad045c052235a5df58ee4c151412e5b97ec760f77f907dbf203842f6b421e5a737f396ecfc85624bf58cc70aab2aee0acc18d35c46a8577d4b3efd239b2a868b413262d75b9d6aeffaf18c3fb02d1f97aa433e8f422e4ea77f3b3faba6b09e747f009520f39a44336b7c61e2b214b20931bfac09dd7034a281bd0a6f3a7694256cc4bef53343dd59785c3afff662a8f6bfd776f8d8ec265559d2cdfc1a1574ddd229d7a67001b87013d42db8b1ba73e4d641bfe8f3b37068716e172559cc5e4b98532a5bd2315e81c66a07b68a9a4ff0a2728c45d423dd33b3a03e2fddaa30edbfa838f0670e2ee9d181e3d086fd3afd4849523f71d8e4d188ed0f4bd2296dee768ed789c7728b0b5a0003c6e57363e9408c8c1d0ce64bf818cfaab5d9e42b855fc6083f018b0b7fb341b4c2f4f8c71dc2b9df04554703395b1a82e0dbf96cb415b9e617e2e23a12153f23b9797377c892c3b0104565ed2ebca1835fb7ffe5d9f69d3093b54b76255468f552b4fa247287efca8394604ae4c3e0e74e1d6932380d7d0dd17a82f3dae2c3bedba8cc3e88c50b8d32abcacdc2e64189b75715ee6ad50f856f8ca9d255137bfc61a396a3363702348656ed725020b758c5fbe07ea0898521e7d1fcd0073837cc960fd27122614a804638f0b0a384beaf270277e4a2416de67f6ca68aaa9996725b88e4b61e540c2a39f4cdfdf917002567c8133b08c1572c46245dcd86316192d298d4892f81d3d9fb11e69aa42eb68c5a1a37e0457c093885917f8f69fc438a754cce35ba84811c1721e0bce633ba9eb4007e7e0651dd8f925c5fdbbe13a21f44bcbb9c40089288a2d12c48480a1c8e21bfe3cf3699e1f1d2ebb39a90e22927f6c1dc87eab34fd85fde5c5cc2bcee76b2d70b870431c187c1ecb0cd4917ab291ae2bf0b890c676e52637065ef2cb2721f68223c8f350 Hey, password is required here."},{"title":"【工具】Obsidian笔记","date":"2023-12-02T09:36:00.000Z","path":"posts/obsidian/","text":"构建 hexo + obsidian 工具链 诉求 提高笔记效率:什么是 Zettelkasten 卡片盒笔记法? - 知乎 提高 hexo 开发效率:搭建 hexo + obsidian 环境 todo:还未掌握 obsidian 的核心功能 功能 汇总一些 obsidian 值得使用的功能和特性 Features Hexo Obsidian 备注 Adomination √ √ 不兼容 Todo Task × √ 有空实现下 示例 Todo Task [ ] 测试 todo 功能 - [] xxx [x] 测试 todo 功能 - [] xxx ✅ 2023-12-02 插件推荐 核心介绍如下插件,对 hexo 有所帮助: 1. Templater 通过自定义 post.md 模板,可以快速创建 hexo 文章。目前需要定义如下信息: --- title: xxx date: <% tp.file.creation_date() %> abbrlink: toc: true thumbnail: /images/default.png --- 这是摘要<!-- more --> 当通过 Ctrl + N 使用该模板新建文章时,就会生成默认的 *.md 插件开发 官方的插件开发文档,但是很残缺: Build a plugin - Developer Documentation 具体步骤 先clone官方示例的插件, 在此基础上做开发 cd .obsidian/plugins git clone [email protected]:obsidianmd/obsidian-sample-plugin.git * 进入目录 ```shell cd obsidian-sample-plugin npm install npm run dev 此时重新打开 obsidian 工作区,新的插件会出现在设置中 如何发布? 通过 npm run build 生成必须文件,然后拷贝到插件目录即可: main.js manifest.json style.css 如何调试? new Notice():弹窗信息 console.log():输入 ctrl + shift + I 打开debug窗口 Reload? reload app without saving:通过 ctrl + p 呼出 自定义插件: Luhao’s Attachment 背景 由于采用 hexo + github.io 的技术架构,文件结构为: * /posts/obsidian/index.html:名为 obsidian.md 的文章 * /images/*.png:存图片 因此在任意 *.md 中插入图片,需要采用如下格式:  但尝试一圈 obsidian 插件后,发现不支持带 ../ 特殊字符的替换,因此决定自己动手改造。 为了避免重复造轮子,挑选了一个插件魔改: 593413198/obsidian-attachment-management (fork from this) 主要修改如下: 修改默认 setting,如将 attachPath(插入路j径) 写死为 themes/pure/source/images,将 attachFormat(插入名称) 写死为 ../../images/${notename}_${date} 已经打包发布 release 版本,链接为: Release 0.1 · 593413198/obsidian-attachment-management · GitHub 使用效果为(图为 obsidian 中截图):"},{"title":"【RealtimeRendering】3. GPU","date":"2023-12-01T15:50:56.000Z","path":"posts/rtr-3/","text":"本章主要介绍 GPU 的硬件架构和管线,重点理解 SIMT 和 Warp 的概念,并结合实践理解 VS & PS GPU硬件 关于 GPU 的硬件架构,参考 这篇文章。 Warp:GPU并行处理任务的硬件形式,一个warp通常包含 32 个线程。 SIMT:Single-Instrucion Multiple-Threads,多个线程执行相同的指令,高度并行化。 GPU管线 下图是GPU的整条渲染管线,其中绿色表示可编程部分,蓝色表示固定部分,黄色是可配置、但无法编程。 可以结合 renderdoc 的 pipeline 试图理解这一整个管线: 对于可编程的 shader 管线,虽然最终GPU硬件执行的是机器代码,但是多家厂商退出了适合编写的高级语言,例如: HLSL:DirectX High-level Shading Language GLSL:OpenGL Shading Language 通常 shader 会允许被离线编译和存储,这些被叫做中间语言: IL:Intermediate Language 图形API 每次图形API调用时,shader阶段都会包含两种类型的输入: uniform input:指在一次 draw-call 中不会改变的常量(如投影矩阵、texture) varying input:指在一次 draw-call 中会变化的变量(如顶点位置) 图形API的发展也十分迅速,如下图所示: OpenGL 和 Vulkan 显著特点都是跨平台 Metal 是苹果与 2014 年为自家产品研发的低开销的图形API OpenGL ES 是指转为移动设备研发的 Vertex Shading VS 通常是可编程管线中的第一个阶段,前面有说过,它的输入就是一堆 mesh 的二维坐标(附加 color、normal 等信息),一般有如下用途: 顶点动画:草、角色 地形高度:通过一张 height-map 模拟地形的高低起伏 程序化变形:模拟布料、水面的运动 粒子特效:创建一个粒子发射器 … Pixel Shading 这阶段是基于像素颜色作一系列计算,其性能也与分辨率和Shader复杂度直接相关。 MRT 早期的GPU,仅支持将PS的结果输出到一张RT上面,这样的绘制效率是很低下的。 MRT(Multiple Render Target)技术的推出,支持在一个pass绘制对个对象(GBuffer),这催生了 Deferred Shading 的诞生,这里不展开描述。 ddx ddy 硬件提供了像素求偏导数的接口,由于GPU并行处理像素点时,会将 2x2 个像素放到同一个Quard中,因此计算偏导数十分方便。 换言之,这三个像素的 ddx ddy 取值也是相同的。 ddx(v) = 该像素点右边的v值 - 该像素点的v值 ddy(v) = 该像素点下面的v值 - 该像素点的v值 推荐阅读 mipmap_level 的计算原理 Compute Shading Compute Shaing 部分缺乏工层经验,后面来补空缺吧 DX11 引入了 CS技术,它充分利用GPU并行计算的特点,使其扩展到 深度学习、神经网络、量化计算 等复杂的计算领域,而不仅仅局限于图形学。 它的优势之一在于,可以访问任意GPU上的数据,具体有如下应用: 粒子系统 Culling 景深等"},{"title":"游戏引擎岗要求汇总","date":"2023-11-30T12:43:01.000Z","path":"posts/3K6XB0W/","text":"b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475debb79baf718116b8398ebb7a36965f5712db04a990a04196bf7d89b9188d2f59696c274d67c5d338176316d0990e58740237b16c70080fbc3d186f48f65ead60c9f3efb14c16981a609c6df80b790f379e6b80f20ef476aa62233182f1be3173b510d09a093b5d25c7164e684586e9473ef084c4f40e2dbda8db3191087876f0c531d728a5d1f4d23091f9671910335bdd2dc460cf799674f06ca53e241a6a722de1fd81291bb9a501fcb5a6c362b80ee2ff346f53bed741db17b867184dc18722db17c8cdb37a99a8973386368fab795f91e4a480544b15380df3928c3ac7402e2ad0413032d30ed13b2b4080cbe28adc7a9539cbdf3ec9351fbd236fd587fbde15dbcb4010ca92202814315437c081c4c06a533032d0160baea7e0ee8fe85bf7e6c31851b65834dfe4b05ac88de74a61af7c67c4d3e749e09db00f6397cbccd52e6b2bcb04e043643b6996a587a228936b537377f69214573b844fc9c0ce0ac37729a985faab847d80733cb1538a56907f96b215a1db2de50ef00c7d093b717e3c3e3fca1befc064c0dd0777403eda61a876757d07d9a4fcd4219ab115fdadf41483cf5cc776162b659b42edbe066104f8f83c7a8a3bba84aa2c9a0b5be0e75e6b8c6bcbd4fca788c536bfc0783504d5a5cbc5110b0d2799cb6366cab37538acb97cc29005709c1083cc134519c08a9027d97d8c3fad8556e516435723465e677b68d6f1c1845160bc92f94fe8918a209f5c741e4525f16e877714d16e42f143a1668f46787ba09645c4309424cae18bdc04952a058e8c426677c54143f95c548671fe6d3bb1e4a7b5288a52ee4ef6b09ce8a2bf41922cdb08af1c78edb0dfc3e0d07e7a0dcb282156645c3a498946ad854b6c4d1ff25abf9edb9a0d77de321437b76b92eda816c0315484daeb4385812b7d7f44b1b19a98d5cab10e89399e50cbe83685931f879617afca50cb67725623bff904ca86f9eb022ba29d39458c30953be5ad06278c28d1b30cae0493be7004b649f742c79eaf2e39f8fdcec066f5ecf3bb4d1847d080cec3cf2a7f02f65e9be3653542e80b392788c3bf2e1c71c3048ba8b02604d6f84ae52c22fc414ed73a5f117a50e053d547ac6fab8dd12a35cb3853d679a5184c6d207486f6f1e922e10f7dc47538bfed34e52ed540f65a9b1bf7fcfa6544b7f546c30238152b62b8c3865c50661f365c792c624690b8f304945edd7f9aaa917c8d2ca46bc1ece0fcaa191fff959b902d92fde78ce97a8f560ddc49524b070259f067d2ac966bc1aa345dbb831e5243db9eca324f4947bf4187df5f60201dc5efa81ba85474939e5ea86851ce248ed2253a3cbc69028789576b5fb51ed4c2bad29cc637ce36c341aac3bd2a86d2383154e25b803b1004facbc505b52710a67f1bb9cdce38104133136316cfc9ecf937e3ea40ec87cb1501e6bdfdceb15b979e26b89133cc26f39a73e17ed206a81dc353da0fd49e6e4739ac40ee145890a5d96cb2ffd1e7a0faaa6cd03fc852802fb097e261d58396c0a987368480aa8daa89426b90b48a43c7fae6d65dfa976ff0712281cd0bab23333f90fb1fa708da5e28386c39009faad83daa2ba9de1f1d81252f70712142cb6a686175603c032442bcca0fe95041d39e728df69d417a1b22eb7d013bf257c65f1eb5d415924423231c84cc33760c19379f8731c851f677963494dbdc9a4f1b74b672ffd9f42cac9729ca2f43569a61f507ef8f364ce9ba22f71a343c9d86bf69d3d349d28867e0cc2af9222e5a9afa0a0c42d2a3198d318e5b0ca804b2a9b4d9ec6881623f4e7f26a633cf096e3f5676bcb15416d3f904937bdea2bce2b5ecd6d45d6c84e8e66f828e8d532eab2e7e784a853ace44cb35b26cc1add287838b2439320bae546673c3319109cdf7f0da5d1ace15e69aad5218699460fff3dda1b7b032a8088957495fce20cbf8b8080c84e0a3ed3216c7693789a4c08c3b4eeb623869ec051fc9d022abdadcdf44637b849b117cad419b40df12345f9c918dc71a5448e800fa6bc76f6f8858f5819dd1a83874c28b71a80abcfb9801979971f31bb038fccb0b576657e84edc6939095c095aac95f2eb4d828e4d45b83f5e728ca4c78f9455a2856ccd4cc41afa34d5d6f273dc2d995d98112474bdb67c478ba1738f9a429e3320985d19b9480f4dc19ea4b5de91f6c6d680af49dc8692fd15d69e62153f76db26d4681e14f4e30cdf20b427eb643d498af62b5bf66201b7f1b02302f6699aa1639200726fc153f955cfc5ea69186c3a414602764aec0071298ed540fad5f459c26fae893705cb83d2049458b19c6afd389206847ca503cf11e08228c7b858ec02219840725abf66d0d203b61ccb0bc15c16cbef01bfd8eb0dc0bdf63ed8b8b64be1bb1add8c1bb0bb3d1d099f9fb5bcabf05ef3f1b41b4142747bc25889ec03715a701dcd6b4995bbf1fad6aaf9f43891a797eb4d22e1fcedecf000921678fa63fc711479bd2bf193b0abbfdfb654c5d0eada2146e2d78f5f6bfb29b554dedbda140371ed26acfbd39f865655b5ccab336d942a7c9b352202e53a8e01e446b0340389ee7195b4b5fab00c46d1c3895ad8495dabcd32fb4fcc5fa36d879a419e65a2d59e58ab10085f223a80935fb3490a46ded7f7dbb1702be610c16ed8a560113ce4a0eaf7899bf08209106d10105f976ed591694ac165dc2c960e771cbd06cc1b1fd3cb457261c216a8948c8146eb9cf86cadf24f06e7daaaffd9478cf5be817f809a93d4797b4d20fd880d88923b348967d01d665446e151a914817267611a51fc73d4bd690f478dedb7552fe4faa4668d64a1b7b56aaeff10a0b9a4e9e7d17e30fc0eca048448c8189d4fbc92de70203f3d0548220541ca2a63d52632b95e78ccc9fce208721131f935aaa484c06fb99b2f6c8f2063d4ead37514da0faaf07950744a38fd32a1d680f03fb509b073a5362697cfaace58b78e58c9da46e71ec5dbeae73b26b1a996a03b6821db5f1b668308d5de6b123087cc081da7ad46777148e2c0138451757ebbbae3777112395d1735ec273fa6da7c0024b13431d655e98194662a3356cb03a31ab4529b769b018d1747c12137629c5de0b813f7e274e6c51cf640e355f0961eddef8d009b24f1470e00d40d37c0b2379326f8c2f16ba5330b6c7776bf05e8731f6403c6f821ce19ded55084c7a2e31d5625ae8b2d277204db3a540d5d7b56accbd4bd5b56ef72ffbfc6af148d2210796cff7087299f712648145b0ffa1548d90d215a4b05646aa819c8deeb93bb4e8f6dc7610c55f2376c260a8ba2198f4b663e7f9cbc5b6d45b08d9a29b5750982a1dc2c33a26e47257d085efc3cc4ccb1ab31ce0af8aa642aa50129b91ce4fb5736daa613e5e2ca5a680d631bb80805d207612ea581ed325e70138704871b4b70d67fb9909fbad2798f593b7997b5dfc9ede7ab710a4d4c194e6f2dcbd89d1946b24e19f4d4960d7da95ed0fa60071ea9b0f283256eb396486a1b7f10ff2d33c0760aef10ec679e7d0d2ed8ea34325b3e0450bf03d267c9c280720c3ca60a9f81918212f9e73c3f48afabe8110cac633d2ef8b1253e539c87592d560613a4a54b08932113e0ef1390a894e19fb3afa3bcca76813bf1903fcbf8fa0436f4ae1f513881abe025a43ac50cb61084414b03f460b97612a01b6c6ee89b3d454f6e4b886beb16852892d3aa4e42e761962bc004810c91e02ed88f995a5bcb395495b8b87f02cf2ff5d61a05b87a74e3943a4f0eda2d5e8c6a6bc4996d43e03cdbf7745a9de853af84a4618da1047374498e6b3625dc24af4a868b92515eed3c18b5bb8acdd51833829037c0f506a5c091107d5dd8ddb975cc183153ebbee48cbcf1ff487709c5ad2d69c4d90bc8e77caa34a43dda6525d9725e4785367c23385da93bd335589d4777942c0c32d9473c9de5bfde2749e5948c56eee57904819b85ded14953db42eae7dc4ab72dfa8c680b7a1ab0170a5c4a15df24f939f2078a4d7dc184466d33621024bd9c87741119524e24c1e4e738b84faa71b34f9cbe18b066db8aa4dd5f44cc3ec240ed50adf0baf49a2fe80539b272d45a3abe176bfff93521bfdaed776d82ee5a05aa75624ff55de3521136cf3571fafeae09ff1e763dbaac45fb68abdb066ce77f6b95e200c7d2669505fbd1457edc634993fdf86396f4caaa9ef00c9ab7182fbfed9916607c232af7d4db0f934bf393bc507c7836faffd5a3514e99fb5ac50a88e1526c32143c288e1dca6a1b1ca1f2fa6bccded1781568a7b95602856656c3626c2c04e92294f7a9f3abd1021733cd30845b2fd9b5e105780d510dfe682d37a717f7d4943fbab78ca7d20ed7ffb6bb0cd84f98464337b827a34e5b593d94050a88978a024f9753faf12d047c05dcea854e0a55c22d8fd852140a3535243b8d4ad974b40974b4c9e0cbd20e45e8b8a3e2862d0f6176b9959b4f539f50cefdf24351260e4cf975c6eca1893e0aaf1f19c8aa21f5e5f4b5c3ba8681ed99cf635ea99aeecffd2395ca92df079171d609e31ece572d21ff85e1070b3ef355530df65ce869af5aa0466f795a8225db6eedbf19d9680c6d83a4fa800a2ae1ad28aa21c7cc4a0342f11025ed159f64f6aa6d3c21bc02ca86879f71e23c738087f149b5c50786b13eac7473eeff40c12837ac4cfa8ff0005eefc53c740d968a5ce8c16a03f7af3a39b8a823688f54541b6abe3e28290a3751a71312912238e0b75f8e7fe83f3f0eec7fb6f9159c319efa0052112364cd132c15e6a80f30cf7b03ad7fae29f503628012ee002993c030b93202b4301c8d7d305c0bb1335a3dd4ea1f82bef485c784e4d0baec1d5c76bdda24480db4ad9e86e9776e876bb066aee75561e4781b65b2e49bcd392b50dd14714475aba37e24471b30d4e5d385607953440de9ce97d73d260488961f88ad46329a1f72e68950bd222e26289bcd287d047678af090beab87fd2ff1eb7e5c6eaa8d8fc7de2a5f04406acef76415caa0eedd262409d068ba5873e58a6889d34028318c39677751f7839b0480edfe8fa5dfc71e653545d107067e6792be3c7dd84e41a422196ef31c92d54998e564266780e7799945d50089b770648d8dc7d13845ae114628bc7fa1b5c628f62eab23a4aec8c22a38d90867d14f081098f12c17c96983a5a388d1eadfa3b57a868f4bad87d8a53a838687426463154480589417cf8cd392f0d29b7f559506e48d2aa8e4ddcb67bac1db537f9223c14e50b64a62b5148d33e8ec6e6640ce017c14afbb5958b835a2ea9e371001aac248026ff4ea8bcbdc69ad08b09fcc68368783a74cdf7dd0392ee414eec5b4a40aec6bc7d24caf48f90bdf9f6b1483ca6d493b0b4e01062aec3d950eac3eb0194f1b14922ef3a0c4083b1d66b07b400752204a464f2a1f7c364357be8530ed890cb8e5918511607e07b3665e73cd2b31312f575cfa28a2119672bc85df732a5a33b91eb18bf5fb3e30b1ab1a0ddc80b6f4a5a8e7910123769f169b229110a6ff2e8fba10fbf2b1bde72600e110c459a2472544fcee4a1b1b83de29b10059f659fcdd122c7b10ad2f74656022a93e205e837950d2e578da257c4fece34d742a720df0c1f478d5f63e82dce41397405a7691c1b23bad5baf5668c0cbde588f1b08218a037d434f6cffe3ca670ee19d39bff1813e61acacc79d355f8adab6f1463cc6d87a948d5faf197f5ccadf6c60066eb48526e16257bbd5eda2d4763a542c7edd9530596a197a3a0f412116dd7a643dcb78f963d1444dfe288f6be49ec77ea4c6adc227ea63592121476325b02f549273834f53b94a4effdb5f71afa01b1559dc706b8ea641fcf42421860720cfd42d6d9e0b5bc5ae29f681fb11fab60685ee170c5c13f582221cd4a58ca2de1239a4eecede737930174db32a8d54ef1ecd3994d0812a083fccc3f5d602c1aeed84573930cd3d00e1c69ab81a5533ec1307829eb457e6c3b6b9ed35ce0fc232012d5cd6bec3956cbc19cf6f2a3474894a77deb377252be51309e62b38952bfb2c8848636522014e551bda54987cdda04375a625c21887484c14176087a5c3378ef7131bfc913be909ddda65d48796d3ba18429b203fd531ac90ddf1d80c912cc10fef148eea96a3159bd19d17f131a563fa500d8e4ff8aabf637f2a2086c7e902b75ea124bffbfc71cf13d99e57439c7fcff864fe33068eae4f019f6dfb227a54d646b14c5a131d87f000e0599f6d01030742844574014f8ca942e00bfb7ae983fcc691316fedaecd8fbab9e1cf4ebad96272110075266927cb208b6bebb3ccd6e3f24088c6ae023382f49df1a0f45609786bc2b192e0058deb017ae17de93630868e3ae519fb59543b8bac090fdf3c6fa80b18784162e728d231192693845369ed4875721c3f97f53f155e7b8b498a03925b87eaa132086000d6012f9f190d1f15688ab4e13206ad288e1960fd7004666c76965c3495c20c309671bffdb30ebe9ecbcd08455ae74b2f0be97fc677b3ca314d11783b44d7462d92df010993284ab054b1b0f2d408b7ea81cca8f861f0a8511d266d5e5f82d8d406dc96870c8ab885898b8fae70385e05ef1a310f9842961a10c24fc705860146d117034a194a333e7faedff11cf4f418cee260b7eca85b7bab0cc0afd25f473ac6b018f6ec7b11d99baa70dd583a68b509eb51e184ed4c55b82e8ea022ba7032bda77826ce16280ec673f576319c4edca1af9c548ca024a1a07dc4eaa84ccfdd9162f6651a38a02a91f78f7b2c5a4ecddc39601e5c1426d4bbfd7a386188c8e802c7ae43eeec89202ca45504df5a87434ea97067402897c551f8afa0f605c8a521acb2200671f06b08df4cd330598bcae48cf4ff605ee78896583966740e1d2f22ff9768c5557f4a4c659fb6beb5b5bdfa763afa401eec20ed92a20c08bbbcc35e8b7677d118570f20214745d75ed760c36de6dbb8f54cb8c95c097d328162e18f393cc1ce7d7d103446c4919e366b128163e720b30d9c71100c3a6c0b6de686b4be3389cb71a008c1b6f9e35cc9edceaada6322fe7644eb9990053134d195aab4edf8c8d7d5d7d4922c677937b706a5a412d7117cc04c9290651b4fdc04befea9d5ed94a65a228d594b06f7a25f55ce730bf886a131387f0f7cecd4c09fdba98da97895ffafd0b5e382e6edc4bece618c7be60b67ca80b593a7d99c0306d21d6f817737603873cdb13ac852c4668a67b9200096ebe4af2dea257cb2c577db037a26788d194745faeac00e35b92e73b24e851725fd4f39bb02abf74dea7594cd199fe708f678922e1289bedca381311880e14ec9299a11f6c6c91279f09fe4d4d89e9cce746510f68992312b7a6527f6b4d3a6e1a8ea55b1ae196d204e2b318156078f558e7225c7e47a6ee3806410a746fd95f16fb22b1fdf08665c9aacf00868705f388a487cb2c9c198ca2756273b818e51a5bbe95fb9a6007c249456ed0f7b07b6d4d7053c82b0209ec72d6de278c54b3003f6a316bd13475207b9ffd0f810e5cbab9e2b70aeaeef83583ee83e41b24a5a0a1cb4096bda2b4c3910e85440a160da32ea16639b5dad4f838f224cfcade35576e17df266f797a1ad26228ee7b991674e1d60344ac52d7127066e496d5952f0bf48ad9e1dc1f8b1dbee40e6507e4c1e71a781de825b7453d507e2bd9a0ae69558c5496f99221d14180f39253aed556e203732ed7fa12214b6041a2d42e808a5c6c627b4839bad1888cbce2498d37fbc9493c2e3cfedc6afa3c2fc0c2e78b602967628a12c32064ade5a382e2d7a07f19bc3fe3d2771686158f495aa1b9aeacf36bcbdbb3b52e87cef4888f733d1534ab11f8551ad0221768a39d92fb9935d124a2ea0b1317e28fe7a02de1855796150092a25d64ffa6b97b615d9b73194b6c0c5fc8086ebac2b8cf90ff330aacf4d1567fb2589a5e07f5a7285b1f792e734d1eae8b73994c23a5c04b44bb96e470983d66e46f5aca3e9c4f261addb760e97acce34365a1ad800d1a09990948181bd6dccb1081dfda3c7b55a00fae239b82e0d8cb3f3fcbab449f9ecc26562ab9b513cf69a95ec4c285b53eda2c03455895fecd5ca0fc686f61970572cfa7fd0fa92ba175a0e8cc692e1669efec359f57ca198c9be22ee2e894b68ee2c6a0eca97b5115ada9acc7a87af67b97bab1c84e434fd48bd28ea9c5201d605fcb31dc7b0ed56cd9328885503b90c495c61105a7394ac67334a42980973fe89633f604001b3fb45c214b733b8cc07b50aed57898dd59084e4213fe2523f077981fc942eda9c884643176238fa0ebfdcdb3f8a3d6c6223160cd70b788da4ccd7958cc0e5d445d5f74f75bc5c9af7d1b2928f655a972003df88b82bfb7822f2891f5aa4826fd4f161e25413af5e9517a6e37c5a69c34fc429844bc87e1e5a8d9e1e747f74e014017a87083e90baf5e7c30f5e0282bba74f2c4be954dca4d897f9386e35b359c28bf7124a9ecda0f2b20ebfa044437bdc694687caeaeb116ccbf1dc45255e67584771ffcd426968d4aa717d7f3894d4a7c70ca216cdff84768b1760388d3a9905531e36ae480a1f228586194cc564092b91cfd939b86a7beeb548b4dca51e4faec2d5aab87469eabb4417b6feb99c62d450c4fd294dc96526287cf61d5971c04680d324441591ec8331fe20769db5cfbdb75c63a2ce73179bffcbd52c9ad5d24e7aed50d5b572134c9cefc96992600c5a858f0af8572906ed6281ebf59637698bed36eca4917f4ea169826d6a32afceece1e1c27da7388bd9b520b878360bf10f39eacf35fe59f6d5766c6d7612735fe3054ef0dd4e8be953820475b0d499a449e2d6103a37384294734d36948aed6b1906701f87d17fe248b227559bffc7698034d94236e9adf338417038f9c9ff75f5b99c82bba887677370190aadc34aac5e13d6234d2f8cbdbf53f86729bdb06c860f39e938ec2fca708b83dcb7f867ff131119308f7b434ab86c9ad35ce15cb0c760754f4bb59a72bbd67b4014793024e096d9a4a66159705367d0709d75f81204d1660976071ee48f7973b4e18d7fa86029751b783136d9ebee635654a6561e84427c207513c9f548330b39372540ca565c698fdf7ec0c02b4190ffb734f62149546662cc8787d54c6e60222332726828ef4a9da0e66f8fbb47148f9104ad8da6db6f2e57bfc395b6f48ccb159af890951461612e52033c5c474563b0a9a9a67400bc2274e92ceedc271e7d21e8051d16c54ae690daf966de50a0a0f8e64ad45d8d21744abd14d51cd771c6430ad083242537f983f1af5937c549e5cad5083700416f15b9aeede143a319b469c8ef32f99067127369bc0a99a74f634d7e703e680059bfa50d9aeb1996ae0835927720c5a6e9512d239428da94f4ee6333ef8453d4bb3ac664d9d9aa3d930b388645da6e5cb94b44a729e066d12798f7f2635a0972efa834c9554da62bdd35b4626fbe5167b5f3ee685a4e0746861031e2232b9211bd1b9b2bdbfca58250b167f4182b8db4e37f0c674b860fb29e57c46bffbe9ac8847a2d0849c3a000bdfefbe2ade4bfb476e5b168d84446d897f796b6477fabbdeb75cb8f95d4f0479622dc70e28af5e7acdd7d970e48ee504b076946f23985bf89cef9449f3d95497ad4f7be48f745e5e6b920755de158acf4f916baff44b4dc3c72b885052f3bcf2452ef315bd5d76284faad1e1d662f9dbbd282bb81d1634b748b5851d90634b70fb505e2a2e692b7f86ee0fb02389a3d75ce0e65bc4efe287acdb8ad60fe83cd7348c5e3e24f92b23b417aedc4749af5acae09b856db58782b75e965b6d76adb467dbabeb8cfde7e20938bd5976daaff959b9f91996d3cf10df6b1d5602e0b8a8380621dac8ba906e57871c73a943eccfbb05567d48a791a583c081616df24d3d464967b15921a9ef3f46bb1de189939e7523374be2c2fca7194162d41ac9b33070d33f3b5ea66718dccc3d93dbda6abea5c1098b3702689951ccdee421c0a4c741ae3feee318dd4a96f94bca9033e055474d41a1d0bbb89f3668203330fee3657c3db45483132162073e6f6b59899fc815fb3627e4244a999df15ddb11aca4c253e6c0442e722d4360d5535ad98a94e3997650f7eb257ec56eda68965b545fea8eaaad77631c20b1cc8962a0f51c89cb0339c69ae7350648508807002114cd603fb75f8695c09baa1484a1b1f21c81cd05139863040342cfa618119a8e7ac1e59394363aaf6c181386cbbf46bca103c198095cef38586c5a592fb6f677cfe2eb6d43410a50a93935622ebba35b61a1001778a805d6d9b88be7e1d44eb117ec0c55765ef24adad9e5a2e37a361f4712ee131864ff8abddfc731c0532aa13bfa5c5cd89c6a009194389a7542a6e568123825d099e726e1a189a36d313e8cc010edf43817b0562ddd48c28d3db1923e6e39c431082a30c8278b2bd178bdf5aff72ec34b94efa2e3eaa5ca1d7b47f56a1b22cfbaeaf320dfda9913ccb54505b41eeb6b1cdc75bdb402c1ba357c378571bf1a2346526db585f6f59ebd3117ef435ae7504ef97ab6a494d1ce5fa789279f6cde07367afea75ac6b6cd710c02e751b39d0612f570ce1e591d25b24c0cfc003b35eccffd2af2bbbfce50199309a799df98079888ef6ae5f48de7ba41207a8d35cb04dc8ddf6ab6db7d29c59b878142a77a4ab0f2ff8a6cb124baf60392c8b8b123da6c0178dd12e1513af881b05357f224276f314a388fb20b7801e7bf7f7835d57439b2ad72e7b7e39257aa68018598da4bc57bdc05f44fe1f753aa49e66c51e2d6f803cb36725af835cc26a1dbd24695dcc47aabe5919f72feb2d380e86595610a82fb7ceba4fff6fa2041111454afe666e7a27316ca5aeb21f5791b876c95ff078a8c65a8d9870d857f510f08cb5ca2fba4b6a381606b681003e4d862b6f86a4ad4b9f1dfaa9cf85225557159d798fb0104ac8b4e95c63ad9512b940b56762914348716bc0e9b13bdcd06a1260b64b4d7104313eece8066b5b0b29071822e7dfb914cb47f3a07b9eb8b698adac5a5a1a32f90e2fdfc49dc3746641316149a18e512b39f744bef52d50ba093e055949bd4068732891d154542cafffd9ccfc3318f2eb04ea4e999501f6829379ded1788ca19ad2f0b6bd83f5fb2f99e0cb4fbc6ead8c8436869f0ebed2d5bc9415e5ae94d810538f25d708fb870fb0d32c0df1db07cf9ec7575d6ddb01ad81416dd0928a91aea7d6a7647865fffc5492fd1186135889e4b81c4caf9e2ef24c436dbb07b35f9033e421134c3859066333169736ac58d32770baec6f35be38d429de4fb25569bb5cc33716457e34208a111bb74a3f23e7b3cf3fb16b1c010a3bf24cbe3cad618afb0bd9feb878cc484a39c6e0c139714e2906ac9abaed062445114e6a8883e3a3dbc421acfe865866c3aa0f7bd4a55449a2d9c3345b9aaa945a74da8ab046ff7d76bb3623b2d7590a02f941bbc021b463502cff23511d66a309d57f7256d4edea9acb739d5a3c3f7a3a88ad7c625149d2034d80cd037cae167727d4330ea3915bc490370204a387d7351285f7dcf4699531b524ef00d354c5503dc07acaec4e67be0240437a09b716498b98364ee6207059f1c673c8575724a189896a969235f5d2f36a734f241467e3aec6bfcda5e463170542da564f31dec9db76757155d95f4a1cfef199296952d3514b5ec7b5f3c89ba7b594c79182f34bc29e4b7da88b28ca8cd58b80ab62fea0e90396422c0b3d35314b7a8bf1b2ae1a781d56cd4deea89623c6a99daa5539538015d99f9278608da4c6d9736cf5f71706329e2bfebbf7ba2d096c4bf04c7249317904a2824d814359dc81294874c344ac5e6f239fda2c3584d59340d49903f32b751e1c75dc7eb89b069053806050b5c52d3599d5036cd06c23a356904d2ac8bf21d20bed634820f281f0a24c3bc2a6f187ed8c257691daab3f35e61d04b715831b49860ac9988b2c4c98d92fc0065909a1948b8c97fe89a00904e32f4a62236664f8111be74e07004f4db26ba4b4efbefb9a347d2f73ebfa9cd4f6311730537c3db2c56d8d9e81fdb64b2586566904846a72fff3a8eb9df86d3e8e9bb098729ea28a13890510fb60860f17df76b46eef6ab20b4bf1be65f2dfe433b3e68ac63d9e7cb99d525fa4b272c2cd0b24439726185011f4c60852132fadfcae784631cd510aa56a9107afdd399f2514abfeacffa9284a93f5d93ab0b339d730cde541f56e5dcd83118dba19d4d1f867bdb98a2ba6c70effddc6567bbfe455402abc82d84b08b60ce4cc5224e4cc825b29fd127b505de5c4dea7f831455fa3a9e7b3531959d7e4f056136efe1281a0cce34fdb9bee542b205eeb91824ce6bbe5543781e94c8c16bfc4c35434a58647e531e942e3cea266ba7d5c0a989f715097707c50bf85c12bfa31951041f0ed8277145117b5093b22e862773d232aec3ac3ccca8343ee08dcf7c356328c69aa03db3dbc7f4d5779b152aa0551d05843f92007c2554bcdc48aa07559207d65e83a0fb044c4b4111f3bc2e07230f916cb62b284d0781cf7d96ea8579a38cdc97d3acdf4eb9372e498029b5e894320ecec1979e22a974dfefa43ea48e4061949963a03c2a446d35b83f615b47a7e8eff03af174bbe23d0000e539b30f217a2c732a4a83709763cdb43bc2f824d14a24f64905c1c7d15c181831273d84d838a85016d3c69285cb28ca5641399ad8f617751fa1883996b98fa15b19b3c9d3d4f1bcdb3047180edbc9998c1f528cfb418e907b1cbbaafe96da6a7537c823f38347476182ddf2f8d274507ee3e8ab783de1a21ad87cb7b4d3edbdae89ae9bc138da16cae8c8346fd4ac738b77a952ebbb96c316a4a7005b1a4d5bfc8c2ac1094612b1ae40fd94e635791898484c35907e57f84cc9e65fe0454a077de39a9fcdf953e2d9c8b0670935cec0bb1ebf989090d5f2fb5dad01d6660183f83e3832bdedcc0f932ab1a78dab4b1702ce25352f1ddbd98bf83ed400e0a1cca9bda285135b3f6f42c58395d323ee98bd6053f485c7f290073790d8faf9f25d74186c1364fbe32589277662a8992b0dd1d0857698dfe38ea3a4e2f23912991814dff303c1d34d95c9f3728d76387bfc8f6ad692aa4cd13de3c37a1c6c75310c1cd5a64c9575aab575e7e88811e9789cd2690a373130549cb69837c929802c4f625761065528456cc0f2caaff207993a8470bcc80a1b5850dc5fc13b21613fb6c747a4f26095f6c4131b34cea93ce2f94959098a47ef251c953013c410b793375f6d0d9739f934db417d39efe28a512a793e15a6e47d50a4a2b038da1d55ffccb0fd1cf626bb761f6b272036cff0c138ed00e7bf498e93ea69332157f148f658cbd3a603dcd Hey, password is required here."},{"title":"Tracy Profiler","date":"2023-11-28T07:53:41.000Z","path":"posts/tracy/","text":"性能Profile工具的使用、接入 导读 Tracy Profiler 官方 pdf 手册 Tracy Tracy 是一款开源性能 Profiler 工具,它具有如下特征: 支持纳秒级别精度 支持 CPU、GPU、内存、锁、线程切换 的采样 支持三方集成使用 Tray 虽然作为插桩式的工具(手动插入标记段),但其对于原生程序的性能影响却可忽略不计 (约 2.25ns), 因为它是基于汇编统计的时间戳,官方手册中 1.7.1 Assembly analysis 节展示了 x64 的汇编代码。 如何集成 官方推荐使用 git submodule 的方式集成 tracy 源码,但是又强制要求 client & server 的版本一致,不然连接的时候会报错:Incampatable Protocol,因此还是手动下载了 0.10 版本 的源码集成。 Step 1. 配置CMake 集成源码后,需要配置新的 CMakeLists.txt,可参照如下改动: # 打开 TRAY_ENABLE 宏 option(TRACY_ENABLE \"\" ON) # 添加子目录,注意 tracy/ 下需要包含一个 CMakeLists.txt add_subdirectory(../misc/tracy ../tracy) # 将 tracy 库链接到引擎 target_link_libraries(${PROJECT_NAME} Tracy::TracyClient) # 添加 include 头文件的目录 target_include_directories(${PROJECT_NAME} PUBLIC ../misc/tracy/public ) Step 2. 插入代码 因为引擎是以帧为单位,所以需要使用 FrameMark 告诉 tracy 一帧的范围是什么。官方文档推荐将 其 调用处放在每帧渲染的结束,即紧随 glSwapBuffers 调用。 FrameMark 最常用的 Profile 函数是这俩,区别是后者可以定义 tracy 条目的颜色: ZoneScopedN(name) ZoneScopedNC(name, color) 可以通过 ZoneText 传递一些参数,例如 shader uniform 的名称,以观察哪一次调用开销大: ZoneText(name, size) Step 3. 开始 Profile 运行标的程序后,注意打开对应版本的 tracy.exe(下载地址),点击 connect 即可。 对于高版本的 tracy,下方(红框内)还罗列出可以 attach 的进程,非常贴心。 Profile 示例 笔者实现一个 OpenGL Demo,发现 Render 部分的开销比较大,于是集成Tracy后增加了一些插装,最后定位到是一次 glUniformMatrix4fv 传递时的开销,如下图: 最终定位到 glGetUniformLocation,它会在每帧查询 uniform 变量的位置,开销非常大,改成 缓存 或者 全局固定ubo 可以优化掉。 其他 应用层可以对 tracy.hpp 再作一层封装,便于使用,参考 urho3d profiler tracy 对于 内存、图形API 的支持,网上文档和使用不多,有时间研究下"},{"title":"【RealtimeRendering】2. Graphics Rendering Pipeline","date":"2023-11-25T15:21:06.000Z","path":"posts/rtr-2/","text":"本章介绍实时渲染中的一个核心观念:graphics rendering pipeline,它将图形渲染的整个过程,抽象为一条流水线。 渲染管线的开发,有两条重要原则: 各个阶段可以并行化,但有些会依赖上一个阶段的输出 短板效应,性能总是受制于最慢的一个阶段 本章还将渲染管线细分为四个阶段,后面会依次介绍: Application Geometry Processing Rasterization Pixel Processing 1. Application 所有与渲染相关的CPU部分,都被统称为 Application 阶段,它的核心诉求是:计算出所有需要渲染的 render primitive(点、线、三角形),并输入到GPU中,给下一个阶段执行。 重点学习如下领域: (CPU) Culling Collision Detection Multi-RenderThread 2. Geometry Processing 这部分完全运行在GPU上,输入是CPU传入的 Primitives,输出是屏幕上的 Pixel,它的功能较为繁重,因此被细分多个小环节。 2.1 Vertex Shading 先明确CPU阶段最后传入的数据格式(参考 OpenGL VBO, VAO),它是渲染图形的所有顶点信息的列表。考虑到它们是基于 模型空间的,因此要通过 MVP 变换转化到统一的世界投影空间。 同时,顶点上还包含法线、颜色、UV等信息,VS中可以由这些信息进行一些廉价高效的着色计算(当然效果比较挫)。 2.2 Clipping 在经历过投影变换后,我们得到一个 (-1, -1, -1) ~ (1, 1, 1) 范围的标准立方体,出于性能和可见性考虑,所以超出这个范围的顶点都不应该渲染,即被裁剪掉。 2.3 Screen Mapping 这阶段的输入是经历 Clipping 之后的三维坐标,输出应该是二维坐标。 todo: 比较粗略,没搞懂具体做了什么 3. Rasterization ⭐ 【GAMES101】Rasterization 这阶段的核心,是将二维坐标,映射到屏幕坐标。 假设有一台分辨率为 1024 x 720 的显示器,那么光栅化的作用就是,计算 1024x720 个数组的 rgba 取值。 这些 rgba 取值再传递给显示硬件,就是最终呈现的画面。 (上图)左二阶段: 通过确定每个像素在对应的三角形内(参考),以决定其着色。 其中 Triangle Traversal 就是遍历所有的三角形,并对像素进行插值,其中学问很深不细究。 4. Pixel Processing (上图)右二阶段: 即经典的 Pixel Shading,逐像素的着色计算,这里不详细展开 这阶段所有的颜色信息,都存储在 ColorBuffer 的GPU内存上,通过 RenderDoc 抓帧可以查看其具体内容。 visibility 另外,更新可见性 也是这一阶段的重要任务。通过每次绘制时与 Z-Buffer 比较,即可以判断深度遮挡关系,这里是硬件支持的算法。 值得注意的是,Z-Buffer机制对于半透明的绘制很不友好,因此需要严格遵守 “先Opaque、后Transparent” 的绘制顺序。 而 ColorBuffer 的 Alpha通道,通常还支持用作 透明度测试,即 AlphaTest,也不详细展开。 Stencil-Test 通常还会将 Z-Buffer的其中8位用来实现 Stencil-Buffer,即所谓的 “模板测试”"},{"title":"RealtimeRendering 阅读计划","date":"2023-11-25T15:15:40.000Z","path":"posts/rtr/","text":"方法论 建议阅读英文原版,遇到困难时借助:翻译软件、Chatgpt、毛星云的中译版 建议结合引擎源码 + 实践落地理解,不要纸上谈兵 章节 概要 Ch.2 渲染管线概览:四阶段 Ch.3 GPU 硬件架构 Ch.4 Transform Ch.5 Shading Model Ch.6 Textures 纹理,【Graphics-2022】Texture 纹理 资料汇总 realtimerendering.com RTR4-EN,RTR4-CN"},{"title":"【源码】开源游戏引擎系列","date":"2023-11-23T14:57:42.000Z","path":"posts/2ZE2VGG/","text":"引擎汇总 一些老引擎(如 KlayGE、AtomicEngine)不支持 VS2022 编译运行,需要额外修改 一些引擎(如 ogre)只能采用组件的形式嵌入,没法直接当成编辑器打开* Engine 特点 Build&Run 编辑器 Profiler KlayGE 编译失败了… × urho3d 代码风格,容易上手 √ Tracy Atomic 继承自urho3d,C#脚本 √ √ ogre 功能庞大 √ bgfx 图形API丰富,简洁 √ Renderdoc o3de todo godot todo cocos-2dx todo 注意 编译前请阅读 github 上的文档 如果 cmake.exe 不清楚传递哪些参数,请使用 cmake-gui(推荐) 因为某些引擎版本古老,编译脚本写死了如 VS2015、VS2017 等参数,这种情况比较坑爹得手动改 编译相关 error c2001: newline in constant 这个报错一般是因为文本格式错误,可以用记事本重新保存为 UTF-8 BOM KlayGE 截止23年还在更新维护,但是提交频率不高。 cmake遇到错误,手动解决后还是无法生成,已经放弃。 urho3d NOTE 重点学习 urho3d 代码风格、项目结构 截止23年1月已经放弃维护,且已改成俄语项目(?)。 抛开别的因素,代码风格和结构非常 nice,同时又不显得复杂,值得阅读学习。 支持两种图形API: OpenGL: urho3d\\Source\\Urho3D\\GraphicsAPI\\OpenGL DX11: urho3d\\Source\\Urho3D\\GraphicsAPI\\Direct3D11 项目结构分为 samples示例 和 源码部分,非常容易上手: 代码、注释的风格也十分清爽,这里给俄罗斯人竖起大拇指o( ̄▽ ̄)d: 推荐一些模块的阅读: Graphics 渲染模块的API组织 xml、json 的模块化管理 IK、2D/3D 的物理模块 Atomic NOTE 这部分源码和 urho3d 重合度较高,建议一起对比阅读 Atomic核心源码是继承的urho3d。 编译脚本只有 vs2015、vs2017,因此手动用 cmake-gui build 比较稳妥。 严格意义上说,Atomic 算完整的游戏引擎,拥有 runtime、编辑器、创建项目 等一整套流程,通过启动 AtomicEditor 可以打开一个项目管理器的窗口后,参考 Unity、UE 的实现。 ogre NOTE ogre 功能庞大,但是 GUI 真的丑 全称为: Object-Oriented Graphics Rendering Engine,即 面向对象的图形渲染引擎(不禁疑问,这年头还有不是OO的引擎?) 支持几乎所有图形API:Direct3D 9 & 11, Metal, Vulkan, OpenGL (incl. ES2, ES3 and OGL3+) and WebGL (Emscripten) cmake 时遇到 imgui-1.90 缺失的报错,建议手动clone一份 可以通过官方demo (SampleBrower) 进一步了解引擎实现: bgfx bgfx 是一个 跨平台的渲染框架,支持图形API如下: DX11,DX12 Metal Vulkan OpenGL 2.1,3.1+ OpenGL ES 2,3.1 WebGL 1.0,2.0 值得注意的是,bgfx 实现一套跨平台的 Shader方案,后缀是 .sc,有空可以研究下。 README 首页提供了很多基于 bgfx 实现的渲染 demo,可以结合工程代码理解: Cluster Lighting: https://github.com/pezcode/Cluster#cluster Cubemap Tools: https://github.com/dariomanesku/cmftStudio 推荐几篇 渲染分析 的文章: https://www.cnblogs.com/crazylights/p/13555816.html https://hinageshi01.github.io/2022/05/30/bgfx/"},{"title":"【量化】OrderFlow 订单流策略研究","date":"2023-11-04T17:59:51.000Z","path":"posts/orderflow/","text":"b3dc07a81f6459d120ce338ccca5504603780459e0306826441972681af56d624582f341449102626e559c75af687736c2dbe4e6baf6a934a24c0f78af13a83ced8b74c5e59aa1777c6d8a7c473d8eeb65368e9c8d836d47c12efb54e65da712f7ce30f43d95ed5d1faedfb647a5ab9ead8e3ea35bd6ffb81f3b46f64dfb989dad5998fbd968d8322f354aea44b5ba730decb5ac815450b1672014ccad9da2c73da7476fe270af5d2cff0cff70aeac6ccfd1fe160cace9d4e1a0ddc43cc0c379326660785b5fa0934f65e2fbaba535db0dbf085a0c6ca61625a8846c4855b0bb0f52ea598f22a9c9a53c1c2151f04eecf16c58c96444c417e73d4037ef69a461f975df7e624370078210fb5f983c29ee8e3f0ef2c6cc50e939615ee37e33c92992883c6c9ff8828c4bee8ac6287103ab26f6c658c0aa9dd6990058d3f440d0cc673a6d3db6cc7c6b646feeeee67bb10eef676edeb707e4d7b9f10b7243868021dab0b094fd3638281b2bb15d96acb1be6b5daec423d2e6ad1c133e3eade3055787e356da765caa8c93f298959510f5e4ce2c50265106bfb4408cfff89f290d2e96955727eeb52a627ffa26da381cad9a0aeaf46a1cf4cb7fd56a2090c3762662e44863a550a9bfffa3a98c86ed1db8194b6a9d66129cfcf34367f3c86c7904d8d915251f1e5dcd81ff6a9936d7f52369388053ba60113acf175086dd5b31521dbd052dd0a7e59248f5dbad97ba6aa059a6ab76036ca2dc21e607d79c68c041e1ee288628f460fa00c5a79ee62d0f625d2c767a7bc8e5fa99ca83b009a20d1db280d774f026ba2a8f343156db04b7cab70872e48ef48d7db1ddb4e81711d788a4ebc7741615ae93a92cdb89b685fa8f0096ec789dbd769ad90ec5c2f56de877398efdd0f56a840891a627e008d0c402015ee2225d3a1f837d5eef62bce525e0ebbd5a432116e54d8099f7623137ba9ea7c1431bce4c902435fc690d589958813c54c09118355a8ebdbec39ddbec6966144b43f689a0e1b267a1f97a4b9048f352544f920109c7ef2ad0994092ab990ff3e84441639b2914f23c312a608acd23e3551722bb515062b39835df49e0507719aab9b52a65ecc00603c18d51c3fd613aa4732536ed84399e7246f81a40c36d5b42dbd59dec4217f1ff9d054dd4bcae4d8c3406ce5ed05cbde2b0faf6cb7410312d768f1ecf50af72d695fe9b8df12c4ab45d6ea1398a998d8c221e92134a79d1e0c7f7d342ad164d51a331501f97917b1fdb9badb901a5007a5ade86f2551d5a002273a622a5bffea692dba9c677d1b8efce53dd4d9443ec2fc638c5434dd7fc5d3d1b7cc49e92a2b5b623cc6377a42cd83382f2c47751cf8da811c35a9388494b086f74486b57127c61e1d96cec892d4af68ed55fb7c7e2d27cf1dcf90e28578b680936476d65f1a6a26eec30a0c1ce4a3829af2edbbbf9c935b6e91537f7e2ea5c46dfa534f3bba03e3c0b5e4d2ea2382acd99c108f9d740f5fc27780cfb92c308d72310e13d6b360d700e4afee1d33b6fc8e8c722a2d8a8b499843ec2cba4ef4e707bee1a6c5b8cc87b509af29ee50f69302df04f61b3ee2cf240496fe12271d89e601ab5d9587453b985cc3a08aa45a51db840066ff380afecd2440b7fac168eb97337d4192908def4a519b3f7545a190d01467ae2202e1cbbaecc16eb1139c9744c839747f60c7e8d9bc60368612b0cf8dbec3fc1631d070a8dcb15c11bebede7977de65a9e3c77331dc68ec20bfd528fbcf1973bc11afd1aee7919edb34e0ba1e93b121d6898bdc893a3c7a9742951d30d4c066aaefd4d03a88435b4619a4061c9cd1926a70805849ae72caca09972271f205c51be5a43e1dcd10703b06e63f7c1ed8d3e4c5bc82e52612271b1a9c22ca1d4684c7841bd4148a584f6ca6a7b809e72593f435800f946213709b7c81ed7a6f8d761de4caa4e5e86d6da13f6ac8921c77ae62d7444ab9f5729ac4df2e434cd159a5faa1e87fcf58b552ab594ffc917e8d4ce78cb1dff1ac758aae1a2f00622df3e18dedd2901c4279321bbd29cef2c6e5b3ee23e91982ac5d5fa2046def80e9fa6f92d58ffbdbfe1a6b3a30943b46e671f2cff6201ed6b123cfd8967986aa0b1865ffcba603ff4ac1222391a8c1ea881033b77bcfa2b3d5667267d787ff997b0a547ed2e19b9b12248291d1bf90b7db8975413cf50152d6399474bedfcbbb1b53b703545972071895cbd40678f9384c59cca794c1517670a64d392bbfbbaca3a471e509107d11f18bb33db61fdaa63b3a36f109148a7b6d586f33266282222de2b8bbb4c81fbe32a59f6307562810eddbb5a163bc613ea6b7267f1f7e3c978d5071ce1caf590ed32246a7cf964eb21211327d0f85fe2ddb5aa0ca5d34f578e9ec9b343ba4b7b46fae4c6b484c76b56d0ff42fab92d68db4ec25805b27caf8b56e8c86bdb28ce85e8d29e12ae9dab79c758df57678da22d568a332a049b7216ed61fc1b3ad25bea9dc6cac945d179e16ea474e731ae155d96bfcf7b3e5aa67cc843fc7d35168296fcb81454833ba31d0eff11af7ef78a87ad96ff99f08ab20cf0d0df37e291987a012d0ae647f0f9d2d94386d26f1e5931d0f703e1fdf1bea15882564ff4ab46f7885463c7a56722cbd339d32ef5474e3fa544db9c5913658d9c724fa190f03a5369feeaeb612c83a65f96125deb436098588c9eb674a0d4bec78cea3fb11006194495cca518d92da97e65b8847a084c9b890be7867686d5de5a9a85feb332bdd8d165e3a5d7c2849d068aed5dc19eb0c6a93c2d8a06e090226fa29a097a709464315b123706245d85eac0c8dc48115caa926077a9fc04a12c3bb23855f7879f8cb61a28982c6c38fc5585e3ee5c37975f7ee96756cfb770141b7e29a8a132a4f21e50564e68d082c52d6513ed08f878ef3d4b27f57715d0f1545c66ce3c12c5a6db5d3a15db707487db6bae609219a293e5ad8e6eceebc7507809ad490f6b9b5476761668d5e9e14b68668eb3e137784acb245546c196f073bcf7c42330dd938d55eddeb2d14d370fa0320a1d1c2224b1fac5fcb156f35c0b55942bff19ef9e47a6340e90e9fa763b71f3e88f98b5b9c6c52199b0da43c685e8a47480f6d34aeb88411c612f90d45ad0a086548fa391285a827fbc12becd67ca3505ffb9fb50e679db618fd388e45918e92c54dadf8cfddf08f7a7626d152a31c327cc9a8a6abcbf02d7d91d301c2e166db7bca6a6ab2cd46d31fb0d655bb6beaebd2fe16b55b7e39e53eb74faae64c796a6d45226b106180c153cb708a47f6a69ebb4e5726b6e3a511fe440bc46710f0edaf058ca35ec0f15c21dea3b4827be686d8a87c437684c168f6b7a4d2ae3f01e4a376fa660bdb8dfe9f5106017be3298f5e2fb423435d84213a1dc17dd2bdd5b4474d640c46e591d4f87520af85b62c524b8e9bf5aa83ab8e1b98e89390daf542449a48b02aa02c691dbe354292310677796525596ad33232c1c48742e4867e3c0a77cc9b58dc7b264aaf126997d934e85b18515c0e680bbed9dd8918d5308d43108ee26f0b3d62e007f455133288deaec3ac798817b9458c9bd88373ea5eaa823850aaa32bd899774d0d4d87693194e319d93004a850618a9767e02a512eff348d8afc9d03cb6b0dec2f3d10dec3970e324476184b389d5d0603251d0a90d2e3e758a32ad20e6bfdfc8993391bff054fc0785462bf63df64b17f993ffd3c81260c012f41f66d3b4b6c5047e2bcebf2c9510558e2dde850542246275f5dc9aa5ecb350ed03f03fe84a896e52f74afa4ebc987bf267f466aff06552233ac2f22262d62d77653ecac72d7f5bec72da6b608f07dcc63642c91219057fdba0a625e4d8beba0466f78386b9e5595b5c97620373ab0edc63a8c9ea0e9d54ac017c74a87a91252f46b5733f5b8ea4da1d6b3b0a8d2cceb3837f3586d0c5f59296e61702e085f3450eaa73ad4c8335fe5bea2c101294ec4e6a3a44c726463b91897b39cd710f785daa67260d8c96bd02447362701ff85057db5b5dc6e0886f55ae77af47213da90dbfb5c377a8e8fa334c15e4c0ef40cbfd0113046efb76b985e4bc8cffed63933db58180a671332ecbf14c4e78bb4bf096562a8967a312eaaa0e7c508e637c8013ba5e753a14fb17b0296b91e81a6ee88f280211b6b2a944e5d19f3dcbec103c6e69f232096541ddbfd64a3199502ba6605d2925b1c913a3515875127ce36cdbf0622774a12b3e001002ce08d37f3ccb7e4aaf1efeb97c7465ba538df0ffa8a600d8d32961c32dffa06f484190f8b1db14e9d99a897d3bdaa62afab5a5bda81b19f476a04a7f5653dba71de1a516fb81d14809ee628e9faf78782772571f9c528e038c1ff8408e1c654a9b363b77bbcc903f4cfcab0d500b789e33994368ccf8530fdc0b299930a14881477e87f2fdcdffa56b2f9780315ce06e299a1a0b0c85f8f2b090906d2e9556ee37e83e8e331b880cc87f1bad37e0ed79a02708d4844f0136b949dbd3c074985a380b662d48a934f4ee1711097ef9736a71cf7534ea8c85565428a7408c324de2066c716bf6576b03bb3b78b983f33661490cc0976a7150e17f835ba09590ab9670e220e7bcc3ef253cbafd2bd3cd92b8ab432fd6db13d11476a38ee69642965324e1cd411bc032547706b91001886d3c44bd211cbe15d136912942596e167d48f832489f1c9031bd64ff212ee6b4e89c4429480a4b9370d25fcf2d889e2ad7afb6f80444e755a7f30ce42d4366193eaea3b6708690b15847ac317b6a75da7ab1c8363440addc9f162e68612b3e4515e969e71f049e2ec173554fb10a5ffa5a999f54fbd564c9ddf595ec5244ff0211a7ef8ec7812922870086a213d01038d0f76d6371fa979303b5e3653e27978b565b8b2569b9095956f5df76c65bfa64b12291646237a0d022c099c5191d213ca318acad044192c307dfea73c104724afcc2e6cc29505a3366b8f3fb73a732221f09c4db95ee59d7aea7d185a492fc668e36f1ef9395018c4eecac0230181133387525c6a8c6f9448f0324ca88f61df08a8421688249d9a01938acba3c42f4e97e96dbaf290a96135ed97b7b7af67cff5a6648d9d3a0ae2730e5693465070683d912dd9631f81bc692c7c8232ce73d87459a232344bb06fdc09e2f70f96bb1c92f0bbfe541ded74a5508a21a7f3f92bd447ca30e1f35f383d3962008289d34c40d6d49eb74774bc31af3bcf3bdabc05972126a98e696c5e7e8e923be2e5b30486fa7c4ade5d2979df62cc094d1dc4f2919483deec9e745637c8f561563e11d7475d2cff990e7db05a30d2e29fc6d48ef3259f477472704e16aff4ccc3765d9a250e8d1766d8baea68a3b91093e833f5326dacc5c55b2dedcd0b1d23cceed1a64bb1003f4fd5a7cfe4af6ae229117010cf9b07514f2f1feadb0356d4c0ea4b0c26d5b2b647241e035d65cb476a9f1a55e131708e7b4ad03918916b52342a6361f35716a260b39bcf6876490f0d213d0a9264a7277191098c20743d7f754676bc22bff0556c63dc955e741aa4c4d58d5fbd8720a503cdc37ec5b67fa0b13a6a80f06821f5ca52f63d9489a1e00b208218c7135d42e5078d143173944f188c6e1518d1567bc3768c486aa656b3e071f97131385b7c11a613679cd581a4dc69fe3fb7c24b49a4778d1a90e13024b522e1d44a70997d436d2f484e6be1a57c65fb292e94dd3ff810215b312d752a13ae10af71ab554c30245224060bb6747b3004378d4906059bc154efb5bfd94c3b82305089912ba62586ab9c9fa79fbc15e72fcfcb943739584dbcc8ce5c1ffdec4d0391561d5e3f52817dec927b56b0c938c85d69260ee30819b0dfd688dbcf4fc09a46dea592ca6fc9c498218f0d81c1981af450204f07d0a52afaa4bb9c4591786f8bb444b9d4b6402509579be36900a2fbca4b0c2f42255a8777687bde8e9af2e14f872190ea8e69d6b876217c7fac0cabcae3b5def97924576b981db5d1068a6071395e916c84a6e7e28c62688a3b2f489b392bb48ce550580fee2aac74343b9254fa749a723b44543e7c89e259c8685e017987ada3431d1198946c06cd75122cadfc2725e8085e3d380877bc5e9f123a85744974198592310b2da5226b9275250b9fbcb7c1fa1f853dfad337aa9b6bae94e350a01719e2205145b1386e11bdba77dce442e218692a2ea4269b9ff4b4a62050616ab38001a7d290782255e925b72b4a3b9440615f2f3c1ac63b2db7a1d6a89a544876439925ea75b167a9b82401cd1bfe8c5ae2489edc84b210f5e550d99027fbc06fd3bc8cbef9f805096cc7d38c8ff026473213bcc4d03e67430f241fda1d598ee75c844d4db4ebcff8d70323fbf8b9041fb18f01b2d6c1b0a4941fb182af6e2c56370b53398ac2ec92e8d1e5b9d8723b5ccb6fedde1b7ab09bfb5818cc1ba22ad098f5da1886ad4ed432a5ed5edb0565074ff1b9ca559d304a72a9e6cb45b2dad252b565c8e353c85eae8aa04e2b321f2c26cf923d93035a01acd6c97021e1fa8951fca534ed031a39c485c6a9a857986a183a732ed97908e849d814a50c7c498fd2127715198f9284f0a78c8923f0883c1757bf3f0f61e1937631f953f3f5cdd7e697345342c76d3baa07b5f153cb5a73f8ee6470d1342f433305ba02a15ee443bfd1fcdda5a45aa788ad49bbe57c96cdfca8cdb5af288405cff7530a0d93f6e589fcc68af561efdc9356280f2731d51e059a535007a4ef824057b78c5eceb7bc1247b79a76631b12804d6657ef6589d3f2d937de329eab6d7edd2704ddfcd6fcabbef71ccf75cdeeaf58374cc014f6d77fc35a257b4b5d0fe4c35c140a5efea382ba00b872cf097d23c32a38a5afbf2c566a47b6a293118de9768ab783dc07d664a6789ffd7460df8286221375c2d0d77119991246eda63c9988ecbfb81815906b35a4417ff005bc299773705d9f4bbc5152230013d245df90f2ca0548b0a4c33f9b7c6d0d0b251b72cb790cf106e7eee4a2e1944952bdbcd0903bc883083c8fc4f7f3b755707143858f07eca95dcc4a2df12c32e9460dcb38e19b0c38bd06de0ee21c37bf157be5592dd67f36859c288057a2aec0fb6d915160b1efa1fffc4190f13d32e6c82bb4d51f75d60aeb22dda25711d74aef02a5d7fd5590eac292b29303c5fc2306a48cdb0947bf6a2bcab792aaf4917ae2762a024610312ca617b80b77b2b37447dbb789e7db60612ef1ec19369afaf233677a75ed4861e75d5d14ec87820be74aac9a4ea11962d13d4f7b00d3b0a4acfd3ff63fda2f253daf867dca1d35254b4580ad1d9e0244f3062e550d935bdad19c0811bd3a8c781deb838819624300ab55f00d74c5db68114b1f8acad12cf01854116a3df1ad92fdfcd3c3af23cf76438029a91b849b57902348425f6b61372f738b18dee55976182e269f321386bd0232fef9924a40cd1f1ad998d3867393bfdfd0fe86200803d4f4ca6472a05fbda01bf00c2fa35d4765f21c1aea3dbb18e03a978d6ee980bbdd3d679454e11d2f1e6e9aa80fa2c19931231dc1e93c74e12daadd315c318686f3f6faa1c97c00922acf20166613702f32c6536a06d0594022c4f7014d545ba59238ed60e6882a5d6542100ac0f677a5102b741b50fae374556e2857dc4226954fdc2ecf524fdc4df2b13f1eb0375e57ccb00fd95eb9c880d1d0435057f8186be77c71785b1dfe83982c7a725f13097b598e9bd688b41175455bccd0a6a97c08e424a77aabf2f904b27d72c8d9f86e40c9ff8c11a69c2635d7b214ecf26142083c7927534e79a782765686615662c2cb07107f4762e2972a459eef3a7975647b22d530813837c84c11e2dd6d1d9899e5da1e4bd1dab0661129a6f6417a768f0bd1807ecdf621d121285e476e1aa69e2e3cb1b7989fc6a2eaeb049a460d7a62d5db494a1849c0c875c5399de8b1df17602089e9925e1be7b3e9cecd17bc7d8896f9c045fada73a4b7d837f9544e47449990766c51f732973b049401f01e5c1a3ca06ca93175cd7f986268a04128afeecb3c42e159f1f28d68233df3b3ec5ff69f5bfbd459df477d1dce269b7d28fe06b09a5fce8be321a45e75f50a7837fbc723e01bc37df0516018d85050c8fa13c1cb09a0d19526f7af889e7fe30a56cf58e0ebc277608d5ad98a3d4cd35e4bee894179389c6c40ffa6cd7d155fd01b14494cbc4a6c66041fee34423bbc03307c0801376bf05340ed09297dfbc375a7d03e4b0d5cc4409cf252579ff7530c6335265d1ded492761468c501e05950c01a615150c3c793e3f10ea99bc10324eb24484aaa4bce6d00d8c5f01521335cf550bd057ac2426efdc2927e4ed17c19cae3764561bb17eae941db1d4dc4dc8bb2acabbb27e760e777e75806fe7304d3871fbee9ad38364438b11133687c34c7b6466eb0ea263f6ed643bd443bfa49e61c56495d9c2c898211e4e890741e4aa230e359089f6c29f08047fbb845332b577c096ee27ddf414ce04973e32c06a69d5743d4e6260ca56d0d0257bc1cd8a05a96ecccb0e8b045612335058f12020d54ea1a929c0a687b00ed4a7a63000e4ff916f71e1429bcbd1be2fe7d3d96758f8b83fee196159b41e1a50a268fcf948abd1f21d200358b8df9f953229d6fdec867ceb54e3f33407cb2997f85e775595f0cbf10e273004c29057512b2df6f0d7387f84e54a420f6b55903e17b83e61eabbadbbddf723b3ad85dd4b6e58edd90b94e1f9c3ce3caac5bc0a1b7c37040f006d8812da4b59f4bac997a74d5ae52f0eae1b3d0db48b171a39190b54df27f9cf1da5645ae1589cf2d7b372821a24af1a633a7827376ba47b5906f25bb207048f228e0992e21f00a6ecae2fdfad5487d84d1c240d72f570498bed27161f016f2c05e39c4f67b42388b35dc4a1fcf31b371c6a459bec5f131de632e912d34e78ebc1f681b5a1a7526ee47b6c12632899636817a37782ab81e53c756f89b406310e152eca94e5790de55206f8ed40ba6a7e9d15cf30c7d7430309d3cf87a2bf09b98efcacf8079912402f017b45d71c32010a5617a8fade3d675db1bf7ef462c0ad1f32aee4b2a8191ee76c5705f3e4e7f43d6614875e421be0744a6feb178474ee26ef459cbbdd40d4198d36664fa1b9b390396de50143b0c531be1cf8287844a8d703105cfa1b3268ca0fbb9c9e4d16c77af21eaf1f702186b23f744d3d325b6717e9e65a6ff2b0dbf0320ed1a0feaa33e04b57eded3957b0dadd5bb9d3194f6d6d95c45c30b6390a44c545a363ee1df9de55b2226ad944672560849eaf6f7fa28f2538a6305b5b35d8b0a68ef36d82955b40897bd5df78c5bf2469e7725b587c14328c14c0277acd954f8524802a1b86cbc5073fd47c02b9754053db5239216bfa10985cee5b515a33633d8f95e87b20e0b488bec5cd8c1e36852ef752d024446ff78788504c61aa2c93c1229dab715dbcefe4c494d28901f84610cf0d7c0fcc2a5ba17e79cd70c05a059e4cfbd2a27031589b825a6159b227ed006b75d5bce7f90cc168d3eef33c63cef64cbd58ebf9e6a0aeb008b826fc574ddfadc5994850d6804220485f8cb26d861a52aca7375981bc3ca64d87d9bbdc2354784b415ea3472b4cf43cb9f29104c5240a2b57f3c9f826c523a6b5ddb2735e422d76f884b20254751ee5a0a802e52fb93171760a43cea797222f400582d0df72743ca9a86e29b9f134cb359ce8faa63dab5a16e14fa4a2621dc096facfb6af89cbe4672add4f2730e78f976a00e2ab454bd4fdbbe3947d730743136cd4cef90902919c582b85e548b42d4d853ccbb085ef2eb4d64d2148d06c60de9eb97841f4d1f6e65592b340a1512efaa8d5429fe3282012ea22aae28b423aea2cd683325f367dc6066ae7b85721d533566c5b5aaeeb3e94e01262b966c57a86abdf64403446be85c79f7ed63b3e24c5853d89d157b1cfc959dd99559e02babf7ef33f42f8bb6af9f4baf4976d2876f32cd591837f172ed3cc77960542115ff15338aa01f77253df5ee2e2e98e4cdb2c16f0c1fcdcf2242312d87c5a7d6b2f1867105850975d57656fc5ffc701f3aa820cdc5695098f240c2eba9102852688606d3eccbcbab66a42f9f5d9ce5f59b428633ded4e6bed88c3ba699796d0cf8b6f16c514f65a8ed8f105f3b99a8a258c5bdaa8bc7c82ab234228f48c802004df2bff74942be5320c4380e90c59d55dcf6398c0110789934a163fb64793b1134cb51aa4343a3ec107ddf561e800eca85ae7db7557456f3b2fff5a912ad785a21a4440f534997c2218261db4ec51893d4569d73a40628c9fc092132420a55145838e0a3797b4989dbf7aace3bc226ac39e5f1f8a9f3583ffd63992b002c5e4fcc9a557c9550971c0d3ac8b2f4de67c995e58fae41d6963733aa4522057ee4c2b2a3a921510f7295f2c6ce1af579c41834dd419e8f7163c1a7e578ba99055f21bbff567a7f09ac190b598acfcd244d5bcf3848166eddae4c578d59888b46e2503d26d3a80dc785b5ca295ea1c461ca9267172a829f1e86b83328da757afc14a016fc87328e68fd0ee5882121c2c90d5f53a5ee625377434307fce6300a7e68cdef2c1f621e47733ddedf5c8924473e9a14f05c5ed8d25e4f78bc2f6dbecfef02c48af87b8ef3b6d16f400999ba994d9b076fd347ec70c8ee8d493708f13cd853ee577dbae20f177c728659e257256f45e3517e1cae32b9a970fae0b76d3c158317a2740a5f6b0bbe16654bdccf0951da074e08fbf73d82862a5cc6fe560b130d9697dcf18c53f602e6f7af97468a817d1c133a49e6e15461a55a8d60a67e36e064a26cf7df270af389acdac2aaca563d12ada0a16005c7b46a1dd49ebb3035ad35de27cac62fe0125611e4f25057290f13469c3e7c04f7f60966d87b8f5123272cf31a6716666472f45422fc4c2d814602467441480073fa9910c04170211fc97fd34cf49bb7cfc5025760b9e02fc3f4403d07d422c48505ce5a1af9a3628316c4fd015d2cb3a57fc7e10c490e58d599e2751c153326831169e73a7b4c472f8bc0faa86b208337fe5b31f7ef3f512d9b0bf96f95ccdcbbef1ddc5ebee6cc444fd4ba19fc3339a55cff946dd10502c119b27a7c701c92c8d8a9a9fd257f6a98e358617743ae30ba246231798cf12545a18f55be9dca720345b328553dc5e08b2e823a1b1c6862cac96a8dcd0d344ec6af5b9c12c8dcf27fba2d0fa12b6a2dab62e85ad0281c5ae8322dbbecdce7f7185dae7d46322c505f461d7bbbb62dbdd111c05d303fd78c52e16aee0e4e94d3ff6c46fd70cfc31999c0827afad2aba2314ac7802ad81222a55beef179f1607f82aebcbea7df8a1e269ee8b472ad8391a7e6cb49e41db84adc7991db0e68e41db5d9f7e02cae2b8b075717ab465f0ab42d8c73ca31ff0bb1195c29b68c83b93cf2620eea1a46f55259c7a8166cdf81e7cfa1823b72a0720da9dc9e0609f053b625a4fc181c0eb42505ac52b0e2da5c29d9a10396d56a71f67550f86bf7f320d7ae04d4b7d333b3baa1e5748a8d0df4cc164a0d0e238e2db0a488c6b1f8062af689850b8fcbcfa8fa11d01fd1c4b859af1c02b2fdf8f98f196f2c19849633915fc40fe6cc49deefaeb775a1facd5acf379f8ae03d38814daeae0c9c3dd8bc0532ff8a841b8094dbe0dade848f6a3b9facc3a184d69e569c8f2744add0386abf1211657ede1089dbbc8472412d5867b9cc15f78053dbd6154542a9a115063e5e325173a5dd14762d76349f1d84ed48c54ce19641c6231aa3eb631fdd73447d0e5c666bef3ffde1bfde750a3e51fa2f3707cbfbc71a373d4d5aa97eac167a0eaabd79e81e4d9c8c163ac845f9f3218b8c0ec8f945db736f58ef6af0e50186b9bc9ce037298c7f888a87f15e4d7c844b74d2224b589c16aea40a6438c6f58a2b26805a8ed17236aa72231e91476fa43161703942cdb7e473268e1c944ece7203e15ec124fb4015b728f718b193ba9cdc33d53ab812cb699040850cb30f2092a1b3687d3dd6833af489a540a1f5af29affcdab7f4f64ca0441c106d6b5dab055a4576e20dcacbfdd9e4bce1134d704ed94cacd95be69d9f0a4d378d29f2ea5833e46d9177e02d1273e34890c57ecdd5b543fcd6ab6bbbd5499b2539ef79855fdbd04cc31a84908192d6a4916eedb5fb6181f9dfc9783f92d278fc3a8a046069d6dcdf04e820ad9f4752b09bd853ad4c41bbe45a2da712b2ca19fdeaf353e81bde4d09a400fd5b293e046b8dbb6f2ac0302e3fac1fcbbc06c663e842c0ef96020c541e093c9ce1e2ecd8be9f8f21f546556c442db02016ace0bbca86a8040626ba544357bd6a716db3f03471c5630ae7250ef4f326b6914cb74ce5654cc6f065c40af7812590b8e220f137b8db6f15f065d4a867fa3f4dff46df70e7580b0500d09007a653cbd6f5c77096ec306c802516a1bac9fa5296cd95de9380655b200cb8d35929bd9c6e25651a21f676d254c24149b01427301fba1c033ef7da16ce20a6a2d74e654e6a8d2b006026050249b0e235bb2131d6700c947ee9ab93379d9637040d64d9fe9ac2affa6cdba2fc983be722f88f7e8dc2 Hey, password is required here."},{"title":"日志:2023年11月","date":"2023-11-01T18:05:53.000Z","path":"posts/2023-11/","text":"b3dc07a81f6459d120ce338ccca55046116de9af7135eccfa9c61a82756948fc80f892c477e2c8b4f49f1e28d8dc47ae33730d80f767fde161c078e95f5e43f85de758fd4c096799f9d13ad814c1721eccc7799ffc0b0ed8f3323bac374edb4542d4b5ecc52709d59318a85c2a0f698dfa8ed10a11c0c274da6eae32926a611b121fce93d73f6b752644fc68763d978715773fd1b7e6237849b16198cd5c0c5b15bdd5cd8e7e45e8eca0808e869012976a2883cfe76dbff330bb4ff1ebdd04513d1236babe8023bbe1694b45decdb6c8db6cea8dec71e2c7a6a6cd817e6e73052da845f197fdb8f843dc73242b78366ec81ab9ba3e9bf95a12bff3b57eb410246f16ed1cd6d8bfcbba40e7e4682e3528993029a8644e9b67abe062aa8ffe4306fc4f921bd6a091bf4a5bf6659013a45484c289b524d7903d9d5d0ec5d959c29a63a9c08018d7f0ae135aeadf4acdd2b9afc60f40a58862a6fea6c0626ee4a280f88eaacab2d26b69861df931ba6ef033e2fb668eb9400ae60c0942e2f96f3a385f3efd7bb72f186deec0e2589643897cea705f8fc08db94bc42e514e78af72e8b31d09e55ee65414088485eced1fbd231ac522f01371264b3117c5fedb9880f701f9a86a1bb6e937612cea1b0a452f5a1f53e25920da26f59aafca162cc240e111b607181e030cc15d71c672acbaf0d79c8f3a3ff126eec8474dc627e0fd0878320fd7694394be3c982f0a5da5d3b7360480c0e56e4a7ec05e623703dfc939afd5462ba28880385ef8ed0c25cb3eab4b474ca83cc99f29fc401e6208f35586f0d4d60e109f3d9a50b019506b4a7060b7ffef86a59ba815899fa3630ddca779f4f93dcf105aa747254d494a84d4b2262366bea5fae7e0c04e3f77fccb7ff1ea4418911eefe2d2eeca9774c17a4cec9dd55ba9b54e83890fc10a1072d598f6876742170db759c622b43cc111dcc27daa5afece0ba63c0b067ccb4742a2cec7f5a607f17265f0c41ecc926366be95dbd865870584193d9e549e251d42eb0578ad0fe9fa13ccae1b5e8289a8e060ad611f0eb1882a225a108ff2afc8cbe826158431b40de642de32408cb7dcc6448452ea45371d2e4f05e8b2a68cebaea2bd470aef7e655603642c7970198fb5c0eb9fe526a310749a26b810671133919b2b8574e7f83b2a94aa266d5669680c946479b79369494fb1c7519cb6954120adbe40eaeb1f038edea0c28506edffaab1303c9569d8f0b814b4777ebdbe5a37d63ca59d94985f7aab11e90527cfe43074af4c903e98561a6f39e8930dca543dd9b3df0ca9e95de5f35a095cc9d21fed4732a10365e75fd7f308984ddba97123d47dafa7bb779c59439a4e7b3e8ea9b622187e0d56c4378d0b94ef5bfdfa82c93de92780be970a0bedc4e62e5a40fbba68c3da3f4cc5a93fe14dcf0318f96152a7c900caa590623443382efd44dcd2657b15207c066e9af0ada28ae072a7c91b74b63b5b1c0e7c3206050fafc992d66984ce34d87fabf8d89892f006623779b8dce14de9d0f26c779c517b53913630963c2eb718f4edf058159f1d3e3e6073390e1a69e68fcbf93e687de96a44735597e788ef0a5f7ba6abba0d8e1ad2c21d544dd1c249980201b02c098369857310767b4624aaffd1d74925991a7d8be859429ec311374da864f37135144ede9a3da8af642202b33f1cc4f57300849f15624a5a849912b3c9930c4217af34159374166841393e368f2a2c3d551d41e02f52c5c11e21aa8e6cfc5b1cf976c451c832cc78a0d357b16173660127f9bf2ad1a6932249bdb9d5bb44b0a6517f5b62d888e7031cb4c66676b337397b444edd334d6fd53d938adb64d95856c2df12d62433ca777a573dbdca3f10a5a8643d9425194e388a85002b95683aa94b7648a6a2770534b9c6ea47ae802d0ad45c448acef579c52fc62d760e3f07da3ee8705b8a9091c583a14686abb13efb32a388703b9619efa190534e4da50e85db3035e8f870dcda18eae9515cfae2fc1fc30c8e10bdc758581aa6927075793111d6d287ef5616bd516d9b0438ce646c04fec9ed2f3da2566f8819e4c3949f7299b40d3b0bad810fe5b638bbba55832396ff8877f327d22c9bb8004df56c4f7a71eb2fb45b6342120c11902cd4509ac4700ba646ef2b3b1bbda852686bfff90c520cd693e5bff4bf1871fed4f75068144e9a7073d5aaff143879de5e5eb5ad41d6cb450450cf72374ef316b45fbc3fec50a0669f3590c38822d5f587fb11360ba76b9a53177cec356a9b8ae8454663da72442e9d091faf69661e5e5a3434059ed35c211646a559ff96f070299a40fb607c9620eef53dc6c387aad7ab9fff4b1effe2c5ba0f19f33703ff3bb737a5f0297a591aed80112aa925fa7e3da8880a46eee943b22277c45fa39ee72f4b11c0e12a932a210a203603abe97de62f07dfb23a32d5ff24315b2bf7c0fa2d295d818a88b64670ed5113deab7248dccbc19c6c80da13bd8a740a5ff06dd195ae6a5bcc2cab97ddd9eaea84d4740feeffd7513d85d37fb5600e78d298f05c22d4c62147a7d5e4bed8d07dcf5ccca91844b515fb0ecc763b37e029648aa4a1c2481e81c50a3dfbb23da18536cd9ab0b52fcb02462edecd94617dd0e35da4829e277a93724e8065782057e444acf67d393ceafb4a018b23e9491215aa611926f644cd3e0b1686e3cec067391195cd6cf0a7137f9daca16dfe63b8aeb933ee9b85788f92a300b6286409486d6abe37786e1dcca6a0972ad42b852db2de6de059305bbe83c9676fa1b8696cb19e5e07f67a9c7ae50867939da8a2b7a7e6cba12179227f4d77be33afa861b1d9fdcd2f946f8148cc617b52e8fdfb48dba05b695639528e4d84ba41863ae01052ffc2dda4d4b38a3ff91d0fac0a7b338550c2b1df6e2d9cc510231045c57e93bbddd6b1af06a713cf8dcc6092ee77ff11aefd582a68c003bf8a2befbe6ff5a23a5aefbc24d35ffd93ff02057eee22cab81806911845ee7ee3026986f0c9321ccf3d310bb96f744293a342507efaa914af591d57906bd13f560799d1ee7dcb63f49e54f7287609420d47108ddd28db331d786accbc0d88131becd4f45fb139f5c59a88baa5cd39922d3aecb03acec7df2bb2a23b76469f53e7321a4b57f793991c1241a3dfdd7f62c35edb025e184d40b242eb157d6f2a1499df04525824f2703913de359322314e442d111b7943e185c67d15279d470ea1de81a87af6e582853c4148ca7d3d0f445b99e6df72e41aa5ded909868ec4c19581e57f41e3914b6cdf8ceea74ecd9fbaae4250489669e15daeff789c6bcd3283958421623a395eefdb6861f050bc2c038a67d095f8dc194f67ec798002e415789b596af2cb818d4a9228796f908b0a9cbc5989da894aa890e58b33b8d90985faf7fa90fd37327abde5ae619b8a530373fa13b2b96b96c9118ba38799fa6858fcebbf1bbbf67d95ce3c21cf31f2c9d7c6afd0a65d8870a74b1404c5051b4f7ba7797346e711b4d0ec8952eb66cbd857954173279c1a4597ff35f1f572bab6b78834905399656d40a7c46d0f522643a0a7e9f9ceda637b026d161b78e39e2325efa25202ac214039708ee7457ff23df036259185b63e018b58122d564bcedd1f596c179a49079f7ea636a9333ad965e6908dfeed9210f920e55b094d07cb0728944ded731fe3db54c16a20d3dbcda5c4f177ffa5209905889b69607db9e8d0e7ba7ecfcd576fab9a717daf2205318646fcc56329247785c8da4c4315e6706a72c705083569787f5193cfab1c9fe0d7e16da2a1536361edd53016b66db0e99767edcdd039a44362735d3b0dc11a3b569f2b05cc1a39343ca16ff9beedf8b32f994d9b2c29f7a5f38fe98c709ded2541296b7b55336edeafaf32be246a774790065253ca3e6c06445deb5d710703c4608242817664614897ea9929948b0a8762139789b2d649206c64af2b3763017dac97afd001a2590f6feee0fa150b314ab6157088a48f819c0ca172262496b4319a6b65934bc08cd4b4ffe73783c9edbbf6493059af4d3c1eef19a2b7bfe186e813abf2e8fa66f86cc8623930117a8b4b7aae0b40477327aa84f4183c4e89796091dcbec3123629852a3cfc6401a62d54b397a58689056d5644b7879d460b7c4ec8127ede7fe606f547d798a912f2dd4f12e3cb66d67638d9b196034b5c2000b0795d6e69bbfd43d588e5fa55424e8d84f7420571987ed85d8ec7ff6ebd306d8445b6cfa915e74fb2eb45b7ec6a9696e2badf9e6c9da6d076b6f8990c8620b4eb14e306656f012afc0d023e6c712e08090d6d76ace02f0ec8e87952faebe339d85b4ce5ca557b507eaa0affd9cf847800eb53c496e8eb48ada73ad4c394757e0ccc5e58304ecc8c2c558dc8e398e1a605d10640e948f4439e171f3e90a10948010504e03690dc4b600a5c3172010ed8638c22ec9c11be45be3cf433a6beded53ad09d468bd5c165d17b868b231563e2176ada8f01a6206c43b4e4221f8e8f5e9fe8d59779f293064313032bb88512f61866088f141675837774e69e3481d93bae69d461d29b61f255345f3b6ce97113baff07fdf676308ab1cf094eed387a0dbc109d3ad39628cfc2692add385e077cae2fcdc2d72a6913fa7eac4dcd7a8d837aede621aeddf26718710eaee9857672e3ff55e82aadf1098ff553656e4fb658f28bd02946e92735d6886db90e015bda10e5d516cc132070944d16725746c45c6409ce0566f99e38007e97f079c3b51c6b9ef82ce547df437c2f9565d3a0566884add6d05f9a48495b020af6821b1469e2238dcf84af12a8aa85e0dfd6c318f28a6f0bc2f6d44b1b11918fabd9257628071cc27b7138753805b679a462af7988878ae0fcad6dbc73d7ca118b8711b5c0c4a35ffad9af382fca0e2360e53c96671130fe9df3aa2091084c1562c0383582822dc1378369c812829d13f664f712ca75eafa1b7a0d58e556b9704e075e59e04acf2991612dc327dae929989bf6f9173274aa5c04a4084304456cb87c79efb80e26c7f3168da956054ea72661577ca080436f1abd4579587b8495c3b0134675187bd92dbf1390d8c86dc3279ab6d8aa6abdace711160e49a931e72b7f93c2f652fd2df2b8cbf5f82d32b6d410005fa3b762f81f0cbe63c87fb588d7ca1ea51996ffc7ad8840f666d408bb4b42ccc2e3d8bdbebb2e22edc73ad0529b488fb0c7293b7b6c8e03a14eb43d4a3491c76a5bdad00139c31ff222df79e38acd0dc19c85fd500cca9df1839477b52b9afa813397d798f2bde2f01e9b3028f49d28eb10c65def56d5f55ee77215f2b42ffc8dbb0deaffee77f62033a4e1676e41426cf862ff7a944f86352ca2c01f09b94b4cd5d0015d8e92310391fbee1ab8090fc692b24a4f50c1da46c3e3201ac671af6652c2a041638723bc589da0f86b8c77f8f7b1e774e272a36637aba293cb566cb3b88da12b331f048a774cf6542ba626f5fa8bfcc9083caba17e617487cc02c7bf36481d9fe22181eabc9466d68ff5008500e516bc5d7cd0a121e9a871481fbc19718b7e8753fa4d970999843f4ed11b09bef81553f278cb168b1f74121be7cef677bfed112770af728d8447d2d3bfeda9139027459d532e3895b201fa683dcf39fbe26765fa0f0d01685646404e01e583e4c42b09af25b35c907363cb30c9491140d049e5c6e3c2c738728f3c60e3d92bdfce4e09c8af4919090112b3531b3ca91d1898cb0eda16962869c051b5d7d2221a5b6e8ed3a4bad7296973791a491b0fb8f215323ad3d75feb3ccba6d30d2976083d9a13f2a09b25b6f776a32df601935da832e8e72d30196c984e6159bbe2081856ab6c69ccc059b4443d71d8695b917fe4ced80776c175a33911cf26539fc7975e00861bc33fa6ff5cc9474e185ac6dc43be103dfe057c07a3198873a96243be09d5aadb9db9258e19d14b25dad4a7111fcd901f71b26395ed7aea3aec00d59644990a7f57b117080115bec02dc7b59c49c7bbe95337d7909d742133f52381bda06af811306206d28a71bfa9658b41dc619bff64ef4673818bbca1cd9d4e0ac4fb6c56683ecb9d2ae4cd0b4efb88566bd8517428e1ef6c866b7e2d35a92e0f993530d0254ecdd56847f4e301ba4466db9194cc457b6342b8e7d2163940757002893a2b158f15249c0e3b2588a2b614f9b624a6652884f644b10124087ce2c8ee1c3f255943fefa03a1b625eca4e7d253bb7dd0b1af69e833b0b5f47bd5493251687822e549f1078c07f56e9f24d4c7dcceefd9fdeec3396ed384182022543409d4c19e5e75c81b894d96e19208c61fb8543d0a17daad7e9f039fe028b929322fc835e2a42a5f11a0c527f1b7227e1b099be74a9130d2845b9efe58641ec4e2e464d3fe5e86b361f734255a544715e1be6f497147cb09ec363fb429693cf135bf53f08b2c0219e3f9b8456c570418a4ede7b75f8818be2e82b06055f1761a1ceacd69e597374e475930d8e0bfb0ddbecfc390b2179a033336262deb1f6596d9a626ef12650242db6a8c0c53918e1979f70128a3b1293b7ff0bffe833b7ea81316d81fbfe57d0991cf2ebfcd764ca0fae076313338fad6394b15dc3dadc243f7cd1353d582c85827039c75c2ce20b0e251745ea499fc949591f5f3334f0a51344e4f24289e02c9bf2dc8353a83a5f9e8bd22e23498d3b2c056b89d94a51cbad71a45cf4921f694ba6678504cc4559098894fb93dc7a7ea45f98111a9a901357a8f63e99db283a6c0b66acd86866866259ff2e92fdb1e6df5f245da8ed74d6a665974f040a698dd5dd3dbe434b9d424abff2eb91bf10d1f92fa1e3fce4e2ffa8a44e1aa65e22ec140fc88cdcfca428a65d7d033c12d44a233e6b5933a67c77b66bc1e0bda8494e441b3dd13408db37e837378b2dff08aeb464af3ba908d896f56fe2b5c81572b34966ae26b68c42bfdcba79d76a3fe567be1d40560fb2a64c158cd661dad94c81f24e3b1ce2c14ee96eb3d8a588a480e58c9d48f6a9f556420de2b78326a59dce0783fef7337c18575e8d76501dabfd0ff986cd5f24ab5379eb54b35d2ce4783f919734148b2cfa7bcefdc86a2f26f9062fb673496b8ee2f1feac7f6918f4d54a5d2d171be11ffad887efee26d9e1c6ef17e289d314e630e7df257709c6359c7fd37fd3d8ec49533e0ac236a7fd2aa69319a5f72bb21f19b1300822ab23443acc51cf0b4dbb392af842b112529cf175cfb39a404963634a20789dacf69745ea03b9b422af0a97bf40460f50187 Hey, password is required here."},{"title":"Nova Engine","date":"2023-10-30T15:43:32.000Z","path":"posts/nova/","text":"98b8a949c3f5e1d69458982c9c25fd5a277e65ba1cda03eb6d2ff14892b14fd18dfc2abea7d0dda28454dd441dfcfddf5256ea3ef416f3008451c8ec4c947f50581d8adf8a746cad45debbef0d23ccb525a4257da90ff423f8ca55ce004ca272b74a907fe0ff56e7528111a01a18638d4d83151023295ba2a119975b827cc19ac0bc0848395477022d70d26ac953938d1533b93399aaaa586da713023b7b2b9bcd360cd6723f66b6505843d232111e629554b5730f71cbead394b1a4a2e2ac0b4635f8c31e557bcbe3615ebd7659557debc984516eece166fa2bb07e6096b0424c8e543f1b340c98c20a46f00e12d570b47f3cb47c5a297be2e7c5972127b16d08653009db4bc3c1257c4dc59d60b0072a6fe996f410fd5c002abd1736b32fab0afc00981d40fe487ebc25e0b98c80d078603dda4d2d6bb38742a37cb8e23f706c0cfc7d432bc51409a7a0c886cc431fbdfb3656fe46d9050d4e3994854dcfe5d588f2f97ad74e90b1b7a923f3be92cd81f9903101995e557fcd1b509ef4ec6e4c1a80cf9c2107d244039328b27a46bc8b323ff66049e09bdcac86466fa26e2f86929e2553b5fa5cba6d7efdd0264945c216672e7e6b66bbf7e3e4896bfb31224514935a6a7431cfd7bb00ed7b11bc116202520d192c85cf5d842cc8916ee3b4e5945146e7138d29debddaab33ebe506905103eadc7ccdc104217abbab3a2263a8c8af83f3bdc97128c4ba30b0dae7d7dc849f4978751e8fb1fa4aea07c18f472369c7a1cd16185d09e0a8b0070b381a8cca5c4cac2f257056cd89b3a05dfd0814f8d3e9e7e33331941ce42511d6ef021837037d7ee56388771b9ac46dca33d117f41cc1f4463e9ad718c6cdd72d1ad9d9731fd0c64c888121126104f5a68ba9b9909e1e685745dd39e3989f4c4323576fa2d9ba6f041391b62fb2dda65b674193a9caddad6bb36be5f32ea2a0b295792ce6e88e71448536ca06ae6231e4003f0265bbc97fa58c7d3be1440b73d20aedf631ce798abfa145562c9419b9f5354f4dcc9a2c8313bf17a0e2055f76cd5bec1b393a14ec04db0a52a96eca4645d3d230322dc0b60818c10ea1dcbdb1de4d599fb7310a880523f773dff70669b4c252f1da1880f8a2fd18eaaea212e3c061713893d77e04abae033d6cc45c207be76cf70aafb4651ab5bf11b898cae922d71e240874d95af9b4b3fcdb4ebc71e0c388fb49c785d244065bf381b9a58cc5b7fb81e97663c20d9a233f28c9f14fb5e6d3d6e38b3f74991565dfcbd9068fc8ee8f2ea120e28c8fce730a6297c8c34fb0100ff587902adacdf9d3fff4dbac341dafa0fdcb8f8245def639f0403e1c95dd7cedfdd3d83148eed6a719bab33bb668e25a3b2c5488d62aa8d60a0ebc91698024f97d5a66a1896111f5b4eec3c87cbc3ea45c50923dc4329d567fc10a16dc5c79c3ed9b036afab408067e0dab6245f91e3c667100c6654779a5fcf13dd09944be78522ee20396bba575a8c81f2bb65d2812bddfc46b7bea2af5329f749a4f21b3bbbe182b12324104605d4c8eaa0f69fe33b3d0687bf1893a06e3bd28ba092f808ebb484a89d1fe78cbb2b335b8c41ef7db341c8ac715cab721ab37ce32b587f5385f9b930ac9e27133d5fed5c49067f9a2d6e6d1dc46994f20c26aff1d0fe82852f0555da8d972c404b9f2b0e802bf548a6e126ca7abad02eb22efb13c2d9e4a2ce26c2d8296fe3ee64165ded9fbf2aba4c35ee79beaf732532427f9ec103db9c5e8aca5e9b25066766020702c00d83f7c2a2e489f3f3f52b4191a7a7d3a91a15643774fe89bd8e6db78dfadcb405cfe51b48be0323aba941a9a49113111a63bcfa81a3aac36270e9e9ce8ad76fd8e7778fe422254fe122e9f8398dd030b9056b92f68208456021f727a0fd6c2bc885205527c71d0b06e24a594c146bdcace19c280d5f167020f0729f847ff474ec573794ffc682429cf8a8c32ed20f3e26ebc4a8545a0472eff5de61bbfd699b38facf320f079fae4445b5ba7504489d9f0f6ad272cc00f1ab1a8fc9655c6d278eb58bccf458bc66adf8867a64e22e90880f08ed400f8d7c17e6b694d027eecd081dd9a95c706006010ea64e6f702ad82f38e487c0ce6fe94a94342e712e1908fe575615f457f938ceae07d2401080d34e14a8a86e9485f94ab86920bc4586f83a2321d6ebe66f967c2fb66d5c70cd811aa2adda4ee0a78fde29c0cb62651528eb1667ff21cbb31e2aa0540fd5fcc6e2e16210e3899e776847505870652b414ba7c39120322eab9bbc813ef5b1a7fdaa5b166cd8417fe99f525dbe0226e5775fb6a7ee3924d02cbdd25976cf16fd2d6ec512b3d1266b3c415664174487a8fcd149b34ff6a301b30b59bff9e1ce9d12bc15716060bd9e0a462ab26f93419cb7e67f3f3627d6e134bb879c3eec1a567dd3a05faf991404a176f5bfc2fee949ea2e5c661dba1762d8b388272630c47f052a2049d27163ca5644c9e34c44cbd1930760f54dba94d30e40c6a324ca032788125a952b2ca3e015bbee2f1697d97f7d1a48462bcdcc51b0563b502155fc14de5d2f72e2a03bf4cccffb734f067ec14c37f5831a04cc401adef709da660b8a06d1563fbdd87f849d0784afcaa7817856cd504d6934d1d3affeee40b405ca4dd8527584fea0733ef1c7468fb4e7b12e2674e6acc31f115b47ed03fb2b80566e652507d35f2e1db77704b33821e7caa952a6d8973417113ac1cc625eb96e4310a7f512796c4d4a9a06d579a3026353dab195c98c55302714cd3d63f1a067cb8b8cf53946a1ac1de06f8310c382672c8656f95b4d91efa54ec62ccae180289f2e3071c3106447ed5e463fab2d6d767503f36e45f53401acc6b0dd9c591314d7ab2aafbeb26e3e87b7c187a27e8ef1910853c747ccebad2875a12ab831f21d6ecd9edce1b7ef6de136353ed77986df45e1234f0e0f25e61f0723259493ba08ef477c1a80fa2b00ceac9e8788021e171fcb112bc40a2dd0a9f60a1ddce482349225ecb53b46d5723332d2bc934c483c30af2b2facce2acbb55d6a868568a178ff8d855dbbe3321d7667a0525e6ec80cd56fa196cba9a17f34989bc66610c9dc94079bbcbf45b58b7fc7bc650a30e1187edabec79ff596ae459b34016470e3d9a0881e5f12e279bc02f39730cb6b00ebb14622f006efd63269d03d44c8c4c7974aa30aa3a28b2fab6d6bcc88a3ea891cdfd071aa3ca99a0d5d69b70b9016562d5378f142ed07a086dee2ab3a0dc22c48d4b3d0a3df51a5c6fd54e024a72349b7478ffb93fb4efd01a1650eef8531533b51daba00752411c2df1b1bfe874f7499fd82ada82849fa5cf21e34e333b59e6d8b2d73f45c8fc15abdcff54fd894fb6f6cb71b360b2582fdf231ec24b2f1a6d4b513f76c7ffe56b0f6adeb095946c3011382f7f26c63c00e8a0de5a57e4e9db0341aecfa64e782de8c865878b26bce19517eb4caf19c92f5bcfedbb5f0d83abcedd657c5a2eb1edd0bc00a66afaf7f742de1d10663aab64d87351d29fd6dcc9619e8c23410e72570b413a6652395795ba70684b03a7945ca2191f428b73b2a81883cad269c321ae6e7d16200748fe3a814e54ed0bcac8b900e75ec9fc4d16a37cdcd38b61bbf823964dd590da92f043e444e56eb09e4c786e77ec57c390c80d1fc0d98875b7858e5bf6075b965f720495aaf147ca97dd70987d5e7f1944388548a317ca562a57fa4836305e341a0290c791af398afe160cf1941775658ea653ef506ebaf391249c44e8d6e60eb1b7454aef378f510f62a234d97dea7bb5f23127f3aaff9d54613e4cb6f9ec228923d3226242ea096f32bb88b70b68b0387111f49db2c2647ee2bdbcc74edd8ad1e1bfb6d6e9af3e58bdaa9bfbc0840c9036d9c3228875a0e85a6bd53fa23466acc580cccfa0c7d5ceba672d231e987df6b539703ee2db348d8d9b4d31120341a5540108e125b2e4d5709770757174d08b7bf64cceda2897bc3c70cac7affba939ce1195ce3e12703bff54ae8d5ecb8e2013ca4e33d262fcb4ebe6e561257dec64408b194303d4106f3af4e5f9a50436bee606935f6a0d17449536720b625c4668e251ed18418919829d4d3f893a49c26ed00ee2ada578b399846329ea143235293ece9d94e4924d047fd3e04d23a894dfe3df9196bbb59ef4a8c8888e9ce8c08c1acce553a61dd75af230bec4b3c751cf75f304da1df356d565f092c3ebe31cf5c23eaa05c4151e316a0b85c5c5483e9f783b5b4812858a66f57b9911330876f75a585edc6615ddffed7de51a9903ff080210e538560bbbec7098138760af90166ff14eb1b47b5493c524a11b1d96c9522f14d2456a824edaffd1e5cc351ec4802b037b8a70890723e3ace88a040e6478fb9d9354052336e46ccba1dec475932816eddded95a7eb1f652667542d2996826a481424acaa26b82e5d82da63afefb4c1d71cefb3eb67e73fe9d1d67e0190eb1625eb8c01de4a0d6cb35ce10aba4a5fb0facb308d772bd34830233c307b01c4bd65a706eac26f793d4691ae58826e131b6714eb6e7dd7beabba90f13689d531e03bd5b909d745ebb133b1a22275481eb43265e7f2ad1af0837efb3ac09655159e3762d1f3b2aa47ab05624c48970c8cb068429a95970f537f20843ea29ca2fef63907ee20e2e2c40b3fb003f555802721e8cbfe5afb6a6d71aa8f5bf42a2af81d0295c48b701a85f1f1fc3313bda07cca7551a17893419527eed6db33a4ae153d5dedcce31986dc020cb8cd60f5225174bf966183de6aa0f7c39d8c33e1705ac5b8db1b976af6df34365039b38ce959fffcbfc9c5c8ef3e2a7386646610f419128188cf90707178bf1615cef1b00ecad2a5f9d6cdaf8e92e3a63c646cf69f99202dedb60cd75df52aa874ece68dc49c0668d64878a55a3de6b39a213442e6bd766876b5fad4306a682d15e51b1f5c1c366a7cb8dd48d1d5c63565b86ba79551a23a59948874b95d7c48005206c2501b4d2c4b8f88b0bbe664c03e09a128737db348bdcc5f8e5a28f21948907d3a49a9c5b7d8f69211ce2a81c8c036431cabc954940944f9609cc2de0b05c1eb5129c3564226ba81bfe9fd627e7f611ba451956f3ad05f9291e81bba015baf72708ecc1114d738a37853bdf5c3595a14bbc97258f7125aa04eb0226b7febf5c94e0fc68dc09eace80130cc77168209eec4c71f07c8f57473a1cbde29619715de8401c668f626f773160440898243e8dd63923f7b8e7af04715990164e8f1d8a5f965281677af6350cb3e843d1c022b73387e735ba3ae2618351f96161b7591248a5c97f84fad91f9467780daf07c45eecc098e7929f2ff162848fb4022a79a5d4a39db2c76164a0185aed837857257738ea8714828c6688d85632dd9837d0e1ac904da4d5d74dd2e737ebe1fb835416467ed732bdbd8cea907a0aea3a32e1001da086fb411a866b0bf2815a7d96899dbf802191fbe4be1de30babc3b4f8cce2d72d64e89ef69385c8264e35d9712dabeaeb7f40e71e79ec2a281684bc6abc9007887ea1799850426d2d6eacd01f12b0f6ebb601cf50e95e717aad0d70976bf28dd9251c0c5154beb417245983f38a2c61bf57a3e9d351879e70a6383438f151eb078cf22c71b03baf7db9cc5ffe3c34721fb9aa186d7762ffd48b9b17b3680e0aa8d482bb6b6978ca0fe183579b7b3ef73d010a529243d1decfe59c442efcfc023a8f0ef426fc011bf13e9035a24266f6da832f98adb9cecc7ba76843b048b30a39963a10a83743bc4a8e630276be5d09b7dcb3d0b2822bc37ff683b50a3f5d13d0e49063e5ff9038bb44378efd6d9f77d80ea9cfedab0489c793550de7f480acfffba9983af00f2e774a6be5f2bae994ea9f8f1ed0fe345af9e4f68e3cd70725348668047c8173659a43bdd1faacbc471f52d3959d8cd299a89ef21e2e3ab474ad442f4952e158844921c7d81b041a703d4f6d92c64b3087971a588b16e40ba15ce2fc6e7d46e91b3dd9061637250b2713ba41e36aeda7697573fd729f9698eef4f7bbc2d73374ad8da64aa27a925b58236281a1bb99e038f1621bd9f8443585d16f1cd2b035c6af8b70c650518da5906b86afd5b402d62e71b0d37f0d5e2fa9f27f8094f070b1aef79948bea47c5c88e20dd01a41b7ce76df171cc655791794948e868fd9abb7f2ce0030fb467ae810c2c345863fa0fbf6ab3167a03370d1b4c3a689fc81e9b33840281bcf8884af3cf379ba9fc3c7d8268bbe3438301aae439fb503814b801b929542b739d3312c095109d0254ea10f7e363004e736d4f8d89ab07d048b26e9b297b838ade71877fcbb90991ee3b820de8a374edb520af7e30f97a8cacd6bf1c196c32f242b504122d2ce75c78ebd265cf77cb631ea09d9b83b6922dab5549462b9481343abc8e57ed28baa7b22a3e583e280b992e52f3a40cd5040221597f33ac7ca3ab91f9064c7cea9aae3051ff005ef9ba9c92866de7d24419c1ae10de8c61bca8e80793230be7f40c972e3f1029cea465bd32c67a76f49ac6f44e7b72e4aee529bab0003a0d492abff3be61463e5cba22536558a0db01f180c787a63de28d920f0ee7de795a564b0e9c9a2d2ae9f3e5da36c7d9f0bfa884b1dd024cce9e9497b69bcfb5a69f3a6f773d5d3f83cf61f2b501296f488f3fadc890fdc17207a41880a5b2d4000b9793b88593ab6d2cf0dce2bcba6a33014483e416c90751ceb0388440102dbfb1e4244b91a70f13a28874fd42b28ab70124987557e292a54beb0755f105fd91ded78987a3af2a2d82bbe96f28fc914d219027555829469dc0249e2fa74d5e113f5e1c4fe0b057a2a7278a29c5fed2eea2c6157e7405f2169b9f8810044c814502de9d8740386ffe649278a55feaf81bbae6d19004b284980591871f816c105bf7bbc699cc749a4a0fd659bdff6e42e076f398af4718a82c6bec21ffaf016d1b5b1603e5edafce2e921bd91c14cadfb60c0c92bc7dfa3b495ee280e654a55dd89c8c320d5fb67d456002a7d233e916c15104455227d13a4d0af61f1e2f34f323b63a0ee72d3fba427aae19f46d19732375b113feddbaf7cc2e58da8b9df046bbc71ee0585add1a5e92332f9c26653af9f35622712327c37b3b492a2b2222f67d79bb73ea863f078b88ac206efcaf02aeb0d5b17563162a395c2d603bdc84ba843936c7d167d1069dc26a52f6fb989b64acdc3764635bcf92bef379a0d1bf097337a9ea8183fec05ebaf8c78b60f8beb1643883942574f4c6c8c41140734692f83b4a73601405ed30154126bf785acaf0f8c18d7867ff37ddc8005492ab05cc86671ff7ed129b8bc912ddeb0eb1e6683a1a75f28297276ff2769ece81592e91c1f61eec801eade17ec3ff13db390b737a76c363045cc8317bd79eed503f003e437dea69fc2db39b0c001290b90afab13460ce98a9883ea54b53b9d7a5881f2af028d2fa3e646283002278044e54a44d1202538ef08c18a075cc7f2b5b05b4022b2edf22c7be01e3664a5fd00b6d2c7c90f06648f8ae2b9acd1e51ac22035541d48e9c155038cda6006a0d8a872c20e0259a7cd819f665051a53398c93845668e90b77072228cbbcaa63f33e159f69c12b64218fb1d151d67db47620d7a062f396bd43724aeb7ad940c7135c7e4b25584d3b44103ea75710ebe007b6ce996d3eb8ef9f23292395e2b4776efb1780c62fbb079612a3096211bea0ac99113fe50e26738db59c1af3b89769089fe9c61b8be06707d3aac46bb50118c5ad8a27b78f48fb1fbf980501215f357e8bdae43004917be829db72af2c562abf707bf777d6797347c7fe9ad176696d91157bdf743fc30e976970609f0525ad2a763f5c929c0395d4a55cb443040845bb525430413edca528fea2806b091851e7b60a4c25b7e73915e13fbc06a1a3a5b4e539ef7db686d4cc3e5c6a745571776a81a8d3b97c92b6e1bf83b6f99403630c588812d40ca35d17453703493cdda1227945e0b9e6e0d0702033ade92e1139d99596fb5a230d2055c65770bd0957ee5c58c8dbdbbb0623553907b50878808f7273c023d754b4d830b2d42f5481f665ce202eaa5934f93e443d2ea4c17ea5e91afd348caafc8fd22c2f12e16e60e483688dbb04c10051823fab049cc2f8da194e39de0cf9957a990fb0858000b452e6192a71e1314dca0cf373faf673336615864159c5d908acfb4a1e178f3d74a71a2edf4811cd1c73e6c8c4901231790391e3e3fac17a7fae830082b050db56a6ab7e2a2eed2fa987cc4007e2df7a4a76f2e43070fea66cf09773bda8dd2f4eda03ce0c3f1a06686df1ccc1342b8706071ec361f5d157c37b1346641a7ee9f55b7d4be46f75c1114ba35d2b7ab27db7b9b980c0dd63b4e3553b55dc4f82cc71faf9a881b4ec04392db94ab4a555304a715ff03add3963ff698e3b76d2c6186aa70ad96886cb24193c84db408e384ca00e24e0987a855751baba6534e97ba245fde3014a81692fc167d45f73555eba58c5a6c0649376843c2ee5281d99e941ec43caf39520664da52c1af1f5532301435a71e7cadb590cbefeb2bbd1c65b6ee8340be93745127e1c47457ee7a608dc56f22cdfeac6f6985d4e420b16765308ed0879258ff5ef80719bc3f7139f3a9bc54e9e25dd1db5903675a4426f934c618ead760332eacf97e56fda706dbe3d782113901c1b66497a5405f39409af7cd3c5a8e07e1376bc41dcaeaab89fec7807078f901a8026d1eb4979e3588ffefbf8100602772276231c71ccb50edafd6de2ad2942d6a0fe14d17b676be25deec30858f0a25fadf8950abe6f15169dbe73bbaea411cbe575b052b94c73654369413fa9674675d466d76ba900c9f6d2c8559290c4d475f22b0c7178894077e79416e2c47a240bb90054f471111632609ac2a7ddf045c1a52e0c848ab9f72b3bedf24088d9ce82fdba83befc8d147ef304feb6811b1d61c26a3d3a4f64ca83601c3dff17d2342af07131841e5e2383da5a65d45cb2ea68284c9b3eca23e1e07fc61f9af4e80398b2f7e0f13bfad2fbfaa2b26854044b84ea1bf514a524f7092f45df38b33f6e85022079360df1c2724542dfe06e0bcf4d9d2ab5aca28b87097a11146e6eb3077e5b0bda32ebd76db72bcca1acbcb18d8b7160ffe724398657a9ca5046834e55ef6c5a68e76dad029c6dda8f713197b884d14c1f237ec4e5fb882e7f674d1cee2bc8fb92fb3ef7463b8d95929bd723faecec32be19f7affc294ecadf90caedf05db7876f0287815133d0e88d58dbba780951bd37b1ff99d5985ea6ef115104c900f048d8da6beb3f969001b89c2bda2216fb3b8289791ed6f6aa874bf239d8ab6383bee9e3e2d2367140b6ef683b1f47e48cfa4c93f313a279194939f17896208ae93d37d3e66b6f12cea8aae7a6d1fa5c0d212e573de19ae68e33ac38edcb9378e761a97c974849dd9f33a104a4415980d64c0ed7951e5a68ed839f629916235752166e90cc1dc7d484bab5b1c190509e566817257f6180fb9669680a357a122c78ffb9191ba2e8e17f6dacdca34b5b642d4bfb052c70b64cb7220154f8bd12ba8fc25aa3eb34a14648f4b69b0f16cab1a1b96b50f079bfb7e985a4de3791825e3cd449aacb419c63413d1ec6f369f1454e6120f90866ae2daf31356ff2ce17ad46a358524f3e23a423f3f578a9e2264cce0ec9d0935407c3d0e073612c332d04fb4bd9430ef2dc388436db57149f0fc389463e8e0d4d1033884daa1bfa21b8ceb161b409634a9b1d1a6ed30ac893c71d455f8363e44703cf81f970c2c40bf08a643a6dab1f902a3c1a104a3777be613db15b8e5bbe29617d644f665a8b6a2d77bdefbfbc0dbc705c2ea4f8bff9a10d071f495c Hey, password is required here."},{"title":"【工具】LaTeX教程(附模板)","date":"2023-10-24T15:05:19.000Z","path":"posts/2TXFVDF/","text":"Latex 对于学术党的作用是写论文,对于工作党的作用,那便是写简历了! github 上有非常多的优秀简历 latex 模板,可以自己去搜,改起来非常容易。 本文简要记录 Latex 的安装、开发和发布流程。 github resume: https://github.com/593413198/Resume 安装 只讨论 Windows 环境下的安装使用,Linux 实在很难搞定中文字体(你都没好用的图形界面用个der的Latex啊…) 下载下面的安装包,直接一路点到底(就像你小时候装盗版单机游戏那样…) latex easy install 开发 Latex 每次编辑,依赖于重新编译生成可视化文件(如pdf),这是非常影响开发效率的(对比markdown)。 聪明的 VSCode 爱好者没有错过这样的机会,勤劳的他们开发出一款 LaTeX Workshop 的插件,真正做到了 所见即所得。 LaTeX Workshop 使用方式,在VSCode市场安装该插件后,将如下配置写入到 setting.json: { \"latex-workshop.synctex.afterBuild.enabled\": true, \"latex-workshop.latex.autoBuild.run\": \"onSave\", \"latex-workshop.view.pdf.viewer\": \"tab\", } 此时每次修改完 latex 文件,按下 ctrl + s 保存键后,VSCode会自动编译生成同名的pdf文件。 接着使用 LaTeX Workshop: View LaTeX PDF file 新开一个窗口预览 pdf 的实时修改即可! 发布 pdflatex ${filename} 其中 $filename 是 tex 的文件名(不需要输入后缀) 模板 附github上一些优秀的简历模板: 中文简历:Awesome Resume for Chinese 英文简历:Awesome-CV"},{"title":"【SIGGRAPH23】Large Scale Terrain Rendering in Call of Duty","date":"2023-10-19T16:55:34.000Z","path":"posts/siggraph-23-terrain-of-cod/","text":"导读 这篇 SIGGRAPH 主要描述 COD 中的大规模地形渲染,并大量引用了之前GDC的技术分享 NETEASE WARNING: 已脱敏、与工作无关 技术参考 SIGGRAPH 2023: Large Scale Terrain Rendering in Call of Duty GDC 2021: The Terrain of CoD GDC 2018: Terrain Rendering in Far Cry 5 GDC 2015: Adaptive Virtual Texture Rendering in Far Cry 4 大纲 Why New Terrain Render Process Virtual Texture Adaptive Virtual Texture Cliff Shading 原因 Tri-Planar Mapping 问题 Stiching Fix 1. LOD之间缝隙 2. 与树木、石头之间的缝隙 总结、展望 TODO: Displacement Decal One Material Per Vertex (OMPV) Why New Terrain 作者阐述,之所需要开发新的地形系统,是因为传统的地形系统无法满足 现有的游戏需求(即策划不满意)。而传统地形主要有如下两个特征: 地形范围小(如室内地面装饰) 依赖手动编辑(如魔兽争霸地图) 作为对比,新的地形系统提出如下三个要求和挑战: Lerge-Scale(水平 + 垂直两个维度) -> 解决性能问题 runtime 画面多样性、细节保证 -> 解决表现问题 bugfix, tradeoff 依赖程序化生产 -> 解决工具问题 PCG Render Process 作者展示如何在forward管线下绘制一个朴素的地形: VS Input Vertex Shader 的输出就是一些平铺状的mesh顶点,它是没有任何高度信息的 VS Offset 这一步通过采样 Height Map 的高度信息,对 VS 顶点作出一些偏移,从而模拟出地形高度的形状 PS Shading Pixel Shader 阶段采样各种 Diffuse、Normal 贴图进行着色计算,为了弥补地形的细节,美术会叠加多层的 Layers 进行混合。 通常还会引入底层API支持的 Texture Array 技术进行优化 Quad - Tree 因为地形只考虑平面结构,所以想到利用四叉树进行遍历、剔除的优化。 以COD的地形为例,10km x 10km的总规模,64m x 64m的单个地块尺寸,所以约有2w多个地块,即使经过视锥剔除等优化手段,还是有成百上千个Chunks需要渲染,因此性能压力非常大! Virtual Texture Virtual Texture 是 GDC-2015 提出的一个技术,在弄懂改技术之前,先了解它在尝试解决什么问题。 继续上一节的结论,当开发大世界游戏逐渐兴起,每帧需要渲染的 Chunks 数量急剧增长,每个 Chunks 都拥有自己的贴图,这无疑给硬件内存带来巨大的挑战。 但是如何解决呢? 参考 Virtual Memory 的提出(即物理内存无法满足需求时,计算机抽象出了虚拟内存这样的中间层),计算机科学有一条非常重要的公理:即软件(或硬件)层面无法解决的问题,往往可以引入一个中间层。 因此 Virtal Texture 基于此思想,它将所有贴图试做 内存 意义上的贴图,不会一次性加载到显存,而是使用的时候才会去加载。参考 GDC 的图片: Virutal Texture:内存(磁盘)意义上的贴图 Indirection Texture:寻址结构,类似与虚拟内存中的 页表 Physical Texture:实际采样的贴图 VT 建议单独列一篇文章讲解,可惜缺少实战落地经验,这里先贴一些有价值的参考链接: 浅谈Virtual Texture - 知乎 游戏引擎随笔 0x14:UE4 Runtime Virtual Texture 实现机制及源码解析 - 知乎 Adaptive Virtual Texture VT 技术有一个非常明显的缺陷:不论 Chunk 距离相机远近如何,但在 VT 中的像素比重却相同。 基于这点,AVT 提出了一种基于相机距离的 VT优化技术。其中红色框表示距离相机较近的 Chunk(像素也较高即 64k x 64k),而绿色框距离相机较远(像素分辨率也很低 16k x 16k) Cliff Shading COD游戏中有大量山坡和悬崖的渲染(参考PUBG),因此开发人员在 GDC-18 中花费大量笔墨讲述了 Cliff Shading,Cliff又翻译成 悬崖、峭壁。 先看看 Cliff Shading 首要解决的是什么问题,当 Chunk 的贴图使用在平坦地面时,表现是完美。但因为山坡峭壁的y轴是非常陡峭的,因此会出现严重的拉伸 tiling 现象: 原因 将世界坐标 (x, y, z) 映射到 贴图空间 (u, v),首先考虑如下几种朴素的方式(即选取任意两个轴采样): 因为丢失了某一个维度的信息,因此效果是不尽满意的。 Tri-Planar Mapping COD团队首先尝试了业界著名的 Tri-Planar Mapping 方案,即分别从 x, y, z 三个方向投影得到映射效果,然后根据法线与三个轴的夹角关系,将三个结果作融合得到最终的效果: 关于 Tri-Planar Mapping 参考阅读: Triplanar Mapping Use Tri-Planar Texture Mapping for Better Terrain 问题 采用 Tri-Planar Mapping 方案后有明显的两个问题: 性能非常差(采样数 x3) 远处 Texture Tiling 非常严重 性能问题先不考虑(因为是PC端游戏),开发团队后面主要描述如何解决山坡上的Tiling问题。 关于如何消除重复,最简单的方案就是引入随机数,这里采用了 Nividia 发表的一篇论文中的噪声函数,最终的做法是将 正常的Blending值 + 噪声值, 观察黑色框中放大的部分,可以看到明显的噪声值,但是整体的效果反而更好的。引用GDC中非常经典的一句话:“单个像素是错误的,但是放到整体(平均下来)又是正确的” Stiching Fix 新的地形系统采用将不同 Chunk 拼接渲染的方式,那么是否会导致渲染的缝隙、不连贯呢? 答案是:会的。 Stiching Fix 重点讲述了COD如何修复地形渲染的一些缝隙,而 Stiching 的中文翻译便是缝隙。 1. LOD之间缝隙 如下图,不同LOD层级之间的 mesh data 差异较大,会出现同一个顶点在两边的信息不共享,这在渲染时会出现明显的错误。(具体是什么错误?) 修复的方式比较朴素,即 将边缘的顶点,移动到另一个lod最近的顶点 跨一层LOD的情形 跨两层LOD的情形 2. 与树木、石头之间的缝隙 主要是做一个贴地处理,即根据 Chunk HeightMap 的高度信息,对树木、石头的高度在 Vertex Shader 中做一个高度插值处理,从而实现将它们 贴在地表 总结、展望 地形系统主要细分两个方面: 卷细节: pcg, texture layers … 卷性能: avt,gpu pipeline … 从 GDC 2015、2018、2021 再到 Siggraph 的集大成者,任何一项技术的发展都需要持续迭代、集思广益 了解一个技术,和实现一个技术,两者相差 1~2 个数量级"},{"title":"csv, hdf5, feather 数据性能对比","date":"2023-10-12T14:06:39.000Z","path":"posts/data-perf/","text":"导读 这篇blog 介绍了金融相关的数据特性,它对于读写和存储性能有极高要求 本篇blog 会结合跑测数据,分析三种格式的性能 测试结论 结论放在最前面,测试数据见 文末 数据量小,无脑使用 csv 数据量大,如果坚持 csv,请使用 zip 参数压缩(尽管这会降低读写速率) 百M级别以上数据,推荐使用 hdf5(而不是feather) 性能强于 csv,读写快5~10倍 hdf5 对于 Python/C++ 的API支持较好 feather 虽然性能更强,但限制更多 TODO 切记! 此结论并不适用于所有类型的数据样本,最好自己针对性跑测,找到最适合自己数据的格式! 对于期货 tick 数据(由于大量重复值),csv.zip 压缩比率能达到 15%,feather 能达到 30% 基本介绍 csv csv 全称是 Comma-separated values,即以逗号分隔的纯文本格式,常用后缀是 *.csv。 正因为其纯文本的性质,常见的编辑器(或者excel)都可以预览csv文件,所以它的优点是 简单直观。 然而事物都具有两面性,就像 json 存储格式,直观的代价往往是性能的损失。 hdf5 全称是 Hierarchical Data Format version 5,即高度层次化的二进制格式,常用后缀是 *.h5。 hdf5格式从设计之初,就是服务于大型数据。 feather feather 是一种用于存储数据的 快速地、轻量级的二进制格式,常用后缀是 *.fea。 它早起就是为 Python(Pandas) 和 R 这两种编程语言所设计的。 目前广泛使用的其实是 feather v2 版本,它区别于早期的 v1 版本,这个不用过多了解。 测试标准 主要从四个维度测量性能: 1.写入速度 2.读取速度 3.磁盘大小 4.读取内存 (TODO) 同时考虑到金融数据的存储格式,大多是 int64、float64 和 timestamp,因此也会分别考量 在这三种格式下的性能表现。 压缩性 因为写入的csv文件较大,所以考虑 csv 结合各种压缩算法测试(压缩本质是牺牲性能、换取空间)。 df.to_csv('', compression) 可以传入 zip、gz、bz2 等等参数。 benchmark 使用 800w x 10 的DataFrame数据,取值范围是 0 ~ 16亿,格式是 np.int64。 数据大概长这样: 0 d0 d1 d2 d3 d4 d5 d6 d7 0 5680658661046001 6886564689964211 777947290120004 6223515736992396 2823728071993317 8416657213663291 3500805963228465 1009748152605397 1 8952870328278778 306015862731108 9726241400443289 4237512935832667 2875425479333067 6311517969042662 878720088161354 8787118273065033 2 6858523177136352 1733196075769152 840572662722070 8438133907754012 6671944540650125 1147224095855703 1583106816125259 2798068568773141 3 2940819554234759 3142545317839947 4641209159206074 8194125756197731 4958881218032026 4405637321734842 500692399773906 3531617942462136 4 8149680042981168 6793238579260437 3891639455885689 3690167863144449 7552248224604567 6411717840330018 9556078695826276 4851668202438122 ...... [8000000 rows x 9 columns] 测试源码 class DataPerf(object): \"\"\" 数据 性能测试 \"\"\" @staticmethod def get_random_datas(rows, cols): \"\"\" 随机生成 rows行 x cols列 的DataFrame数据 :param rows: 行 :param cols: 列 \"\"\" data = np.random.randint(low=0, high=pow(10, 16), size=(rows, cols), dtype=np.int64) df = pd.DataFrame(data, columns=[f'd{i}' for i in range(cols)]) return df @staticmethod def perf_write_speed(): \"\"\" 测试写入速度 \"\"\" df = DataPerf.get_random_datas(800 * 10000, 8) PERF_TIME() df.to_hdf('perf.h5', 'data') PERF_TIME('hdf5') df.to_feather('perf.fea') PERF_TIME('fea') df.to_csv('perf.csv') PERF_TIME('csv') df.to_csv('perf.csv.gz', compression='gzip') PERF_TIME('csv (gz)') df.to_csv('perf.csv.zip', compression='zip') PERF_TIME('csv (zip)') @staticmethod def perf_read_speed(): \"\"\" 测试读取速度 \"\"\" PERF_TIME() d1 = pd.read_csv('perf.csv') PERF_TIME('csv') d2 = pd.read_csv('perf.csv.gz', compression='gzip') PERF_TIME('csv (gz)') d3 = pd.read_csv('perf.csv.zip', compression='zip') PERF_TIME('csv (zip)') d4 = pd.read_hdf('perf.h5') PERF_TIME('hdf5') d5 = pd.read_feather('perf.fea') PERF_TIME('fea') if __name__ == '__main__': dp = DataPerf() dp.perf_write_speed() dp.perf_read_speed() 测试数据 1. 写入速度 注意:因为 追加、覆盖 等模式会影响性能,所以重复测试前,记得删除已写入的数据。 format write time (s) csv 33.5 csv (gz) 135 csv (zip) 128 hdf5 1.8 feather 👍 1.3 2. 读取速度 测试接口,全部选择 pandas read_*** 系列,会全部转化为 DataFrame 格式。 format read time (s) csv 10.8 csv (gz) 15.0 csv (zip) 14.1 hdf5 4.2 feather 👍 2.0 3. 磁盘大小 format file size (GB) csv 1.10 csv (gz) 0.53 (48%) csv (zip) 0.53 (48%) hdf5 0.55 (50%) feather 👍 0.49 (44%) 性能汇总 以下统计的是 相对得分,数值越高说明性能越好 支持滚动条"},{"title":"【量化】爬虫获取东财数据","date":"2023-10-05T14:40:04.000Z","path":"posts/spider-easymoney/","text":"导读 这部分主要讨论基本面数据,获取行情数据看这篇文章 详细的爬取标准文档见这篇:cores/datasource NOTE: akshare 已收录所有内容,不要重复造轮子啦 爬虫基础 本章要爬取的东方财富,数据结构非常简单,在爬虫领域中属于入门级别,使用 request 库即可。 下面代码示例,爬取 贵州茅台600519 的一些基础操盘信息: import requests import json url = 'https://emweb.securities.eastmoney.com/PC_HSF10/OperationsRequired/PageAjax?code=%s' res = requests.get(url % 'SH600519') # 贵州茅台 info = json.loads(res.text) # dict 爬取规则 东方财富的数据(示例链接)主要有两个特点: 优点:链接条理清晰,便于爬取 缺点:采用拼音缩写的命名,贼坑(例如 yjbg 表示研究报告)… 先分析信息页的基本结构,第一层级是下图红框部分,东财将它分为十六个板块。 我们会挑选需要的数据板块来爬取。 以 股东研究 为例,内部还会细分为多个第二层级,例如: 股东人数:每隔一段时间公布股东数量 十大股东:前十大持股的对象,包含增减比例 机构持仓:有哪些公募、私募基金的持仓 … 功能实现 源码: cores/datasource/Eastmoney.py 先实现一个基础的爬取指定 url + code 的函数: def crawl_base(self, code, url, fields): \"\"\" 根据指定规则爬取 :param code: 股票代码,如SH600519 (str) :param fields: 爬取的键值,映射到中文 (dict) :param url: 爬取的链接,股票代码用%s代替 (str) \"\"\" url = url % code res = requests.get(url) info = json.loads(res.text) info = { fields[k] : v for k, v in info.items() if k in fields} return info 后面依次实现爬取不同模块的函数,并选取有价值的字段 … 操盘必读 板块 股东研究 板块 其他略 … def _crawl_cpbd(self, code): \"\"\" 【操盘必读】 需要字段如下: √ tszb: 特殊指标 √ ssbk: 所属板块 √ zxzbhq: 最新指标 https://emweb.securities.eastmoney.com/PC_HSF10/OperationsRequired/Index?type=soft&code=SH600519# \"\"\" fields = { 'tszb' : '特殊指标', 'ssbk' : '所属板块', 'zxzbhq': '最新指标', } url = 'https://emweb.securities.eastmoney.com/PC_HSF10/OperationsRequired/PageAjax?code=%s' return self.crawl_base(code, url, fields) def _crawl_partner(self, code): \"\"\" 【股东研究】 需要字段如下: √ gdrs: 股东人数,通常股东数越少,代表股价越集中,则更容易上涨 √ sdgd: 十大股东,包含持股数和变动比例 sdltgd: 十大流通股东 jgcc: 机构持仓 jjcg: 基金持股 https://emweb.securities.eastmoney.com/PC_HSF10/ShareholderResearch/Index?type=soft&code=SH600519# \"\"\" fields = { 'gdrs' : '股东人数', 'sdgd' : '十大股东', } url = 'https://emweb.securities.eastmoney.com/PC_HSF10/ShareholderResearch/PageAjax?code=%s' return self.crawl_base(code, url, fields) # 其他省略 ... 处理json 写入 json 文件时,需要注意中文编码问题,下面是一个万能模板: with open(json_path, 'w', encoding='utf8') as f: # infos是要写入的 dict content = json.dumps(infos, ensure_ascii=False, indent=4) f.write(content) 数据存储 结合多进程爬取五千多只股票池,注意处理空数据、网络错误等情形,最后分别以 json 格式存储在本地。 数据应用 量化金融追求一个实用注意,那么获取这些数据究竟有什么用途? 除了常见的用作策略因子外,下面展示几个与众不同的用法: 示例一:寻找 “A股大鳄” 股票市场的股权拥有者,一般有两种,要么是个人,要么是企业/地方政府/国家。 下面我们尝试统计 5000 多支股票的十大股东(从东财爬取的数据),然后稍作拟合,再按照持有数值(也可以是持有公司数量)排名,便得到如下图表(绘图来自 pyecharts): 带 香港结算 字样的主体(及HKSCC),一般香港/外国投资者通过港交所购买的股份,即所谓的 “北向资金” 实际持仓最多的是 中国财政部,他基本持有了各大银行的股份 其他排名高的主体,基本分布在 石油、保险、电信、证券 等国有行业 支持滚动条 看这些国有巨头的数据没啥意思,下面尝试筛选个人持股的排名。(筛选条件很简单,长度 <=3 的便当做个人) 排名第一的大哥叫 魏巍(竟然是28家上市公司的前十大股东…),冲浪查一下,发现他位列 牛散F4 之一。 而排名第二的 徐开东,也是A股赫赫有名的个人投资者,跪了! 靠一己之力的买买买,坐拥如此多的上市公司(虽然只是前十股份),其艰难程度可想而知! 支持滚动条 示例二:财报公布后股价走向 TODO"},{"title":"日志:2023年10月","date":"2023-10-03T12:39:50.000Z","path":"posts/2023-10/","text":"b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475de0b1305a637ea913974dff7410a14ed4dbbb40a5278eb14bbac34e59e12a284c7a294ab69a495d47e11e78ad50feb1070946a0084538613a8500fde76ee182cdc92156f5cc629c04691a80beeb70e034667984a29cb1db27ea11974521ef316310dd65dbfbb2a0d60f2a8ce6cab9f6cd825044604fed45aadefb22ba2321e19dee0566e6736947a45ad09061320fc246487e86167f696ce0b59ebd851781c74c35b8570834a7fb16ccddf85c541da4399962ab09e1e1ffa50d762e18e62c34615594554095f4effae7f3173f3242f7d285478fc1e6d0eb779e3ad11f1012997a7691d132e80e6dbc913b732d2137b6b8f57f694a4bbc70f994c2da6ae0c94e2f6c8c831f133927494996ba5792fd93c2fdfe0eb6f6f9331eea96358ec38968250bac0d7c070ca45382255703402e14b1c12f4b6575d19e98b134e304b397b7aae8ea19642fe8f01427dc67ab652cf56b4ef5303ef984d86a43e75e38f31fa2ee88ecf4338d7b0525c22f6a0239dae2eaa2bd1f594c7ec15c9ecc638c89d4a07eedaacb68410825e683c07c0953c99245fdb7567893d747df61cd912a11503c3fd4da64a36e87856618b775733cabcdeaad064836cce2c64d7777e31bffbf3b39f702ab793910862d380501b3be50aa526265eeffb3b510802d4dad98e511ae0ffed757f91e649b5fcc0f8cde1f9bcf6054be8c2434c50711ad3d54cefc96c0776fd334c84f3108aa4b6417393d4310021ade3ea87c841c093886cd78524a429172dd29da1e7d5002c8e3f28656021a6591edce5666dd9ebe1ebb1576689139f329ef1b3c741bfaec6a1b6de1d6f8c143382824e1ddd5744cb9ad223885f43f7541ff956a5041c29f957d7769b29b067fa9e16305dddeba77fd3d04a2555f5c4ce9a76454a000b2b4a15d2a70892d99bad0d2d90ec4b4d60dc1a6d7b8985a49136637a08f639935102fa843b0443b7e75c9c5d7d86d28a203937faaef4dbd7c0ec8cdb379bd118e686bf5b65b558f325b738f12bc4c1652b0e1d2b65cf17afadec738ae39b244ec2208fe3e1bdd5177f6d94070e482093d1d5978e52145163cd7d94c8778e1a680be7ba2f3f264ffd2a5bf487519e45a4623775b22a2e801bb986fbe8a6133c32a786e9c66149f540ca4273f922926e6460a0cd2caa5e13d3652f2fe9a9c7e7ce8681b3f5c0b404fc870b0c61404454d36dfb3b1bf7b3909136046e8290be28e6c82d5e974abd3f0d207ce677d455bb5652fe53927ebb2e50a3ad6dc1a77f5e10e74070ff59cadad82fdecffee5f60f8805f4cddc270416c343e126325b9b49963b9df72ea0f287ab445fc1043cb9aef960efaa7a3d0a07f47737e5fafc5f31872ae381de5d018a507ccf9c5448f48bcd96516a314284643c241f2f5bbb525829025833bcec3e927b1c932b24826c601d2601d2e6146f3c8e46aaec0820ffab38a31c0f50b209b3b7c70c50e09c4467049ed8336723a3f20d97294072fabad5419102c589a6f6f3c4d5468ec67265c88039e30424385ca8fe9b51a50cc77e21b4d73cc71e4c5754762c60d83470e3ce0022755d7630bf3ee009c2fd32128da5d155cd35950b47870348dcb92e52402d73f450b4ae56ca94748c88787f93e5733555a57ca77c19fba83dae595d97246d435444a4727be3dcad505593a74a4ade670032088934539d924c3421e219af2ac2a2958ccfcc5fff60a1e47c6af06e0774055f0c0d2de9438dae41dfe9c6bd0a89788d32a233d77987ac3980510444340c6f8d11750631d3175bde177b8de7dbfb0ff25056c396cd9dc597cdbcecff5483b543d0b61141bdacbc76edc296657bf3ed88730ffe741ef90d1ccbb96fc265249afd200e7d8c9c08c45033cb197d713a054d6acd59cda40c54b0b030a60902e1b3d56fec60e709de21d706604e83333058be491185d29eafc2e09269503fa7652ad30dcddd84707bb35ec91aaa80614753e2914023a70c015ac6523427291a18d2ce214643949fd34bee0c7041cae1560f10499faecfb8ad48b8a3dcd4d9ef69e1f5c3787935147bc2eee9148843ccad1e9c8432650d5264bdaec7a30567126e75d40df65d85805a1ba9ca65819c9d7243d222074570717b270263fe110785473367e29bfc344acfa6db173b1c493eaa081c002380484c10a20b9f2e547e499c7b962e6d525bafea81b322aa5e3107ea01f095e0ccbfac0dce867ed1ba0574b012b3f644314c22cfc0f5004c7ae06ac7169c5fc35d711e9e577b18bcf4c2d02ac44aa220d1ad5849852e9e6790786b0fbb57e3a0433492067ab8b272b275ec9f645c0553a4531feb03bb9a34e876c7b5fac23f3fd63ad4236d5eceddb7f5368d2d3379b657affc6722f46140b31e719d1aa60ffc182c06bda7b7132e9e95fd229cd2fa6be754dacd1fc7380a2cdeeeecf1652731694b935280793a66f55e84abc0939d3dfc37f9376c817a69db0f9aec926c62dd7ed73c18934e8464071e2438cdd41df20875220542ac80c0e42446050e34ac6c2b82c48b1a09d827963ceabad89fe0d2f27e706e994698edefd67f6c731fa454e864e439894ed3f2b2822bded480a79fd89b9b1eb2aa88f756bf833ad0e5f0b6cca0e6acc7b3905b17bb51b7a36ab6e64552515ede61d857ef4d9deb9216f1045d88fe20f12916c13c2d35c3c8160bcea7475ee055dab046ec85d6451b72ef6b746f25b48f246a1ef0bd8f6c187f40f362e3e3bfaed162ee482e4e90eef9c761b03c24d21e8428e34753a3c59f332ff38300514805d1e0439e0c31526fd95aa8057fcbeeecb21191d65c71f881b62d2d32c0932abcce78ce06c196b69d3a52235a2e111fa91844fc2e4a35c2109adf47f8fc7514e7cf9d6d952193955c9f5015782be51be7f6b865e246671950599d9622f8a5caa07087852873ea794853e026b52d9ec8ac97c906157dcf22fc0ab0a9ad96d13235511031257462db138b40592cce32641b7c7e9d4437346a223b8cc84aeecce2e0c567110c3c7359caf7af45a859cbb4cf5db22849eacc9e4f646b87b104be5bf2cf19090b637e0b6def1c42aeb4df72c802237f84bfc02f8a89ee7c752f5899fde421962f2d161ef0d496e5322d06c132bb9233feda99aa5bc0d40c6614a199813f4516ba9a09b7815c9d36f4dd75d0583b59616dece07380381a0bffb0f4208893603e9c7cdc2a58a4046229b5b2ae349b57ad4c4ebc859bae8660c9f6d70e473334eb93620444d39b496d16055ff13139cc45967df24fa7ed83020f1d6582cd0514e20e0e5c223ccbb248b3a49949748009f9cae16d0444ba5cbece68e2f363c963781a3ebdcf8bb7cae8104cde698040da97ca058b1142b22317c6239955e3a6f64fa652ae3fea57ab30d924a176db399fe01e7ac5cc5b2a9c9eb6c3bc23c349ad434598e1629f47cd7ef8b26261dc0e7d5381188dfc84da3333ce06cf53365a116f82fb3c923b358de4d71f172c13e2f63e05032e53bed7aeb24d2cf8c9139e4ef654d7d8fdd96b1eaefa19551f4251866c5d64e50b36e958872f784a33f962e9e3461a100b69e1f582ad56c157cb03ee3555ff810ee77674ad863fc441d16faf4b5ede8bd9a27a448dd1b56ca0beb534821532a0a8f94abfae979025eedf79bf09d46edcb3945aec4d39814ff9496abe7d7290b5a7ec2805aba7f1aa2cd1372ee01b08ec80d4e261be65e56706d3cdcad34ee48cab6cbf3759f572bf37be3fbe073e5f8a1231a893cb5de0fbf3a263bf81f83099e65266a4c7001e491126d6dba91dba73066b885e816269dafa4dcf3ce0001629a66f84c9ac6745da2c500c3b7adf669b0e58ea9d1e1079986e48a3f8f4e1f13cb652a3133dcb987c9bc3389b3fcc768100d993a757f4b4bdb8afd938b52ac7c5d61b0f6ddff386f2e4d912d83a2912fd2706023f40c46e7aca9ab04bfcd28d32dc021f556f640c51f7e4ebf359f952f05632aa849d97ecacc4f530f41264dafd11ad98db8023ba1a37ba3b7e85506739a52bef589a5d31559729be39b0f9e907c42ff7baeb03dff2d9aed0f0b648992aca08d58d5a74edc69d3892b3f2da6b719411b8adf55e8d8e6b24e40476c28a66bf4b9993920c4054c63c17a27451c1590bdb1330b1406f0e527bc9d9aae5ccfe0a023c7226f4b4c241db936a8c1f1002aa6d0d53da8b7e3a9af8b3a03015678abb07cabf06ccc9412fa3a4a6d1ba80a70841591bd300a9cf0044a3ad2c80a0ead283cb856137b6625c8e7dba6b5435085ebc2f82015dc60e17260e58a1edc7f11456780b6d2cff008b1d39a4c712c9eaf0841dbce5f3afa0512c4ff1009124e78e624a60760ab864c4dc1bbac03d2a3514b95cc9dd0930f6342aa895253cfd566221379818e52acf7cd1bd81102f03bc746717c90da817ad3512cd04223c8c0264e1c132c1ba99bdc393c6bf6f1e0c3fd9606392a586aeb832d47ef3443f471e8aadc53940ee7af5bd09d4cdb902bec979494423797ceb224e3835aab7d83dd3ff30e9ddd2bfb21f3c875011e3d057ea216fe67c429def8efbde1cbbc4a817d2eff6515c8d41fc4437e4c557fed3e11f301a2176d9a9975dfa7bb26b5a01ddb2dd2a029ad768b35b9ffd0c0182b1363715aa3712e2ac4d9e4639dbe1706c87ac633520a33f561bff60b47c5348e444f31dcb5f29f8c91b472abbbe11bba21d0708cc00c295baaff6deb696b6a51b17929ea32968f135e3cdbb8eb2409389106ff82a9d16449e99f5ca66a08fb647245f1c67860a1af24b2dabafd651f8e465f7cb724505aa6af55d00f8c24b7e4abaa09b8ba863880df79122b3a1f278c3612ae87d893cc9e64c38efa5600f9b88d01139a5fb90dbcf5853da2bf6f277d1bd7307b60f45e0286761364fde5cf15df78df87cbc6d84d8f42aac9582c64de72c710f3ae897eede27990d1821c8b0e8f66bbe58ebca1483ba5532e9cc84139961597f385b73f71673215c1df7dc53ad7a7cda73358bc2b79c7679ace10c262de19a6ee8e268b892b55b5dcf7215e9aa0947415b7060a85d5e4474ed98b5109452a8ad06f1afbf3b9330d66e2b5f2a17551d78d7f00f2dd3154074770f7b2a15bd82750a57ee1e75f8ea299ca50ff1ad4b05b5761b2cf7aca695cfb4789ec59d7ad60cb68d5d4b589c3d98784313d7b739ba6aa7f87bfec47b5631b9b4b5538f79347f9a5810b5dc06f35a91fe03b4119c8a34ec656fa66795717976edf0e3139a379b97def36bc47e7d6396724459212cc2fcd7dd7bb937939ab2c9b8c60996d534b2161920a6c3b1e006104080646182867139100115ff5ad598967e90337e31be254ab62c643cc38c43d9caafe25c15b831c0360cbd6e981a7a9ebcb32b16cc9d1f8942cd64d060823a54f4ff446342c2ba1c85b4787f5a3f838710751ed280d1de49f2f511707bdde67b9d7e61d21d6708bdaa2a962f0b16c6067320a6b2d6b6348abdf9adf4ca9ff0adad3709a4495835811869970b1028864c971b44eb4f5209b461dc916d627329bcac943329b759578bf7eea4e5fb7162565759ccc157100d37efeb0a6590e1807d29b1a6db569eceb58768a6bcce0988eee5441b362db819b545bbfa13af723aec5e9ef9ffad8ab78371d7c962c6a08519dc841fc0390804f45cb168c98fabbc1aa7ed77091c9f971497faddc6c1f81038f5ef585c191d162ada31e1c6086dca1ea0b7eceb1d4740765241de5baee0fce2dd819f39ab54bf20af4fe49cc8b96d5d0159ab8a292233ceb7619ebf2cb01d984f5e5565e9706e039c0dd83c4d0796e85008d8a24f271d0b43713a7ff8817a8d9ea726e01033d6a314a34a1d7386ab9a9338b8570370a279d2a9d729ec24d19627a693ca47f8864574b329e55e560fd19860ccf024280af1803a4b1b9303a072eccdcfc319337ac7ed1b4003f7589949493c0f9019da27d9006674093882a9a61f5a29c19581d50df32d9407f1c1ab1b9e02a27657e547861f4b7916ffe6b86cb8ba96a05826d250cb88b1184e28a7dd9620217740ac1c697b8033c8cb7f13d65c178ace97ba8f772c14f094d1c99740498881bca7820709de94c2b452b87c7922bff7c4626ff58841b0f6096f1e5fe1b6e7dffc6e196e7defa94b26c204cf7216c2e061985a38ee880897b65dffd29a3e18c532b74d9eefd9ab47ba1882969a2db6460d7116a97da69d7fcc096cc6ad06d858814d58d3d75a2e29d4b109e6cac09e80c7586e1e6dd94e01d9a555a533aa0679dd504fca015aa43c6d8e2cea29ac4b3b4dddbfd87634033cf254c2584ca6010e6bc885cd13000ea8d2a1fdc3fd867834d374571c52670be4950e9a811627126a36feed5e51ef85fa57474b64e8ab072a3359b0307a9b18f9e891777b2fe1c6a8959c07d109c80499d1d20fdfb6b7e3fef3a073c4549fe1a38d645cdb699c9030ffd390e8016fed7b7198a8c7393313199822a665048aa979fb005ada5a52c492b3ba58486ec967325e212bf499ca26e6d1df4dba2e40d9622e45e58b282858dbf2836518dc9afe5b08769bf5f311 Hey, password is required here."},{"title":"【AlphaBet】cores/datasource","date":"2023-09-28T19:07:30.000Z","path":"posts/cores-datasource/","text":"b3dc07a81f6459d120ce338ccca5504625d42614b93020ffae3fe33410b4b3b7b132d51143295cdac39b893eaf193f19c019943d7e59177912cd230786c73130f76d9215c2bfeb61246a440ea10f1093f8813644e0539c6bf43af0a466ef4a3869f03545f2371c4406e374164f0141df491302831c878c9bcf684755d710c17cb4b0117718299701c3e92d7e680e2d2efa6064966556690e77b81a309d85d8062be1c5b16f52b901b914a1111d41228d16b637e33b3fb8dfd796dd947c22ca30563139d3e404d882931db783f4126db99a7310744b338a985b524a9dac8083a2a5a5aa404a7c2aba3842b8f7170f7f93a7593facb8e80f8a87e073ace10df1f6817abad52038b81e62d6a1a3b5bc4d762953c1267b1f408b00733262f7146a94e3cc80d2df634050dee46a6355ee9af83995a7b5f4b80a5627ca71066f60f41aae74b02944fe44fae3e3890718da71a40dac85b3a4877d7fcc4da2b4de3a90168081f652bad4aa16c8811955fe8cc5f31d48e9e6b338f3a16d72afc12c1b30979360689fc8ffa09031579ecc5c27ead8281c37a80b03115545b47cd91d5c57bc6503fcf138d0842fb48ab0e4172b9d4698818bfb1ceb274eac3a0d6b9c3588d345b8728b191f6c2a76bc31b7cfd2696bae0070bdb02e358907df38197b78dcfe73ee8453a3b0e5fe968017f26b66fc424a40812601fc222a55e5611d4a8588216b604291816ee56365c21979ea5309e05fbe4406a199a103f743cac2b4f5490ed210d312f7892b3cd2200a2d21dfdb5da61de40922326ab8ba2ad850c74083ed76534b200289b9d29bce4d5386cdf542920306aebdb9c3f8c6298c1ae30e68a9c24dd7117c5df3fd9c7deea388325380c97ce330b91d3ebb1fb0b033257940b7290eb571146644e8a93ac55e78aff64e321aef22ec9b15358bec56c2b3fd8da25dcf16af0bce5d57d453df12ea5406d0a74b0eedaeb1a9c7e47305f5b02c5446a0c9e4274cfe77954f49a64ba12cf0bc45a1a2dbc0afbeec373af4d6afa3495d4a6b0ecae0de608b00b884fb1f557d13f85725e01671ea88d5ae21a0b9330dacc81cfae76a95a04b0f2f1b2571d53274d9bd3ced52a666e7cb23ad553ee8208fdb807b4bca86092ed2c75bf74b1aa541e634cfdaf7324570274d5a956d43d0c9c8acceb5fb4ae741343a1541baec4798a1b90e46785bfb6776edf372f608809bb214bb938602dcad522bc85947299cfccbb3b82ae0bbe06a228ec5f5a51745a8d47a7b49213278d2a1a69f87deec6e6333cdc71635161ad51d49a3da3b09b8cb0192c4b44ced1a57ec859f6c767cf78e72532397dfa8711c1f4183761fd657018ed7ced1c41bf6a38aa2e17b945ec39a9d6c89bb4ee46ce084c5b3330350c1bd3181a8e43fdfa5c469f32b52d500899ce836250a75f55b36164f858641f417acb902d14ac00d4d578249fcca2cf13f02610a0ccd46ced8d1cc6b877443681997f635223b29c19f432eec63583a9cc76947bbb9aede27e3627873d2b3d8610ce976af5f09a85868ca27eadfd0b221d308a5eb00f98807aa234033dfeae4cb79d123d75a928c28d987d08d220b6ed82f922dee7f6ed63a927892e7d05c9cde9eda1910da379fa307677b2db814089e95fcee0ffa8fc77f67c28e89f4ed744b36c0074dcb5aa5970394a2edde2b6b372e5428552027fd5e152f7eea267dbfbc5fabdf6fd30a482cf5405a855497613698139b650e96e3697703aaccabc94e9238ac7df7aede7ceae407823a6caf5b164c88e88229e5976781c65d137c5292b3f212cd62548e6d26e38d0bbbe1ab74922bdae5cbf75d438082e7c9728d810d48a1b33cc1caec7f360c988efe754987db66faf5fd146d5765c046498601d43150f3757bd9c7d9b181f65e9c8c89a3a3c9e56788a8602a30f688e5c8ccb610f82d8433b46b8f0f503e92b6911a4d6633471246ac12e1e5f62f11f01c986fb06b5addbc2de2ce804ae602539298e728b3f0e32374a24f62117947617e2cfa584c4bca77296877f07271b4939fcba9d133e20b241aad65c20bf40012e53e045794079658b7f7a220d47d109cb8b96949cc37b3ed39be79c9cb1392049b476c81b62080e3de8759f1bf32b32ee4e9ee66804d3e4aeb7553126894666cde4fd36d5f2ef3bad7a9e634d680eb8ff3d3042813fbc24c5c4b945a115c949a473a24bfbaec6ff4cbf93a93f5bf8cc293a2dceb77e9f3628928510109302f70147ea35c95213168f585545d129ccdb24e6716774cfb7eb12a259c0df7814a4a60fbc879d91c15a3270a11c72a5a96e1672a3e910525e270d2c0b754261d7098c285ddf9632614465392df09cf6f2a51de2971f6c69fd925e139876990cb1bcf76af3cfb7e21390d3b7016115f853ce4e841a31fb157c0d117be05c030ad3112efb7942eb8d3dbe53e43d7184449b7ccd32d8436d758f449ff4a043d6532db51be35aaa720278150f8e6eb37932a71ed5139e001a99c48facbf8611cd32567bd0e753900ff548157b6ce628ba80d2c8ade57a43c3ea1bcd95aebee89bf3f707aa6664778cf378e97f171404f3ee2e545cc9bf5d7c232bd72830d02c078f09170e8b574a9b5a42cab62d77e5232b4b999990bb37577904b3cf30e0c679148384c355ea7fb3ce2d675f8de4d94caf6823840f508367ae512364309d5dffb4898c5b3acf9df38621fbe5138c62ce20e1c633c9f4ae8bc31ec8d1fe3960bb88f3e22b9d0eba64144c14abde3e14d2002abd7ec57ca4be265d935e3ae86333db06108373e7190f261c4f2ad274a39f8149302e5075d6a3075ba6f7caee39e60187a3c264520801622fddc943619b4e5c4f498208b58b00028dcaf273a431f46a4bfbeca5634567593b711b9f97d99e86c9c6830c9b23d00b7ba8994c780ebd5bf6fbfcc8d9896f86fe27b7c5a6a229bac38dd0c2143c8c7bb0f36a9f1d84f08577692a18a63a89c0ac402fbc820fbc5f2ca4f77adc3b37d5967ead5548042170e890818b097ea0a4c69894fa848ee39ae14ed4f25af1260c4639f7f6fcf1043476872034a194616834c15f92abb40240298b77d34744b032b51a3b6f9aa52bde8064954929e2866f2ddb45daf630b7e7bd664b9b68cd9b860753fa59d2ca39a8d881f293b8d66cf49e683716a976cee7cf7f2a2d3fa744d024980e15f97ee49a5747b530c4f4278563e0bec4e6304f9b44dc894f1968f59ff5604d76b33c9ff92d225554572f31459f5d87de3ca513eebca5df10843581438779d165326cb5d35828017a6f52c3e377a484348a67e05db4fab09e77f6a3feef9656696e69a77a6ecacbdae0c7195a2642c21080fa630f6e583fb6c11583aba3447986b0d6df732d96454955b759f154245c0e5e1367cfb9d8a40081f153af35403a042f64ae92c75055c9e1f03660c6fb602907bd2eae4eee7cf4c39970d6671016ec3b630cbb48106f4eaeb756b0d2363ec2adfd62ae770df9b89bf2d0ede1ae709cc8575e9fac8c0cd910006b240843190fcf320163afb1421a16e9c45ac152f9710427344d399639ced68ef07bae2ed4532489e64cc44ee12c18a034a29a6ab44588297e462533cda884585696884aeb5d5bc96b4c268d84af086b462dda95a296b2314dcd3d2b8d5a0dbf6835cc9890b23a3ea8d10d9cc426a5988b60c01a78761c618e67010b6804107b0f19286d974fb2ef58a43f7afcd8d04c018bc8e85696dbb9471b33f8cf0759ae8ee3584f31e317f62d56c94759ce59d0bfd7a85eb2e2970030b682b1a0cc66aa8a5939ff4ec137cb3f5b4b39dbf861bdf1e47df54dcb75b9d2c8e351baf67505f4c5faa964e6dffd043bb756c1df8fada50df8aaa4aebf68a59d96830840bc6f21baa66c77ba968a8d808f62d3ff99684371737a11fd15189a59960682831f4b542f0e8e2e9db8ddbeb6bf8209bb1aee6fac64a7f86695a394b750d0fa9cdf30fd6eca3fe769e5dfcaac87e3db0ce32dfa790becf81afe76974b440265c8e0d4a5f74ac3fba72bd63184ab603fb0f83a17bf7e3ec04fb2e63dd72ae99865418a42a0d8ab8f89dd286a58d3388eb89cd501bfa28504fbf2a372883d1da58c20f001964d472c9fb278adf2e50910d455d0c513900afb020780b044f930ba9903c79ddb8fd0b8273faa9a2a577dd020410a56f951844b5b76c6e9dd7e8e0f80b7978fe8f98f94936ea06ebd420de5a02a402afa6ace3de20bedecefee37398980e251984eaf0f6779aeddb7fc88ac76b9f3c67832ad6a56f0420d01aa75883ceb5dac50d64d0c158500ba22d12e8075665da3e407c19661a8818a7ad7a0191cd6d316bf292e09bd1932a8a13299a5bf07fc683e33dd2c5bf9eaf8dc8dc2383e719180758e38c7a3e1e0ea3dfbd047026d6993060cf03eebb117a814fd115c98f6806dfc1a1a7cf322f1f53125723ab68cd06305de0b0e06b2f538a57c2cbb18ce5945ee5cf15fb357bf97317d634e94f18f01267e147 Hey, password is required here."},{"title":"泡芙成长日记","date":"2023-09-27T15:00:06.000Z","path":"posts/paofu/","text":"b3dc07a81f6459d120ce338ccca55046d81ca386354419b6b77d6b55182b8ed301b3c2040d78f9d2f509a2f04ee45763854c7c389b18ca9301f9504a65415536caed1f6078a5933a1e86da75ab9d764257ebf175c8703c4a30d15bbbf7d92b3e403a1caef391b1614091ab770b4e4595b54cd1b310f8dbe1536409c8de8f79bc4348c1bc29c770dfa2315e36ce54739a172780106782350060c52eb57d286eb75fa04878f3cda90fb70f7cea791758878e87bd9cea4101225d90f42573e9709ba039eafbf4ac3e239721445477b2bafdcfe803cc6391fae439e7f1a958ddfc372c8c5d3d8d141fd4a6afb2f9e54b7b3b41df860024fd18e08c38f6e52b445e4d939b03c40370f1e6b774e3b96f4516cdaecfb256a2cf30b57347eb8cb5e55a971b8c485bb317b25d86f0bb77d12436a0c4580debbf73cb1ef658a04fa8e00e45bb46dfb81abd531bbbcb397b64220491dfceafb37390ae4c9f9163510289192df6b4a6344439d771050ceebbdcc97c64a6d10bd96f462781511c3392a795ce2ea09d63547e217b36f148524427d0e0479768f0474dd3236f3a4511c3af35a1ee4b63bb6a81ef4c7f23117aa444ed51ce8893dbb891ed914fbc62b39350da2622ac8cb9d5848e745e8ab58c01e69db620d0f75f64a2fe19aa7c1f1215ef04e78a6f32e4cc5d829e6e3e3a9ea1f833531299902c6be1ac12b99b1afde30b3718a25a9e5db3d02f6fbf37bd04f66720b67d5e989718f121b016ecc5dab29b54b1b92946fc4540d0ba419f5c57b90c085211c31391e5c10b025cd4fd6e3e9b3207259b277f1edc6437cc367f06b2a49aef1458648d2afd281e27ba5a3d143e5925818800faf5e622bc250d3d3a60f518d6583a63b4c7e16d65b4ef39a65e4a057a2c3b6e370a408964621073ba6f300667b6f1575d60f5d322cf642ef70e5cc1f1d7981482633e3b29eb99d2c2eaf62abce6c1c61cd3735ce383cac93be0ec97311acfded14c1ddbec0f5ef581f35ff2ac3d361086e3d5f93e8a370a7ed8953a4173 Hey, password is required here."},{"title":"AlphaBet Engine","date":"2023-09-26T14:33:32.000Z","path":"posts/alphabet/","text":"b3dc07a81f6459d120ce338ccca5504603780459e0306826441972681af56d621023a3b1e87082893d7f9f5a9cfdcbb2d60925685910ad1c3f4c7c6703be17e884d98345b93e039e1a24d1a8ebed568715753ccb25d7df8c5c7bae546cb9dd2d7867d1d67996d19fd8b59c04551be490a583a1a45c65d3695787a511f866d3303b0cb5e321ad68da8c1ef4da903d3e648ede58035c1885789bf30ba933ca7e4f14435d2f066a07bf5e0a85980ce41972a9957a1b08d33989c6bbb481850b08c8011435cf746a31c7aa1c540ceb73d5f324e874511482d50b76c6fdeb25c7cc40b23444dfa998fe7d941d124645dcef994f823caa949e964f6c6ca7dd790db5d6273289337aa3cf4e96020fabb36c413aa93d7f8dbada47cc068065082033da5bc32a18b7f9043518cce839f66e6f1da77013912881d1c9da15b53c7edb06fb745bf474543fd299749c4db849f1f39d49a0d52044cf4f305220b72b0f7a0c8df6d39fc0b2f883f756e1ef9b8baa252f48fbd2a56901ced22b0cdec9099b37f293bd34f4ccff931396d57b0fc61a4535fe3c8bbc0ef66fcafb23d87de0be56947237d9bb89ddb050fa33288866c5e8c1a1d51d5d6c74f152e0fa6a7cacb824dc10370b850c541884aa4eb486b6430a92fd0d761cb9d9ca005754aced8c48ca64a0cfc4d690b3dbdf8e94d305ddfe0ce93b3a7b4ed5c5cd41fb2455a611c0f265aec0729761616b8c9bfc6eb1898eec51f3ef378e79a6b3e9add02a4052a05881b50cca21d04bce7158a0547ae526e5f5036bb6219215baf73a9c0613194c202f6e1bc28b62bbc857203209779ad53d641fb8089b40a3d4014f7b99d85866e3b1fb934c28f4f44ee7a07bf62b193b6f68a946d13e61a24a591499ebd4d983bd891b5111cd749957a85822ab7cecea20964c86fb79d298956a8f8956ee85f1385caeec00ec1502e68f9f0696adc88c7466688161c7e01e427acabd1fb584dc52e8becb8736ce328dfaf20732c8bfb0cfead620f7a445684e8ee43159b10a2ed172222c548cb70fc24ad7f48a95bf61603a41677e58f60452d16edc4703afeced57360e37545447936078f32a673c562e62f837bdd5f391c5038b30f13ef14fc5c13be8b9eb2b084ee3526e9859c5da33eddbb639ba349178ec46e48737c41644b6150e4d0337a3fadea4cf15c6e335a2eaf6fef3ec7036f474c0b6bc87f181846bdfa2b8add16e1ff55e0876f2f80df750516dc21175093a79e75f401c04f7a6b5dcbfa622d6713854b33e89ab18e7192cf473be58bbe91dce95b0fbd9190945a648af318acd6365bef13fc74cf6e1c316ca4a18ef3bb44c0bedb330f9b238cd822ea8a128002d405c85f9682efba2b9dbf8e7dcb9cfb2ef2087159f42f59ae0977813e1414d74e04f85a24937d24e8a9f5239b450183ef81e496cd6b099ebfe8fe101ad51f0b66d998a1580ffbcb3aa029e504456aee6c15fd66efcd8b2357479e16a0d049c731a761e6c99da2d7ad6b7d6afcafe8fce034182ac6d53ee37658e6799d05d58a0fae43c83fbcaa4c5be77a7a8e17c6e8724d1b3580556a46987132aeb834015a1615e6130b0748c34af6e6c192b77fe6edeb5f2c9a85235b48e22f0c4835458c30173697b2cbcaf71cf5297ac5c5e474b81e5b8221a4c323b180cfa5822d554af7330272880352ed6686e40f2327a14bdde8d0087bb74e285c5f1d7081a75771a58d2c980671f82b98cdbcdf3309ae3550ba3a30684252fbc2c3095ede8ed2be54a1203652b517041ffd79d186bb52d348a614564085d5400df9c856f736b6bd2c0b594912fd604833dd3d7d74b512a71f391198a00fef547bec560bebe46c0c62c76d38f7e79b8c702fd6fc4d6fa9c27de8f1adbf21f7e4173a10f2e8d087ac680c7edac98d474d89b4f5d21a8cb50d5363b601ebe2155ac376451 Hey, password is required here."},{"title":"日志:2023年9月","date":"2023-08-31T16:40:43.000Z","path":"posts/2023-9/","text":"b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475de0b1305a637ea913974dff7410a14ed4dbbb40a5278eb14bbac34e59e12a284c7a294ab69a495d47e11e78ad50feb1070a468302317925759b63d81d1a890d91bb247f88a2922744d34b0dccccece2ea7b156e500c0039cfd827310258914cf02387296f49579859a12f70fb57cd977715c82a3748c321a9a025e128a2cce7f1de3dc03b281972aa543ebeddc996b02a417d9199a94914424bfe81c44ba3eabf2c80e4e270af2ca8407ae2a85ff69b0c1fc536f95c6144a12dfa7ffb8ccdccd84134a688fc70c762745ffb27c1d7f5c04309f944cf5a21383babeb4809120af766173cd3913c8397f6e4c96eb7384f89954fa660cd52a67795cc60cbbdc39c1b58ae0a544dfa11a95645a948f05dae5f8e4a73672048fdc14fc952a5849dd401a3784b6c89bd951f0ab835ac1a4a9f70d444ae242f4dea85b2f9d8d9a7e9305be6ab28cad14ba0c2b7a571fa964999e0ffaa760a2fa7600eff40d891f5e0c65b1fc925a7af5120ff3379ed5731a4194270d519619feed43fd2b6a44c393002a33c3d67c746dee94be0af2ebd54e04a4b20731890f02c5682483b33ee0f28b7da3574f7d08c5f02bdfcf451a36a3333061975b30b4fe8d031b74c782518cab023906e9989aa95522ae03c37da39cda6fde3a576407660e733a7059c3a0f80e97991159b711d583d54f3289b252d70ab0038a7b288566f8903e8861af824a96f2fd0e681502bb84c68c7b026dbc1cdd48f77049005108be76f62fb72f817997c97f0a8f300c100eded8b35f427afd0b17a654dbfa3b8664dd933d00c755cdf46020a148ce26e82760da79b1455507d496518b8d707ff0bd13b0f0cae9e6b9150e6bd32060a04054c6c41140d97ff93653793ffe18ffdf7701d29c0df7b70ac5bd75df406b5d979447871f37bf928d3a76d46f5a28d7d625b444c0ce0e2f3cd6051830a7e1248d45e254e2e93a3f8570ffa7ecdb11e0084db744bbff2c61f2a851c84517e281db403919018b838efe44a580270d83c4ac3adf7aca236dfdb85b7fd93aafc866dc3671e7f15d4182c17043fd91ab23a517451a5e71a719c1324e7e3dfc13edca5bcbdd2c9b5d44300593869ea5e70b4eb2ff3631497ea992cf96107bb632236d00fbdb9c878d90beaaebbc8f45e1759d1ba4c940d200eb660b09a2d7a68b42600a23764e4725996059cd22d90473009bb50ef579822bb481b46b4cea1cf58ae2935fcb9ecbb2a88171e006ff6590583cdd66434631bd5139a8ed79443bb5c9801f184c9c03197e895989864722c465a8c92d88993a6ba26faac43ceac0f9d2522580fc5a24994c852b0e1d47360ca5dc0d1acdbff9c25629eade6fa687009be6397ce3048cf795071d7508e2648a2eb9a3ddb9d5be23570d89bed72fda368d04f427c23915628a2f5059728f3ff6e6c3a87c545465a0faefe26a2386232928017e481e7430a618cbd7c9103d7af6370f88110a6b8b9d62268d1cbff439e51921719ea9251416d227e8ba25874da2394926b328b29b3faca6fca6302468c78a87ef6bc4c01cb9f62a094255ea8246c27b6ce8a36f16e163ca3dc533f8a5f48fb144bffc910c17fc5a85963b6e1b858ac3a3132a05be5a976d78a9e6fb95f5f84845679039f951011c1f52454b4b1cbcc3f8c68549ac3b256431b44b945be0e8717e3a978636dbb454c22a3ba7ee90acc36290bf6dbcc507973d85ee7cce3db7ee3c55b722f813b509f5583b81643b46d9dae38a1eb98a81fe3165d046dc0038d9a2b098352bb655fbb1a7c6ee868de2e0d357748abd976dc89d3d9516473dea8143f4db516ef8c6266fb05d16357670eecf6f2214745bd9aed35dd3bd1612b1840885f22aac29f36731cba2b7c2756c3fdee8b3eda90fbf4b4d487b41128e61d60cf12927750a4b28cf4722a8c936fb7cf1f39a16ba7f0beffe31079dd0f0da462ec44d62270ce4b56fa66293ca95c218faff7b72ef70ef4a42d4ce50a65df10ed61eb32189e6d63bd178a84bef850852aeb50458b826c3fd2857b49428b1b630c83a660b88ed767bc7bd30cca9051f4f1d10ba700e3c969403170fb1372c610cb89a7e247e23f6e17a4a19fb93a0ac559f54e5860c613f6cbf5f52a99ab5493d8fb016cbd948c890e0ce001c3d8649f8a9f6a7f1e9965566fb2da0e5d5147152467dc3441b953aa054ce38a234ba257ddcbeb17f1403224f03a5e68b5ac4036f51b43530a457d19fbb98265858b4822281d07af0a2f205395da54b88dd11529fff47cfad68d842268b66497d90178bf85c43cd1b90048a8c35c8fc177d01f8eb428ae05768673d2dace3e426e2d193aa40c273702653ba8523fa752413ffb2ed2635efbd05be85aa69665531381c78f9a1242f6d49bf8829459b60da404bdcf775465d01daaf39b7b02288a68cae6531459d4cb43673baab5bb4eaa4431fcbd599542f45ef3cba579d3ac2a0beee72ee6143bf869a92a9ed20546b02da8f8824741a7f1e9c6365e19521183e0c3cd6d174e3ce008e0c732d88055f11eaa1efaf15d4c5ff8f0f8b0e441a3496936a181830cc1624f2c4b6a3a7fcaa4f2365297e7c14a9b5f111a8f317120a249afe2dcba85e096c54e561a8031184a8bfda19ee31a5783193dc26ef82f4c8f874638f3136272303f5b52e698f3c116c3e551d4cd811bfb16b2fec613152056dd53548cad1eed03474e6eae3b9f1edf61e2563d8a088bd8819beff6246d5ad831cf1a7a0e5d0cc297a09c71168c074f83a0f11369ee0907b819b86cc3eb91024f97e39b3e2ec1a0bf22cf1cd40bc9dbc86c06ca2a383b92be92c9d74589d4c966e0b6aff543d472add9881655e6559591ffe23738fded6d6bc322a1c127bbf88316ab83357c026e16036d9256869038d1d8bf344203421b3022affd4ded033c4d93d7c57f90ec36c5a7b8cf820bdc8f7f22047068bbcd6c71568ac32bcd20b7c41ce3cc026f8c6c756ed6e45ad43ba20ad54f370019d5ec550d3d4f07ceabc8e020fc37a95efdd85c5a3ed55d822f7441abe3c3fc70d2232fde2dfd7bc0ec50c274b62bc6d97a8931c289af2546e1e9f1fa217b0558fcd5a51133eadda1af7044ef317c241ecee30918013d92f8ee6d26da319a50adb30032d14c7a675c7126b857031bb01ab3896bf291c9f0313d8f0626b7b6e87234ca7c3f02e80b6983a156322dcb24997ff4b057b782058a6bb085b240aabd8186ee9c7e194bbd39639fe548f77a66f02b9f02d74db5246c85798d729bca96eb78e2205520c0fb216f26961e238d47db3bcbb2fd251296bbdaca80b2d83076954021e49a50d59698ebe9cb871a58212dc4b16fe25a926c5aa9b709f74b7f4fb8050eecd589c61ae60bc5abeb4f12ae3159f79596d30f8f3015b3fa8a5c50dc01527284da248747592689bbbad3d8de41bbc2de72772a3152009847e702a1fe23c8375822c9cc9375506fbcc5194c028d57a04f468c3bdc16c19af651e4c825fad715874e0408efe9f89b1ec866d87324c287685820291228168c903c9bc151e6163a4a466f6d16712080916781157dd02d1fe9d0e59c9f0741ad046afec2b2f4dc3c3c526ce3248326501e06aff3574c5d3a5bac9479b3e3584b738c748f53e29dfd7d999134ae922a425eecd06c2c0fb709f1aef9d9df125ddc07c9d02676dc6a705ffe7fa9672f57380fe88c9595ee6e341ffbc7b38b1cfe37c87a2bf1d35c7a770c1608bd1e4448542270913c880a56d372816a30bf8556edf8391bcecb8c89602e996e9958ec84c168e447117a650fba0ab3a297b566d3d5b58751be39798437f2fbb3eedbc03d8bb886f2a03b3e879e5db11708a8930ded16ad669b3dc19e811fecc967da05ee0b2a0b628da60abb45611736c790241679ee46a6bbfd7234aa86cf49f41f984971fe8c23e4e5296748c245ff7dc6711a3813139f69677a74385089d7f5a8ba439be63a72ed81caaed1891586b5ab4dcfea75a252bda347489b172babde966483bc78f8fda30b15edcc176311091f72dc7c6e73172658813784dfbc837e3a283c3cca3ea0d5f41718ade096db607377118b75381d2d9759f210790b8d1f6e5214aaa61d8bb0a4d0a3256aef437eada410c0d34563bcfd4678af647c9d4380af2e82b9e2b3924ff01715d6167e238ac868b5151c2cf48eb3e3c6b11b8369dc99be04c2653eedfc5711f21b48ba8996167b922b512b5d56f484d311251eccc94b7735c75c6735513c6e8cbc0f343a7cfe485dce5af6a7206980141a69dd81aefbbd0c9858428990567e3e2397fc22db911897c79e30de9ec0ed698357598ffd73c5674549d588a091a4377d73ef21201b319ad28ea3798ec1bf2cb3ca074d76ffff0aee2ad816041e6bb381acc79114215db200a8e7c724ecaffb92c20363763841662dd2a7161a9cac3a818d7375a0b4d83217f2056b0e3a1a52e98ced53619aaf537ce552ee0f4c433736752cc26c50ff86a7c402b3ce926cf6a27d2f003168c6e8b46003c967b854817d635c494cfaa2c53be2dbbaf32142dd503b1a45ec027d5314cf92988e3a8fbcd975a0e0c7ee04654 Hey, password is required here."},{"title":"【AI】chatgpt入门","date":"2023-08-30T16:58:13.000Z","path":"posts/3GDXTCB/","text":"OpenAI注册闭坑、GPT Api调用指南 注册 OpenAI 跟这个教程操作即可:ChatGPT最新注册教程 核心问题是两个: 借助 VPN 绕过 OpenAI 的IP检测封控,直接挂美国 借助 sms-activate 接收外国手机的验证码 调用 GPT Api 开发环境使用 python3,通过 pip3 install openai 安装依赖包。 注意生产环境也要挂VPN,下面是一段测试代码: # -*- coding: utf-8 -*- import openai openai.api_key = \"***********************\" completion = openai.ChatCompletion.create( model=\"gpt-3.5-turbo-0613\", messages=[ { \"role\": \"user\", \"content\": \"解释厄尔尼诺现象\", } ], temperature = 0.7 ) print (completion.choices[0].message.content) 开源推荐 chatgpt-on-wechat 微信聊天机器人,支持GPT3.5/GPT4.0/文心一言/讯飞星火模型,支持个人微信、公众号、企业微信,支持文本、语音和图片的处理。"},{"title":"【量化】数据专题","date":"2023-08-29T15:40:15.000Z","path":"posts/quant-data/","text":"导读 交易需要与 哪些数据 打交道? 如何获取 可靠、实惠 的数据? 如何高效地 存储、读写、计算 数据? 数据管线 交易数据可以分为三类(从左到右): 换一个角度理解:Bar是Tick数据的重要性采样(有点像光栅化),K-Line是Bar数据的可视化展现(有点像Pixel-Shading)。 在这个处理流程中,信息的原貌是不断被丢失的,因此越原始的数据,价值含量越高,就像《舌尖上的中国》所说:高端的食材,往往只需最简单的烹饪。 同时也不能忽略 图形化展示的意义,因为: 主观交易 依赖 K线图、技术指标 等作出趋势性、预测性地判断 量化交易 往往需要借助 Tick数据 去解读更多的市场微观信息。 如何理解Tick数据? 交易所收发交易数据的最小间隔 可能是每一笔撮合成交(A股),也可能是每500毫秒的交易快照(商品期货) 数据类型 狭义理解的金融数据,大概只有 成交量 和 价格 等关键值,但真正的金融市场是错综复杂、影响纷繁的,需要从如下几个领域考量: Fundamental Data 基本面数据,主要是企业的营收、财报等宏观信息,传统投资领域中的分析师,往往是对着海量的财报作出投资决策的。 Market Data 市场数据,主要是市值、市盈率、股价、成家量等金融数据,特点是 频率高、时效性强、噪声大,提取有价值信息的难度也非常大。 Analytics Data (第三方)分析数据是很宽泛的概念,可能是机构的研报、社交舆情的数据,甚至是相关政策的颁布、天气信息的变幻等等。特点是获取难度大、归纳提取有效信息难度更大。 考虑到数据获取的难度因素,我们一般基于 Market Data 的数据进行提炼和研究,这部分信息获取公开、透明、平等,且能得到的数据量也是最大的。 下面介绍一些常见的市场数据的提供商(获取渠道)。 数据提供商 这里推荐几个具有一定性价比的渠道,相较于个人投资者(爱好者)而言: 渠道 价格 准确度 覆盖度 baostock 免费 中 A股 金数源 中 高 期货、A股 天勤量化 部分免费 高 期货、A股(18年起) 掘金数据 高 高 期货、A股、数字货币 如果你的策略有所起色,甚至扭亏为盈了,后面可以考虑向专业的数据提供商(如Wind、同花顺)购买昂贵但准确的市场数据,有句话说得好:贵的东西总有贵的道理! 再按照股票、期货细分来看: 频率 数据体量 推荐数据源 A股 ① 5档逐笔 1T /年 tqsdk白嫖 1990年~2023年 1 min 25G /年 tqsdk白嫖 5 min 5G /年 baostock免费 日k 累计 2G baostock免费 期货 ② tick 单品种 1G /年 taobao购买 / tqsdk 2016年~2023年 1 min 累计 1.5G tqsdk 5 min 累计 0.3G tqsdk 日k - 数字货币 tick 掘金数据 1 min 掘金数据 5 min 掘金数据 ① 国内主要上市股票约 5000 只,平均上市时间 11.9 个年份(截至发文日期 2023年10月) 上交所 2288只 主板(1727),科创板(561) 深交所 2827只 主板(1506),创业板(1321) 首支上市日期为 1990年 平安银行 ② 国内商品期货、金融期货等,约 80 个种类 数据格式 推荐阅读:csv, hdf5, feather 三种数据性能对比 复权 在理解为什么要复权之前,先理解几个金融市场的基本概念: 分红:每10股派发6元 本质是将股票市值中的6元兑换成现金,发放到你的账户,等同于套现 拆股:每1股拆分为5股 本质是因为股价过高作拆分,单只股票价格也会变成五分之一 金融数据中的市场价格(包括开盘价、收盘价),往往都是不考虑分红、拆股的背景条件,因此经常见到股价突然腰斩 90% 的情况,其实并不是股价跌这么多,而是因为该上司公司拆股了。 因此,复权价格就是为了抹除非市场因素带来的涨跌,让价格保持平滑、连续性 前复权和后复权 前复权:以 第一天 的价格为基准,推算后面的价格 后复权:以 最后一天 的价格为基准,推算之前的价格 知乎: 通俗易懂的解释前复权,不复权和后复权有什么区别? 财报数据 核心是区分毛利润和净利润 毛利润(gross profit) = 收入 - 生产成本 净利润(net income)= 毛利润 - 销售/管理/研发/财务成本 - 税收 以白酒行业为例,假设一瓶售价为880的白酒,其原材料成本是80元,则其毛利率为 800/880=91%。观察国内相关上司企业,就能够发现 90%+ 的毛利率是普遍现象。 但由于销售成本(如广告)和人工成本的存在,其真实的净利率往往在 50% 以下。 指标的含义? 毛利润衡量的是产品价值,毛利率高,说明这是一门好生意(白酒、互联网) 净利润衡量的是企业价值,净利率高,说明其赚钱能力强(九安医疗 …) 留几个没想明白的问题 对于没有实体的 互联网行业,如何衡量其生产成本? 毛利润是否扣除 员工工资? 为什么要统计毛利率? 光有净利率不足够吗? Pandas Pandas 是 应用最广泛的 Python数据处理库,在量化交易、数据清洗中非常重要。 性能相关 numba 读取较多个csv文件耗时较长, 如何用 multiprocess + pandas 读取? from multiprocessing import Pool def read_csv(file_name): return pandas.read_csv(file_name) file_list = [...] with Pool(processes=6) as pool: df_list = pool.map(read_csv, file_list) df_all = pd.concat(df_list, ignore_index=True) \"\"\" 推荐在read_csv里将数据写到一个全局的dict \"\"\" 内存相关 为了节省runtime内存, DataFrame默认读取的是float64 & int64格式, 占用内存大且浪费 np.dtype('int32'): 表示int32类型 np.iinfo('int64').max: 获取int64的最大值 np.finfo('float64').max: float64 int64: 64 bits = 8 byte 建议如下: 不考虑负数, 用uint代替int 能用int16, 就不要用int32 为什么float64比int64表示范围大? 占内存是一样的 时间相关 这里要写很多篇幅, 先介绍Pandas自带的转化: TODO pandas.to_datetime(df): 返回类型是pandas的timestamp, 可以访问.date(), .day 注意项 读取中文报错: pd.read_csv(file_name, encoding = \"gbk\") UserWarning: Pandas doesn’t allow columns to be created via a new attribute name 正确写法: df['name'] = xxx 错误写法: df.name = xxx Pandas Warning: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame 一般是链式赋值时会报,根本原因是尝试对 copy 出来的 DataFrame 尝试去赋值。 下面是一个典型的例子: df.A.loc[df.B > 100] = 0 # × df.loc[df.B > 100, 'A'] = 0 # √ 复杂操作 groupby Pandas Groupby 知乎 DataFrame.groupby(by=None): 按照by这一column筛选 Group.get_group(): 获取指定的group, 返回DataFrame merge DataFrame, Series之间任意合并 注意left, right, outer等几种方式, 底层就是SQL的逻辑 merge完赋值如果不对齐,可以去重: mf = mf[~mf.index.duplicated()]"},{"title":"【名人访谈】BBC采访马斯克","date":"2023-08-23T15:14:25.000Z","path":"posts/2YS1Y71/","text":"导读 Youtube采访视频 2023年4月21日 B站解说视频 马斯克的核心技能:屌爆的思维逻辑 + 烂熟的辩论技巧 这一派还有个大佬是罗永浩,可以看他锤王自如的视频 TODO: 文章排版不满意,等录个视频锻炼口头表达 采访背景 马斯克方面 阅读材料:维基百科 埃隆·马斯克收购推特案 马斯克与 2022年10月 以 440亿美金 价格,收购美国社交巨头 Twitter,其过程经历三个阶段: 【提出】马斯克提出收购,遭到Twitter和市场反对 【后悔】马斯克发现Twitter用户数据造假,尝试放弃收购计划 【被迫】马斯克迫于法律诉讼,被迫收购Twitter 收购 Twitter 后,马斯克主要实行四大措施: 大量裁员(8000人->1500人) 开源 Twitter 内容推荐算法 删除垃圾机器人 退出收费认证服务 BBC方面 其次,BBC的采访素来以 尝试刁难采访者,角度狠辣,制造爆点话题 为主,围绕收购案本身,(从BBC角度)有如下几点值得 埋坑: 【道德角度】接手公司后大量裁员 【媒体角度】Twitter充斥越来越多的虚假信息 【决策角度】收费认证被所有人吐槽 所以,这次采访不是一个歌颂丰功伟绩的 “单口相声”,而更像是互相博弈的 “双人对线”,可以说火药味十足。 采访正题 ❓BBC: 聊聊Twitter收购案 这里马斯克谈了两点,【1】为什么停止收购,【2】强调最终收购是迫不得已。 在谈【1】时,马斯克举了一个非常形象的例子: 你想购买一袋大米,本来约定允许10%的米是坏的,最后发现30%的米都是坏的。这显然无法接受。 ❓BBC【进攻三连】:1. 你解雇大量员工,2. 你裁员的行为很随意,3. 你毫无同情心 这里有三个观点,分别是【事实】->【表象】->【推断】,层层推进,层层致命。 马斯克的回复很经典,其实核心是马保国的 接化发 (太极): 【接】:部分认同,但留有余旋 (Musk:确实裁员了…) 【化】:转移到对自己有利的话题 (Musk:公司账面只能4个月,不裁员所有人都死…) 【发】:用刁钻假设,反问对方(Musk:换做你怎么做? 有轨电车难题…) ❓BBC【换角度进攻】:你是世界首富,为什么不自己掏钱帮自己公司? 这里马斯克没有技巧,全靠真诚+卖惨:我贱卖了很多特斯拉的股票才能买下Twitter(别再道德绑架俺…) ❓BBC【问句埋坑】:你是否后悔裁员? 这是记者经典的疑问圈套,不论回答是否都是下策。 回答是:马斯克认错了!亲口承认裁员不明智 回答否:马斯克心狠手辣!裁员毫无愧疚 因此马斯克直接不回答该问题,而是侧面讲了两个自己的观点: 【1】公司有自己的运转规律 【2】卖特斯拉股票很困难,它还导致其市值暴跌(寻求弱势低位) 但如果光说第一点还不够,因为是 贱卖股票 让马斯克把自己放到了一个弱势地位,有效阻止记者继续纠缠。 ❓BBC【聊政治,埋坑】:Twitter被你收购后解封了Trump,他何时回归? 政治是敏感话题,在西方也是如此。因此马斯克直截了当地说 不知道。 到这里还没完,高手厉害之处就是,抓住任何机会宣传自己的企业,于是他说: 我在选举投了Biden,但解封了Trump,说明 Twitter是自由发声的地方 牛逼!但BBC也是高手,顺着自由发声的话题,立刻谈到Twitter的一些负面问题 ↓ ❓BBC【开始抨击】:Twitter强调言论自由,是否助长错误信息(言论)? 再一次经典的疑问圈套,马斯克作为高手,自然不会落入俗套。他直接反问记者:谁定义错误信息?,BBC难道没有发布过错误信息? ❓BBC【开始抨击】:Twitter裁掉整个内容审核部门,是否助长仇恨言论? 这里介绍一个背景,大部分的社交媒体,都是通过人工(为主)+AI(为辅)过滤仇恨言论(如政治、宗教、法律),但马斯克背其道而行之(裁掉部门)。 马斯克仍然 以反问起手:什么是仇恨言论?你用过Twitter吧(必然用过)。那举一个你见过的仇恨言论的例子。 【若记者举了】可以逐点反驳击破,因为很多仇恨言论是片面的,如LSBT,且没法在公开场合说 【若记者不举】根据 “谁主张 谁举证” 的规则,其不攻自破 马斯克传达的观念 俗手总是去证明自己,高手往往是在表达自己 抛开辩论技巧,大佬传达的观念也是值得学习的,马斯克在一个小时的采访中主要传达了这几个观点: 【方法论】想生存? 降本增效 一方面裁员,一方面裁设备(虽然导致服务崩溃) 拉回旧的广告商,提高收入 【方法论】开源核心算法 就像餐厅把自己的后厨公开到幕前。 只有公开透明的算法,才能让民众感到安心(尤其是社交领域) 【价值观】社交媒体的意义 马斯克不在乎赚钱(前提是企业能活下去) 好的社交媒体,是人们信赖的真相的来源,且人们会自发去评判和追求事物的真相"},{"title":"【C++11】lvalue & rvalue (references)","date":"2023-08-20T14:53:43.000Z","path":"posts/rvalue/","text":"C++ 左值、右值引用 导读 Understanding the meaning of lvalues and rvalues in C++ C++ rvalue references and move semantics for beginners Move semantics and rvalue references in C++11 前言 从接触、学习、运用 C++ 至今,左右值引用一直是自己困惑的点。伴随着现代C++的发展,它们开始扮演越来越重要的作用(如 std::move、std::remove_reference…)。 这篇争取彻底搞懂他们。 先看 gcc 一个编译报错,为什么 666 = x 的语法是错误的? error: lvalue required as left operand of assignment 编译器是在说:赋值符号 = 的左操作数,必须是左值 lvalue ! 换句话说,这里的 666 不是一个左值。 int x; 666 = x; 左值和右值 如何区分 左值 和 右值? lvalue:指向明确的内存地址,又称 variable rvalue:没有明确的内存地址,又称 literal constant 下面看几个示例: int x = 666:x 是 lvalue,666 是 rvalue int* y = &x:x 是 lvalue,y 是 lvalue reference 编译规则,赋值= 和取地址& 的左边必须是 lvalue,不然会报如下错误: error: lvalue required as left operand of assignment error: lvalue required as unary ‘&’ operand` 区分 左值 和 左值引用 int x = 1:x是左值 int& y = x:y是左值引用 function reference 函数的返回值可以是 左值,也可以是 右值。 右值 × int setValue() { return 6; }; setValue() = 3; // error: lvalue required as left operand of assignment 左值 √ int x = 100; int& setValue() { return x; }; setValue() = 1; 左值 → 右值 左值 经常会被转化为 右值,如下示例: x, y 都是 左值 x + y 被转化为 右值 int x = 1; int y = 3; int z = x + y; // ok 上面经历了一次 lvalue -> rvalue 的隐式转换,很多操作符(+, -, /)都会提供。 右值 → 左值 右值 到 左值 的转换是被禁止的,如下代码是非法的: int& x = 10; // error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int' 右值引用 🔥 C++ 的一条重要编译规则是:你无法绑定一个 右值 的地址,除非绑定到一个 const 类型,例如: int& x = 666; // error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int' const int& x = 666; // OK std::string s1 = \"Hello \"; std::string s2 = \"world\"; const std::string& s3 = s1 + s2; s3 += \" luhao\"; // error: no match for 'operator+=' (operand types are 'const std::string' 但是上面的写法有个弊端,即无法再修改 s3 的值。 为了能够修改右值(即临时变量),C++11 正式引入右值引用(rvalue reference),其符号是 &&: std::string s1 = \"Hello \"; std::string s2 = \"world\"; std::string&& s3 = s1 + s2; s3 += \" luhao\"; // OK 上面的示例看出来用处不大,因为 rvalue reference 真正大展拳脚的地方,是在 移动语义(move semantics)。 move语义 🔥 阅读资料 Move semantics and rvalue references in C++11 移动语义 是一种利用右值引用的技术,来避免拷贝临时变量的优化手段。 为什么需要 move semantics? 💡 假设 class Holder 是一个(内存)非常繁重的类,考虑到如下的构造和拷贝构造函数。 当调用 Holder h1(h) 时,因为 std::copy 造成巨大的内存拷贝开销,如果后文中 h 也不再继续使用,为什么不尝试将 h 转交给 h1 呢? class Holder { public: Holder(int size) { m_data = new int[size]; m_size = size; } Holder(const Holder& other) { m_data = new int[other.m_size]; std::copy(other.m_data, other.m_data + other.m_size, m_data); m_size = other.m_size; } ~Holder() { delete[] m_data; } private: int* m_data; size_t m_size; } int main() { Holder h(10000); Holder h1(h); // 调用 std::copy 带来非必要开销 return 1; } 借助移动语义,可以优化掉上面的拷贝。注意到下面使用了 std::move,它能将左值转化为右值,是C++标准库的成员函数,后面有介绍。 Holder(Holder&& other) { // 赋值 m_data = other.m_data; m_size = other.m_size; // 清空other的状态 other.m_data = nullptr; other.m_size = 0; } int main() { Holder h(10000); Holder h1(std::move(h)); return 1; } std::move 阅读材料: cppreference libstdc++: move.h 阅读 std::move 的源码,其实只是作了类型转化,将 任意形式的_Tp 转化成右值: std::remove_reference:去掉引用 static_cast:隐式转换 std::move move 右值:直接返回 move 左值:转成右值,并返回 /** * @brief Convert a value to an rvalue. * @param __t A thing of arbitrary type. * @return The parameter cast to an rvalue-reference to allow moving it. */ template<typename _Tp> constexpr typename std::remove_reference<_Tp>::type&& move(_Tp&& __t) noexcept { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); } std::remove_reference remove_reference 顾名思义,就是去除任意类型的引用,借助模板实现。 核心是对于 _Tp& 和 _Tp&& 这两种带引用的传参,需要去掉其引用的部分,只获取其类型(通过 ::type 获取) template<typename _Tp> struct remove_reference { typedef _Tp type; }; // 特化 template<typename _Tp> struct remove_reference<_Tp&> { typedef _Tp type; }; // 特化 template<typename _Tp> struct remove_reference<_Tp&&> { typedef _Tp type; }; 三种传参 1. const T 常量值传递,默认有一次拷贝开销。 如果是 builtin-types (int、float ...) 推荐使用这种传递方式 2. const T& 常量引用传递,& 避免拷贝带来的开销,const 避免被修改。 但会将生命周期延续 3. T&& 右值传递,避免拷贝带来的开销,推荐复杂结构体如 std::vector ... 缺点是调用者必须传入右值,否则编译期间报错,如果是通用接口比较难受"},{"title":"【C++17】refl-cpp","date":"2023-08-17T16:27:58.000Z","path":"posts/refl-cpp/","text":"品读C++经典反射库 导读 源码:veselink1/refl-cpp blog:refl-cpp — A deep dive into this compile-time reflection library for C++ 这篇博客大致是英文版的 直译 + 自己理解,旨在提高对 templates + reflections 的掌握 精读blog ➜ 理解源码 ➜ 上手仿造 目录 前言 compile-time反射 构建类的成员 使用 macros 组织代码 函数反射 遍历类的成员 前言 refl-cpp 的设计初衷是: 支持 在 C++17 及更高版本 提供编译期反射(Compile-time) 的方法。 支持 enumeration,introspection enumeration:类似 Python dir() 枚举对象所有的属性 introsection:类似 Python getattr 访问对象的指定属性 支持 类型模板、成员模板 支持 attributes TODO refl-cpp 的设计避免如下: 避免 使用宏魔法 避免 Private 私有成员的反射 避免 运行时 按名称查询类型信息 compile-time反射 首先 refl-cpp 是一个 compile-time 的反射库,这意味着它不会维护一个 runtime 的数据结构来实现反射目的,例如下面是不可取的: struct TypeInfo { std::string name; // 对象的类型名称 std::vector<?> members; // 对象的所有成员 std::vector<?> attributes; // 对象的所有成员取值 } // ↓ 维护一个全局的反射数据结构 std::unordered_map<std::string, TypeInfo> s_typeRegistry; 相反,refl-cpp 的做法是,通过 cpp模板特化 以一种类相关的方式(type-dependent)来存储 metadata,例如下面的做法: template <typename> struct TypeInfo {}; // ↓ Point类的编译期信息 template <> struct TypeInfo<Point> { static constexpr char name[] = \"Point\"; ??? members = {}; ??? attributes = {}; }; 构建类的成员 上一节提供了存储 类信息 的方法,但是如何存储 其成员变量(和方法)呢? refl-cpp 使用一种新颖的方式来存储: template <size_t N> struct MemberInfo; /* 第0个成员的模板特化 */ template <> struct MemberInfo<0> { /* ... */ }; /* 第1个成员的模板特化 */ template <> struct MemberInfo<1> { /* ... */ }; static constexpr size_t MemberCount = 2; MemberInfo 是类成员的模板特化,因此将其添加到 TypeInfo 的作用域(如下)。 typename Dummy 是因为C++不允许成员完全模板特化,而部分成员的模板特化是允许的。TODO template <> struct TypeInfo<Point> { template <size_t N, typename Dummy> struct MemberInfo; /* 第0个成员的模板特化 */ template <typename Dummy> struct MemberInfo<0> { /* ... */ }; /* 第1个成员的模板特化 */ template <typename Dummy> struct MemberInfo<1> { /* ... */ }; static constexpr size_t MemberCount = 2; }; 使用 macros 组织代码 上一节提供了粗略的 类 + 类成员 的反射方案,那么如何声明它们呢? 答案是借助 macros 实现(作者不是不建议使用宏么😂…) __COUNTER__ 是非标准库的宏,每次调用增加1,且从0开始 宏展开后的代码,可看示例:refl-cpp-deep-dive-5-generated.cpp 另外每个 TypeInfo 和 MemberInfo 还应该包含如下内容: static constexpr char name[] = … static constexpr std::tuple<…> attributes = {…} static constexpr auto* pointer = &Type::MemberName template <typename T> struct TypeInfo {}; #define REFLECT_TYPE(TypeName) \\ template<> struct TypeInfo<TypeName> { \\ template <size_t, typename> struct MemberInfo; \\ static constexpr size_t MemberIndexOffset = __COUNTER__ + 1; #define REFLECT_FIELD(FieldName) \\ template <typename Dummy> struct MemberInfo<__COUNTER__ - MemberIndexOffset> \\ {}; \\ #define REFLECT_END \\ static constexpr size_t MemberCount = __COUNTER__ - MemberIndexOffset; \\ }; // Usage: REFLECT_TYPE(Point) REFLECT_FIELD(x) REFLECT_FIELD(y) REFLECT_END Tips: 借助 VisualStudio 查看宏展开 鼠标悬停在宏上,点击 Expand Inline 函数反射 refl-cpp 还提供反射函数的功能。 为了区分成员(是变量还是方法),每个 MemberInfo 有一个公共的 typedef,它等同于 refl::members::field 和 refl::members::function 两者之一。而考虑到 函数的重载和模板,这部分功能(相对于反射成员)会更加复杂。 refl-cpp 通过如下方法:TODO template <typename R, typename... Args> auto resolve(R(*fn)(Args...), Args&&... args) -> decltype(fn); /* Imagine 12 more overloads of resolve for different pointer-to-member combinations (plain, &, &&, const, volatile qualifiers) */ template <typename... Args> static constexpr decltype(detail::resolve(&Type::MemberName, std::declval<Args>()...)) pointer { &Type::MemberName }; ↑ 上面这段代码理解起来较为困难,我们可以将其功能拆解一下,它是为了解决什么问题? 想象类型 A 具有两个函数重载: f(int) f(const std::string) 当拥有一个指向f的函数指针(&f)时,编译器怎么知道调用哪个? refl-cpp 实际会帮助编译器 推导出正确的重载函数(通过将 &f 作为参数传递给另一个函数的方式,来直接触发函数)。 resolve 没有任何定义,它只是一个 prototype,作用是作为编译器的一个提示。 这种方法总结起来是:传递函数性质的参数(由 std::decalval 产生)。它的好处是:所有的参数类型转换都适用,即我们可以通过 MemberInfo<?>::pointer<int> 并得到一个 void(*)(long) 类型的指针作为结果。 遍历类的成员 前面讲了如何创建和存储类成员的 metadata,这节介绍如何遍历它们(compile-time)。 核心思想是 借助可变参数模板,创建一个 TypeList 的类型成员列表,并提供枚举的方法。 template <typename... Ts> struct TypeList { }; !!! NOTE 这块讲的不是很细,没搞懂…"},{"title":"【cpp】Templates","date":"2023-08-13T17:47:03.000Z","path":"posts/templates/","text":"C++模板、meta-programming Function Templates 模板基础 下面是一个最简单的函数模板示例: template <typename T> T max (T a, T b) { return b < a ? a : b; } T 是定义类型的变量,它可以是 int、float、任何class… typename 是关键字,template<class T> 是兼容 C++98 的一种正确写法 上面有两个潜在约束:1. T必须支持<操作符,2. T必须支持拷贝构造函数,为了return 下面是简单的模板使用实例: ::max(7, 8); // 8 ::max(1.2, 1.5); // 1.5 ::max(\"abc\", \"abcd\"); // abcd 当调用上者时,模板会自动实例化为: int max(int, int); double max(double, double); char const* max(char const*, char const*); 编译检查 模板的编译检查分为两个阶段(Two-Phase Translation) 定义阶段 实例化阶段 template <typename T> void foo(T t) { undeclared(); // 未定义函数,定义阶段报错 undeclared(t); // 引用了T,所以实例化阶段才报错 } 参数推导 编译器会根据传入参数的类型,自动推导 T 的取值 若引用传递:不允许类型转化 若值传递:只允许退化(decay),const和volatile会被忽略。引用会被转化成引用的类型。 int const c = 42; int i = 1; ::max(i, c); // OK: (int, int) ::max(c, c); // OK: (int, int) int& ir = i; ::max(i, ir); // OK: (int, int) int arr[4]; ::max(&i, arr); // OK: (int*, int*) 多参数 模板允许定义多组不同的参数,以如下函数示例,其返回值的类型是不确定的: template<typename T1, typename T2> T1 max (T1 a, T2 b) { return b < a ? a : b; } 返回类型推断 从C++14开始,允许使用 auto 声明函数的返回值,即让编译器自己决定。 template<typename T1, typename T2> auto max (T1 a, T2 b) { return b < a ? a : b; } 在C++11中,auto必须配合 trailing return type 使用,否则编译报错如下: error: 'xxx' function uses 'auto' type specifier without trailing return type template<typename T1, typename T2> auto max (T1 a, T2 b) -> decltype(b<a?a:b); 类型萃取 #include <type_traits> template<typename T1, typename T2> std::common_type_t<T1,T2> max (T1 a, T2 b) Trick: C++如何获取变量x的类型? #include <typeinfo> typeid(x).name() Class Templates 模板特化 这篇中文资料说得通俗易懂:深入理解特化与偏特化 源码 推荐阅读: type_traits 模板特化的作用是,针对模板的参数类型,从而定义不同的实现。 只要你教得好,它可以 “见人说人话,见鬼说鬼话” (有点类似 函数重载 和 虚函数继承 的思想) 模板特化实现思路是: 先定义基本模板(能说话) 再针对每种参数实现特例(能见人下菜碟) 下面仿照 Python 实现 C++ 的 type 函数: #include <iostream> #include <string> using namespace std; template<typename T> class TypeId { public: static constexpr char const* type = \"NULL\"; TypeId(T t) {} }; template<> class TypeId<int> { public: static constexpr char const* type = \"INT\"; TypeId(int t) {} }; template<> class TypeId<std::string> { public: static constexpr char const* type = \"STRING\"; TypeId(std::string t) {} }; int main() { ::cout << TypeId(1).type << \"\\n\"; // INT ::cout << TypeId(std::string(\"abc\")).type << \"\\n\"; // STRING return 1; } 模板特化 规则 模板特化 符合 函数重载 的两个条件之一: 参数数量相同、类型不同 参数数量不同(特化只能少于等于) 否则出现报错: error: too many template arguments for class template xxx 示例如下: template <typename T, typename U> struct X ; // 0 // 原型有两个类型参数 // 所以下面的这些偏特化的实参列表 // 也需要两个类型参数对应 template <typename T> struct X<T, T > {}; // 1 template <typename T> struct X<T*, T > {}; // 2 template <typename T> struct X<T, T* > {}; // 3 template <typename U> struct X<U, int> {}; // 4 template <typename U> struct X<U*, int> {}; // 5 template <typename U, typename T> struct X<U*, T* > {}; // 6 template <typename U, typename T> struct X<U, T* > {}; // 7 template <typename U, typename T> struct X<U, T, T > {}; // Error 模板特化 代码示例 refl-sum.cpp refl-factorial.cpp std ⭐std::max _GLIBCXX14_CONSTEXPR 在 C++14 会被替换为 constexpr 实际可以展开为:constexpr inline const _Tp& max(const _Tp& __a, const _Tp& __b) constexpr无实质作用,重点是参数使用 const & template<typename _Tp> _GLIBCXX14_CONSTEXPR inline const _Tp& max(const _Tp& __a, const _Tp& __b) { // concept requirements __glibcxx_function_requires(_LessThanComparableConcept<_Tp>) //return __a < __b ? __b : __a; if (__a < __b) return __b; return __a; } ⭐std::pair [cppreference] std::pair [stackoverflow] What is the purpose of std::make_pair vs the constructor of std::pair? c++14及之前,std::pair需要显式指定类型,std::make_pair不需要 template<typename _T1, typename _T2> struct pair : private __pair_base<_T1, _T2> { typedef _T1 first_type; ///< The type of the `first` member typedef _T2 second_type; ///< The type of the `second` member _T1 first; ///< The first member _T2 second; ///< The second member _GLIBCXX_CONSTEXPR pair() : first(), second() { } // ... } ⭐type_traits 以 is_integral 为例,判断是否为整型 template<typename _Tp> struct is_integral : public __is_integral_helper<__remove_cv_t<_Tp>>::type { }; __is_integral_helper 是一个标准的模板特化,非常简单! template<typename> struct __is_integral_helper : public false_type { }; template<> struct __is_integral_helper<int> : public true_type { }; template<> struct __is_integral_helper<char> : public true_type { }; // ... true_type 相关定义如下,其value变量就是一个bool类型的 true /// integral_constant template<typename _Tp, _Tp __v> struct integral_constant { static constexpr _Tp value = __v; typedef _Tp value_type; typedef integral_constant<_Tp, __v> type; constexpr operator value_type() const noexcept { return value; } // ... }; template<typename _Tp, _Tp __v> constexpr _Tp integral_constant<_Tp, __v>::value; /// The type used as a compile-time boolean with true value. typedef integral_constant<bool, true> true_type; /// The type used as a compile-time boolean with false value. typedef integral_constant<bool, false> false_type; 反射 先看看什么是 reflection: reflection is the ability of a process to examine, introspect, and modify its own structure and behavior. 通俗解释,反射就是从一个对象(object),能够反推其类型、成员和方法 以Python为例,getattr 就是经典的反射功能 为什么cpp没有反射? 反射会导致编译后文件过大 cpp很少用到元编程(相对于C#) cpp有模板,足够应付大部分需求... 阅读材料 CPP-Templates-2nd 英文 CPP-Templates-2nd 中文版翻译 cpp-templates-2nd 中文版翻译 C++ Template 进阶指南 refl-cpp What Does Haskell Have to Do with C++?"},{"title":"日志:2023年8月","date":"2023-07-31T16:35:13.000Z","path":"posts/2023-8/","text":"b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475de0b1305a637ea913974dff7410a14ed4dbbb40a5278eb14bbac34e59e12a284c7a294ab69a495d47e11e78ad50feb1070429e58f3fbd737c20fb16c7243eddbb619e52c31eb507d6765e3b14dcaa7e9d13492120aa6f2d9638cdc372f2d3c81cf4232ed0874302eb77e0fba53c7b0ac1b5e05196961ebceabaee4415690ead6a710c137c2757694abdfd08eb8c2634a5d753274da2b1c7bef744740058d16d7091d22fa8304764b0d2f6294ca8c6a207e9631f260ac16e68b32cf9dfed4653d1dfd7ec34e029768744ff31cf46be9716e7586f7db03f6fdbb5424f4ac8476c5cf96e9dd587787781164d9d02b9abc39b1840ec2cd478ca480554f4049c941e50195a77f0b24330ba24922044d92ee1b7b07c0420d90b435163e9b6ff6a187c600a683de6c80ee99f40ad6f04c57b1038b59b7c5fa28d202cbb5b68ac169c96e956c701fa2733dd351daaf2c77420ef2c9d81b4f6567415a90f9806bbd90215c38f75a31cdc983a0ee93c2590e66b10f2f88cf627e9e7230e093277675ba2ffa1f8a9d71e9763c19c4389b0f2a0f8903c564dc10ab53981fc487ccee24df6454c9466089470df32dfe841773b273a2e9212119f7c977a60035ea301d47eb5078523d993c6370d7cfc69c6bec52ff9cd8c59c346f4af5adb805c21e170d244cd7ff29f9d3f70acaf5041b2b2aa0e2a48c06b1e7fe726ad391187a60e8153790242e965db2f618c5cb9f90bac81e9171a4082f761d56fd77065727d7a01568bdf1a92fd6b69a47d66ae586c041362e158cf6918e643df04270fbf2d7b6c83b81a3cabfdeb4cec43465d5d0d18f24d8eccd0eecbba24f24f0ca3cfdf63d71f027c56dd478870b5bb67d334dbe2b1d1e1bcd505a731da11641c35e59e867a6c505cbd4078a924a22c3f47ec3268a07f420acf3c6ed043d684e69bd88468885b77dd7385241e94561c5a2132be9a941fe048e6fb604daf1ffb603538ba15f37ac2a09be55fe1b1e40ceceeeea21877916031087ca10ff470729aabadb15870ac7a684dda40c886f3270bea5db51aab42a40b4c64579bc15a5f4489b5c5c1c5b433251d46b7188000fd63a7691272641c1bf76e00f4a8558e58d69bb2b2c0130608f044657dd53f25cce7b98e4630c9d52c24136a428286ecb66a66313c2881f168df75a2cb8453c0bcac9e32cb1434d6809996eae2b590ebbdaac6c9009739126e9f8e92976ce7d71b9bcbe7e7de2fe5ade15b4d1e8dc3e147d0be96aaad8c939e1e122aeb55ba72ba38d37329e489f7c6edae50f15673c33606c8b801c6b4ad8d2f770c80cafc9a3dc5912a35995572ad088d582045bcd1b1d6b7839cb8a03bf6de7aea3d25c808f25e92bcac20dd3a8d76aeaaee615e7f58c68cec17e2df0d43f0ff1b11f23e5e684e3e8ad0679ebb58877fa208ca34f7014e80b0c60429634ff4d88cc0aadae737dd83f077ec2e164fa493b7e95e55e979d8a315b57c50699903b463027e33cf890ceb17eb29ab76882555cf0acdce817698929fdc6ae42c726c15cd09f9876c2e4c2824c265912a1ce677d096667d049cdcd82d88a78bf874effcdc566f641455cd98637d4b7880b733545ba61714b012fc1e09c3988cdb32023c4e6679d4772f3aefe2be7b775447b05182da8750b8eb0ab9c7e2057e9165432399749ac975f2f5f4ed9a4d0dfb53a9fca27c539f3cde6dd71523ca62fd8503a0b26edb8ca15af6be9aa9350df5e93595bde5fe38fa8f77daf14cec1eb557af4dbf77a5503fa04f201a89d536e268281174dda349431b7b582df05a892d5baf6104a9c2dc4f0127bee4bb2d694cc63c7e401c3a8f9de45d2ec27306206a40444f066be06109511c0049c38c1aa3b302645ab2786db16e50d6392729ab724f74631f65b029ee199ecb8aa9adba4985c3323b83133db3508595c9a146c051f9669c9a6d793d656024b6e68fac42f21b9ec4cb0f6e456680fdadd514588c7dc2fcb1192d10b7a4ed512f92eb4f510849f16fd9bec830f1a9cbe52a5a344143e44e0bc9608ce50b3a3c96ca8ff6f033fe415254e1bc2a50c2d4e56b784876cbf8004c45f63a45356c08199fbc1f433cecbadc18e21d5fee429e8b1292e19f125e57d4aed4f05a0b2194a6e17d423c01774476f04219848cbbb9699ab58bf26ddaa99130657470c6599aab71db4077a077b2784db5bbf7a108f85c27cf2e90dc801ef7f4ecc3ca15e28099c73bb4d8ca8604537b05f16d23c66bc7c2e152fbc478293af95ebacf9c8796a586d70ef75ad618fa2d3bbc5fbe81a22d8ee36105b70adb9f636c3cd69228e9b53a463e890f103ddc72f0603a6815e32ffed8a79e7467a91c3ea6da318d7d6a2c1bc4e440b3987bec38c4fe6724ef9ec7ff7367ad45351339ce7e2fc68db732404b75e057dbb658e05f68678fc99eac0479ed9aefd6521b65b88cf162dc1ad81e2fcc26d259805213d06d68e632d484e95cd8bb09b8954262b5063afe43274e2033352daa9313db01c405cc29528aa7a6467b926951bc8252d36a967916a8f7b385dbbcf51af18bb0f8bf5d2d99cb334f9ec55f15b7c5e4a6a49207fbdf3ff0f032feeff053f0caf51525b5d934f0281b317a2591f7863541829e6d3e4dfd10f495d34e76a37a4cb1994dc5956695f2af5b29a34c58cd54d6c1b6b4a25d80244d0bba77e336c80bcfd98c258c670d3b0c5cc7cf34acfb2ba60c885f80373bc9ba82e83e3c18432bdab9d5ddcae387b5e0c552cda7fffdf87128451ca3c5a874d268fe2305bbbcf5910a4b978866328649f891014df094ad555bd32f76972994bbcc2302e8fc6cc5123317a028cfa93a9d2aa4d2e006b7a8e9895a90e49ea4a65b67af51c19e8e993a954e96235e9f150917fae2fd1705f21782d256946066b4e4f531af3c5cef513d4ed399f7c252c3fb5097e828a0058ad4e1089dfc8df4f5d0293d9382284328ba9c7adbcdcd09383dca737191ddf9b2969e6b02af4212cf4361894ebc708f00b16e7c64ff10e4432b65fe2c7b05e4889c5808949737998e7f6335bf48a4354828d177ea3dffd66c4df7c03a187862eab302fca251e5a5be70cce19df939b5efbdb527f6004dc35c8fa4bb309e4d1047dc84157379579bc9c7bf6b1805b97d117a654c1d579765989e0f4b12be0958da5b3c7c9bf86a15679a4fecd60f11f83ad11b9091f80343d3397747d9680c70f8e3a5db6f49dc2c7f0e3e0a91abaaee24635d20d1d72a74afadea6040b9288356a89e19f86781940d1a552ac5b327a1fc06959c7a3433042a2e98ce411575f087822beb5e58303077bc84f16d07bb4c36f7bad34b2b575af27e0ba4228abdabb3380d6c2666514d82f82814eaad75e3a39b999d563dadc7074005acdd320836230eaa8ff4f153d5fb1e6e3bf23891d8409a3aab2b9ca7282fe15c11b6f9d7819574ae7e05b42b29f5ab944dc5c26af551487c7b4dd302a40741f28a14f50391abb1684ba1c8d79ed5211ea977d5f7038e733432f469761af877cdbe2ae70f6654976e9fd65c1b841b0a7a75f4f3a6dbbdca6e0e7712589a8d683b86e9dc4624813421f786966137286b13ff885b73c912ff105e233a888faa682c5f2190772d64fc9f820da019d49fc757bd442fc9ab962a212e1cf55f700765fd22517eaf05202fb28e61ad0c354abb781ce08359a497ae7efbb34dd52ad49db933da2f5138b95fe37a89753584c755070361a9d1721eb707164d8d85cdbfd53011fb8ba98971f45e6eb6cb849a955902875d84a7bc9fb98477a81b1d2f077d2846381889e1ae1171cd9a86d92b0b8666ed73de0a282013ef9a009dd51b3b6002b05210c4ce883d2e13d27f61c577cc6af7d69ef8ede74aaa73554d9ef9591b1c00fb1863ae9809614950608dee1b35b60724ad4fd4ae5c1b91f0d9fbe7482e4edd2cb6182bc0af102a823b9b45e62376af8dbb0aa24ffd3e8137e3f9390ead53f34dc2204a239e1e7307cb8ae198c70ff0f3e62488627ed20b48da9cdd6aa7e3698774c8002e82f6dedd77abc97301269443fbfe203103ff3807139e6472ed40a4d67ec624e141e50e1234ba20e93df4fa080cc6d5f4a62ebabd5b63d51f198c9b8305c196ff559ca00b1750a747ca1a187c2b3f1f0548f99b3006a70b70ab4458f3de77fa25050ae50e9c31a3345876e12a48d5184846e6a711cfeeff25f984e5929465a19f8811addf40d930b75c6ab0a58a27d3ff51e949c2875b6621f6f4a986b5110114321dc1c62390aa67bd88f77a41d5b5b49322a95351d36782034a6d45b6db85ebafa79cf5b3f3002f2a714e11576d03b026f2c256c12ed04f2163daf93d2c500c7d3053827c1eadc2450f4a02703ef5bc1ebb2021a84029a68b25a2936d76341ab9e710263f2bc3688cefb26405a6fef0fb5d4ff2fa28aafbfe3016880820bcfd32b4b1fe203124d4027a483492bba57e5121f2a9b3e31e7caec33cf72b586944dda700dc922f626593558cb60ebfcaf656883a3786aaff323b53cd38d02dd785670eb9ea232692ae40965f1ed43fd933d0fa4f95cbdbf9bb03809efcc82eefe4020e15d6bf2b9828012589fd5074d139aeb55b80438ea9f500f06b899be9b2cc0c575dcfcaa6da869c182e2fd70eb3a7a75b5361ff28150b215d50851b8c3a0050331587f0959a2310b71e957ef18fad48dc7e16efdd8cc14ac82c89012eac8bc0738413646a19b38a5ee89c54a934f28be319d9aef11a7003d34a8d3b16a3e9981e1e640e04809a94d566ae69ab90e58ca1647844939159901d632c0744355c4fe0759d6f3b66b7430db18cb85f659cb2c3357cc99af3ea7a92540b2e6d6202e5eb3f7f0d3b686de0f52ae301ab564c490c744190cee9daaecb8ab644fc24fc6699f3c099b006f6281229c303afe87c87d297f77b14ea36875bdea69b667ff9e9271596408077c120dd67d2b14c77f1f98c4577981fc649984b6546e07931ab91755759537debd2dc68560228fac2fcc2738522e975060279ca6a0c9d3df1af329d432ea7f0da40b3e3c9587c54e1fe1318c6f118611f372a7ca1b8e8b6b19847e189de01880cf9be6dd73106aa6c6f1e548b2a4bcdca64c33073302ba8a9142032406c65d52e4a18ad95044c4d4bf78494e4964b79a7d35deb929af3c3e9d05dfc713ab5018d75a564846ad762b58e1f64e4bbaf0508b3301a2bc7111a3c3df41f1657bcc66354ad3c152d5aab95c5ccfca48fe101f199bd13bcd76e1e64b6019edb0156d481632228320c98bce931dbbd413db446b783d853edc9013bd3db83f1327ad3d3d0c3ba5f91915bf9e8bd6c56bb34fcda55f88f0448cfb1c3148a1b44a7c0b131237db2c01847746e6abced888260001b2291ade4763a4a54f64f0bd722a4d218287be3ab6e579d4785459b289b3faceb7feebf7466101f1dbb9a11f5558cb2782a709029f69886a06e621215fda6addadf141548d7307141b6a9ab599c41b8c0435dfd4628551db893f19af55d67057f4d01e230c3dcdcb45858fc84978cbfc864005b8756a53f7412b6f8fb3fa55b821c2798d2ff4f8e2be30f3bdb5cb0113f230db06059ec2500a601b33608107b7cb3e2b8fcafcd29e768d28d97f4deee56a8f3688b1541cc5963efcd4a4cdd04368a41676bff2f02a10821025119b95fbcb4c998d5f58754171aac564c41f95f304f12401e629853ffdb63f5e8911acf87978144439b2c25616242a622a3ab0ea84343f63e7c59bfed97b2e7682dba574a411641410e38a60e5be49f6197e6e664d5310a0e93a92d97d48e3c596ef3577e50ecc6f11434e02222b613209878a9b07cd3552df6d1e9234dad172da1f9378ac6a98a71fc0099a48d9572225e193c84de989f5ea68110f3a77eea1 Hey, password is required here."},{"title":"【vscode】vim定制化插件","date":"2023-07-23T12:04:39.000Z","path":"posts/vscode-vim/","text":"导读 VSCodeVim 是仿照vim的vscode插件 本文在其基础上添加一些额外的个性化功能 VSCodeVim vscode vim主流插件有两款,分别是: VSCodeVim:仿vim的插件,功能不全 VSCode Neovim:基于Neovim,功能较全,但依赖nvim环境且配置复杂 笔者一直使用前者,因此本文全部围绕 VSCodeVim展开。 缺陷 😞 VSCodeVim Github 拥有 1.5k 未处理的 Issues,作者的维护迭代速度非常慢,因此有很多缺陷和功能不足之处: 不支持 vimscript function 不支持 vim bash 【bug】经常Esc失效,弹窗报错 vim.Escape is undefined… 需要重装插件 亮点 🎉 打开 \"vim.statusBarColorControl\": true,可以使底部 statusBar 跟随 vim模式 而改变颜色。 本文希望进而改变 Cursor 和 当前行 的颜色、高亮显示,并支持 config 配置,效果图如下: 改进 💡 支持配置 StatusBar, Highlight 等颜色配置 支持区分 Normal, Insert, Visual 三种模式的颜色 下载链接: https://github.com/593413198/Vim/releases/tag/vim-mode-1.0 配置文件: \"vim.statusBarColorControl\": true, \"vim.alpha\": \"80\", \"vim.statusBarColors.visual\": \"#005f5f\", \"vim.statusBarColors.insert\": \"#5f0000\","},{"title":"随手记","date":"2023-07-21T19:15:33.000Z","path":"posts/idea/","text":"b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475debb79baf718116b8398ebb7a36965f5712db04a990a04196bf7d89b9188d2f5969b089300c199c887ad4490953d1810357cd375ac06e732d356d4ec27b642aa2d46c8fdf198d48a4b9620936f8c18efa99cd53c0e6c5befbd6da9ebb0e8d473ba88daf9814bf097589053470cd244c49759b29258898ae147d3b92406541ecc1426781b7f965d69488f8227468d25ace43c6b69c8a19f97354453e8ff96435fa7c142eabaa7418866597b071d73575a4fa98f09916a3b6256959c82764411afd8f6b01c5b6bf4975a899e020f3dee128569e0a2113d6e5eea3dde855a24ae2aa431bfe1d66cd3cb55d7640d0b6991fc435e20626245ee0a53ac26e04647979c2561d16a51e30ef1d5cb013803f9b1ec1543c53db0e0612835d22a88763eb021d58fca3f98f8f9a466b23ec85f6ebc9730cbedc2ec3beac3e1e4aab13769ea50c4d0877e05a485ad6e80e8d95f90bd71e51ce624b4088c4a292994308c83bd9615ba9ceddd1b2b013e68b6f954bbbc2d9526df35e8df607ee068bf2417f8ed2c68c803d5fb036aebfda9e5852e0f2c3ee3f60fbd426bd7ca8d81e142b1688443566187802f206bffc24c533d88c4021b98271f489ff2a0cd9ea4c88b47d3858b8c28ff89929ef8c82a3e39b86f12be544c44b5b1ac7357322938f17d796c107223e3e61e2536bc9c9f685dcaef7fb967ed3725e32d02590f722c180df46dc1d6322835f1b42b4c0ff08650d36700c2cea5c141feef7d79b13c80bbb0a8fba54ebcc23f3b67fcff332fc61daff746eaf1288abc1a86a4b97fa64346f234e566d4f692449805cab93c64aaa500b9913cc61f341bb1c65079ed93ef988b248c860b2a24a2b209ce9d754f2b1df62dcbcacfad713f6d4f65a96b028ccfb0e64a60c723e8f75a4cfbd52c94d1aa50ec3f413505e15234a5343fdd00f9a2d2a242ab4b88c06310315d84f1696a55134767fcb9fb5b093e54bb71600ade189778bb8cf3319486269c47ba15f22b60da1ba2c887a3ce32b690877586c23125c5caae8c4b4f08f997e8c9d8c1dede3170a78325570a6d259c6b7d7bf646cee7820267bf00a424187c4d543ee633880109906ac0816150aa126eea1faf33cde18cf6254edcb7c2c071e56f9d2183c91a929435e0ecf10efcbaefd4c14bb5777dd02f1308a39af4ba1ce6efa86ebb83c68aa34bf2747349b1e89322955d7a4e242fff5f1450355ed214e1e4fd646f97ed41bd89803dddc681979222e476d7cb879c749e3c9913ee9b7dde54851bd09fc9a06a957c05f84c2c96460b8aed2018dc25c9406b2f4990c74ccd2a25d64296e0d49020ef13c45c63d02ba269b846ce3a4e82b9509c453a5cfcad94c8d1c45880f36269638ec83a6493c9abff864177c67b6afd206f214b626106ae6b8af1f00edb6d913fe7af03933a9bc36204dd0bd83d8dc516bc13d0f3dc06e86c4197b7997b4cedc11e5ba376fb69c6ffd4d5763cb0a283e280add5fcaf2cd74a2bf7312a482efe56e8584f6a1693dcf8eb0cf0608e91c3fcbe1f45847790e57eee74537715dbf6d530802e2f819775c0845d24efaed4e8f7a1d810d5b1e7a13082323c5e65e3d68094522cc3e7a09b4ddec448c7136767c556f99706c8c30b77b15786986b2b0c70c12f9e0d0fcf52d8d60ee8183eafc441fea5f4902530332abc8cfb1ccc2bf088b4500d3d6a7fe11f1659c507082c930b1152c218b97d777d5635a5cf90d07aec0b8d8bb04f75ebc051641881c3e872afe92c0ed0cf34109975f4f0267ba0ba76f4db3713cf95ca18c7b341e2fe8ca647e8186769199c1ebce7b0daebeaf25b1072da53ec5cbcd20904ac3bd9d6e09f05a6746a6b4e0bd35ada69c341fa3324e0fe20691e5dd826c282b73b44512d303c893fb2e22c852e93960bc777e8db71541108dc4acd1bdb6f37d75cf7f86cd379bd08e3e7ede3d43bfce30f4921f422c6ad1a7054fc4fb8ccd075f703e829db9d36f7af079988157ef75debc08c652e340a6869f985805bdf8d3a6d1427527e89b902c659ed49359b1d48f5d7e33d84a76128c70e88802c37333976de3f35d3ca317e4c5d13e5b203794057d577e77762524375cc1fd05c893d6831bf561fc3bf167fcfefdb1a8f079324b89759388db39e616a6bd912e8ad37dd485589207f5b08d8bc3e8dea37b2ff9915517b6b2336bb71021de0553ac5ffa57424e585ed24f6d5a7a74379cd57129b4fa665d59b87830009f757eb389c87de4de889b175114c31dbe93252a58ff6508816b150c823fcdb470b1e3afa3c72d89c2b286d1a8a55a24ff70b80d4f3186d417c167a6fadc795bdec784e17bfc58345aa3f4b72b748acca5e855c93611615e3efc6f82d278fad1b7c3b1bbacccf7335df24986d58000dc01036a3e8c87387b02d19706b935530ea08be797e0bb7c96752b892ecb5c6f745952c3f0b66b975525f5d98d3f6746485317883c2093436e3def6214f5d0172f17f77b6339efbfa3a2ac0357c482e655141a50419dc038eebbb3d89f8932330f3138decf4e0adc55b360ef13c9754f46cf27f63aed5db0a8b62b025636d4462f5f69df3463ff9401e5ec3b82a852dcfe5d8f10ac928a37709cdcbf42d24964e396011849da2131346eb6351893bb29fac891a33cc5fc6d7abf5b5f8247b10ccb5925973cca20cd475cb7062ae2813cd0328192019256c2d2498f954b6f4ab5afa42ea751b2670c85c4de46234fc8ac19a7a3f17bde9218bfde8fd2d5c2e128d6c1316f361fe5f4b5a3f7b1afe4c0b7145972e79b2967951048bd6b63f0acaed Hey, password is required here."},{"title":"【表达技巧】跟罗永浩学演讲","date":"2023-07-09T16:52:35.000Z","path":"posts/talk/","text":"罗永浩15堂演讲私教课学习总结 导读 B站视频:【演讲】罗永浩15堂演讲私教课 知乎:【学习笔记】罗永浩演讲私教课 前言 开这篇是因为近期,有一个在公司内部分享的计划。 因为不是技术类型的讲座,技巧因素占比就会很低(而技术分享更注重将东西解释清楚)。所以萌生学习这类演讲 + 表达技巧的念头。 认识罗永浩,最初是 6个亿 的负债梗,更深入的是在 脱口秀大会 作为嘉宾时期的发言,对其有几个认识: 听感:表达清晰连贯,但嗓音条件恶劣(据本人调侃是老太监音色) 逻辑:知识涉猎广泛,且临场反应迅速(后者是幽默感 + 经验累积) 内核:善于制造一句核心话题,并反复洗脑callback(如脱口秀的“大局观”,如演讲课的“因为大脑就是被这样设计的”…) 关于这个教程 基于罗老师 “演讲都是有套路和技巧” 的观点,将其演讲课的套路总结如下三点: 为什么要这样?(赢得认同) 如何达到这样?(方法论) 举例、类比论证(深入人心) 十三个要素 这些技巧比较多,难以短时间记忆和掌握,因此先记录一下核心观念,剩下的在实践中掌握和理解。 精华摘要 开始一场演讲的最好方式,就是讲笑话或讲故事。(千万不能讲道理) 对于晦涩或陌生的事物,要巧用类比,如 谈判就是找交集。 开始主体内容前,用三段式介绍提纲。(思维导图) 讲故事的三要素:冲突(吸引注意力) + 行动(故事的发展) + 结局(表达的内涵) 其他技巧 1. 用坐标系描述事物 举例,如何看待 “量化交易”? 首先,一个交易策略的评判标准有两个维度,分别是: Interpret:(金融底层的)解释能力 Predict:(金融市场的)预测能力 因此,引入一个二维坐标系,甚至可以类比不同事物在其分布,例如: 进化论:能解释为什么猿猴进化到人类,但无法预测人类未来进化的趋势 地心说:能预测太阳东升西落,但底层科学原理是错的 最后,根据 量化交易 预测能力强,但无法自圆其说的特点,可以将它放在类似进化论的位置。 该方法论可以让听众直观清晰地了解事物的多维度特征。 2. 结尾升华主题 几天过后,观众未必会记得你讲了什么,但他们或许能从情感上认同你。 这很大程度上,来源于结尾的几句升华。"},{"title":"【cpp】Memory","date":"2023-07-04T17:01:35.000Z","path":"posts/memory/","text":"C++的内存分配与管理 导读 理论偏:【CSAPP】Virtual Memory 本篇结合 C/C++ 了解内存分配相关领域知识 ptmalloc,tcmalloc,jemalloc ... malloc/free 阅读文档:cppreference: Dynamic memory management 使用的时候多查阅文档,注意 malloc 使用时要判断 NULL 避免内存分配失败 #include <unistd.h> void *malloc(size_t size); void *calloc( size_t num, size_t size ); void *realloc( void *ptr, size_t new_size ); 分配过程:↓ 需要考虑字节对齐,注意被释放后的内存也可能重复利用,这也解释了为什么野指针的 undefined behavior new/delete malloc / free 前面有介绍过。 以 A* a = new A为例,通过 godbolt 查看汇编代码,发现其有两段逻辑组成: 调用 new operator 调用 class's constructor call operator new(unsigned long) mov rbx, rax mov rdi, rbx call A::A() [complete object constructor] 相应的 delete 方法,也对应如下的两段逻辑: 调用 class's destructor 调用 delete operator 下面重点展开对 new / delete 两个操作符的学习(推荐阅读 C++ Operators 和 cppreference operator overloading) 先看 libc 的 源码实现,可以看到是对 malloc 的一层封装。 如果类自定义了 new /delete,则优先调用它们。 void * operator new(std::size_t size) _THROW_BAD_ALLOC { if (size == 0) size = 1; void* p; while ((p = ::malloc(size)) == nullptr) { // If malloc fails and there is a new_handler, // call it to try free up memory. std::new_handler nh = std::get_new_handler(); if (nh) nh(); else #ifndef _LIBCPP_HAS_NO_EXCEPTIONS throw std::bad_alloc(); #else break; #endif } return p; } void operator delete(void* ptr) noexcept { ::free(ptr); } System Call 程序中的内存分配有三个层次,如下图。 最终调用的还是Linux/Windows中的操作系统API:如sbrk, mmap… 因此需要重点掌握这些系统调用。 brk, sbrk change data segment size 参考阅读 cnblog: brk 和 sbrk 区别 linux man 手册中描述两者的作用是改变 data segment 的结束地址。 通俗地理解就是,brk函数会重新设置 heap 的高位地址,而 sbrk函数会根据大小来调整 heap 的容量。 两个函数的定义如下: #include <unistd.h> int brk(void *addr); void *sbrk(intptr_t increment); mmap map (or unmap) files or devices into memory #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length); ptmalloc"},{"title":"日志:2023年7月","date":"2023-06-30T16:23:22.000Z","path":"posts/2023-7/","text":"b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475de0b1305a637ea913974dff7410a14ed4dbbb40a5278eb14bbac34e59e12a284c7a294ab69a495d47e11e78ad50feb107058e3275f75d936a554eb95bedbf96a7783a11b9f4096cfe1861725619e5a5317633279c3d957779453d57b5fa0ba64fc50c8a8867249b1e4e1b679e8a138ffe4e44ee9fd3637dc99e76ff334587dd2acf41dd9805cce4bf275cc1a585ab11cc84c7949a8c4e30c48778c74ce922226777c41d0403e225f7f266e27ebdc913e4f3b0bc811d619052f5d10bd439c7abdb7fa6a5c74b5a8b4c939d638e648ea06be90c69907d299438ee2c58f9f5cd7175c2f88ff07ca6f3b74e3c1e7e424dc71a31cd92fca1339935722708f2b8548366dfbf220add8202ae63bd0f61d1e2b8c30858b8e5cb1961461f31aa3f597e5e1adb652a72f747502c39390ff9e737034a058c8b11d06863b6a5b5f18a769141cd8150b3ea85bb1fcc5cea71202f61cca8762fc37fd67889befbc47916f913e2eaf895c32be1a951878ab4084c3d1edb2064b915ad9e3bf068fd9971f373c39af676c5edce063465935625bca670a695cfa507b62838d46af4a3a09099647d87c4095d6987040ca113d938096f7e41421c4c42540e606f60e3c50871af5464d332b97615d22822f5b4de3b53ce8d075e087c3877da3a120a16f7843f00a382283056c5d46ae2fc0e6a171f53b4d0a505614245e4bd03f8ce78719740161abd0ec71e84075ae40c4eef290dac819a9f645843f06cc21796aab1595932bc05ac984401060207100fdac93fff9badd829fc74e4aa0ff13ad6de5f506abb0b92ca5094bc5701d737ab28659315e27d070c76c7706c6f59bade15915c6e8af214bf5d33e5079f8731299b0517e571656bba9a7f6dac72cbcb797357071cb2b1f3a2e8bc99f014ac31b7061759a41b78bedc543d61b02123bdb85dafed55536872f1a0dc38ee17408b28d3068b3380b45b8c9b7731b685ce690733fa7bb0b046762bc834cb6d3efce14c8bfb6afa611310c3113d3b013e4093659a2a48aa3089a1318ac8fcd09fff675f13a5233ab6384cf7925e71f21e353542b173c100abbcec5bf92fc11bb09f325d4ec782f0696e264e9cb221f2642675a442a6816d0aeb80f8e8d1aadd9fb58a0e922db618dcdaf16363fdf09978b0534ae4e82f629afd76021debd8d45523748fe3186c4d48eff1b6595de700c0a8c40dc8a16f1bb53369ee838147f0d5a4befc78d9967033a18982453c17291a958380f29e1e3b465b61fbe779710a948056c77454035a6a5668ed000848adbf7b32ed3ef3b468684f935e395e234f0ae038dd5e307672c0307feca67877cf94fa61a02dbed70dc0af8062bc7c2f2f28badb1efbfbab9d894e0faada08742527d8eec99a24809289b8d172f2671044a3f8e7af52d54d1a047608eed3812c87afe68392f8fed5d632ed3067b5f2044004674ff317dff9712ba114861b3f4dfa85bfe5c5945dca7edbfafffa8b31a713bfd00ceb01bcfebc88b71a24cde8aa420dd9e24782fcd074323667037681f430b19c6d33960a6e64132617487789c83e6d8e4e6368ef7ccc33cfd4d2e793c1cdd85220f79849282aa3b0647b633c35cc41ce2a95ad6e88b2514bc406caefc067c2c6b813640760aebbd31967d290c7465460a5653606ce7f031560448d18fa8a801cbd322a041491269b000a4c300ef2d5b2ea6737a19048f88fe9c3fe396b494606614240e4d40299d791fd209208b152306cdf5dd3a1b2d740b6c82a95663ab48899b29398a0471b01a6f21a4178f5ea3282f78a4c53cb4e2c55ea6305b8c31ba87014f627ab418a8268d05ab608d25a614c4e16302be97ca3ea984d8ce30fdaa5c0c1be976ed17e200e18572fd2aa32af070b9c0e92bf578edbd0999f078f2d5890d6dff137366e0b770cbdb3bf337f9dffd1d9df9e927552ac556c096fede1f78c0f3437be08b9b4c415b37372f8eeec4522a45b777fc42fb3de3022f276ae2a14c0be67a7b50aa7b88dcf1b9a2969ab6facd3171091137f5595ab72ea3f6794b6cd451b9fbc8ff63f5ae08050318651b4b7396e24357069e5b5b98d5e2420a1608d2bec7fc8a6291541b2e582ffccd9fe8f46829eec756351542037533ee005b49cf6ad720508c8ba52241df29490f736d43e4499dc19456630ab1b3f0523e5d3086e33f6894a67849ddf5df832343e2f25a51258ecd0976ab807c5c725c497551eaf7e18ea9b8ae42d58fb6036bb473f25cef48bac20cafb2680ce68f539de2aa25b514182a920363c7fe185fb2ae5bdd073fed91413cebcd6fab0042006aa984f132442a3eabb8b916221dc04e28b6fbc769f47af0ce4b48d8c26a0922ebc74decb4704d5081f3de44469ea1cee6479ec013bc7d3d01acfd074a1dd14714cc76ed0e0d40c0ae2e9272bc569d16d873945ea9b970472d1f14c06176b346393d85f2e250821022e6e0afc37b7de9ecc600defee81e84e6488a01327726484df413200d2452ceb4bf5cb646e4aa7414421005864f56e8f2ba147388b8b1baae2181b46c35d274835952f653c063266fff48f5b2544b4e796dcca215c4751d7cd32aea23ab9dfa3a734b207f7d242c9b233dc789acbdbcddd87d81a3c6e19ab6e80903b6ee969c979e474d4d9d6eefbdff0aca5705d5b6cc46ae392b43baf10b1c58db7c5f6a41c4f6f160e6c418f015f5ce902a777b9fb3a68c933ad43b785a713d592f90533d9e35cd46a52457821e478e81299fb886670c073434ab388a983c821f435c6a5e4fbe5467a51b4b64ffda543c596efd8ac17da0e35858ab328ca385342e6f48fea6e59cec55b96ee5a434e15db34246d5dd739ee47cf7327444b4a8ed9a34ad63ac5b67ac91fe50034d2c8040e13d0bc09a982d1dea672fb15741e8b4c32850ee74fed103e72ff6a2c209e144d6f40fc5000750037d9c8a26027943f81d6d18d51a62dbe6d9f5fa509026d84e1a04759e7bc5fa6a56810ebc0efac3e0b664050b40cb81d45e314c7f6359ae273dea19615be0231e71eb9b371b03e1927294f235df8258efde83e268cdf6287013c856c50dd270644dc0fe66bca434d88599ba2979ac98a49a1cbb1597fe888ce001365bbd36205714f8fe02c520a28d79e438443f801d2fbd75181939935fa514ab474be8be724be174ea15808bd311f77770c06c9e47aa5c333f1221c534263a48e9edec4f96655ccbfd2bfa0144eb9acc66c0c7ed770c6e994e39ea71ef0eeefc85d5446b537c4d444cc97ce842fa576526a5e72fcaae3030184fef7d61c964f58813633eb0d767591785324ae5555d29b2f41f94b7eec44ccb3395ac00e102be44dd5087956e4c75fc8520dc0b400f17c2254c126098aa7e4de23a4d1de8bee988069f3a55a21a51f1095105bd73fa67a7e61c9d973f8913b18f518b3a7a58b868f2085105d10c2c0bb7bc953fbe73248a20a3a13c1e60bab87f8a77e881d91e5c7610fddfdba3a12225e5abd6e314320936c28990e5a5e2b41de8a5fd0a62d7ceb97c4720753980c90d2239edf31e5a5b2d1ffa3670ef5ee81f55183e3507deb6d7fd3e725c1cb537f1f9af7efe8dc88a5f88699887a64c1c3b48c64f4af05f8b9a6438b1e138b6c5f16393a8ef1fe294a48cb441fa97363a4b7c9813a77b81b37ee8a8b15011b00846bffcab0d63ea748d98560edcd6f0cfafdae5954685387b758a8be894be25f8ff7ee14b97325172efd3540b6330c1614cc9762544042525b2bcf5746efe9d77e7d1b44779a5e70b50c56451d49b32f8ebe8d8988d6e717b5b66a73c99477098e1ebd46733d8bd4e601533e9ae10c12d2816bc6570a0b1b4cc5c8fadaa4bdccbc2a3c7ee08da116a07d5da8386e6b308126725e3bb4932e8e1c580f683f2b287788e6019f6a3bc988cffbcdcac0710c073069fa833dccfcc8a0f99d28d7fa69e19fc2c66db3611ea6f6e26091e414017a87aca34b52fc55c0e82201c58f3c4ddd6eb20d4b937df0011c764c38180c77f7906345ffb1bc2481a9a71e95478efb068c0f729103295395fe4e520409d1e34bcc11047f4232109c4bf58e6cc8468729f933d61c4838c84f2e51d1874d888d25104fb8907dc662506badf3468df6815e29b8cf87471827046b12ff4b32d1e2f9554398d6fec64a80bf07c646ea392a01a9100ed91fd36ac1aff05f9411b0ffd7fbc405edf970f897e9ca2d50ec34c635997e90d19fb5d157720f64b53bc942d84c9ff7ecbf4cd362b36711bb239812135bb53fd734f4f49752579105e281f3c9bb3e179e5dc07878bbbdc0947cba7c2d7b3082ea3fc49a9d83006827b2850485ca538215902ce96c835c7331f6e7241df62e5cadd5b213d7214951a8583833cc814db2ad9d53a89852465de9e185310955c8b0ca70440022dbaaf57f94b311fd4ea6f804c0f3e09131aecca1ec0632e0d7f51afa28878254cd26fa53119347fcff91f21abee456623734fb7b5374aafd46a76cba87041ca4cd9a70117ad157a6f0d80b06f8abb937c1f4c407994c487be113a0b76516989dfea1c8960227c5ed2037001c4ae4a1255a37916b07c73f84ecb0774f9b1664c3097043d936b8f86e4e1169a69ed9642f3f42f0bd978f870b7a8b8a77363c935ddba62db8122aa89ca7a31c2e64116ac055835f260c9909ccea7a8e4b2b9b21b3bfb393ad84a1008b10eae75daa7082b6b1ca529317d83dec85e6d26e01dbac488ca60af61be855014be1a31602a3be743a619f27dec9be89d5d012273f40d8f5f24aa14eb0a052e06674e20a33cebdbc9f6d3fabef0a1457cfcc1934ea78acdc93b31b0ceeec14a8b82a778a2ff01373d5d798d8686634cad90995475f6b9516b652fd3d7ebf89461a18f239747010baa7ea9162fad6feb1a5a8ca8ab00b33d8f511948ec004b4f1d8a88119069968118906c9cb16947e6466cf02cbb67b3e5c1078a7a3013a2d4dbcce04f4f83184a56c53df2a9f6b13b18392cb35ab054251518756c7707ba7d122ab0e872c9658db3167434a413a3d1a0154e53b660a270ffd9c3680d854ecf41ed637366e188630a4a33d1f7f0ce528cde65b51e42375024e2892f5613c5c1648a3425e713a7fec772355deef1515d4b839152a69d2a04477456c8e32de38a7b365cd7cac98240f47b29605838a62911f3b0afe0219dfd85a1d4eb0919ee0e944e48edd913ede081c769ade7e003785916b6aa8392019300e9de3019f8360574c0b59373e8cc97ce8f8a9d4bcbfecfae97890e955a1d395f8866b69aa1fc4d762075aab9cc7f02de0cb39e26a3ce8de63bd86ac238a94b96ea625cb2abdad9adf1a1998b410690ac48f92462ee997d1dfd30717f07ef368d0ff7d0e76297a410477a502c56bb8d5dbc464cab8165a8b1f28c8403b89205db9611ab4632b8d36bf726522de64c074831ce24a46523cb9f425149f6adf19a9a8b90e1ae59726e19a9fccfa885c957f2d768d663e6bd6cb2a81a829fcb59c6b50dc1b3fb3854f4d4f5382e5ab7768f21e3cf9bf4d67aeece25e00a07c2f095508f75defdf8f02855916f1af2d10cb5c624e27349696a9dbee336380ec1def04174218493332fefa47bbdbefde0f84768c260620c209e6632d305e07fd8beacf69e737f100a242e92cb1dc362a41f60b814a006b5910da961f816b60c658d82c45b235bb6deb24335971fa7b05d5821cb3246568034f54241765f9207a7ae30ebdb9171b8b0aa750095552a0fdb34288a2a45e19b6722b8bd1b0d51b73a50e6a333ff67e66a36880eb2148296c032454eb1b04c023caacbaf215dc049c8ac96334e163d8487e8ad576dd2c19d9d067fd78181407e907e34e625b4a6911f437a3a2f714f4b12807c1e2ae876c45cdfb25bc3df7a89a0ad179906644c253f6ce3afcb0bdf8d60e9dfc40fdfe0604d71eec4c21aa9cfe769a57b2fa453a4c1dc0025d2006d4fa5c51694e5c68e601d0fe1bec94b566e70d33d24d9f2079ebaead15915d931c9f925b14a4b7cf96d2c15bb373f1469b8c82fc0d8244cf841ad262a8c2a83ede69738f136edc452bda82e4f20b64ff8b9015ed73c32193ff9041860e8543d5af18833b4672860d23eed8b25cad8ecceaba4237b7e5e01be2084caa9de9092c001a15463bb6354cc7c3d21f2849b24e4215b4c3f23f2c94d36568840605d6b0dd1dbf6c3aca47abd3d640290628f189192be94d5c1dd0bfaf445781c85da0509eb4b1937e4a1d0dc2a18f7c417bc2344d3e1a1d38d1fec462bb3f8eecd1cc6068b1be349f39d42f0c5938f823cd37651df324ac31ff604e021abddfa7a57ad445d01ad434f97b776d8d4ef460714bd2aff528a73795d3ce9fecda6d94c74b857544b84cc03a26eed59b1f2f0188a6d9b6e04c6a1b8683ab75acbade2facea552f14e8845d7395bf81c58bc324795527280fdb99796aa41d6bd92a82eee812f4a1aa8aecbc90c3a298e2749d1c7ed7ea68cbc54ede310093acaf30fa37a0add2983a94fe0e70539588240fba297ad1f2f2ef30fa1b3595aff6e586fe5d527d8d4036c8489b4741e90b94f0a41ea5c4209d120df43a98488a67ab9d3eed4206fca4878b206b43ec193f085ffdb3a0fb5d0fd04d44bc524260d99afd0be26569946aa76a1287d8e911a78d1f1c18cb2b81b7f264253b09a355f94e7e851a68e7c1cb1ada23137b309b295b6c51d99b638a5b5a6c988cbae0844b93594e1b5761adbef0234bbf4b094f6f802bbe217029ee65a6e0ae4119a955fe4174c4f34ffe723c6f31942fbbb0320d7bdf204ed34869c382219705d35b9019b0ce4c8b3bae72c56303a314cf497334f6eb6eebc449e7e95a50eab56439a626c81f106b2032a5a4e76fd1c455bda921712b4819a8827c4517a29106a83d1ec535b94097fbc80500d13aa873bb84bdde0d3e78ca01e88b7c43da42953675a3152434caf7b4fa7c10e20061ec3353809c45325076ca42fdd7885bcbf8a93aaf1f4a4042f7ae5d4304e4b690c237c89f86b38b1123661ff89df59c9cf143dc314a6137e2448293c8a5f388e0386476ed28a76048b8911101d17ff2d7038eb12b8214db73f2849516189039c26d2a928c0769b3ea0e6564e8acbd1318aa3d33534e6f6e2e22cf14bb4e00ef121f3ec95150ad0979fd120cf9cafd1435c8f5698f4e44eb5b0ccf823dd890e70eda7393bf45daa52f12ab7bc927f031f79a570889b6b3bc3c5985a3b132e70824f4df0eedf749176ea169f0541441315f7a749f88dd2c6ec87ebe728550d2905f909bd440cf032da45f6deda7a22c839b015a732cb21eba03b074316d278cff03bfae6e083d15a77632066fd65261600e443ea8815565f07161ff2380ab82c01fc9aa5a84b8015aeacf4e3e1d69314de932b7088ace8f579ef4ba389975d05677b5417f6d0394dbc5e4ea351dcff2e79e798faae0062b2400ac36a42f9f38153053a4e14154a741a05a0010fa46f9416ddea29133f014ac415c947c8a8bb602aec9b4d2a18e25a5a77271d34f5f9bce14316374101a57c25cf85a01e7ce4dfe1e4fe3193700595473c8e5a3da9c67faba5e9c9529c20560797f7142c529af7d329ed3f9ecae21816aa8a3073d371d9c315e789d26795f5c14805b28c5ececa85e044ed79955feb0a48f1f25a645e1636 Hey, password is required here."},{"title":"Google评分卡💯及自评","date":"2023-06-27T16:40:51.000Z","path":"posts/grade/","text":"导读 Google将技术能力划分为 0~11 的等级 自我评估技术,判断下一步需要作出的努力 ↓ Self-Evaluation ↓ 基础要求 grade 熟悉数据结构与算法 2 熟练使用 C++11 3 熟悉并掌握 C++高级特性 (14/17/20) 1 熟练使用 Python 等脚本语言 4 熟悉batch、Shell、Linux常见指令 1 熟悉MySQL等数据库的设计、优化 0 熟悉编译原理、编译优化 1 熟悉 vscode、sublime、vim 等IDE、Editor 5 熟悉 Jenkins、TeamCity 等 CI&CD 平台 2 熟悉 ChatGPT、CodeMaker 等 AI工具 1 熟悉Linux内核,如进程管理、内存管理、文件系统等 1 熟悉网络协议和网络编程,熟悉websocket、HTTP、socket、TCP/IP等 1 ↓ C/C++领域 内存管理、内存分配、ASan原理、内存错误及排查 1 模板、SFINAE、type traits、metaprogramming 1 并发、memory order、同步、互斥、boost::asio 0 编译优化、SIMD、ISPC、CPU性能分析 0 C++编码规范 2 ↓ Python2/3领域 Todo ↓ 工具/pipline领域 Jenkins 日常工作使用,无阻碍 3 VSCode 日常开发使用、多个插件开发经验 5 Vim 熟练使用、vimrc配置 4 Git 基本的GUI、CMD操作 2 ~ 3 ↓ 加分项 具备内存优化经验、熟悉linux内存分配 1 熟悉GPU使用,或有底层基础库(CUDA,mkl、openblas等)优化经验 0 ~ 1 良好的系统设计能力,如 performance、reliability、availability 多维度考量程序 2 熟悉机器学习平台相关工具,比如k8s,kubeflow,mlflow,automl等 0 有视频解码和渲染开发经验者优先 2 有存储系统、分布式系统等底层开发经验 0 ## ↓ Google-Standards 等级 标准 0 You are unfamiliar with the subject area 一窍不通 1 You can read/understand the most fundamental aspects of the subject area 理解基本概念 2 Ability to implement small changes,understand basic principles and able to figure out additional details with minimal help 能够实现一些小改动,在别人帮助下钻研更多细节 3 Basic proficiency in a subject area without relying on help 基本掌握和熟练使用 4 You are comfortable with the subject area and all routine work on it 足够精通,足够应对所有日常工作 5 An even lower degree of reliance on reference materials. Deeper skills in a field or specific technology in the subject area. 深耕某个细分领域 6 Ability to develop large programs and systems from scratch 独立开发大型系统 7~10 脚踏实地慢慢来吧…"},{"title":"【网络】HTTP协议进阶","date":"2023-06-26T12:51:27.000Z","path":"posts/http-2/","text":"导读 专栏:透视HTTP协议 墙裂推荐 ⭐ HTTP协议入门 HTTP数据编码 todo MIME-type 使用svn更新的时候有一栏会标注 Mime type,可以观察到除了常见代码文件外,都是以 application/octet-stream 格式传输,它代表未知的二进制数据。 HTTP大文件 todo HTTP连接 前面说过,HTTP协议 是运行在 TCP/IP协议 之上,因此每一次新的HTTP连接,都需要经过TCP协议的 “3次握手 & 4次挥手”,这无形中降低了HTTP协议连接的代价。 因为 TCP位于传输层,HTTP位于应用层,所以可以用如下的类比来理解连接的代价: 开关机:TCP连接 使用电脑办公:HTTP连接 每次使用电脑办公,都需要打开电脑,在使用完毕后又需要关闭电脑。这就好比 HTTP 短连接。而更常规地做法是,保持电脑的始终开启,这样利于随时使用,这就好比 HTTP 长连接。 Connection字段 当HTTP请求采取长连接时,在响应报文的 “Connection” 字段会标记为 keep-alive,此时服务器不会在短时间内断开连接,但是为了降低服务器的无效占用,Web-Server 往往会在一段时间内若没有任何数据收发,便会主动断开连接,断开后会收到 “Connection: close” 的字段。 队首阻塞 因为 HTTP协议 采取 “一问一答” 的模式,即典型的 FIFO 结构,当队首的请求因为处理太慢而耽误时间,那么队列后面的所有请求也会相应地被阻塞,这就是 Head-of-line blocking。 类比理解为:食堂排队打饭,每次刷卡是一次 Request,每次领到饭是一次 Response,每处理完一次成对的 Request-Response,队伍才能往前推进一步。只要前面打饭的慢了,后面所有人都会受影响。 解决方案是:并发连接,即增加打饭的窗口。这里不详细介绍。 Cookie 前面说过 HTTP连接 是无状态的,即没有任何记忆。即使某个请求会让服务器出现500的错误,下次请求时服务器依然会 “热情招待”。这迫切得需要一种缓存的机制,Cookie应运而生。 Cookie 是服务器委托浏览器存储的一些数据,让服务器有了“记忆能力” Cookie原理 Response报文中,利用 Set-Cookie字段发送多个 “key=value” 形式的 cookie值,这些会由浏览器负责记录下来。当浏览器下次访问同样的地址时,Request报文会自动利用 Cookie字段将本地缓存的 cookie 发送给服务器,这样服务器就知道自己的身份了。 因为 Cookie 是与浏览器绑定的,如果你换个浏览器或者换台电脑,就会丢失之前的 Cookie记录,此时服务器也会重走一遍新的 Set-Cookie 流程。 Cookie生命周期 Cookie拥有自己的生命周期,它通过 Expires 或 Max-Age 两个字段实现。当超过标记的有效期后,浏览器会自动在本地删除记录,不会再通过HTTP请求发送给服务器。 Expires:记录“过期时间”,如 Fri, 07-Jun-23 20:00:00 GMT Max-Age:记录“保质期”,单秒是秒。将浏览器收到相应的时间加上 Max-Age,即得到 Expires Cookie作用域 浏览器会存储大量的Cookies,因此需要标记其作用域,即发送给哪个服务器或者URL,常用字段是:(不清楚的推荐阅读 HTTP协议之URL) Domain:域名 Path:路径 Cookie应用:身份识别⭐ 登录taobao等电商网站时,浏览器会自动保存你的登录账户(或密码),就便是利用cookies实现的。它同时还会记录你的浏览记录和购物车。 大概格式为:name=xxxxx.... Cookie应用:广告追踪 当你浏览各种网站时,它们会根据你的访问喜好作行为分析,然后定向推荐一些图片广告给你,这就是利用cookies的原理。 这部分成为 “第三方Cookie”,属于搜集用户隐私的行为,浏览器经常会弹出确认框以请求权限。 HTTP代理 传统的HTTP请求是 Client-Server,现在常常有“第三者插足”,即在中间会引入一个 代理服务器(Proxy Server),它的角色是双面的: 面对上游:充当客户端,发送请求 面对下游:充当服务端,响应请求 类比:消费者(浏览器)—— 便利店(代理)—— 源服务器(批发市场) 定理:计算机领域的任何问题,都可以通过引入一个中间层来解决 代理字段 代理服务器 通过字段 Via 标明代理的身份,在HTTP请求的链路中,每当报文经过一个代理节点,代理服务器就会将自身的信息追加到 Via字段的末尾。 另通过如下字段标明其他信息: X-Forwarded-For:追加代理的域名 X-Real-IP:客户端真实IP 代理协议 针对代理的HTTP请求,HAProxy 公司推出了专门的代理请求协议,The Proxy Protocol。 其基本格式为:开头必须是“PROXY”五个大写字母,然后是“TCP4”或者“TCP6”,表示客户端的 IP 地址类型,再后面是请求方地址、应答方地址、请求方端口号、应答方端口号,最后用一个回车换行(\\r\\n)结束。 PROXY TCP4 1.15.115.4 110.42.228.178 32200 80\\r\\n 负载均衡 当一个区域所有的消费者,都蜂拥而至一个批发市场购物,就会造成堵塞排场对的现象。 因此 “负载均衡” 的解决方案是,在每个居民集中地地区设置一个小商超,或者经销商,而自己只负责货物的批发和调配。消费者 择近择闲 选择小商超去购物即可。 通过中间的代理服务器,将请求均匀合理地分散到多台源服务器,能够有效提高系统的响应速度和利用率,这就是 负载均衡 的基本原理。 而如何挑选转发的服务器,有如下的思路: 哈希:如尾数单号的去A服,尾数双号的去B服 轮询:对于新请求,分配一个最空闲的Server去处理 Cache 两句话讲清楚Cache 浏览器Cache:消费者家里囤(上次买的)货 服务器Cache:小商超囤(上次卖的)货 Cache:浏览器 当浏览器频繁每秒请求同样的数据时,如果服务器不厌其烦的依次发送,会造成很大的性能和流量浪费。因此需要客户端(即浏览器)的缓存。 一个带Cache的HTTP请求流程是: 浏览器检查cache,若有则直接读取,若无则发送新的HTTP请求; 服务器响应请求,并返回资源,同时标记资源的有限期; 浏览器接受请求,并缓存资源; 而标记资源的有效期字段是 max-age,即cache的生存时间(秒),过期则被浏览器自动销毁。 其他常见字段有: no_store:不允许缓存,如一些高频的秒杀字段 no_cache:使用缓存前,检查是否有最新版本 muster-revalidate:不过期则直接使用缓存 Cache:服务器 Todo,与HTTP协议关系不大,可以了解 Redis、Varnish 等缓存技术。 Chrome调试 Chrome浏览器提供了丰富而强大的调试功能,按下 F12 或者右键点击“检查” 以进入调试页面。"},{"title":"Computer Networking","date":"2023-06-22T12:18:00.000Z","path":"posts/network/","text":"计算机网络大纲,HTTP/TCP/UDP…"},{"title":"【网络】HTTP协议入门","date":"2023-06-21T16:15:09.000Z","path":"posts/http-1/","text":"导读 专栏:透视HTTP协议 墙裂推荐 ⭐ HTTP协议 HTTP 协议是 HyperText Transfer Protocol 的缩写,它是一种用于超媒体信息传输的应用层协议,是互联网通信的基础。 如何通俗地理解 HTTP协议 呢? 如何理解新概念? 任何技术思想,在现实生活中都能找到映射 巧用 “类比” 的手段 Hypertext 在互联网早期,传输的信息只有简单的字符文字,即 Text。随着信息爆炸式地增长,网络需要传输的信息被扩展为 图片、音频、视频等等,这些就是超文本。 超文本的核心是 超链接 Hyperlink,通过互相引用而形成复杂的网状信息结构。 Transfer HTTP 是一种 Request-Response协议,即将信息在 A、B 两个点之间双向传输,因此有两类参与方: client http请求的发起方,通常指浏览器(如Chrome、Edge) server http请求的接收方,又称为 Web Server,如 Nginx、Apache 都是常见的服务器框架 CDN client和server之间通常不会直连,而是经过 CDN(Content Delivery Network) 这个中间商。它能够实现安全防护、负载均衡等常见功能。 Protocol HTTP 是众多网络协议中的一个,先理解什么是协议? 以毕业生签署的“三方协议”为例,参与对象是多元的(自己、学校、企业),同时协议还会规定各方需履行的义务,以及该做什么和不该做什么。因此协议的核心要素是: 多方参与 client、server、中转者… 通信规范 如规定 Get、Post 的请求报文格式,如请求的错误处理等等 在 HTTP 的发展历程中,也经历过很多大版本的迭代,如 HTTP/0.9、HTTP/1.0、HTTP/2、HTTP/3等等。 TCP/IP TCP/IP协议 是整个互联网的标准通信协议,它实际上是一系列协议的统称,其中最核心的是 TCP 和 IP,这里篇幅有限只作简单介绍。 IP IP协议(Internet Protocol)主要解决寻址和路由问题,它提出 IP地址 的概念来定位互联网上的每一台计算机。因此可以将其类比为电话号码,而整个运营商的拨号系统就是一个 IP协议。 IP协议又分为 IPv4 和 IPv6,大部分仍在使用前者,即IP地址是4个用冒号分割的数字,例如 1.15.115.4。之所以出现 IPv6,是因为v4分配的地址已经捉襟见肘,不够整个互联网的使用。 TCP TCP协议(Transmission Control Protocol)是一种 传输控制协议,它基于 IP协议 之上,提供可靠的、字节流形式的通信,也是HTTP协议得以实现的基础。 之所以了解TCP/IP协议,是因为HTTP运行在前者上,因此又称为 HTTP over TCP/IP。 DNS 在TCP/IP协议中,通常使用纯数字的 IP地址 来标记计算机,这非常不利于用于的使用和记忆。 因此 DNS(Domain Name System)域名系统 应运而生,它使用具有实际含义的名称来替代IP地址(即英文+数字的组合,也可以是汉字),例如:95.211.80.227是IP地址,nginx.org是其域名,两者指向的是同一个web服务器。 域名解析 以 www.apple.com 这个域名为例,当用户尝试访问该域名时,会经过至少3次的DNS域名解析服务器: Root DNS Server:管理 com, cn, net等顶级域名的IP地址。 Top-level DNS Server:管理 apple.com 的IP地址。 Authoritative DNS Server:管理 www.apple.com的IP地址。 DNS缓存 目前全世界有 13组 Root-DNS-Server 提供顶级域名的解析服务,但因为全世界的网民都在请求,会造成系统的拥挤并降低访问速度,优化手段之一就是利用 DNS缓存。 当你已经访问过 www.apple.com,操作系统就会在某个文件(如下↓)缓存解析后的IP地址,这样下次访问时,就不再需要经过 DNF解析服务器了。 linux: /etc/hosts windows: C:\\WINDOWS\\system32\\drivers\\etc\\hosts URL 通过 TCP/IP协议 加上 DNS 的组合,我们实现了访问互联网中任意一台机器,但是每台机器上的资源仍然有很多,如何对他们加以区分呢? URL(Uniform Resource Locator)含义是 统一资源定位符,即俗称的网址,如下是其组成格式: scheme:指定http、https、ftp等协议 file:本地文件 ftp:文件传输 ssh:加密登录 ://:约定俗成的分隔符 user:passwd@:明文身份信息,已被弃用 host:port:省略端口会用默认值,如 HTTP 80,HTTPS 443 path:资源的在主机的路径,也可能是服务路径 ?query:查询参数 #fragment:仅浏览器使用,用于锚点跳转 HTTP抓包 “纸上得来终觉浅”,我们通过在windows下快速搭建一个最小化的HTTP模拟环境,借助抓包工具 wireshark 来对 HTTP 的细节一探究竟。 搭建http本地实验环境 知乎: Wireshark抓包指南 在启动本地web服务器后,尝试在浏览器输入 127.0.0.1 访问该server,然后按下回车,wireshark抓包(限制http/tcp port 80)抓包的结果如下: 前三个包:TCP协议的 “三次握手” 中间四个包:HTTP协议发送了一个 GET /HTTP/1.1 的请求报文,Server回复OK 后四个包:HTTP协议发送了请求 /favicon.io 的请求报文,Server回复404 通过http抓包,基本清楚了http协议的工作流程,核心是 “Requst-Response”,即 “一问一答”的模式。 HTTP报文 通过wireshark也可以清楚看到http请求发送的报文内容,其全部由 ASCII文本 组成,非常容易肉眼阅读。 HTTP协议的 requet报文 和 response报文 的组成机构基本相同,由三个部分组成,每个部分由一个空行 “CRLF(0x0D0A)” 隔开: 请求报文 回复报文 报文示例 HTTP状态码 既然 HTTP协议 采用 “一问一答” 的模式,那么响应报文内应该包含请求的结果信息,即状态码(Status Code)。 目前 RFC标准 规定状态码是三位数,即取值范围是 000 到 999,其从设计之初也有具体的含义区分: 1xx:提示信息 2xx:成功 200 OK 3xx:重定向 4xx:客户端请求报文错误 400 Bad Request 403 Forbidden 404 Not Found 408 Request Timeout 5xx:服务端内部错误 500 Internal Server Error 502 Bad Gateway HTTP特点 作为本篇的收尾总结,HTTP协议可以概括为三大特点: 可靠传输 注意,“可靠”只是确保 Request方 的必定传输,由于一些网络原因未必能确保 Response方 的成功接收。 明文传输 HTTP报文的所有信息都回暴露在 “光天化日之下”,HTTPS协议实现了加密版本的HTTP传输。 无状态 区分于 TCP协议 的状态,HTTPS协议 是没有状态的,即 “没有记忆能力”。"},{"title":"【GAMES101】Shading","date":"2023-06-13T15:42:19.000Z","path":"posts/shading/","text":"Lambertian、Blinn-phong、shading-tech 导读 GAMES101 Shading CS130 Shading WebGL demo Shading Wikipedia: Shading 维基百科对于 shading 的定义非常清楚,即对3维场景中模型 着色 的过程,且着色的效果取决于如下几个因素: 1.光源 推荐阅读:LearnOpenGL,CS130-Lecture12 Ambient lighting 环境光照,即使在最黑暗的情况下,存在一些微弱的光亮使得物体呈现出一些颜色。 Directional lighting 平行光,也称作天光,在固定的光源方向上无任何衰减。 Point lighing 点光,向四周照射,随距离衰减。 Spotlighting 聚光灯,呈一个锥形范围照射,随距离衰减。 2.相对位置 距离关系 光照强度一般随 距离的平方 衰减,$ ~ I / r^{2}$ 角度关系 根据 Lambert’s cosine law,$\\vec {n} * \\vec {l}$ (结合后面的 Lambertian Reflection 理解) 3.材质 Roughness 粗糙度越高,镜面反射能力越强,典型代表如镜子。 Metallic 金属度越高,漫反射越弱。 Translucent 具有透光属性,如典型的 SSS。 Local Shading 现实中的光照,需要考虑各种直接光、间接光的反射、吸收效果,想要在图形引擎中模拟它们非常不现实。因此一些大佬提出局部的着色算法,以模拟 一个独立点的shading计算,这里统称为 local-shading。 我们需要了解 Lambertian Reflection 和 Blinn-Phong Reflection。 Lambertian Reflection ⭐ Lambertian模型 一般用来模拟 diffuse光,核心影响分别是:距离、角度。 距离基本的衰减关系,前面讲过了。角度根据 Lambert’s cosine law 计算得一个光照强度的系数,其中 $max(0, n \\cdot l)$ 表示任何大于90度的夹角,光照强度都为0。 Blinn-Phong Reflection ⭐ Blinn-Phong模型 在前者的基础上引入了 Ambient 环境光 和 Specular高光,它是一个叫Blinn的教授拓展了Phong的光照模型,其计算方式分别为: Specular $L_{s} = K_{s}(I/r^{2}) max(0, n \\cdot h)^{p}$ 引入一个 半程向量 $h (=\\frac{v+l}{|v+l|})$,实际含义为入射角和观察角的二分之一夹角。此时衡量 $h$ 和 $n$ 的接近程度,即点乘 $n \\cdot h$(Phong模型选取的是 $cos(\\alpha)$,$\\alpha$是夹角), $n, h$ 越接近 则点乘值越大,则高光越强烈。 其中的 p 衡量 高光随夹角的衰减速度,p值越大,则呈现高光的范围越小(如下图)。 p = 5 … 10:塑料 p = 100 … 200:金属 Diffuse 同 Lambertian模型 Ambient $L_{a} = k_{a}I_{a}$ 环境光假设任何点来自环境的 光照强度是一致的,即 $I_{a}$,它与实际光照方向无关。 笔者粗暴地理解为自发光、固有色。 Shading Tech 推荐阅读:Wikipedia,CS130-Lecture14 前面讨论光照模型的计算,但是忽略了一个重点:以什么对象(或粒度)进行Shading? 基于前面的知识得到,任何 mesh 都是由顶点和三角形(或Poly)组成,因此自然想到如下几种方案: Tri(Poly):基于三角面的 Flat Shading Vertex:基于顶点的 Gouraud Shading Pixel:基于像素的 Phong Shading 1. Flat Shading 对每个三角形(或Poly) 计算出一个法线,可以通过叉乘得到,并基于法线作光照计算。此时三角形内的Shading结果是一样的。可以理解为 Low-Poly 的渲染风格。 2. Gouraud Shading 对每个顶点作计算(假设顶点没有自己法线,可以根据周围几个三角面的法线求均值)。可以理解为 Vertex-Shader。 3. Phong Shading 对每个像素插值出单独的法线,基于此作光照计算。因为是全像素的,所以性能开销高。可以理解为 Pixel-Shader 三种方案对比 Flat Gouraud Phong 光照粒度 poly Vertex pixel 表现 劣 中 优 性能需求 低 中 高 需要顶点法线 × √ √ Normal Shading的着色计算非常依赖 法线,下面介绍两种常见的法线计算方式: 1. 顶点法线 模型自带,例如 obj 格式可以指定逐顶点的法线 计算顶点周围 Poly 的法线均值 2. 像素法线 通过两个顶点的法线插值,记得归一化!"},{"title":"【CSAPP】Virtual Memory","date":"2023-06-13T12:33:56.000Z","path":"posts/virtual-memory/","text":"虚拟内存、cache、内存分配、内存泄漏… 导读 CSAPP 第9章:Virtual Memory(已读完,通俗 & 受益匪浅) 汇编(一):计算机架构入门(站内文章,硬件基础知识) CSAPP重点解读:虚拟内存 What Every Programmer Should Know About Memory(114页pdf) cppreference: Dynamic memory management IC221 Lab 05: Memory Leaks virtual memory 物理内存有什么问题?1. 内存空间不够,2. 产生内存碎片,3. 没有内存保护。从这个角度看,虚拟内存是一个中间层,本质是到物理内存的一层映射关系。 并非所有系统都会使用虚拟内存,一些古老or简单的系统就会直接使用物理内存。 物理地址和虚拟地址的对比如下: Physical-Address:物理地址,从0开始每个byte递增1 物理地址可以直观反应内存大小,且是连续的,例如 0x0000 ~ 0xFFFF 表示 64KB 的内存。 Virtual-Address:虚拟地址,是一种到物理地址的映射(MMU) 使用虚拟地址,可以节省地址总线的位数,且利于cache实现。 cache 存储器 是计算机用来存储数据的器件,即通常所说的磁盘。其根据读写特性又可以分为两类: ROM(Read-Only-Memory) 只读,一般是显卡、网卡上的默认系统,如BIOS RAM(Random-Access-Memory) 可读可写,显卡上的RAM又成为显存 为了提高CPU读写数据的速度,现代计算机在 CPU和RAM之间又增加了 速度更快、内存更小、造价更高的 L1, L2, L3 Cache,这部分称为 SRAM(Static)。 作为对比,DRAM(Dynamic) 一般用作虚拟内存的cache。(???) 至于为什么 SRAM 速度要比 DRAM 快很多?这需要从硬件制造上理解,参考阅读:存储技术SRAM详解 page 计算机内存会被划分为固定大小的 页(page),划分页是为了实现缓存的功能。 对于虚拟内存而言,任何一个 虚拟页(virtual page, VP) 中的内存只能处于如下三种状态: unallocated:未分配内存(即不在物理内存中) cached:已缓存(但在物理内存中) uncached:未缓存(但在物理内存中) PTE 为了描述 虚拟页 和 DRAM 之间的缓存关系,操作系统引入 页表 PTE(Page Table Entry) 的概念。通常情况下,它有一个有效位和一个n位地址 组成: 有效位:对应的虚拟页是否 cached 地址位:若cached,则对应 DRAM 的物理地址 其他许可位:如可读、可写、可在内核运行等 配合下图理解,一段虚拟内存被分为 8个虚拟页,对应着左侧的 8个PTE,其中的虚拟页1、2、4、7已经在物理内存中被缓存,同时在PTE中存储了对应地址。 虚拟内存访问页的两种情形 已缓存,即 page hits 未缓存,即 page faults page hits ✔ 页命中,即虚拟页已经在DRAM中被缓存,读取时只需根据 PTE 找到对应的物理地址。 page faults ❌ 页缺失,即虚拟页 cache miss,这是它在 PTE 中的有效位是0,即代表其在 DRAM 中未缓存。 此时内核会选择一个已经被缓存的 “无辜儿” 去替换成该页,这个无辜儿又称为 牺牲页(victim page),同时进行如下两个操作: (物理内存中)用 查找页 替换 牺牲页 (页表PTE中)交换 查找页和牺牲页 的 cache有效位 注意这里的 替换策略 很重要,主要有如下两种(实际应用会更复杂): write-through:直写,发生改变时更新cache和内存 write-back:回写,发生改变时只更新cache DRAM 为了性能考虑,采取的是 write-back 方案。 locality 虚拟内存、分页之所以能达到非常好的cache优化,是因为程序的局部性原理 Locality of reference 时间局部性:一个刚刚被访问的内存地址,在未来有更大概率被再次访问 空间局部性:被访问的内存地址,其周围的内存更容易被访问 memory allocator 实际开发中,当需要申请一段内存时,其大小往往不是固定的(与runtime有关),此时有如下两种内存分配到方式: int arr[10000]; 静态定义偏 hard-code,浪费较多内存 int* arr; 需要时动态分配内存⭐,本章重点介绍 C++ 提供一种在 堆(heap) 上动态分配内存的方法,一个 runtime 程序的内存划分如下: C++ 的动态内存分配器有两种类型,区别在于如何释放内存: Explicit allocator 显示分配器,要求程序自己去释放掉申请的内存,C-Style的方法是 malloc/free,Cpp-Style的方法是 new/delete; 当程序没有正确或及时地释放内存,就会造成 Memory Leaks。 Implicit allocator 隐式分配器,它会自动检测未被释放的内存块,并主动去释放掉,它有一个通俗的名称叫 Garbage Collector(垃圾回收); 诸如 Java、Python 等拥有自己的垃圾回收(这部分不作讨论)。 mallco & free 阅读文档:cppreference: Dynamic memory management 使用的时候多查阅文档,注意 malloc 使用时要判断 NULL 避免内存分配失败 #include <unistd.h> void *malloc(size_t size); void *calloc( size_t num, size_t size ); void *realloc( void *ptr, size_t new_size ); 分配过程:↓ 需要考虑字节对齐,注意被释放后的内存也可能重复利用,这也解释了为什么野指针的 undefined behavior 衡量标准 动态内存分配器的实现有很多种(malloc是非常普遍的一种),一个优秀的分配器应该兼顾如下两种性质: 分配&释放的速率 即每秒可以进行多少次的内存分配 和 释放操作,又称为吞吐率。 内存利用率 即内存的实际利用率,受牵制与内存中的小碎片。 垃圾回收 垃圾回收(Garbage Collection)是现代高级语言的常见特性,它最早可追溯于 1960s Lisp语言,如今也已经发展为 Java、Python 等语言的重要部分。 What is Garbage? 正所谓知己知彼方能百战百胜,欲消灭“垃圾”,先搞清楚“垃圾”的定义。 阅读下段代码,其中的指针p在函数garbage内部没有被释放掉,因此函数调用返回后,这就是一段内存垃圾,也称为内存泄漏。 因此垃圾回收,就是自动释放 泄漏的内存 void garbage() { int *p = (int *)Malloc(15213); return; /* Array p is garbage at this point */ } 实际的垃圾做法比较复杂,这里简要提一下其实现原理。 垃圾回收器 会维护一个内存块的有向图,其中 Reachable节点就是有指针指向的,可以被手动释放;而 Unreachable节点就是无内存泄漏的节点,垃圾回收就是去释放它们。 memory leaks 关于内存泄漏的定义,上面有提到。这里重点介绍两种检测内存泄漏的工具。推荐阅读这篇材料: IC221 Lab 05: Memory Leaks Valgrind valgrind ./a.out:跟踪内存泄漏 valgrind --leak-check=full ./a.out:查看更详细的内存泄漏信息 LEAK SUMMARY:该标签,就是潜在的泄漏部分 ==2392771== HEAP SUMMARY: ==2392771== in use at exit: 0 bytes in 0 blocks ==2392771== total heap usage: 0 allocs, 0 frees, 0 bytes allocated ASan AddressSanitizer 已集成到 gcc、clang 等编译器,添加 -fsanitize=address 的编译选项 并运行即可。 g++ -fsanitize=address -g main.cp ================================================================= ==2392472==ERROR: LeakSanitizer: detected memory leaks Direct leak of 5 byte(s) in 1 object(s) allocated from: memory bugs 内存bug 相对于其他类型的bug,往往更难以定义和排查。这篇文章 调试理论与实践 指出:程序代码错(Bug)到 可观测错(Failure)之间的跨度距离,决定一个bug排查的难易程度,而内存bug的跨度尤长。 换言之,不论是时间还是空间上,内存的错误 往往距离错误源有较长一段距离。 引用坏指针❌ 错误表现是 segmentation fault,错误原因有: 访问非法内存地址,如读取 0x0000001 地址的值; 对 “只读内存” 进行写操作,如 scanf(\"%d\", val) WARNING scanf(\"%d\", val) 会将 val 的值解释为一个地址 如果该地址只读,会出现 segmentation fault 如果该地址可写,程序会覆盖这段内存,造成无法预测的后果... 未初始化的堆内存❌ 对于 .bss 的静态内存,加载器会将其初始化为0,但是堆内存的值是无法预测的。 如下 y 数组的值不一定都为0,请手动为其赋值。 int *y = (int *)malloc(5 * sizeof(int)) 缓冲区溢出 ❌ 这类bug使用中文有点别扭,实际是 Stack (Buffer) Overflows,即写内存时超出原本的大小限制。如下当输入超过 64 bits 就会出现 segmentation fault: char buf[64]; scanf(\"%s\", &buf);"},{"title":"【CSAPP】CMU 15-213","date":"2023-06-11T18:00:54.000Z","path":"posts/csapp/","text":"CS网课中的 “圣经” blockquote{ line-height: 30px; padding: 4px 16px; } 课程主页 | B站视频(中字) | 课程ppt CSAPP pdf 英文版 Labs Github CSAPP重点解读 读薄/读厚 CSAPP CSAPP CSAPP 是 《Computer Systems: A Programmer’s Perspective》 这本书的缩写,它作为CMU大学的ICS课教材,被誉为计算机系列课程的圣经,其内容涉及:计算机组成原理、操作系统、编译优化、网络和并发等。 “万丈高楼平地起,勿以浮沙筑高台”,学习计算机的底层原理,是在修炼内功、在夯实基础,这比修一个bug或者读几段代码更加实在。 站内文章 2. Information Storage 9. Virtual Memory 关于 labs 首先 CSAPP 的整个学习过程:以 labs 为主,视频和ppt 为辅。在github上找到一份疑似官方一致的repo,自己fork了一份用于开发:🔥Github Repo 整个 labs 的设计非常精巧,还囊括了测试、评分的用例,后者是用 python2 脚本编写。以 lab-0 为例稍微介绍其整体的架构设计: RAEDME:实验说明 Makefile:通过 make 完成实验的编译、运行与测试 *.c, *.h:lab源码 ⭐ qtest:脚本驱动的测试框架,值得一读 ⭐ 其中 console.c 实现了一个精巧的可交互命令行,并集成一些用于 lab 的指令。qtest 集成了前者和一些测试用例,它将评判你的代码是否通过多少case、以及获得多少得分。 通过实验,读懂实验框架代码,这两件事都很重要! Lab0 - c programing"},{"title":"【cpp】Constructors","date":"2023-06-10T18:42:16.000Z","path":"posts/17P7469/","text":"提要 hackingcpp: destructors 面向一些基础使用,因此所涉比较浅 基础 cpp 有 3 种特殊的成员函数,本文所有的内容都围绕它们展开: what when T::T() 默认构造函数 T创建时 T::~T() 默认析构函数 T销毁时 T::T(param…) 特殊构造函数 T带参数创建时 即使用户不去声明 构造 和 析构 函数,编译器也会自行创建者两者。 构造函数的调用时机很容易理解,但析构函数的调用时机很特殊。 显式地销毁对象 隐式地被销毁,如局部变量的作用域结束 if (...) { T x; // constructor } // destructor Constructor 构造函数根据构造的类型,又分为如下几类: class Test{ public: Test(int a); // 构造函数 Test(const Test&); // 拷贝构造函数 Test& operator=(const Test&); // 拷贝赋值operator Test(); // 构造函数 ~Test(); // 析构函数 } 考虑如下两个初始化的用法,才能看出调用构造函数的区别: Test t1(88);:普通构造函数 Test t1 = t2;:拷贝构造函数 Test t1; t1 = t2;:拷贝赋值operator C++11 delete 申明为 Test(const Test&) = delete; 可以禁止用户使用拷贝构造函数 此时如果调用,会有编译报错:error: use of deleted function ‘Test::Test(const Test&)’ Destructor 析构函数的调用顺序是编译器严格限制的,即析构顺序与声明顺序相反。 对于如下的结构体 Test,当对象发生销毁时,执行析构的顺序依次是: Test -> t3 -> t2 -> t1 class Test { T1 t1; T2 t2; T3 t3; public: Test(); ~Test(); } RAII Resource Acquisition Is Initialization 这条 stackoverflow 认为RAII应该称作Scope-Bound Resouce Managerment,这样更容易理解,笔者深表赞同,因此下面也围绕这个理解展开。 Resource 首先理解什么是 Resource? 可以理解为任何需要被合理控制的c++对象 file handles thread lock … 对于任何一个resouce,它的生命周期会经历 创建 -> 使用 -> 销毁 三个阶段。而销毁往往最容易被忽视,这会引起内存泄漏等问题。 Scope-Bound Scope是指一个resource的作用域,当其离开作用域时,应该自动调用其析构函数以使其销毁。 总结 RAII 是一种防止内存泄漏而进行自动析构的cpp特性 RAII应用 一些 C Library 总是会成对出现一些功能性的函数,例如: gpulib_init, gpulib_finalize 如果只调用了init函数,而忘记调用负责析构的后者,就会出现内存泄漏、资源hung-up等bug。 因此我们可以在cpp层通过 RAII 的理念将它们封装一层: #include <gpulib.h> class GPUContext { int gpuid_; public: explicit GPUContext (int gpuid = 0): gpuid_{gpuid} { gpulib_init(gpuid_); } ~GPUContext () { gpulib_finalize(gpuid_); } }"},{"title":"2023下半年规划","date":"2023-06-04T17:29:53.000Z","path":"posts/2023b/","text":"b3dc07a81f6459d120ce338ccca5504603780459e0306826441972681af56d627d2064135f70cb5bd2c200b9fa7e4da72155358871900a56565a0ca03e1ca0b2209e5b1092c6186a7b1819d438a12ca9bb18367e9ad80c87a7b8a1c70e817b59deea3abc794afb18b2fb4eb087ba547c9f7fb4264345913dd3318920907316dcea5179bd9ca5e01d1b8786e2a88b1aa4eae0deca44e46bdfdb7be567042d398cbcac6d7d4df07086cf83206bcaf121e4d589d7d0565d82253148efa244de06c29cd4d5e829781744043d93fe2dfc1f62af75b32f34e5f41a40e68bb8691e1520eb3736e007b060d41d8d91ee413128b5226f44d3215acf8b789e6ff0975e3e62f79ee70db266502c8b50ca520cc702d46caf3d7c67da8d8386b133122b85fade61a3bed6e260bc1ae72c038cad6fe0bbf99e31f534ef254436b63749e680c4e714f46885ede1e073036490eecefa8faeb383029a35954e938b9816811f4896c0a795a541390a4b697770100161e92b851e407d5d0512c4848dd59808dacff0fdcb90e1aef6ad65390e7a1ce003b7870d40971ba9c0cfa574d7b7c6bdb744e78bc81188952f0a40625fb1e809c5486608c65409ce55d9c5a93ff8f7360da7c8a7aec3748e27818e16ab4b42813d31134dad3e4b7dd40842675e0931679d5cb30edff2a7a8a4f47e08764a2a9e88f0d8d5f9f369f758ba26ffdb57e2a635e847b606868589b55703b449bf1904dd24a712acd1eb7daf4c8a49f9f5089197ddeb320c3d4901cae1f9a9c724e434fbc748e86503e796facd6dd3fcdba3730cce1cf117830acf018aa9b1741ab5322d0c90aab334e5a95b348b202ca54c5db116fcde85868ef3c9aebdceccdc079b2b39def29ed898a15769e0f04418ff78634d8c15a0ad4243912831b607532f827f8f162085533a2b78b0c6c41ed79aa206f07167aaff6a835000527fca2beba8192ec947dad5be709af5eab40e59f9d9b50e5d78c9a36d56e14005be186ceebfb9a5f5b67a1576316dc25724cef3cfd3918db0048e54fc02629bc21fd6aa6cfeab7235511cbb0ab02967161aaa5215d0c94c16a4452cef8a3b4b63d6d567093255ae382af063bfc522ef48a0e817153353677bd624d2489e2db3174d86da9df96aa43e6eb95a6cb3292f22c2420c5574263288f2112f6b19d4d906aa4ac4215c92512864754025522f7f6a677c5338a549be7ee39c27b6f847a0689d6c85ba82a3342833d2126955cb091037f0500420e932b6e8f2273d532e589fa76627b9a1586ade5e5bdeab52a75c94cb8c7840d13242764c3315e9766c683f736748e303606f16ff783dd541254ce5aee7960e5406d8c93d7264965c4aad2ca95ce82758e5f471c384ad432b8391e535198ffae940355cead6b531e875139853d739c65f158e6813fb2563a8895d06fc55676041f331f5261a1af5b6ae9c2a28de51b4541e9f75e1c408d379060b0db18cdb1bbc30466eebb546965c3df3499d79c8cc82b84775a01db56c7536417d542050316f9e10860b74fa3ad6991c884f18b5228a89c48ac97585c88776c3919b9978cf1ee1cd3438afa27a75db4e2601be958c8a153c688a3884fa99b8e850de02afc99793bbfa333acdb018cda2db870bcae335e6f08f648a7f70f997b2cad84b5c1cb8aadb9a7b713ed645895622a4de770b7e0cd5faf32382c0f0dbf8f9d0a9dea7ad4219c37aee3edec37ecd4bf89073584d488343927931cd39cd8383087ce9115d33a5dbed7da484cbfb75bacd28c7e7cbdd306109bfbd7ab89502c77ab1a83624a2f0c3583cddb4b2e3bea5ea1999d9e288394264d6aee918a17f744974041fa574173fe389442fd2d6dd5f0b8df797519d2a4cfe611da63189675122be6655529dfa93ab96fc17347dfd73ca5a5a18526c8628b6f7de1f23846ad476b490b09eb3f34269ed3c35e190733b375b8608a8980b214b9b495eaa6eafe7de3c48d576918989fbda4cfec6fa6ea08c53bb267c092a9df327f34dee0f3aab47618b5a30deb7bdff885f0c2d9159377070a3985b9cccb3b178778a25045628254511c037c5d09a694a9d385e2599ac2cd0afefdca85b759961239aac2e0e8dfb1394b9e08c649728c3d78d5a707ec9569173a04430ca7cab9945c3504de158be47f63f06c64a51d4eba302cb38b24802e930e9c21bfaf6f83c47de5770cf82059e97ef64c9a5b26dcfad0cad3d7048649509d1742089aaae0afc8a58bbf26f80a2c52c58ef2d4e530652cf3051e453c5335f99c3a6f4ca8a6507f797d2f4abb1fe4d1750bc0c10c79939ce7a3ae5eb0f509a35ce5bcbfc0798599f026ee28b83dad9c0b5dea2eda10deefefb65102150300d50e2299839d645f94320446d46b057a7eb89419b266a2a7431c9307a00ca4b4310239114d8f49129e2fbdd29fd92aa47e4a024c9258c1e13eace88ced8c13f892b0ffbfb1962c247ba0c70ca8c97b717561137dee9092be7156444e3fc275d9a6eff1a5cce3092bc63bc734cad52504d00d760b7f0e285ff910a50f5e04ebbd42beca35efb64276e4c88f1a61dbf1d82acd1b3b3f6d40836e481b5749a0768f9855834200bde2e612a3cbc365a1aff8bb100e51434e32b644f433fd6859f63b7cb8131cbbbc01768548b6d7bee77b62db4832d973c5624a4b5045eb001410210a94520faa75b3e39a2afd239d8f462fea07a786501bb179998ae12930f248e26e7f6daddf96b633b0e7458a9f3291a2a2fb828a666fbdf0a115d95b19cb285fab3e96aedc77534c6de0a48c7bace0e099c760d85b68c1a6ffaaa67fc0cfb16fead4c4819e641f22d58d65a9e722abdb1b28e2e2f703f70789ad7ae595ed44c9529d9887a0b0b212732ffb2a05a3437bdc97b35e380984f2d5b01625bd2eecb144da6a411578656b0d8e1307294eb0f3747ab85e254a88673bae19c4126b9bffcd0bc3e013046ed28aeb7c5b5a31da0f7aa3112a0a5f14034d0586a472f748fc438f06a3726b4e3a3752d7763a8a6f6dc022aeb65afc4d5e62b76c8038aae5cefd26d16a098bf571997ba87e8e827370b8fe56e6c242942c30f6d096b92db9bc07dad63053a59497ecea0c12f4cfdc57accbbadece0f77f83b6fe05a2edd4382dc8ae149e03ece3aa36dd781cc464d2936faf8cba8b7791d1c24edaab2f48cf4592ad616226a96c9525b4654242c079907f1b194362c9907d4105f8ed35fbd781e314ab8707143376b8f5cbb31381ced2ecd949dc3bf1740215beed1a81e732b7d291c6e1293ef565b1dfc18dcda94962b41ee60b49608c98c72e006db11ff214b85598866d56a7a4c311edcc695e8c5929b88328270933be230a4c1f0f067e3ff9b85daf1ec2dbb99389032598ac5d318f5931db079804fe7464c1692a3722f78d4b354d30370f574199b43fd7834223c8795c75f5b2bb072ebd50c94c0cb0c526dfbb5f4a95e545422da78c0c9162e6796825ad2cc411c4851ed504c439f3bc35387243271365f6e8f2c10c0dbff198200fcd294ab63abbd451fab2581f0558d57c28f588db827bdd753d2434c4fb0708430c897cd8864e9e768095755a680eb445a8f9c91662848f1f48dd4f331e95848312081d0f4e942a1988a00cbdadceded39a69539cc24b9dd566aed4fac04aef85c7b5974d1e61b99657b5446cc762c752bffddcb81eb3520f35c9565f287691ff82b9604722c4d3a388a47925f874502b6347b500b8ae12f8a51a4882a8f9c89d633ad2de5de654a2ab35c6f92a0cbaac318779d53cb7ba27ec28a52d4b829e43165a01d18d70d42983fcbc6cd0bd253eae0d3f5f3a0dcff0b99e929b5f182241e85257754159908ca7a07064d006717019168899f43607fb2afc55bfe4ce5dfb0b873dd0decb7c7d66c449bf1cd0c21a2d0af0670b198a4743b64f78ba4b635ec53fce53689062285ffe1764716af951cc3b3b4a2cecfb40e9fc3949d4bd8ae15915d802145f7dc4ab5444018d59e6fe5d033e185fec4ed553ace380122de98a1366c218f00661d6cff8aacb61b5eaea4fea91ff8b94b07656cc5cf3368ae8be05957e2055f64cf7b0fadd0ed4b171552d01ebfc2a10e916273e93956582235115a9773c60f68e58447fc3cadcfd80ef92e5fe79063055060197803b6f0fadd0752254187909f946fa5bbf5149de9d0713d079cb6525ec8a5c90e3883185c5ab0dbbf97506a2ce9ea1582861d5a070a0c09fef9c1ebf6167f7a38ba90c156ebf3343933fb4074c3bd42610f2aa0a86509f5afef0672bd4db627b27deb175b735feb0f6758ca4aeee384f60bb9f27d1346bdabce9c886dc0880cad02b60b74e4b201207e5de7912c58b29ed022de071ca9f73c459039b895bbf812fc94178bdfca16acdc6e4ffbae7a926368f59c49e975ef811c75f76fcd443882c15c3752bbeb6520d1c9d1733f8293320aa4c3975db184c020f48bead7d75903bb83e6f7af21386d95c729ab109368e1beabee2d8a5849fca655f062509181b50d7153f8a591023c16584925a0d6982e64097109292b9bf3ac7a0fdb4ead69330c46672b660549e46e4234906124d5b147af99b3e4e9d9a5bd8d565ece71a00c2cc522fcc0bc13492b90d930929b56a815ae7f43cb16a8e65bfd83825f57dd3b72e1c0b269d37d2aaf730133d8b9e8e61d29cb8d416443afeaddc2ac58a03c4335baa0c640cbb799115e9be96fddf433160e84ee3f1844c0062785abaedbd8c3baf4152d9cd29894054a0e275d5e560ffa8d1dcbbbe0861d1270ba4c4a0754797e5bf5af186798df862d64495ea8f9d73f726e8f9c30871592fd517a544309ddf04ba4e2352eb71b5dc8ffdd0cabe7d8babf1ba90331d7acddcbe00e24a4aa2d828e76049d27e15d23d7e50d1eeb10a30157f13914f03bb56a9a0caca41779723c497589f0f9320c6bce22a4987121bf489e5115c601553443131170d0a467b5481488567ceb0dfe1ba6be43d385fb31075e27d189a5ce376250bd2471743ad75f102e90bd38c40c09736bc1dfcf51378c7ba1713713b116db871302e6fac1dbb5bd0629bc3f483a69c9f4fd92ec3d03d6ec35c8875ad36992790c9eabcf97a93efe326c748f8e5e29beae6cc0254d11c09cd18ba7fc001a42d2d2e8b86d3365a9ad1a1180df9084579b92d769cbd09cec40e7af6d31cef3e4682adc054e323b6d60921e1c21d89f91388f06d91efffbcedad3774ca360ee912265dc4f6492564149d3a2bdbf8cf6ed38e945d8c9505e488f1b16c59f813f9fa66b44a6fc5ef8439edc6fe42835f0cecb331f3ee87c847c7a217a2db20e0ad17f8ed078d8ede84edfe70650385c7b273cf227a05d7ba0902f746ccab426b0fad88bee013e23cb071375b0cbdcbe45f807f5fbf6a2a5de2d2f75051b0a4a716f815a3ed711f8991cec51c40a553ad85d812c0fe5d1317c01feac18c8c2dde49447fcb7798444966ff24f898e647ad6adab71221e5840456209c5e09c2b63a640bac552f21a5a060387c87014348063d7c77ba51356216e12e1cf7420702195ab64c8d6e77d4804c11df2efb003a633850b2cc397007aaddc62edcc9b40794121d306648a1977919668848118cf87990b03456adf1b939a14f5cbfbe0885efb082679e35a6a17356467af1eaa3e9f7bc44d542324efc312692ceabac6fdd35934d02e2d5818791060ee8d1f0e1d911dc529b3ae40784d5c62fa967eaa99a4e63dd954026ba0ee9cbf8028f00afc4bf705d8cd2e181a735ff640366a81a2b6ba7228c084992410d94935e6e5f8519c1d796472bebe0cd2e19301dc8d2cfbd4ef8e9876b6653e4ba110c7c150e802c6cff4088fd4eb56877932b81495f76ac9f2276ddb3b7eba60b3b053c39beec734109b38dd391f25fd783d1134a48c868698c70f5f93835a1d64cdbfa36e7decdc3753d260abb142075ab48db0765e364d3544a463a3418d4a7f0a0c30d238905ecdf7dc99670b027f5c4167b2634162545f8da8fa2d76beaa018d0302dddbb54b95d4dc96660ea864652976bed49c350a9c63cb3c4166d967b9bac072b2879a924c19223526a02936054c2de58a509f6089651149dd522ac0cac132f7a6a3115aadd7ebbe1c977579fe76de6606dbe8772e4985345dbbd883ff633806982b9ed20c2b86503f95c6e7bd18f9c0decb3d4f358adc18dac3ac6c763c3fbc8e552e71b6396de78601bfcef1baa2f6766f90f9dc58ee38ba78acbd66cd7235165bb465bf5557455aae9516da26ae78593cfec43390fe764fc61628084aa36207781d47f844b3df730d8b3f8cc5f9ccabdaab2f30b2f38ff2d13c1f5fee1e7445c0ff6f8ed1f634060ebfd41815dd16a8ed09700ecb4ecf73e944b420caef6834f47801468c1dca3b0d99970cf1147d3ff75cedca0d1d3e7c29c29bc92dd4886ffff80d4e76b604fee89bed839fcaea2e35d2130e5e8ecb070a5bfe91b86840e8d49da23b8bebd4b7042a5bc347b0d7c38b9cb87d53a3417a23e6db9f0416952f6b69379ea9fc7f464d3e94066a9414d33590aa22187339a2b58ed94750b9b0ddf2c3f923cc6077a160275a9a938482bad9ab57f68d544a56153aeeab43d51b9d1055a91f15c02534d9dbe36187cfa4a7fa9c0321dfd7551e336248f6c36baed65216bd58aada07e76e63250428396f7ce3a15c3bc13e95370efea05466db252eadb3fdf94e0a11da55a328bf370ac71f80131df31223dfb5fc39ac259ffc99d61baa238f645b9100e132ea265b060a471aa011ce866a9aeda077e4b843fad5d22498717f3ffa1b568b123115eb7b60023c7ea301042ca5ee8a208185f747d36349876ac1ba2880a095fbe970d1dcf07ed4b5b2f02fdeed563e7f65f750c42059292be539581f24011bffa10900cbd774ce6228688e414b1ba30728d6f2db46a4dff500e2c967d6a6e7aa386a6873554d85649513100c9157f1dca62249f17504d5e51ed7bbe192a47d9c6136a7422e2b26bb000970927e02e7db4be08ed08c0dd2386b3e6fc338caff88ca040853b90cb49fd25fa0e8337211edfcf7da67bc02c60b596f4146df2cf508b06dd090fed029d026f00e82bea2bdb13f0495ff23c8c59e58ffc1ea47efd7aebe97ff2e2a22a6dec15566c06d2ef4286eccf1bfc05d6fdb3a9399cf1373d5ac2f041c95712749904a0e2b4c27c1866968f08915349e0382a9ae12ec8afa040a4deb612086510c2b640371c9e0539e1a62e0ab1dbbe0efba458c30160c3696fb8a592de6fd77e864feac0d12b05e0b902bd8bba2a3b93ad0c7282b13e11add64fd404bd0f8734387b091889677ec868c8f51f43fdd2d669b7d7d523b3cc41d964a879fb7b7ca74a25751c668af88fc4ff6bbb6c8778229b6b766065baefd8c9b2a7ba3bd0abcd21189d067a79a94eea609b9c24cdb914b9fd90cdd474c28c21a1ad58f16397ac5e6b1e72ae52a11f78a98c6d8d89da18c188c119d1f9b8bcec163cd2271c4d818de75224d0e1c4be266ce1972e40f697423846b7e6535d827b7b0dd08464cc6ab9b20c046f122e2c81c4d8119fe30198c098e82a837b64c10ed5e86c282f4ce4bb99d8b08a4ee960e6f64264b9527dc468514ce347c55add83de82d00ac561741932edcb2df30f0ae256f4054b2e4cd64ec232580b29f9ad308746aca088e02868a476ac9cdabe10458fb91a7ccc379fa34cd00e5eb29907a534b23b5ece8242f52f182e65fef70230b3106042dc4ba2f9a31cb1256a496b9048d381d37da3aa4f35b1452a3c14ede553e94b1097127cd3f5f8bf132972309c977a9a3dccd4698beb830f32171f6c10b56edb2d9f774df42017bba7716353e86b662f1b44ebc8a34dcfdf78413d923872294d34a8e7634004b6925d17a81819dba4a8b51462a81418f22d604712ace179367929af531a87a9f9f46b00a60aeb7c1144176a75f5d641738646e24d1dd539666892af68bad8286958840f93d6b56471d2bb555f9362d32d5d4a097c52b68e53b1892d8d169dfcc4fe83fd234a9635b645ea3b9835754de060c53d78eca8d1b816492645b7e8285e07b332d9ed11f3224234b9a0dc8e494de914408439f3986831610f8b851f3374608a3320f18e6a45376c63885985d887fd4654fa91f3344efd0915097f9710d61332c28d7339ba86ba27422551213ca07eedd66606a78c075344d08e86c3610ddda822716425d461b67d6d97e1f1ee42f4f7775d184458983cd26638f0c942d8f6aabd10c562ca6debdbbc366dcb949284714735c0ebf8db62b13fd43751344fa5f05dcb047350102f45e6af6030a92cb9dbae6f4a09bad2561370158fde2892e55381820a50376a78d8ec298e1e5e5b6e2642dd09417353f02547586c59cb07d01e Hey, password is required here."},{"title":"【vscode】如何配置断点调试","date":"2023-06-03T16:50:52.000Z","path":"posts/vscode-debug/","text":"包含python、cpp、ts/js等调试 概要 vscode debug 官方文档 附一张日常开发调试用的 vscode调试面板 截图 环境配置 vscode 虽然是一款轻量级编辑器(相比于Visual Studio、pycharm),但只要搭配正确的插件使用,依然可以轻松拥有 IDE 的断点debug功能。 launch.json vscode 的调试配置都写在 launch.json 文件中,它的位置是在 .vscode 目录下。如下是一个最简单的 python debug 配置: { \"version\": \"0.2.0\", \"configurations\": [ { \"type\": \"python\", \"request\": \"launch\", \"name\": \"Python: debug\", } ] } 常见字段的含义和配置方法如下: 字段 作用 必选 type 调试语言,需要安装对应的debug extension ✔ request 支持 launch 和 attach 两种模式 ✔ name 调试选项中展示的名称 ✔ program 调试的目标文件,如 main.py args 参数,格式为 [\"1\", \"2\"] env 环境变量 cwd 设置工作目录 port attach模式指定端口 console 使用哪种终端: internalConsole, integratedTerminal, externalTerminal preLaunchTask debug之前运行指定Task postDebugTask debug之后运行指定Task 一些常用的环境变量: ${file}:表示正在打开的文件 ${workspaceFolder}:表示 workspace 的根目录 python python一般选择调试当前文件,要注意工作目录是否正确。 { \"name\": \"Python: 调试当前文件\", \"type\": \"python\", \"request\": \"launch\", \"program\": \"${file}\", } python2调试 新版的 python extension 往往不支持 python2 断点调试,需要回退到2021年左右的版本 cpp 官方文档: Debug C++ in VSCode C/C++ 的断点调试略微复杂,需要分为两个步骤: 使用 gcc/g++ 将源码编译为 可执行文件(默认为a.out) 使用 gdb 调试 可执行文件 1. 编译 首先借助 vscode tasks 将cpp源码编译为可执行文件,配置如下: 详细可以阅读 官方文档:VSCode Tasks { \"tasks\": [ { \"type\": \"cppbuild\", \"label\": \"C/C++: gcc build\", \"command\": \"/usr/bin/g++\", \"args\": [ \"-g\", \"${file}\", // 编译的cpp文件 \"-o\", \"${workspaceFolder}/a.out\" // 生成的可执行文件 ], \"options\": { \"cwd\": \"${workspaceFolder}\" }, \"problemMatcher\": [ \"$gcc\" ], } ], } 配置 tasks.json 完毕后,可以通过 ctrl+p 输入 Run Task 并选中 C/C++: gcc build,可以将当前cpp源码编译为指定的 a.out 可执行文件。 后面的调试需要用到这个task。 2. 调试 接下来配置 launch.json 以实现对上一步生成的 a.out 调试,配置如下: { \"name\": \"gcc/g++ debug\", \"type\": \"cppdbg\", \"request\": \"launch\", \"program\": \"${workspaceFolder}/a.out\", // 需要调试的二进制文件 \"args\": [], \"stopAtEntry\": false, \"cwd\": \"${workspaceFolder}\", \"environment\": [], \"externalConsole\": false, \"MIMode\": \"gdb\", \"setupCommands\": [ { \"description\": \"Enable pretty-printing for gdb\", \"text\": \"-enable-pretty-printing\", \"ignoreFailures\": true } ], \"preLaunchTask\": \"C/C++: gcc build\", \"miDebuggerPath\": \"/usr/bin/gdb\" } 其中的 preLaunchTask 会在按下 f5 调试按钮后,先行运行指定的Task,即编译指定的cpp源码文件,然后调用 linux下的 /usr/bin/gdb 对可执行文件进行调试。 Node.js vscode 对于 js/ts 的开发生态支持度非常高(毕竟vscode插件都是基于typescript所开发的) { \"type\": \"node\", \"request\": \"launch\", \"name\": \"JS/TS debug\", \"skipFiles\": [ \"<node_internals>/**\" ], \"program\": \"${file}\" }"},{"title":"【cpp】pointers","date":"2023-06-03T14:58:55.000Z","path":"posts/pointer/","text":"cpp指针、地址、引用相关 概要 指针是C++学习中绕不过去的难题,需要对计算机内存有深入的理解。 hacking C++ Pointers Learning C++ Pointers for REAL Dummies todo: unique_ptr, shared_ptr ... When 任何一门技术(或者概念),都是服务于具体的需求,或者在特定的需求场景下,才会诞生。 C++的指针也不例外,它的出现是为了满足如下三个常见的场景: 持有(任意的)对象 在避免 copy 的情况下持有对象,这是 cpp 最重要的语言特性之一,通过指针可以实现对象的引用。 访问动态内存 这块暂时没理解,todo… 创建某些动态的数据结构 如 vector,链表,树等结构。 What Pointer to Object of type T,which stores a memory address. 指针永远指向一个具体的对象(任意类型T),也可以是空对象(nullptr)。 指针的本质是一个内存地址,这个地址在 x64 的机器上占 64 Bits,即 usigned int64。 指针的生命周期,和它所指向对象的生命周期,两者是相互独立的。 TODO: 通常说的 “指针” 是指c++默认指针,不包括如下(有空补充) std::unique_pointer std::shared_pointer std::weak_pointer Operators C++有三个操作符与指针相关,分别是 &、*、->,需要区分其不同的使用场景和含义。 &: Address & 的含义是取地址,&a 会返回对象a的地址。 *: Dereference * 的含义是解引用,该操作符后面会跟随一个地址,*p会返回地址p上的值。 ->: Member Access -> 的作用是访问类成员,准确说是访问一段地址上的某个成员属性,经常会和 . 使用混淆。 对于指针类型,使用 -> 对于具体的对象,使用 . & 和 * 的区别 作为不同的操作数有不同用法,区别如下: * & 声明符 定义指针: int *p = nullptr 定义引用: int &a = b 一元操作符 解引用: char a = *p 取地址: auto p = &a 二元操作符 乘法运算: a * b 与运算: a & b 不要在单行申明多个指针 int *a, b:容易产生歧义,建议多行申明 **: pointer to pointer ⭐ int v = 5; int* p = &v; // 指向int int** pp = &p // 指向int* nullptr (C++11) nullptr is a special pointer value NULL 完全等同于0,nullptr 本质是一个空指针对象,注意两者的区别! 作为规范,nullptr 通常代表 无法访问的变量(value not available) nullptr 编码规范 指针初始化时,赋值为nullptr:int *p = nullptr 解引用时,检查该指针是否为nullptr:if (p != nullptr) { *p = 8; } const pointer Read-only and preventing pointer redirection. const 搭配指针会产生奇妙的化学反应,一共有如下四种情形: 牢记口诀:const 左边的永远是不可变的。 指向的对象 可修改? 指针自身 可修改? int * ✔ ✔ int const * ❌ ✔ int * const ✔ ❌ int const * const ❌ ❌ 下面看一段代码示例: int i = 5; int j = 8; int const* cp = &i; *cp = 8; // ❌ COMPILER ERROR: pointed-to value is const cp = &j; // ✔ int *const pc = &i; *pc = 8; // ✔ pc = &j; // ❌ COMPILER ERROR: pointer itself is const int const*const cpc = &i; *cpc = 8; // ❌ COMPILER ERROR: pointed-to value is const cpc = &j; // ❌ COMPILER ERROR: pointer itself is const this pointer this 仅在类内部使用,本质是一个指针。 this 返回类对象的地址 *this 访问类自身 this-> 用来访问内部的成员 Memory 接下来从 内存分配 的视角,理解指针在计算机内的存储,以及指针运算符做了什么。 根据内存对齐,char 和 short 在内存中都占据 4 bytes 指针的本质,就是第一个byte的16位地址(因此 sizeof(p) = 2) 下图演示指针的++,会根据指向对象的内存大小而进行偏移 pointer += 2 或者 pointer -- 也是同样道理 pointer[3] 等价于 pointer += 3 后的取值 ⚠️ Warning 指针使用经常会出现如下几种错误: 1. dangling pointer dangling: pointer points to an invalid/inaccessible memory address 永远确保 指针指向的对象是合理的,否则出现 悬空指针(迷途指针)。 error 1:访问 未初始化的对象 int *p; // p not initialized! *p = 7; // ❌ error 2:访问 nullptr int *p = nullptr; *p = 7; // ❌ error 3:指向 无法访问的内存 p = 0x0000001,这是运行程序无法访问的内存段,运行时会报错。 2. pointer argument passing 当函数的参数定义为指针时,注意传入的参数,不能是一个非法的指针。 void swap_values (int* a, int* b) { int t = *a; *a = *b; *b = t; } int x = 3, y = 4; swap_values(&x, &y) // ✔ swap_values(&x, 0); // ❌ UNDEFINED BEHAVIOR swap_values(&x, nullptr); // ❌ UNDEFINED BEHAVIOR 3. hard code 通过合理的编码,区分 * 的频繁使用,尤其是乘法和指针混合使用的场景。 *p = *p * *p + (2 * *p + 1); // SO MANY STARS! 指针习题 当你以为入门了cpp指针,尝试解答下面的题目,常看常新: 已知如下的地址和取值,问如下输出 cout << v; cout << p; cout << pp; cout << &v; cout << &p; cout << &pp; cout << *v; cout << *p; cout << **pp; 解析如下: cout << v:5 cout << p:0x44 cout << pp:0x48 cout << &v:0x40 (=p) cout << &p:0x44 (=pp) cout << &pp:0x48 cout << *p:5 cout << *pp:0x40 (=p) cout << **pp:5"},{"title":"hackingcpp.com","date":"2023-05-31T17:20:56.000Z","path":"posts/1GTNS16/","text":"hackingcpp 网站的学习记录 背景 hackingcpp 是一个偶然发现的优秀C++学习网站,开这篇记录学习过程。 pointers constructors & destructors Environment ISO Standard C++ C++语言标准,前后经历如下几个大的版本。大学教程所传授的C++基本是围绕 98 展开,工作中使用的 “现代C++特性” 基本也不出 11。 时间 特性 C++98 1998 the original standard C++11 2011 almost a new language C++14 2014 some improvements C++17 2017 new features & library extensions C++20 2020 game-changing new features & libraries Compiler 主要有 gcc, clang, MSVC 三种主流的C编译器,其跨平台、兼容性、代码生成和优化都有所差异。不过 99% 的开发场景不需要考虑到 这三种编译器 之间的差异性。 看到一些资料,都会说 clang 的性能和内存都要优于 gcc,这里不了解所以不作为何描述。 gcc/g++ GNU C++ 编译器,使用最为广泛。 clang/clang++ 又称 LLVM,一般作为 Mac 默认的编译器,因为其同时支持 C、C++、Objective-C。 关于 gcc和clang的对比,推荐阅读该材料:Clang vs GCC MSVC Microsoft Visual C++ 是由微软开发的C++编译器及相关环境,只使用于 Windows。简单来说,这是 windows C++ 开发的 唯一 最好选择。 Running g++ hello.cpp -o sayhello && ./sayhello C++是一门编译型的静态语言,(区别于 Python)其源代码是无法直接运行的,而是需要通过前面说的编译器 转换成二进制的机器代码。因此一个 .cpp 文件的执行需要经过如下几个步骤: Compiler Flags g++ -std=c++20 -Wall -Wextra -Wpedantic -Wshadow input.cpp -o output 需要掌握基本的 C++编译选项,如 Warnings、编译标准、优化等级 等。 编译选项 作用 -std=c++20 使用 C++20 标准编译 -Wall 打开一系列的编译警告 -O0 优化选项,代表关闭所有优化 当你在 windows MSVC 开发时,借助 Visual Studio 可以在 “属性页” 查看和编辑所有的优化选项。它实际上是提供了一个可配置的GUI框。 Variables 最基本的变量申明方式有两种: type var = value: 通用写法 type var {value}: C++11 标准 undefined behavior 时刻牢记初始化变量,这样能避免很多类似Bug 注意 a++ 和 ++a 的区别,在合适的场景使用两者: a++:返回旧值 ++a:返回新值 Type Narrowing 低精度 向 高精度 转换:OK 高精度 向 低精度 转换:NARROWING,有信息丢失 Control flows if (statement; condition) {...} C++17 允许这样的写法: if ( int x = 5; x > 0) { ... } switch 同理 using 使用 using 代替 typedef,用来作类型的赋值,例如: using real = double usning int_vector = std::vector<int>"},{"title":"【日志】2023年6月","date":"2023-05-31T17:12:32.000Z","path":"posts/2023-6/","text":"b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475de0b1305a637ea913974dff7410a14ed4dbbb40a5278eb14bbac34e59e12a284c7a294ab69a495d47e11e78ad50feb107058e3275f75d936a554eb95bedbf96a7783a11b9f4096cfe1861725619e5a531746d668c7ddc527dd363ab656ea7f69ac2f25b2c496b27abbc3824127870eca0d8a2f8c80addbf39e2b1e80923c0a03fdb5696fde6983b2df072a7b0cda199e20f9fb9c7d770945c6cf667caf0962029a3b039c88bb6c1058092bcdc12419b3ddcf5845ecf891615201024a124bb8479b53886fd25f57cab26c014b23a8f02610784d4555664647631777f5564e260a383be4a83a05ada80e4aa10419916daa8c7ee3856d3f201cd7b910752eb07e88f6435fbea5a36ec4e90944ec768efa1f2e1a5014bcb16bf85702108e5592d7768437de280aa71a0b02bbb043cc44b1f748932a83a7e7994fc6dfff56125db4462b4b7af4d08cc5ca557b0ec57ea956d6b52068f1485d3095beaafd7eec7605af22706f9f5653981a57a9a2ae3751b52c3f310bfa704407c9efcb3e3fe59069aef388cd7b7c99e15a9cf1208b4d91ec742f425851a3ea9b8eb6576162bafe3868b36b322d2ea63f8cb1121b4fb5e8c5e1fdf05419cff11bba2faf692e9a6a936d60afa311b74e218ea590a6bc74a73b69f73559441f99b6889382bea7809cb13db5b09520b8590f4c43dcc3f37db86297cf84b8294e16c40ee91a0937e04de074ad77f504a0132e4f6f7cda0e01e545ce0aa381ea714f26ce0964ec9794113aaf2a1b7f848301ae08219a52326de2b0568df66fb6ecb20d8ab1101d37bd2fca650174996af5c32239ac9374077a98cfdaae53ad0809a9a4bce4ee804e6c94ced7216b791206d2f52adb59a30a472b3a372d592b29ede861aed6acb5c68959ab473cdd7cb5893433942c203da710f083cc6a94625fa29b8e394cdab8382c4ad03e6f9b348315c68873d88705119658cb5ffbf12e2cb9a2befba2ad3720df6c5fc1e3cb01d0d899b84530644d8484bf64f652ae473f9055027a3e2c2620c67906224ca9e01bd3f3bbb0fe1bd534347bf410ca5b00e4f90be13d6cfb4c9bcd5bcb531d0e93e7ace5ecf16b406029724a1a542fad5c39aeb1ce70a55d7d4ee9ac93f58a453b20575bf008cc94fbb711e44ef9d2bab9379c281d375347fd80feb18eec6dfd304d43ed1a3afd5828277544ab5d787f0cb01222db79f50d3c0a91d4a6f13be8c328dc8dee48a4abeaf0b8edd1be22f653601c441b3fc4d0f2d629b50bc51e8a9bc34272a5b831fbf6bf774b7e1e7b98bfec246e770366db1da3da7c4bdd16ff15390034f020bcce032facfaeabe68438bc7554fa951944b3de9d28ae887b61f48c6fa8cdc7923210f011949bb1b62566d0c2d3f8c5644ff0d01d1344a586f83b80b7b6a4c051e670bbeaaba29a70ea111a9b5e52a1d7e77b35fef01a1cc1517e1b4acf7bbf8d952464b2469e7595281c6aa8f8cbee95d7993b3810764a430aba9b205ed73271d4f8b926de93a18209a5654e3c80da19c4c7e4548645f77bbec651aed9076abdc51105923df8039b26e4832ce7c5f81ce1bd4843a0c94e140186b67674b0eb52f57d489e5bc154c287fcc287f49ec3d367f01ba60f58a96644bda1c66b09d0110832b37474f9dd15d5fd2c8af876db36d5fc8391494f06e539f8d5169e26564abb1d4794a7ea77f09f006e1320d8c9a6a470ae779b03611c49f4294073df9edd3692851cc2c0b5d817a7582c29a28fc7e27e02a0aadd76ffb519aa54b6a14b2e3e6b6ba6c60082855eea2f3cad85b1683c6846fd80abbcf1c4eced92d2de564a8a1a9b9e6bd858224336bdc00bd95d96d1e41bbb351dc600b6b0a206bcd2937a32e42d201724cb875db7806cc1a53173ebf8426a7519ff15f5a5992514278c3cf42fed8000e11d4a8b16d6475a0d85ec724d81b53869f4220349a051e215b4dbdb5bf1988039fd8473fe82afb5d1acfeff22d067c91a8c30692eeab6249683e96611937a27c735ea4da30f12953d6065b2b376bd8ccca9a954257cce36b096b31cdaf4a94c303f99d5b73c6504109db78e3848b6a60d85d919c081f4f643b7b9c91e14feadf5b5392378e52dfc34ebb2f6a9f304fa9a48a34d89974ec9a2491241076bfe6a7420bdd0fb3562fb6ca3d1ddbb6a374452f4e4d4441ddc3f5c485d74aac69598ded35ec84fb939a551715f06daab7a55b8dc2d8ce8aba3f545b9b15c8d32413bdb1ab92cd0fb0c77c6e757b98371cd37f6593e1d9afbcbc19894fc4e3d09e9d8a1ce9eaa25459d894e61ae91cb6b57705b2fed7558c3f999c6003dd2cf7aceacceed80886209ef311296b38836443e25181912cc6c213c1fe722d48f735bf84bd67162b19369efce628cab41d149c98e1215d32b01cedacc4cdb2c0bb2a556285ef1cc853a626684e6f548ddb87595cfd0e70ed17259eb33c8847d6af6d5154f610faf112744e4b9f9c6fdb6e002e8779bdbd37c345f82d3b6fa501c40863b09d0d91a4caa334800a6fcb52c596f7eee8dd6d6c370c9687966d74d0b30f8a9f4309b9644422cb52b87b99647f071194e07e9349a69e3d4981608a5f5a5abd477a4abe2c62841091885c550f11ca9bb12dcae10dd3c076c17a9d69dd68dc2f76e7c66471e41d261a270125c89082a206df131658dbaa1e8362d214c3ab22a36e8de23c09ec1da3f6301db6407fc856e4906543be9e59377c74abeea7b0f7bcb505fdd3336eae0bc9261a2b87ee660d7e370f7092036b4d20f1c7296afcce17fdd4eb826954e0630971ab7d2cdb991d43535bf60a329803ce7cd921890cc4773c8ca4e4bd754c442bb9c09b0462bef054a233e029b7184c103ffe13612c5f04d8b9c592ab23e766b5095907973f61d362b4f659593143fefeadd77f43e280863f4030ce558d6a1fe2baa4460729edea3d181f39f10e4d85a0fcec852d0ad14d0dcb1caf34f36d77b9fb0465dc2e3956909009793b7b0ec17575dbccffd8ac872c4a04a11d32341a94a8356f28661784122b49bc986cdf26daea8d118f5c9f6a877a18f7dd866db79ed8382ee9820877e6aaa78dc5805bb08cb9cba6599ac3b2a68dc6048e110cd8ff25598d03883f9fc62e397a94249873f0f90261b3d82993117f30e3c33470cbbee0818296934c209c7b33204d0b10784f0d890118e1c50d57843214d556b67a2ae0aaa17991eaa1e361a34b06636bc5d12ce136fd6b2434334f22b1c0dbf3ee6d70970cf5a1f687d960b3e0b8d1451ee0b4f5b53dbeaca467048977e2f459b50574bfcc051898e857ebd2e9dad17b9f5c61ae2a12c7eb07ce4c9fdd33a53fe5dd7bbc69ee1df207f2f2227face54176b0c86d1835a68e9eeaf8ccf07f480d2a20f95b54f48c7f9ad522469ab094e64304f4eeaa72ae2d7ce1452a7b192938a095a139fd7d64f4d6c87627612adf687b454ed34beef741d0d83561898e6972f96943c756640d4885542019b4a3493655eb94e48219dcc1012a8e2ac5a5e59b9085294b910372861c7ca511fef2131126ec9d6aa4cd8b6c17a19763625de9c1866b15518e3c17cecbda85120b71bb62ebf86fd988d53533785348c6af17f98110d40b02c3e9b873898be3e384b685d447833bb8b1817ac67627bf7cfe769cc7f580856e88c20a2d8091fecd66e4981ea48d7a0f17bd38c470569e2ea574af532b36f476480024afc7f562340f7b68d6e51bc4bb15953183baab5b946ff251c181aae25fd8f7aa46df977cb14dc9bec4b234244e19bd9905536667e2d5d1e3623655c0bee13d09438a2a11f5c7d59b00ddd6ddd411e97f778689f03280e7163c4225ff5c3be3c09dee7907e5b602e873e57c8e17c427d6bad4ae8689a7e2e0f273a4592f519e94f638e991eaae06eb3ecc69ca4ae2e1f299c4fb8d6c740771ee321c27f12965f07d0d1a9f235cf9500f17367cfa9c352c69f7add537eaca3dfa5365505583a7fda66782a91270974c56f9116feed37329ddef413fc2af8afd1690f7b14b240544197486df99933f7352e675bfaec667790c670d7d03468ef4fa1093f1b68a642374b6029bdd6362c2eb743bbdebc52a3ee5928535022bc031d38c835475f282c811dc04e46a25426c263ab9687ba29c7764339c3037087d16ffe1df24d5a7d49acadcb68c166e618c46409080da6bd0b29d28d0713f9a18e74dc5e0d632f1843aad5522814475655f99e4d0d03c2b1ae9f21fb9e1fe52e72596b6589cdc1c529c3029e116b0be145fc683714b0224478c1c21fc6a3b6dc46fc7f90ceb0f7b43dda1beb159e4095e2cbe8e4dad649f4089bbcc6ce24ad8702bebce5d2114671de9a80a01c0a16c2f4d88785c123fa1d0062e99ddb3c292a2dd2def8a2f256292cd40da272c707386d364fb9cf11c3756c41d0cf6fdf99092ce4a2f8924c25bdd3c35e4bd90ed35b0a01f2b845f524f5b985971532da5ad8a254a0454f25bea4bd49f4089e0b4f7141d7f6db838ef3216fd3fcdd1673dd09c587ec394abc16b16ca5f20d65c111f3fed2a89611f07d7347cd6bd08a457db5202bbc41c5b8e7c9d050d7f56d834f05971444c2bd3b46558c7bee0496a3898c5d6102d6ac64be43775ddc4b4519651a0298d49b1dab9b367b28658cad4ce8c779761e722bbaa5c22acfade7b9a69242d0c49f55210e4eedb0b4aed05be3c089916febaeeda1c821d2a2680da84f2332f407bfd23d8e447258e672a77b0fb4381cf18a40f34b809ad176a6b808b9023453a4cedbbb2ac6bcdc5aec4f8145aeaf6704816c3d8e696996d198119476470f3b6bfe161bdaf18b324c1bd8310ac2daf193561e098efde5978922a9c031a15594b5c758ac44ddefe651961fc9e6395aeb00f030b27914e350fa82ee1ca8322abebc2457aeb49fd3f8d387a77729bcceeea8da2ed4b00c6c84f6c0ce633671c490b685e55c8d95d8b297455a78ab813481b706f5e6194d5074b5542685feb1b67f032c04b01a6141b25972d0b48c187aabe49c3d0499413c98f20aaf1651fe203df8709cf3af744e794b334e068a26f6c4a5b1625078b3a2ffa2682e44ae62cf599e6ad9d9d8d0c666cc58b59536b71ecb30ddc3cf3498cb59bc741949915c6cea51b12a14d8997e2c7642812329e5242c1c48dcad787a8abadfe9e02cba4d77eb9155b8177f900eed30913cf82709c2e3b1d5b737c027cad1fc4ff434823f0bcdcae9fea1d8600c28e785281c4c6861a9af55fd8494c4e30c7418e35e727bc5cd77af791330f78533c210cf4199ac9efb4f764a28271b77f6dbb6b6b3e37b035a91e909049a577e1c9399f2539012e91252ca79ed4bc80fba91b1f16b4f55f09e7f163df4fe27a9b71067476985bbd95a46be713a3609c824d2dc534d7dddd894d9e7c7a152936a2178bbc3a8c3d7cd19d4db103e76fbd2700733781c68491bda41a928f578b09b2dfb4dc9324925ce5d7f96adc948924a592afb391ffddce9c81d06db9f893862cc2164b9d983aff675e6b0fecaff02ea9300f5429c74228399f581312a0f7da1d9152e94d457a4ee3d2c1981b0413577d57a3cbd8d3f5bbfa627747d83ac5de877480efd73b149d3d5453204ae4e51c49d3e4a9da268b5dd682cd42e8ab278fe583ad04c7ef606befcb531e9ca5332c8ac8c462044938bf4614e3688c83d8e7b6b33fde3393590ce424ccd518f00a1d4b6632a2d62c1319aa986b1757eeff6bd7fc8bf532abf6bdf0c17cab5523268d4b1945dd086e2132cbf40936020d1a0c9941f562cf9ae9ad4a49bf53a12f2f2fbd8f26ea50bfe1a9437ae8709415f83dab6d3a75d50e24a17de437545c500a349fdc7710cb1833f9f982aea0e0a1c19deb2511a7811118031ab99217e65f0dedfc3cacd8b29df1768bc9a751b71ab38b191ff66e8929b46afe50218c231112d20f479b33f76cd4a2483da6147096314b710fa074499e5592fc7f7c9f5537f574a6e7acf18b857142356aa084844fb0ec730f7fd9bd364ff6b67cc098435242e58112fefea3e0e9fe058960a1b9db35e8241b1b12200f8f30fde740d72241176e4b970c2d5188a9fe563eee26793fb423ca43c55bd5ee5b2e474dfa1cbfd1dec77655fda211da764d16a2abb40aa6f87015e3280e7c247a6ee484dd1eaea4a30131e9c4071aa3a732944365de4df7796bebd83255aa9ddb7318e5584fd9ccd54d94a8eb148b5b2b117fe73d8e41a610b86910de8c99c5c93468085f28a8b56ff2e198d1be9102a0a0d785f84a9cf1840b2fcfb65d4f092726b22cc78e261a874c9e80293f5aba84a4d5a95addd8c1fd6e39d401635f7400e7c6dbf665c0968ced393c84358c22c10bbb6d4ad3f7c6c4002215a921b0a07f0e815d30c7407eede3da2b3df02f4e4325e17680acd8de01ed9df9deda0b845271d5184d3fb3391bb69031dee5a936ad05aadbabb28a5a71c9a2427a6d2d2de014d71fb7d4bb1e9d66ee17f61a0ee36b48ef01c04d9a8cce147a7f3db081cbb9ba2b6df2ee90aeebd5a9dfc05c8a71f9bae26b054a78b27153660c4f5e0366259c970b41cbc939c17396858ee0b02d5fcf1747d2d85d819ef0501be3defdc075512d98e8a40f0508b3c8b800ff657574f9663239877e0e61dcdce5ced9f9d22241c6593c8473366b5bf301805c62ed726a30c9c66b7b448e2b1821a1a4d5bfdb5d8e53171074a14300ec8888e6a5bbe70e3e532f32c19fc46ae15c5928cde94515e83ecdc90a08feb375f506350ea458a2b0ec77432aac460dc2f026fe26a13f9b74fc20d8ab1392a638c50650a02851e1075a4df699242e04d502564bfe0f1360bcd8870c52a7b2a26080fbdb7119cde74c87467392db93380d34a0e9e3324f6157d7e119deeba2e02b40c0e7f5cd884afbe0cda251b5e97841d7ee93c555240a1aec41b03c34d4731d0e001aff06994b662fd9c6bc8663e76b940db9d943558b7a349d6aafd7dc431ffaeef3b3051d30ce30412bb9d31779bf2fd33569c61fd5f1e29d49b032897d71a7eafa6a63e1ac6e326b1478934b050c448dba33251fe40a59e18835905a5a1be08288b022455bafa5e40007ec87d7cad53269d8cb4ba3e3eae7d3f40c8eb26b4e4e731b74f353e8ebf54cf833dc9702e11b68af5a3759ecc63173dedc0e290e4c68eaae8914e96812ecc784c363b188f5e6e0df614a6b49b70b11594302ca8a01e4290a899d7d883421b1644be04783a62a21e31dbc956faaee6d80fa6f944dc84e76c8f62d9fdbc20e0f9226cc24c6b007efd145186d7d21378f1b515e1dd4217085de5b3325d47e3c25f62ba79874712459e86c9a9754d53de03afd702f2a706ab039a7900dd089e48b0bc1f1351c6b8ce9228fae5fbbcccce0b6fe4053aacfd7f61623ba0195a21f91761e33752175e8f054d61b93740a6beee986575bb5bd55bdc445cd179cf507102ecaf404a5ee4d43f4a0d3ce4967b77e6a1e3694f680ed10f3a72b68b9100c9fb2b082fcb2fe14857ae46297185b37b34b7501ec5da1dd188b7a9513c9636185186e1835372a4b754be0d6fdf443dea5d79f21d074fc72e6c606f25fd95de9605878b347aa301032535b46213470b119d9fd2a4fc6665dd9036a06904c5212516d32b2cf80e473234fc7fa2b2df3d37123270a98142d40301053d33d8a633b9f7956ed8bb628fc5cd9cb3e88e7d6b87e2464ca9bf80b281615f4295e97632acea9e52f210dfcdf9510c120c39d7ce147cdf87ba7219f91fcec739ef4359222d1c6dec8564b611e0e9d55b264da8ad862db4c80d9518a8265a233dcd7d2bb3979f0eb0db5c78933a4ca8b479696e9875594b5bb245719e61734600162e473a285eba7858fd48d800ba0e5cfeb010a9e1b5d9c6e80e805d6953adf7ddba9cb217926d55ac921da456ed608ae3e6713885916ae9da6325e5cb1ee852eb928eb45339e767cfdacf80942f9343974d3e57ee8945816afa6c5966f574efa7fab0eb3de811e07427213c855c75faf27d5d5cbb159e5a5aa22ee1ac701550d7e2cc4fcb0dd14a7b29cd5bd27107c70e8df2923b27e505a698548e46f83c32402d4b32c2502d786075becb40b9e8b0461a7b6d1aebcfbfb3b6dd94058b6cccf51330793ff681ae175094edd8b0c14d49ba393a79d3f774ee1848f1c38af2320f1fd3dc5a684298096d216c6c5d191123658dcfeb8cc4f43317eee25d2051130ce88463d6591e040cd6316751a91fd94b74978685c50f408ed7e67adc01aae5343323b56fa88398773aea9eae0eb613357511075de894dba631c21241b8c582c231ec14bddf6fa6bf5fc84f1f8111b267c0691266f0a20454aeac8019fcb471be797e054cc3a94538db5847ca9bd9c1f931479a8ba3240554b9bc589c23ef30c1ba2abac1906bc0152c7b40d2222291a9f2fef971aeddc49e88250d1d23b340eb5ee2d0761ab12c6f385b43c8e9c1966f833dcc2e94c86d5b1f8a3f26acc1f1215fc46961e07a2ca0c520f0f776b9e50106b1a03f31fbfbbf82720b5960994a73f71aff8d8621fe40c09e7f41b33eee7a5dc63a10b4d281a766e141895eb45a359ef4e93d2bc278c9f5650a15a6a5c25c83c9ee0ed04d8dd03050a45e4f9de5dd23ce764576f84c7fad98634918021413483b18f74e7ee18f543f99ef37561a9f9f197d0415adf1fe63b5004e4fb51c8562eccc7b363767c535581541f5e058e64fc4e7220f57558bd0f1bda479e64d468be95e9511dbffb11ebe3eef31532759ef89004906ccb9ed3d474755a517659208696706ecdd28941db3c0b230a2d232b6a04c908e25a6290205b4126e95565592c0fa5edf9758b4e8aaa0c0d83a5f0b27c5a14fa4e07a3f057401adca77d320624562677020ccbdb14f46f16748c78a51c08b6bfa74abcdfd8a4320876079620fa3130c5d42585c630644ab370602ecbe12d547818bab8e7c653969600f3db9995d6f60b9e7fa12e6e06d93bca89801591f18cb6ed119e01f6db64103279faa6a66d3a8d1457214db62f0c78b7e0533078b35950eca3d9eaf62ca8883f53053d2a1415093d74f432dfb67680d9a18be3822c143991ba52849376640b9a9fe600ad6a9d918e3b27888160dd6a33aaa6b4f8d56a1305d7085cd49bddc08b4e343ff19b978bdf852f6cc702a14a585083d5642a95eca1ea1d6798f79d77e3948e4e17cf762c7351573f7286ffec26a2e037561c7d65289b5580bb5e56a10769c5c0f1b22d6c0487329681934608742dd717a427cc62747eb033209c323def4ae34ab0865fd4fa945e43fe1fd3e4e44fa382dcc8ce70aecfed3299a7c7b5c5b3ab030c8cadd3b6162209b9bc90540529fae5a54c492120f66d1fc2bf9be93d7b2d6956c09eb223b46fdca51489848983b5a755a8e1b9bb66460f6e36ad303397c1cc7d6755b296c9b493c114c456e10632713d4eb2b61b1a8a079044160da399770682f90f5dcea6fb152632ee99cf42af3e35047cdd6c8ec24e0f685701a47de1d1995325f480c33a9f93d1ce8b8791a364c9c05ffc7b81375cad1ab0680a904dbd3af0f20a8283635fb3bdbfb9f9b994a036cb6a10ecb32def3872435a9a6cec57c0c955841ee460f4931f7ed2f79c3ebfbdb3e3943f0633df0c670f225e185b25d74c7960f438a3e1ed7d0d851a1b96dc9c316271754dbe83f1906814506e7d1d7522f5684f66c1fbc23c7c5451538d10933a32a7ac915acfabab9f58875db16e2c66fda0243aefb722600d5695aec05e4a75f88c99a168d77d64e80c67cb76affd217bb758e4888b58cd5a9f6afe4c0728b5f1fb8cd96d852c50cdb48af7dbc6bd8ce682c78d162690890273ea13f852e6b7e8459d201716d7d4e619f07ab5651880ef898ec291a666186e636d5d87490e6a4a49cd11be7c6b2ae0769f75c3ad40ccbfe86d3d5af0b9eaed9d02bffd640c98f879976bc7dd211cd857c60379f732b236c9150c9094bc7d3a5ee0d419e0ebaebea69816520fd79a758969a8daee7aac46ddd34ef24a855947a7923c63f581a7dcaea7946d511149963cbcb7ea55b9315c45cfc6fcd89b98aa333c78503cd96e1329ce4a530c5002a743dc4d0528c85396022714a8dc3bad55966e3a15bd733e29ca9d9a539e127d6eb86e977209d87f7c096ad6e4301b33980835041b3022267a09b707238554399ec6b9d6532b74ab3f9c057dbcb15efc79c36fcd890552ee06ff15e2d2f6de98afd386fe8e61f5cfd57fff740540fd3e0782fd96fb83aa10eba2a0a30320018842d108ffffd474ad73379892d5b589b1c41d7ae4938ba18005d367996cada6f3842a04c6d51c7af0bc171a7adb1abff800553be05e7583934698418563bf96137d21e446f81e300e292aeaaa0e8d82fccadb36c04b9454c2b0be2b1698c574da65347ca5edd9290276cc45c84d1a4b340e6dbc63a9e94d Hey, password is required here."},{"title":"【selenium】自动秒杀脚本","date":"2023-05-29T16:06:36.000Z","path":"posts/3R1HHH2/","text":"概要 借助 Python + Selenium 实现 taobao 购物车自动秒杀 TODO 增加定时秒杀的功能 time.sleep的时间把控(短则逻辑错误,长则秒杀失败...) 实测,实战 环境 下面以 windows 环境为例,因为高度依赖 chrome ui 界面操作,linux 暂且按下不表 pip3 install selenium,直接下载最新的 selenium 库 chrome://version/,在 chrome 浏览器输入 以查看当前版本 mirrors/chromedriver/,下载对应版本的 chromedriver.exe 访问网页 只需要掌握 webdriver 的几个核心接口:Chrome、ChromeOptions、find_element options = webdriver.ChromeOptions() 创建一个 chrome 的配置项,实际用途是避开反爬虫的js检测。 具体代码如下,复制粘贴即可: options.add_experimental_option('excludeSwitches', ['enable-automation']) options.add_argument(\"--disable-blink-features=AutomationControlled\") driver = webdriver.Chrome(executable_path='./chromedriver.exe', options=options) 创建一个 chrome 的自动化示例,它会唤起一个谷歌浏览器,但是又不同于正常打开的 chrome.exe。 注意,需要传入对应版本的 chromedriver.exe 的路径。 driver.get('https://cart.taobao.com/cart.htm') 利用 chrome 访问指定的网页 driver.find_element('id', 'xxx') 根据html、css规则,查找指定的对象。 可选的参数有 id、xpath、name… 注意,旧版的方法是 find_element_by_id… 自动登录 前面访问的网页是 cart.taobao.com/cart.htm,如果浏览器没有对应的缓存,则会自动跳转到登录界面。 这时候需要借助 find_element 实现自动登录与跳转。 find_element Arg1 Arg2 用户名 name fm-login-id 密码 name fm-login-password 代码示例为: # 用户名 input = driver.find_element('name', 'fm-login-id') input.clear() # 清空 # time.sleep(0.2) # 防止过快 input.send_keys('*******') # 输入您的用户名 # 密码 input = driver.find_element('name', 'fm-login-password') input.clear() # 清空 # time.sleep(0.2) # 防止过快 input.send_keys('*******') # 输入您的密码 # 跳转 driver.find_element(\"xpath\", \"//*[@id='login-form']/div[4]/button\").click() 自动下单 某宝下单成功有三个步骤 1.购物车中 “勾选商品” 2.点击 “结算” 按钮 (选中商品后,按钮才是可点击态) 3.点击 “提交订单” 按钮 (选择收货地址) 步骤 Arg1 Arg2 1. 勾选商品 id J_SelectAll1 2. 点击结算 id J_Go 3. 提交订单 link text 提交订单 勾选商品 下面简化为购物车内的商品全选。 while 1: try: select_all = driver.find_element(\"id\", 'J_SelectAll1') if select_all: select_all.click() break except: print ('Retry: 全选商品') 点击结算 while 1: try: buy = driver.find_element(\"id\", 'J_Go') if buy: buy.click() break except: print ('Retry: 点击结算') 提交订单 while 1: try: a = driver.find_element(\"link text\", \"提交订单\") if a: a.click() break except: print ('Retry: 提交订单') 代码示例 # coding: utf-8 import time from selenium import webdriver username = '' password = '' TB_LOGIN_URL = 'https://cart.taobao.com/cart.htm' options = webdriver.ChromeOptions() options.add_experimental_option('excludeSwitches', ['enable-automation']) options.add_argument(\"--disable-blink-features=AutomationControlled\") # exe path for chromedriver.exe exe_path = r'E:\\chromedriver.exe' driver = webdriver.Chrome(executable_path=exe_path, options=options) driver.get(TB_LOGIN_URL) input = driver.find_element('name', 'fm-login-id') input.clear() driver.implicitly_wait(0.5) print (\"Username:\", username) input.send_keys(username) input = driver.find_element('name', 'fm-login-password') input.clear() driver.implicitly_wait(0.5) print (\"password:\", password) input.send_keys(password) driver.find_element(\"xpath\", \"//*[@id='login-form']/div[4]/button\").click() time.sleep(0.2) # while 1: # try: # select_all = driver.find_element(\"id\", 'J_SelectAll1') # if select_all: # select_all.click() # break # except: # print ('Retry: 全选商品') # time.sleep(0.2) # while 1: # try: # buy = driver.find_element(\"id\", 'J_Go') # if buy: # buy.click() # break # except: # print ('Retry: 点击结算') # time.sleep(0.2) # while 1: # try: # a = driver.find_element(\"link text\", \"提交订单\") # if a: # a.click() # break # except: # print ('Retry: 提交订单') assert(0)"},{"title":"【GAMES101】Anti-Aliasing","date":"2023-05-28T13:13:49.000Z","path":"posts/games101-aa/","text":"信号处理、抗锯齿 导读 LearnOpenGL: Anti Aliasing GAMES101: Rasterization and Anti Aliasing 知乎:如何理解傅里叶变换公式? 知乎:如何理解图像经傅里叶变换后所得频谱图意义? 信号处理 了解反走样与抗锯齿之前,需要学习一些基础的信号处理相关知识,其中最重要的就是 时域、频域 及两者的相互转化关系。 1. 时域 (Time-domain) 时域 是描述一个数学函数(或物理信号)对于时间的关系(函数) 电脑上的音频文件(例如mp3、wmv…)就是一个时域信号的典型例子,其本质是音频(波形图)在时间上的分布,如下所示: 横轴:时间 t 纵轴:声音的振幅 声音信号是由 高音、中音、低音 三个部分组成,如果我们想加强低音部分,仅仅通过时域信号是没法修改的。因为高中低音在时域中是混合在一起的,没法通过逆变换将他们抽离开来。 因此引申出 频域 的概念 ↓ 2. 频域 (Frequence-domain) 频域 是描述频率分布的关系 频域的理解有点困难,需要结合时域一起看(如下图)。时域上的信号可以看做不同频率的信号的叠加,因此频域就是展示这些不同频率信号的分布。 横轴:频率 纵轴:该频率的振幅(>0) 空域 (Spatial-domain) 又称为空间域、图像空间(image space),可以理解为像素在屏幕空间(x,y)的分布。 时域与频域 时域与频域 表示的是同一个信息,只不过呈现的方式不同。 下面以标准正弦函数 $sin(x)$ 为例,展现时域和频域的不同表示: 函数 示意图 时域 $y = sin(t)$ 频域 $f = \\frac{1}{2π}$ 傅里叶变换(Fourier Transform) 关于傅里叶变换 这部分浅尝辄止,理解基本概念和用法即可 任何函数都可以表示为 $sin$、 $cos$ 函数的组合 将时域转化到频域(傅里叶变换) 将频域转化到时域(逆变换) 什么是走样(Aliasing)? 如下概括了三种常见的走样的导致的 Artifacts: Artifacts 原因 示例 Jaggies 空域采样频率不足 Moiré Patterns 空域采样频率不足 Wagon wheel effect 时域采样频率不足 demo 为什么产生走样? 从信号处理角度理解 Signals are changing too fast (high frequency), but sampled too slowly. —— GAMES101 将图像信息看做空域信号,根据傅里叶变换得出,可以表示为任何正弦信号的叠加,因此光栅化的过程可以理解为:以某个频率对原始信号进行采样的过程。 由于造物主的超然存在,自然界的所有采样频率可以当做无穷的(时间是连续的、世界是无穷多个像素点…),而妄图用有限的频率去描述、去采样现实世界,即用有穷描述无穷,必然会带来信号和信息的损失。 以下图为例,采样频率固定时,当原始信号的频率越低,采样产生的误差也就越小;当原始信号的频率越高,采样产生的误差越大。 上面3种常见的 采样导致的Artifacts,都是因为采样的频率较低(低于原图像的变化频率)。 原始图像的信号不是随着时间而变换(时域变换),而是随着空间中x、y的值而变化(空域变换)。信号的变化频率高是指:像素之间的RGB颜色是否发生骤变(如白色255变成黑色0)。 从这个角度理解走样的原因: 为什么锯齿都在图形的边缘? 因为内部的信号变化慢,低频采样没什么影响;而边缘的信号变换频率会骤增。 如何进行反走样(Anti-Aliasing)? Anti-Aliasing,又称为反走样、抗锯齿,提高采样频率是最直接的反走样方案,如增加分辨率。 下面介绍一些常见的AA理论。 Blurring 模糊化是反走样的一个基本操作,即在光栅化之前对原始图像做一个模糊的操作。 High-Pass Filter 高通滤波,即高频的信号可以通过,过滤掉低频的信号 Low-Pass Filter 低通滤波,即低频的信号可以通过,过滤掉高频的信号 Convolution 前面的滤波技术,本质不是丢弃某些特定频率的信号,而是把它们变成一个新的信号。即$m*n$的信号通过滤波之后,得到的仍然是 $m*n$ 的信号。它的本质就是 将信号在时域上卷积,即求平均值。 滤波器:一个固定大小的信号,如 $1*3$,且每个信号都对应一个数值(系数),如下所示: 对于每个像素,根据滑动窗口计算 经滤波器后 的取值。 示例,对应为 $5$ 的像素,滤波后的结果是 $3*1/4 + 5*1/2 + 3*1/4 = 4$ 下图是一个 1/9 的滤波器,它起到了图像模糊化的作用,其本质也是一个低通滤波器(模糊的本质,是去掉高频变换的边缘区域) 理解困难 如何理解:时域(空域)的乘积,本质是频域上的卷积 MSAA MSAA(Multi-Sampling AA) 的本质是在更高倍分辨率下进行采样。 下图是一个 4 x MSAA 采样的示例,即对于每个像素,采样其中四个子像素,根据是否在三角形内的比例,得出其颜色的贡献值。 MSAA的额外开销 n-MSAA,意味着 N 倍的采样量 可以尽可能的复用之前的采样值 FXAA FXAA(Fast-Approximat AA) 是在屏幕空间的快速近似抗锯齿,它是在后处理阶段进行的。 在有锯齿的图像上,找到其边界,并替换为无边界的图像,与采样无关。 TAA TAA(Tempol AA) 将采样从单帧扩展到多帧,尽可能地复用之前帧的结果。需要额外处理动态物体的情况。 写在最后 MSAA,FXAA,TAA等抗锯齿方案需要更深入的学习和实践,这里只是浅尝辄止"},{"title":"【GAMES101】Rasterization","date":"2023-05-26T15:13:46.000Z","path":"posts/M5TXVE/","text":"光栅化与硬件基础 前集提要 GAMEASA101-Transformation描述了基础的线性变化,以及MVP变换的过程 本文讨论的是在MVP变换后,如何将一个正交的 $(0, 1)^{3}$ 坐标映射到屏幕坐标上 显示设备 CRT 阴极射线管 阴极射线管(英语:Cathode ray tube,缩写 CRT),是较早的显示仪器,曾广泛的应用于示波器、电视机和显示器上。 其原理是利用阴极电子枪发射电子,在阳极高压的作用下,射向荧光屏,使荧光屏上的荧光粉发光。 同时电子束能够在偏转磁场的作用下,作上下左右的移动来达到扫描显示的目的。(高中物理的磁场计算题…) 早期的CRT仅能显示光线的强弱,因此是黑白画面;直到1941年,CRT才支持红绿蓝三种颜色的电子束,这告别着彩色电视机技术的问世。 因为采用电子束的技术,CRT显示器具有亮度高、伤眼睛的特点。因此早期小朋友看彩电时,经常被家长督促离得远一些。 同时 CRT显示器 的分辨率做不高、屏幕做不到,导致2000年后逐渐被LCD、LED取代。 知乎:为什么 CRT 画质这么好也被淘汰,液晶反而发展的很好? 隔行扫描技术 通常的显示器在成像时,会从上到下地扫描每帧图像。这个过程消耗的时间很长、占用带宽也很高。 因此CRT显示器采用一种 “隔行扫描” 的技术,即每次只传输和显示一半的图像,一场只包含奇数行或者偶数行。由于人眼具有视觉暂留效应,所以仍然会看到完整的一帧画面。 视觉暂留效应 光对视网膜所产生的视觉,在光消失后,仍然会保留一段时间(约1/16秒) 例如日常使用的日光灯每秒大约熄灭100次,但不会感觉到灯光的闪动 LCD & LED LCD LED 功耗 ❌ 功耗高10倍 寿命 ✔ 寿命更长 视角 ❌ 视角较小 ✔ 视角宽达160° LCD液晶显示器 和 LED发光二极管 涉及较多的物理和光学专业知识,这里不详细展开,就对比两者的优劣和差异。 E Ink 电子墨水屏 电子墨水技术是由 E Ink Corporation 公司提出,常被用于制作电子显示器,例如大名鼎鼎的 Amazon Kindle 它的表面由大量包含正负电子的微胶囊组成,当设置电场为正时,白粒子向微胶囊的顶部移动,所以呈现白色;反之电场为负时,呈现黑色。 电子墨水的一大缺点时,即重置屏幕时具有延迟,因为要通过改变电场使粒子发生移动,例如Kindle阅读翻页时有明显等待。 光栅化 Pixels 首先如何从计算机的角度定义一个屏幕? 颜色:(R,G,B) 坐标:由分辨率决定的二维数组 左下角:$(0,0)$,右上角: $(width-1, height-1)$ 像素实际坐标:$(x+0.5, y+0.5)$ 要解决的实际问题: 将$[-1, 1]^{2}$ 映射到 $[0, width] \\times [0, height]$ 先经线性变化(先从屏幕左下角平移到屏幕中间,再经过缩放操作): Triangles 为什么用 三角形 表示几何物体? 顶点数最少的平面几何(再少一个点就是线段了) 任何多边形都可以由三角形组成(三生万物) 利于做差值、判断内外等运算 一些常见的三维模型格式,例如obj、fbx,其内部都是用三角形(或者Poly 即两个三角形)表示模型的。 可以看这篇文章:Model and Mesh 问题建模 光栅化实际要解决的问题如下: 对于每个三角形,逐个像素判断是否在三角形内,是则着色,不是则跳过。 判断三角形内 通过三次叉乘实现 参考这篇文章:【GAMES101】Transformation for tri in tris: # 所有三角形 for (x, y) in pixels: # 所有像素 output[tri, x, y] = inside(tri, x, y) 观察如上算法,是一个 $O(N^{3})$ 的遍历算法,是否有优化的方法呢? 加速:AABB 利用三角形的包围盒来加速。 实际绘制中,不可能每个三角形都填充满整个屏幕,因此可以通过AABB剔除掉在包围盒外的像素点。 加速:Incremental Triangle Traversal 可以理解为更精细的AABB。 从三角形每排的最左侧到最右侧执行光栅化,基本不会多遍历一个像素点。"},{"title":"【CSAPP】Information Storage","date":"2023-05-22T14:13:19.000Z","path":"posts/24H1CZ3/","text":"原码、反码、补码、Integer、Float… 材料 CSAPP chapter 2 CSAPP 重点解读 IC S解读 0.30000000000000004.com Bit Hacks(位运算的奇技淫巧) 导读 CSAPP花费较多篇幅介绍计算机的二进制系统,以及“1字节=8比特”的设计。既然人类已经习惯使用十进制计数法,为什么计算机要改用二进制呢? 二进制 对应电路中的高低电平,容易区分 二进制 利于物理存储,磁极、凹凸、光照等 推荐阅读如下前置知识: 计算机架构入门:CPU、存储器 Virtual Memory 大端小端 先看什么是大端、什么是小端,这些统称为字节序(Endianess) 假设 int a = 0x01234567,地址为 0x100,因为int类型占4个字节,所以写入 0x100 0x101 0x102 0x103 这四个字节的内存地址: 大端(Big-Endian) 将 数据的低字节 放在 内存的高位,符合人类的阅读习惯,又称为 network byte order 小端(Little-Endian) 将 数据的低字节 放在 内存的低位,与人类的阅读习惯相反,又称为 host byte order 大小端利弊 为什么没有厂商一统江湖,选择 “利于阅读” 的大端呢? 原因是两种存储方式各有利弊,谁也无法说服谁… 知乎: 大小端字节序存在的意义,为什么不用一个标准 因为 “符号位” 存储在第一个字节(后文讲到),此时大端能快速判读大小、正负 执行加法运算时,高位往往需要添加额外的数据,此时小端效率更高,大端要额外移动 常见字节序 大端:IBM,JPEG … 小端:Intel,BMP … 观察字节序 // Demo_2:观察字节序的一个简单例子 typedef unsigned char *pointer; void show_bytes(pointer start, size_t len){ size_t i; for (i = 0; i < len; i++) printf(\"%p\\t0x%.2x\\n\",start+i, start[i]); printf(\"\\n\"); } int main(){ int a = 0x01234567; show_bytes((pointer) &a, sizeof(int)); } // 输出结果(Linux x86-64) Demo$ gcc -o chp2 chp2.c ; ./chp2 0x7ffc837a0b3c 0x67 0x7ffc837a0b3d 0x45 0x7ffc837a0b3e 0x23 0x7ffc837a0b3f 0x01 记录一个未区分大小端导致的Bug 如下定义了一个 struct 类型,大小是 32位,占 4字节。 假设 idx 存储的数据是 0x1234 当在shader中解析 idx 的取值时,因为存储在贴图的通道中,所以只能逐字节decode。 由于默认 0x12 在 低位,所以解码为 L + H << 8,但是对于小端存储的机器(如Intel CPU),这个取值就是错的,因为实际算出来是 0x34 + 0x12 << 8 struct DataDesc { uint16_t idx; uint8_t count1; uint8_t count2; } 修复方法 利用 htons 将小端统一转化为大端存储,htons(3) - Linux man page 注意头文件,Linux下是 #include <arpa/inet.h>,windows下是 #include <winsock.h> 原码 最高位表示符号位,其他位存放数值 数据的存储只能是 0 和 1 两种状态,如果想区分一个数值的正负,先人提出用 “最高位” 的 0 和 1 来表示: 1:负数 0:正数 思考一下为什么用1表示负数,而不是0呢? 假设用 3 bits 表示一个整数,则 1个符号位 加上 2个数值位,可以表示的范围是 -3 ~ +3,如下表: 二进制 符号位 真值 000 + +0 001 + +1 010 + +2 011 + +3 100 - -0 101 - -1 110 - -2 111 - -3 原码的缺陷 原码中存在两个0,即正零和负零,意味着判断是否要0要两个时钟运算… 原码作运算时,如果符号位不同,会导致计算结果错误(需要硬件额外处理): 001 + 101 = 110,表示 1 + (-1) = -2 反码 正数的反码是原码,负数的反码是符号位除外、其他按位取反 反码的出现是为了解决原码的弊端,即 “相反数之和不为0”。这里干脆从结果反推,为了使和为0,不如用一个整数的“按位取反” 来表示负数。 例如,001表示+1,则-1的反码是110,此时相加为 001 + 110 = 111 = 0 原码 反码 真值 000 000 +0 001 001 +1 010 010 +2 011 011 +3 100 111 -0 101 110 -1 110 101 -2 111 100 -3 反码的缺陷 仍然有两个0 表示有点反人类,不够直观 补码 正数和0的补码是原码,负数的补码是反码 +1 如何根据补码求真值? 牢记口诀,正数照求,负数将数值部分按位取反 +1 如下表,3 bit 补码的真值范围是 -4 ~ 3,比原码、反码多出一个值,是因为去掉了重复的正负零。 补码(正数略) 真值 - - 100 -4 101 -3 110 -2 111 -1 因为补码是现代计算机硬件应用最广泛的编码方式,顺便提一下补码的运算规则: 加法 不论正负数,直接对补码相加即可: 3 + (-2) = 011 + 110 = 001 = 1 减法 补码的减法,实际就是加一个负数,道理和加法是相同的: 3 - 2 = 3 + (-2) ... 浮点数 C++中计算浮点数加法时,经常会出现 0.1 + 0.2 = 0.30000000000000004 的类似现象,导致浮点数判相等很困难,这也是源于 IEEE 754 的存储方式。 首先,不论是0.1 还是0.2,在十进制这是一个有限的小数,但是在计算机二进制的表示中,他们是无限不循环的,这就造成了运算的精度误差。 小数的二进制 如何理解小数二进制 核心是科学计数法,十进制的 $0.525 = 5 * 10^{-1} + 2 * 10^{-2} + 5 * 10^{-3}$ 对应二进制为:$0.525 = 1 * 2^{-1} + 1 * 2^{-2}$ 口诀是:乘2取整,顺序排列 有限不循环的十进制,在二进制下 可能无限不循环,而数据存储的bits是有限的,这就是浮点数误差的根本原因。 以小数 0.2 转成二进制为例,按照上面的算法得出如下的计算过程: 依次乘2 取整数 二进制 0.2 * 2 = 0.4 0 0.0 0.4 * 2 = 0.8 0 0.00 0.8 * 2 = 1.6 1 0.001 0.6 * 2 = 1.2 1 0.0011 0.2 * 2 = 0.4 0 0.00110 …循环 0.00110011… IEEE 754 通用的浮点数标准是 IEEE 754,在1985年提出并沿用至今。 下面以浮点数 178.125 为例,描述二进制的计算过程: 整数部分:178,二进制为 10110010 小数部分:0.125,二进制为 001 合起来二进制为 10110010.001 转换成二进制的科学计数法,为 1.0110010001 * 2 ^ 7,7用二进制表示为111 接着对照下面的公式,代入三个部分的取值: 符号位 S = 0 尾数 M = 0110010001 指数 E = 111,结合偏移值得到 10000110 因此 178.125 的单精度浮点数是 0x43322000 32位浮点数 转换公式 $V = (-1)^{S} \\times M \\times R^{E}$ S:符号位,0为正、1为负 M:尾数(float),对于 $2.18 \\times 10^{-2}$ 中的 2.18 E:指数(int),对于 $2.18 \\times 10^{-2}$ 中的 -2 R:基数,十进制为10,二进制位2"},{"title":"【Python】UnitTest单元测试","date":"2023-05-21T15:16:02.000Z","path":"posts/A20MTJ/","text":"概要 工作中经常遇到一些阻碍开发流程的提交,希望通过 UnitTest 避免类似的commit todo: Python unittest,关于自动化测试的方法,测试理论等 plan: 预期这周内完成,伴随一些工作的脚本开发 TODO: 尝试学习 pytest pytest, 一个神奇的Python库"},{"title":"PDCA 闭环思维","date":"2023-05-20T07:18:56.000Z","path":"posts/120QJ0A/","text":"提要 阅读材料来源:知乎:有什么行为习惯昭示着你是个编程**? 本文都从工作的角度讨论,不涉及个人生活作息 为什么讲闭环 闭环思维,决定你是否能把一件事做到完美,决定你是否能成为一个靠谱的人。 通俗理解闭环思维,就是无论做什么事情,都要有始有终,能够形成一个完整的闭环。 五个核心 通俗地理解,闭环思维有五个核心,须牢记于心: 凡事有计划 约定必落实 问题早只会 及时报进程 事后须反馈 PDCA循环 ⭐ 闭环思维的理论依据是“PDCA循环”,由美国管理专家 休哈特博士 提出,将每件事分为四个阶段: Plan:计划 Do:落地 Check:检讨/检查 Action Improve:改善/改进 如下图所示,一个需求可能需要多个回合的改善才能完成,即代表多个“PDCA循环”: 这个模型完全可以应用于程序的日常开发,过去自己往往只关注 Do 的部分,而忽略了其他三个环节。 后面的改进: 需求提出初期,明确 Plan 环节 推动落地期间,逐步记录 Do 的过程 需求开发完毕,主动 Check 完成度、潜在的bug、回归范围 提出待改善项,即 Improve 针对目前的工作流程,如下是一个 PDCA 的示例: PDCA 示例 Plan ∘ 3月5日完成功能A ∘ 3月7日完成功能B Do ∘ 3月5日:完成A,同时优化了xxx ∘ 3月8日:完成B,同时修复了xxx Check ∘ A的性能存在问题 ∘ B的code review有两处comments Improve ∘ 计划4月通过xxx优化方案解决A的性能问题 ∘ code review问题已修复 ∘ B完成延期一天,原因是xxx 5W2H分析法 What:先阐述清楚要做的事情是什么,确保合作职能可以理解 Why:为什么要做这件事? Who:参与协作的职能 When:交付时间 Where How:准备怎么做 How much:做到什么程度"},{"title":"【GAMES101】Transformation","date":"2023-05-12T17:55:34.000Z","path":"posts/30P8RVY/","text":"向量与线代、空间变换、投影 点乘 $\\vec {a} \\cdot \\vec {b} = |\\vec {a}| |\\vec {b}| cos \\theta$ 几何意义:$\\vec {a}$ 在 $\\vec {b}$ 方向上的投影与 $\\vec {b}$ 的乘积 图形学应用 衡量两个向量是否接近:值越大越相近 判定两个向量是否同向:正值同向、0为垂直、负值反向 叉乘 几何意义:$\\vec {a}$ 和 $\\vec {b}$ 围成平行四边形的面积 $\\vec {a} \\times \\vec {b}$ 垂直于两个向量所在的平面 方向:右手螺旋定理 四个手指顺着 $\\vec {a}$ 指向 $\\vec {b}$ 的方向,则大拇指表示 $\\vec {a} \\times \\vec {b}$ 的方向 图形学应用 判定左右:$\\vec {a} \\times \\vec {b}$ 叉乘为正,说明 $\\vec {a}$ 在 $\\vec {b}$ 的右侧 判定内外: 需要三次左右判定,如可以通过 $\\vec {AP} \\times \\vec {AB}$ 得到 $P$ 在 $\\vec {AB}$ 的左侧 正交左手(右手)系 可以通过 “叉乘” 定义一个正交坐标系 $|\\vec {x}| = |\\vec {y}| = |\\vec {z}| = 1$ $\\vec {x} \\cdot \\vec {y} = \\vec {x} \\cdot \\vec {z} = \\vec {y} \\cdot \\vec {z} = 0$ $\\vec {y} = \\vec {z} \\times \\vec {x}$ (左手系 or 右手系) 矩阵 $(M \\times N)$: 表示 $M$ 行 $N$ 列的矩阵 $(M \\times N) (N \\times P) = (M \\times P)$ 线性变换 缩放 以 $x$,$y$ 分别缩放 $a$,$b$ 为例: $x’ = ax$ $y’ = by$ $ \\left[\\begin{matrix} x’\\\\y’ \\end{matrix}\\right] = \\left[\\begin{matrix} a & 0\\\\0 & b \\end{matrix}\\right] \\left[\\begin{matrix} x\\\\y \\end{matrix}\\right] $ 反射 以绕y轴反射为例: $x’ = -x$ $y’ = y$ $ \\left[\\begin{matrix} x’\\\\y’ \\end{matrix}\\right] = \\left[\\begin{matrix} -1 & 0\\\\0 & 1 \\end{matrix}\\right] \\left[\\begin{matrix} x\\\\y \\end{matrix}\\right] $ 旋转 以绕坐标原点旋转 $\\theta$ 角为例: $ \\left[\\begin{matrix} x’\\\\y’ \\end{matrix}\\right] = \\left[\\begin{matrix} cos\\theta & -sin\\theta\\\\sin\\theta & cos\\theta \\end{matrix}\\right] \\left[\\begin{matrix} x\\\\y \\end{matrix}\\right] $ 平移 尝试了一波,发现没法用一个 $2\\times2$ 的矩阵表示二维的平移变换? (后续为了解决此问题,引入了第三维度 $w$) 齐次坐标系 (Homogeneous coord.) 为什么引入 齐次坐标系? 考虑用 $M_{2 \\times 2}$ 叠加平移变换 $x' = x + p$ $y' = y + q$ 二维变换下,$x' = M_{2 \\times 2} \\quad x$ 无法作常量的平移变换 只能写成 : $\\left[\\begin{matrix}x'\\\\y'\\end{matrix}\\right] =\\left[\\begin{matrix}a & b\\\\c & d\\end{matrix}\\right]\\left[\\begin{matrix}x\\\\y\\end{matrix}\\right] +\\left[\\begin{matrix}p\\\\q\\end{matrix}\\right]$ 平移变换 $ \\left[\\begin{matrix} x’\\\\ y’\\\\ w’ \\end{matrix}\\right] = \\left[\\begin{matrix} 1 & 0 & p\\\\ 0 & 1 & q\\\\ 0 & 0 & 1 \\end{matrix}\\right] \\left[\\begin{matrix} x\\\\ y\\\\ 1 \\end{matrix}\\right] = \\left[\\begin{matrix} x+p\\\\ y+q\\\\ 1 \\end{matrix}\\right] $ $w$ 维度 区分 点 和 向量 点:$(x, y, 1)^{T}$ 向量:$(x, y, 0)^{T}$ 区分 二维运算 向量 +/- 向量:$w$ 维度为0,得到向量 点 - 点:$w$ 维度为0,得到向量 点 + 向量:$w$ 维度为1,得到点 点 + 点:$w$ 维度为0,得到点 $(x, y, w)^{T}$几何意义 $w = 0$:表示向量 $w ≠ 0$:表示点 $(\\frac{x}{w}, \\frac{y}{w}, 1)$ 总结 此时可以表示 Scale、Rotation、Translation 三种线性变换: Matrix Scale $\\left[\\begin{matrix}s_{x} & 0 & 0\\\\0 & s_{y} & 0\\\\0 & 0 & 1\\end{matrix}\\right]$ Rotation $\\left[\\begin{matrix}cos\\theta & -sin\\theta & 0\\\\sin\\theta & cos\\theta & 0\\\\0 & 0 & 1\\end{matrix}\\right]$ Translation $\\left[\\begin{matrix}1 & 0 & t_{x}\\\\0 & 1 & t_{y}\\\\0 & 0 & 1\\end{matrix}\\right]$ 另外,可以用 $M^{-1}$ 表示逆变换(反向操作,逆函数) 欧拉角 知乎: 如何通俗地解释欧拉角? Wikipedia: Eular angles 欧拉角使用三个角度来描述刚体在 $xyz$ 坐标系中的旋转,这三个角度分别表示绕 “三个轴” 旋转的角度,不同顺序会产生不同的结果 也可以用 roll, pitch, yaw 来表示欧拉角,如图所示 旋转所绕的轴有两种划分 世界坐标系(静止):$xyz$ 局部坐标系(动态):$XYZ$ TODO 万向节死锁这部分没完全弄懂 万向节死锁 MVP变换 首先前面提到过 $(x, y, z, w)^{T}$ 表示三维的信息 $w = 0$: 表示向量 $(x, y, z)$ $w ≠ 0$:表示点 $(\\frac{x}{w}, \\frac{y}{w}, \\frac{z}{w})$ 三维空间的点,最终展现到二维屏幕上,需要经过一些列的空间变换,可以概括为 “MVP” MVP 变换可以想象为一个拍照的过程: Model 变换 “将人群和景色放到一个合适的位置” 将所有模型移动到统一的世界坐标下(world space) 局部坐标 -> 世界坐标 View 变换 “找一个合适的相机角度” 如何描述相机的信息? 坐标:$\\vec e = (x_{e}, y_{e}, z_{e})$ 相机朝向 Look-at:$g$ 相机上方向 Up:$t$ 通过一个 View矩阵 $M_{view}$ 将所有模型转换到 view空间,只需要 平移变换 + 旋转变换: 即 $M_{view} = R_{view} T_{view}$ 先平移 $T_{view}$ 平移变换是为了将相机移到中心原点,因此矩阵很好得到: $ \\left[\\begin{matrix} x’\\\\y’\\\\z’\\\\w’ \\end{matrix}\\right] = \\left[\\begin{matrix} 1 & 0 & 0 & -x_{e}\\\\0 & 1 & 0 & -y_{e}\\\\0 & 0 & 1 & -z_{e}\\\\0 & 0 & 0 & 1 \\\\ \\end{matrix}\\right] \\left[\\begin{matrix} x\\\\y\\\\z\\\\w \\end{matrix}\\right] = \\left[\\begin{matrix} x - x_{e}w\\\\y - y_{e}w\\\\z - z_{e}w\\\\w \\end{matrix}\\right] = \\left[\\begin{matrix} x - x_{e}\\\\y - y_{e}\\\\z - z_{e}\\\\1 \\end{matrix}\\right] $ 后旋转 $R_{view}$ 拆解开来,需要作如下三个轴的旋转: $g$ 旋转到 $- \\vec Z$ $t$ 旋转到 $\\vec Y$ $g \\times t$ 旋转到 $\\vec X$ 将 “局部轴旋转到$XYZ$轴” 很困难,因此可以求逆变换:“将$XYZ$轴旋转到局部轴” 将 $\\vec X (1, 0, 0)$ 变换到 $(x_{g \\times t}, y_{g \\times t}, z_{g \\times t})$ 将 $\\vec Y (0, 1, 0)$ 变换到 $(x_{t}, y_{t}, z_{t})$ 将 $\\vec Z (0, 0, 1)$ 变换到 $(x_{-g}, y_{-g}, z_{-g})$ 将这三个特殊值代入,可以解出 $R_{view}^{-1}$ 矩阵为: $ R_{view}^{-1}= \\left[\\begin{matrix} x_{g \\times t} & x_{t} & x_{-g} & 0\\\\ y_{g \\times t} & y_{t} & y_{-g} & 0\\\\ z_{g \\times t} & z_{t} & z_{-g} & 0\\\\ 0 & 0 & 0 & 1 \\\\ \\end{matrix}\\right] $ 根据转置矩阵得到 $R_{view}$: $ R_{view}= \\left[\\begin{matrix} x_{g \\times t} & y_{g \\times t} & z_{g \\times t} & 0\\\\ x_{t} & y_{t} & z_{t} & 0\\\\ x_{-g} & y_{-g} & z_{-g} & 0\\\\ 0 & 0 & 0 & 1 \\\\ \\end{matrix}\\right] $ 逆向思维 “求逆” 是线性代数解决问题的常见思路 Projection 变换 “按下快门!拍照” 投影变换是最重要的部分,因为它塑造了 “立体感”,它分为两种: Perspective Projection: 近大远小,符合人眼 Orthographic Projection:远近一致 正交投影 疑问? 正交投影 为什么把任何立方体投影到一个(-1, 1)的正方体? 透视投影 TODO 这部分有点复杂,教程中利用 “相似三角形” 和 “特殊值法” 求出了矩阵,有空补一下"},{"title":"【C99】setjmp.h","date":"2023-05-10T03:40:54.000Z","path":"posts/3CKYSDZ/","text":"学习setjmpC标准库, 实现轻量级协程 写在最前面 setjmp 是C99的一个标准库, 其实现了 non-local jumps, 本次学习路线是: 阅读手册、文档 ✔: man(3), wikipedia 阅读代码示例 ✔: wikipedia: Example usage coding: M2: 协程库 (libco) 可选: 阅读 setjmp.h 的源码 man setjmp what 准确说是 man 3 setjmp, 3 的含义是 Linux Programmer's Manual. 先看其基本的定义: setjmp, sigsetjmp, longjmp, siglongjmp - performing a nonlocal goto 也就是说, setjmp的作用是执行一个 nonlocal goto; 为什么说 nonlocal? 举个例子, 看下面的 C 代码, main函数 中尝试借助goto跳转到 func函数内部, 编译会报错 因为 goto 只能实现函数堆栈内部的跳转,即 local jump! void func() { outer: printf(\"into func...\"); } int main() { inner: // ... goto outer; // error: label \"outer\" used but not defined goto inner; // ok // ... } how 继续阅读 Description 部分: The setjmp() function dynamically establishes the target to which control will later be transferred, and longjmp() performs the transfer of execution 直译过来就是说,setjmp() 扮演的是定义 label 作用,longjmp 扮演的是goto跳转作用 继续看这两个函数体的定义: int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val); setjmp 接收一个 jmp_buf env 的参数, 看看手册是如何解释 env 的: The setjmp() function saves various information about the calling environment (typically, the stack pointer, the instruction pointer, possibly the values of other registers and the signal mask) in the buffer env for later use by longjmp() 写的很直白了, env 保存了调用处的局部环境,例如 栈指针、pc指针… (这块如果看不懂,需要恶补汇编知识,建议阅读:汇编(三):基础AT&T汇编) setjmp 负责写入env,longjmp 会读取env并恢复调用时的环境,这样就达到了 nonlocal goto 的作用 ok基本看明白了,但是还有一个 int val 的参数,作用是什么? 这里我觉得 man手册 没说清楚,自己总结下吧 如果是 setjmp 的直接调用:返回 0 如果是 longjmp 的跳转调用:返回 一个非0 的 参数 setjmp的两次返回值 这里很抽象,需要结合代码理解 StackOverflow: What is the use of setjmp() returning 0? 注意点 阅读 man手册 的 Caveats 部分,有两点注意事项: 如果 调用 setjmp 的函数 在 longjmp 调用之前返回,那么行为不确定. 如果 在多线程 中使用 nonlocal env,那么行为不确定. 这两点在实际开发中暂时不会遇到,先列在这里吧 code example talk is cheap, show me the code:这部分开始讨论代码 下面的代码展示了 setjmp / longjmp 的基本使用: 核心:记住 setjmp 处会被call两次 第一次是用户自己调用的 第二次是 longjmp call 回来的 // https://en.wikipedia.org/wiki/Setjmp.h #include <stdio.h> #include <setjmp.h> jmp_buf env; int longjmp_ret = 8; // paramater pass to longjmp void second() { printf(\"second 1\\n\"); // √ longjmp(env, longjmp_ret); printf(\"second 2\\n\"); // × } void first() { printf(\"first 1\\n\"); // √ second(); printf(\"first 2\\n\"); // × } int main() { int val = setjmp(env); if (!val) { printf(\"setjmp return: %d\\n\", val); first(); // when setjmp executed, setjmp returns 0 } else { // when longjmp returns, setjmp returns 1 printf(\"setjmp return: %d\\n\", val); printf(\"main :: else\\n\"); } return 0; } // setjmp return: 0 // first 1 // second 1 // setjmp return: 8 // main :: else setjmp 实现协程 实验要求 看到这里觉得自己很牛逼了,觉得都会了,是骡子是马,上实验遛遛: 这里 NJU 操作系统的实验,利用 setjmp 实现一个轻量级的协程: M2: 协程库 (libco) 首先你要理解什么是 协程 (Python Generator 就是一种协程) 其次实现实验里给出的 api: co_start: 创建一个新的协程,并返回一个指向struct co的指针(类似于 pthread_create) co_wait(co): 表示当前协程需要等待,直到co协程的返回才能继续执行(类似于 pthread_join) co_yield(): 将当前协程“切换”出去,随机选择下一个线程执行 // co.h struct co* co_start(const char *name, void (*func)(void *), void *arg); void co_yield(); void co_wait(struct co *co); 如下是一个使用的例子: #include <stdio.h> #include \"co.h\" int count = 1; // 协程之间共享 void entry(void *arg) { for (int i = 0; i < 5; i++) { printf(\"%s[%d] \", (const char *)arg, count++); co_yield(); } } int main() { struct co *co1 = co_start(\"co1\", entry, \"a\"); struct co *co2 = co_start(\"co2\", entry, \"b\"); co_wait(co1); co_wait(co2); printf(\"Done\\n\"); } TODO 为什么这里 count++ 不会有线程安全问题? 其中co1和co2这两个协程共享 count 变量,因此输出是: b[1] a[2] b[3] b[4] a[5] b[6] b[7] a[8] a[9] a[10] Done 一个小小的调试技巧: 当你不希望某些调试用的输出,出现在正式环境,可以借助宏重写 printf: (其中 __VA_ARGS__ 用来表示不定数量的参数) #ifdef DEBUG_MODE #define debug(...) printf(__VA_ARGS__) #else #define debug(...) #endif 编译时增加 -DDEBUG_MODE 的编译选项,即可打开 DEBUG_MODE 宏,即实现了输出控制 VSCode 调试 C++: 因为一直 VSCode Remote-SSH 在服务器上写代码,不适合用 VS、Clion 等现代IDE调试,gdb这种又贼难用,所以配了一下VSCode调试的环境,看 这篇博客; 限制还是有的:1. 只适合小型的 c/c++ 项目,2. 依赖太多三方库等文件时,launch.json不太好写 实现 首先将协程的状态分为几类: enum co_status { CO_NEW = 1, // 新创建 CO_RUNNING = 2, // 已经执行 CO_WAITING = 3, // 在 co_wait 上等待 CO_DEAD = 4, // 已经结束 }; 状态的划分很重要,因为 co_yield 会选取下一个幸运儿进行调度执行,选择标准就是协程的状态; 理想情况下,选取一个 CO_WAITING 状态的协程继续执行,正在执行的协程状态是 CO_RUNNING,且同时有且只能有一个 CO_RUNNING… 那么选取下一个协程时,有两种情形要处理: 有 CO_WAITING:直接切换 无 CO_WAITING:怎么办? 继续执行?显然违背了 yield 的原理 正确做法是切回到 main(可以将 main 理解为一个主协程)"},{"title":"【OS】多线程之互斥算法","date":"2023-05-07T16:58:38.000Z","path":"posts/14ZY2JK/","text":"导读 Mutex (Mutal Exclusion): 即相互排斥, 即多个线程不能执行同一段代码(指令) PPT: 理解并发程序执行 | B站: 理解并发程序执行 (Peterson算法、模型检验与软件自动化工具) 之前的多线程用C++实现, 这篇改用Python实现, 因为重点是算法思想而非语言 Todo 互斥算法的正确性证明: 画状态机, 暴力穷举, Model checking Todo C++, Python: 多线程, 锁等相关 问题背景 StackOverflow: Why using multiple threading to get the sum is incorrect? 先看一个经典的 Python多线程求和, 经典面试题目, 经典并发Bug: import threading N = 1000000 x = 0 def add(): global x for _ in range(N): x += 1 t1 = threading.Thread(target=add) t2 = threading.Thread(target=add) t1.start(); t2.start() t1.join(); t2.join() print(x) 什么N取值较小(如1000), 结果却是正确的? todo 输出结果分布在 < 200w 之间, 通过前面的学习已经知道, x ++ 这个操作是 非线程安全的 查看 Python汇编得到 foo 函数的汇编, 看到 x ++ 被拆分为 3条汇编指令 LOAD_GLOBAL: 读取x取值 INPLACE_ADD: 执行加1 STORE_GLOBAL: 写入x取值 12 LOAD_GLOBAL 1 (x) 14 LOAD_CONST 2 (1) 16 INPLACE_ADD 18 STORE_GLOBAL 1 (x) 20 JUMP_ABSOLUTE 8 -> 22 LOAD_CONST 0 (None) 24 RETURN_VALUE 下面证明为什么被拆成3条cpu指令, 就会导致非线程安全: 如下表, 线程A和B分别有三条指令, 汇合在一起就是实现 x ++ 的功能 No. Thread-A Thread-B 作用 1 load(A) load(B) 读 x 2 add(A) add(B) x ++ 3 store(A) store(B) 写 x 首先明确执行的顺序规则, 同一线程内的指令一定是有序的, 但不同线程间的指令无法确保顺序 一种正确的情形 ✔: load(A) -> add(A) -> store(A) -> load(B) -> add(B) -> store(B) 输入 x=0, 输出 x=2, 这是最理想的情况 一种错误的情形 ❌: load(A) -> add(A) -> load(B) -> add(B) -> store(A) -> store(B) 输入 x=0, 输出 x=1, 显然出错了 问题在于线程A、B依次load的时候, x取值为0, 相当于都对0做了 +1 的操作, 所以结果少加了一次 如何避免这样的问题? 只要保证 x ++ 同时只有一个线程在执行, 这样的代码段称为 Critical Section 我们只需要确保 Critical Section 部分的代码, 不被多个线程同时执行, 即为它加上一把互斥锁 Critical Section Critical Section is the part of a program which tries to access shared resources. 即尝试访问共享内存(或资源)的程序段, 一次失败的尝试 现在实现一个多线程互斥算法: 脑袋里最直观的想法是, 维护一个全局变量(即锁), 每当有线程访问 Critical Section 时, 就上锁, 以达到防止别的线程同时访问的目的, 退出访问后不要忘记解锁; lock = '' x = 0 def add(): global x, lock for _ in range(N): while lock == '🔒': pass lock = '🔒' # add lock x += 1 # critical section lock = '' # release lock 这段代码很简单很low, 但是你秉承着大道至简的信念, 略有自信的运行了它… Wrong Answer! … 继续看出错的原因在哪里? 第6行 while lock == '🔒' 和 第8行 lock = '🔒', 这里分别代表 load/store 操作, 其实已经违背了线程安全的原理 举个粒子, 线程A/B依次进入while的判断, 都以为现在是无锁的状态, 而实际的加锁都没走到… 互斥问题的类比 当你无法理解一个抽象问题, 尝试将其类比为熟悉或者具体 的概念 💡《直击本质》书摘 先假设一些前提: 每个人是独立的线程 大脑内的思想是局部的(private) 自然界的信息是所有线程共享的 (public, shared memory) 通过 抢厕所 解释 非线程安全: 早上10:05(带薪蹲坑的好时候), A 和 B 同时看到 仅剩的一个厕所(Load) 他们激动难耐, 都同时冲了过去… (try to store) 此时产生两个人抢夺一个厕所的情况… (2 threads -> critical section) 解决思路一: 叫号 由一个全局的公告牌决定 轮到谁上, 但是如果叫到A, 但是A不在或者临时有事了… 缺点: 违背 “空闲必进” 的原则 解决思路二: 协商 让A/B自己讨论放谁进去, 如果碰到两位仇家, 是不是谁也不让最后僵持不下? 缺点: 陷入死锁 通过这些 牵强 的类比, 大概对互斥算法设计的好坏、标准有了一些认识: 互斥算法的三条标准 Mutex: 互斥的正确性 Progress: 有线程进入critical section Bounded Waiting: 有限等待, 无死锁 LockOne算法 顾名思义, 引入一个bit的 turn, 即允许执行的线程id 假设 turn==0, 此时线程1想要执行, 只能干等… 违背了 “空闲必进” 的原则 LockTwo算法 顾名思义, 引入 2 bits的 flag, 标记第n个线程是否要执行(因为是双线程互斥,2个bit就足够) 假线程0、1同时标记了自身的flag, 它们就可以分别卡在等待中, 即死锁. 违背了 “有限等待” 的原则 WARNING 纠错: 下图的 flag[i]=true 应该在 while (flag[i]) 判断的上方 Peterson算法 既然 1bit的turn 与 2bit的flag都无法完美解决互斥, Peterson算法干脆说:“我全都要!” 它的优点是, 保证了双线程互斥正确性的同时, 既确保了 LockOne 的 空闲必进 原则 又防止了 LockTwo 的 陷入死锁 问题 尝试阅读如下材料, 以对Peterson算法又更直观的理解: Wikipedia: Peterson’s algorithm A Proof of Peterson’s Algorithm 写在最后 本文讨论的, 全部基于 双线程互斥, 不适用于 n >= 3 的情形; 正确的互斥算法, 其内部的状态转移一定存在环 直观理解就是, 一个线程进入 critical section 后, 另一个线程不断的轮询等待 (陷入while)"},{"title":"【硬件】GPU架构","date":"2023-05-05T18:06:42.000Z","path":"posts/gpu/","text":"GPU 与 显卡 的关系 GPU 是 显卡最核心的部件, 除了GPU, 显卡还有散热器、通讯元件等电子设备 目前的 GPU 厂家主要有两个: NVIDIA:英伟达, 主要是GTX, RTX系列, 俗称 N卡 AMD:主要是Radeon系列, 俗称 A卡 摩尔定律 Moore’s Law 是由 Intel 的创始人摩尔提出: CPU的性能每隔 18个月 就会提升一倍(同时成本也会相应地降低一倍) 但是 GPU 的性能发展历史打破了这一定律, 下图是 CPU/GPU 的性能提升对比图: CPU 和 GPU 的主要区别 参考阅读: GPU vs CPU: What Are The Key Differences? CPU 和 GPU 的设计区别 Real-time Rendering 第三章 一个通俗的比喻: CPU是几个大学教授(大核), 单打独斗能力强 GPU是成千个小学生(小核), 擅长大规模并发计算 CPU GPU 全称 Central Processing Unit Graphics Processing Unit 核心数 (eg.) 6大核 (i5 10600kf) 4864小核 (RTX 3060Ti) 架构 SIMD SIMT 应用 操作系统、高并发程序 图形引擎、AI GPU 架构发展历史 0. Tesla 架构 Tesla 虽然是 GPU最简单的架构, 但是写这文章时, 大概只理解了不到两成(很多概念太抽象了), 希望伴随职业生涯的学习能不断加深对硬件的理解 Tesla 是第一个面市的 GPU 架构, 但是经典永不过时 这篇知乎写的很好, 作者把GPU比作一个外包公司: 【GPU】Tesla架构(一):初识GPU架构 通过几个核心器件来分析: Host interface GPU 所有工作的包工头, 负责派发任务 Input assembler 将 CPU 传递的顶点数据组装后, 传给 Vertex work distribution Vertex、Pixel、Compute work distribution 分别负责 顶点、片元、shader的三大任务 TPC (Texture Processing Clusters) 包含一个纹理单元 和 两个负责计算的SM(Streaming Multiprocessor) 理解核心的 TPC ⭐(架构如下图) 将 GPU 看作一家大的外包企业, 那么 TPC 就是其一个个小的子公司, 它的核心部门如下: SMC (SM Controller) SMC 是负责将总部的各种任务拆分打包成 Warp, 并交给下面的小部门(SM)处理。可以看做该部门的负责人, 既要对接外界资源, 又要管理内部的任务分配, 需要实现负载均衡 Texture Unit todo SM(Streaming Multiprocessor) SM 是真正负责干活的小部门, 内部划分如下: I cache: 指令cache, 将 SMC 传递来的指令缓存下来再分批执行 C cache: 常量cache与共享内存 MT Issue: 多线程主管, 负责内部进程的调度, 是GPU高并行的关键 SP(Streaming Processor):负责执行基本的浮点、整型计算 SFU(Special Function Unit): 执行更复杂的计算, 如超越函数、插值Lerp等. 接下来尝试理解GPU的高度并行 ⭐ 当 SMC 拿到 多条二进制GPU指令后, 会以 32个线程为单位 分发给手下的 SM, 它们就称为一个 Warp, 这就是 SIMT(Single-Instruction, MultipleThread) 架构 真正的并行单位是 Warp. 理想情况下, 每个线程执行相同的指令(想象所有 pixel走的分支L逻辑一致), 这时候几乎是 100% 并行的(取决于指令数 和 core数量) 但是实际 shader 运算中, 会出现 if 等分支语句, 导致不同线程的执行分支也不同, 如图展示了一个 Warp 中不同线程进入不同分支情况: 1. Fermi 架构 Fermi架构 相对于 Tesla架构 的优化主要在如下几点: 晶体管硬件的发展, 可以堆更对的 SM 和运算单元 固定管线的一些列操作(视口、裁减、光栅化、剔除等)逐步在硬件上细化 推荐阅读 知乎: 【GPU】Fermi架构(二): 三角形的异世界之旅, 它以三角形的视角描述了 cpu -> gpu -> 各种着色器经历的计算过程 2. Kepler 架构 Kepler架构的核心是低功耗, 因为物理器件温度过高时, 会导致降频促使性能下降(尤其是移动端) 其他架构 Maxwell, Pascal, Turing这三个架构先不介绍了, 超出理解能力了… 移动端架构 区别于桌面端的架构, 移动端GPU受限于电池的短板, 需要避免高带宽导致的高电耗 当前主流的移动端 GPU架构(如PowerVR, Adreno, Mali…), 都是基于块的渲染 (Tile Based Rendering, TBR); 作为对比, 桌面端的GPU架构都是 (Immediate Mode Renderers, IMR) 什么是一个 Tile? 假设一个屏幕分辨率是 2k(2560 x 1440), 它将每 16x16 划分为一个 Tile, 每个Tile内有自己的缓存, 绘制时也是逐 Tile绘制 写在最后 体会到了什么叫作 浅尝辄止,很多概念看不懂、很抽象、纯粹走马观花般科普。过一段时间再回头看,希望有所提高"},{"title":"【引擎】渲染系统","date":"2023-05-04T17:02:53.000Z","path":"posts/36E2965/","text":"b3dc07a81f6459d120ce338ccca55046d81ca386354419b6b77d6b55182b8ed33140f2401a25c01a514a43101d339e09a1b2e92127965f9324a394488623f13567a42631fecb5042eb8052206cc0ab8768157a03a512d041b85e73b5ad01100c1fa62983540997e855ef137f75a3520d73b6e836afc1e03b244e0efd0b1f6659d84c1feb5f526eef882a052f38bc4d4f54c3b21f7ac35d22a6c7416b81d076e287d11d62c3b5acbc6ae09295d3d7b1272ba11d47a0078530d26b6167f5812214e6a06520ea990373c5131a13525c023f10aa6883f875e56eab9ee5b3a126d6bd289ba09c5d42273d5591d7e3600e926b52797fcd4634ea7e293dbeeecbc31e56415dbedc1c3e3509782ae8969064bf59e44532faef8d76fe16ff8a0cbf01fef3f37b65d5c97e98bb26cfc366a93c4b7d3320906ec8fcb19ecc09dab2ac6085682a4fd0f9b505cdac92091626de5a1e3f9f85ab4ab30ee4d8e8708c785c323ac26a29373c25ea6d70c2a175e5b34fc4100eabe70c375a9e735e462459fe8b5f28e3ecad7aa96d7b86db29953efac9973b0f8aa580bde40c9dbb368e2a691100aff1d77a886ce30df1e4d8de59456f31893bdf557bb11235120d8b98742991d76fb4862b973603cdbb4796bfa472f462456582b6488b101043431424649698a0f3c0c7b6661acd69df55a6a7028da6f6341f418f581b12bc922a4db384e85a031bdc3c716291dcd159730e3d708c102a5785b58186e6aafecf69b09ad841d82a17a53cc71c89dc518535bb8b25623715d35d9b3dc55777ec39572bc6aa1f7f243442686982b172cf41870dd48af9ce70d706731d8d9cf2884c75cb38e3a31cfb3f33906f66e57ccc29f7326116c69782b13bf6d04a91c7524f2081610f88588101c4c3c96020d0fa69749e328a8a5ac71d1c676a843a7ac125a3b561b17955fa3c2b774010aeec09c6f7e991381ef507d028a263c5f6809c70ed8f63f969fe439d1788b676f0168f0638fe2f7f9135b81b878eb1bf73cb732112c5dc64284bfdf7e764db90ce741a2a24cc5e41a9bbd5b9dd4a72f997e492b642f8f489d6f607dfd5be929710cf457350d7068d72584b9075bf45f3149f2a23e2e548360189bc8cb1965e7127041b5ec244a4286c1ac59878609b0facf2bb043eeb9814af93c75f8301a82d4ccd53930a62a888019fff22648897dc0df05c31580ed10f4e6c6a3993f462afc77165ce5afab45472b218d3347a5f20be6991cdcfba1a92a4c51eb3bd8f2cb5a1b44de5c5852baf98fbbe0bbeb7dbe5385a0ac07fd66a70a0d4070fc0f2a5bb25f431a9dc978bbebdcf9fd679ca147d788939bd6d009ddac8ddde3b5cb6b8f9cb0ab7996ceec5557b0a5e0e1f795f8ca4f10a626b2dbe9eece30b029b463de848b1906b5521811692fb082b5769737763807ee0325a7e7747cf46d711323621a47133107ed8d2f47421494d82d0886eaf307199f0f90cfb0a1ada6a09b8fde3a4dbc801e3cb492a129dde62ea2da11600831caedac3f4cd34aa499be6372068718da25e56cfe3df8452a599eeead7dee0464707548eebf99bb889efc716d1df4570c75e6091c3ce6184f0d86d6daa9ca3acc3a68f117003db2608193a5d934e3a69b9dbb0fb25eae3035d46227e2d99a1065dfe791ee901ccf13a3877a090a88e3a2852bb2f6748caa9d61d431759cd56ca123cef02ce434192873d9e24d36b90a926ffe58d699dc40d56d657a4ed210e12a9ef2c111a2b2a280ec20ba73a0e67fec2638962b3a2b5979f0baa47e700aaa171e064bc3ec8429d8ba52a8fded50c0acf30bda1a254249a6b44e86824c183d5df35b7197d3c1661fe6b0bd0ba7f9619b6bff75134d1b7db818ee222ed7e0be33ecf0d922e3e126ff9e15e660f1b0f279bc5736d6e4c630f64ed0ea65e38ad400081a50478cbfe606954db147ccae56a8b8da4ae954598953573137cdc779531faf9720ee47cc72e79a30c015b567da9ddd1b3b6f5047e5f2ffca7df6415867d9c6a87bdcceeb973263c6e382e88909817745b80cc2fd7972ecb826f953da3451fb67e34c1dbcc02df32852b7b85fa11de6dea20f6cb88d687bc5637585025bd23190698758239c8a81d210ad3965e49599e885575e72f6a5bf9f21f9d5cb466199144a00c057f2331d888ae729dc46e6163f4e5060b9e6a7fe6895680be21050b87f502e07014fe0349cad9130be7f6e4519def1a50571e6545d9fdf742f9344ae0715d700252c7da089b2cfd5f49b9010021c26ab1db6d7de4274128ad4d82a58ad776828e720e300f29056c8090b6f8e8fb6c20a103264f7020673648457d1666f16a7ac8690f4f36238e9ac81dc0c27625170dd8e25edeb82ebe881771e564c09cf2a603d50980975affc43e2db38398f05d034a3d83755447445833c61cf97701c5ef95770271a7b5ac8e6f9f4cb66b7548c9a583489f62641f0e3f3913b2003f62bcfe39eabb36d8987946a84ddf81f11460f28a8f5a9eb919fe0951fb09f3f0ea70275bd86579e8b3b3b148bdf070c891b0a8d9d6c2a5eb0869903057edb307378287a955d0b87f73c187bcac8d19b9a1abfb0e31ceb58562fa494fc6bd8de01e164380d83f5f3efc0edcd98aa1bfc4ff3955bdba793e3cfbaaa66e36364ca2007c28f85207ceb5c57e216f7950b57f3fb13c7a6976d3e52ee230fb9a3aabdb2d95330a7f585ab4bc386a1c925858fcd466fefc67de7ac014da69c1a3bebcf5624d6c7dedce68ebae3b77ae55f8f3fa67e2eebff7a7d2bc32d365aa00763082e91e91d0cc7393ff2f348a35d4ece2f1af5a87841ad986d136ad26b7d83f4d8e3e2e385e28852cb7e5e408f5a76a6e0d8ad7d46ecb37c36fa28197047d632a3c0f2947c15715a98a25302c5892fa9be8d6b7d6fc3aff658ca1489b1ec543ad9521e238b5741583cf4fd451adcdb35190bf0ff3aff9fa9a5dddaac615b0a08900cc68f8a23ab65454ee150920c315a66d77750786c7fe17c3fdb8549a38af24499e57f734506026d9f1265be4814ab7fd5610bef4774f9e246405ec4ba577367927519292639082232964f720e499c47026b6db3031c56a544180d1fb9d497f6374c28962682ba9a35127d7ff989a7646c0d7678d57c16585809383f70bb9adbf08798aee8e9a5ea4642c09b64cca3245640f69ca6f17fb7109b218438f810c3709d78402544cd9f69e72e38932b1c6cb686c486cda4bff812c15afbe6a407dda009f35fa50216acf68c1e598153425b6dd2161af56d8f81f602c5e1c6c028b6aeb5aa04b9d392ff9902b074c2789cd68e5cfe80e5bf1caf9e0824102755594943789456920a4495789c38aff64ee91d938dd33152b1b571c1743ed0b5b9afce0a5ff602b8e41a8fe82fec754bdf8357b6dc5ef3e3db4a90921e12b8ba8ba6623d0fc09faf20e7bd923b28a5332e5635f39d2e8e9f52eed4c1e79689fc12771dc2a0338c45101c7369d7c61d95bc83ae27da9f0facc9f0fa7f29a76a13bc1ef6db6d25bf12f2486d3a6a133bb937e761fcae917d11496758b85126ac596de9d1483b4943fdd11d6443efd67fbb87efdda1d793aa92b31af6561e491bdf61f42a6c974e7c240fba39c225dd894687e1618947011e006875d3154f2afd15dc296d8290f963914488995986ebc524d320b83e7ee42ff5466606e8945f9723ced9c98eca1b2d5fe38554e5cf2d4654492a4e58cc05b8b5ebb0d51330b61c96a9ed817ab70d7ba726c410be2cdc089f69850d6924137bb6e7e84599304ad6dff9a9e06716755d0b7c7d586774f3243e7dadb3a9aa716b18b1dab07e2cd993d08d8f03a3a9f807daa6a6d6340b2f77bc4f7dd1274f7b8ee33ee348d766b875da5eb832c4ba93cf89e7c1917bcdaca7ec070baa593f5c1508a55d66e0dc64ad328f9e9bba84decf28e25402114ba11ac872c63c6bf91799227c921a3ed078240710bff4156bc118284e6187b134752dc6402e4798ca86533b83c0880192f7dd73d19e80f046742c8b8456af2dab3355095b27e4e0d9c86581bb23fd11cdd10a094d9e67045935f6cd3b5b66076a9bc2fc77f23cfaed6c20c91f449a79ac2e52856b534e083dcba8c39156fa0d68786ca20ae3ab03461ad3483113734a41c2ca60a4790016961051c2558c937d83f0408fb640fd2269e5b6864bca77075028ce603b95ca98655addc7f565cece99e423f55469316a441caad68ab8b60e12f83aaaf3f746f89b3c10ed2b2c024b17e9e2c4c64c189dcf155e057137f92a0d2f9301acf199cd89e7dcdfe6b2197f0c2a44c78539a35b1513b900336fd50d73c69380cb8cb2ee5109ec65563e01e2eed16d832c07da177190befe055232dd9c2c43cd33e81c1976a704b7024de1a170daa6f168d8db548c9834da44d78ee9f6b6f5b8babe9099813f39231a7a700981c47f08d73b541ed9951eb444b97c60499fffef55bb443338126efe413035603f52b76093f4ad370b67df80af2c0a6a40f66166ded9c3adf76b95b4bd94a8104e8c4398068d25937958e9d030270f5ea9f2902be96c19bc9f81399ded85bcb311d4e008377cfe5fca788fac120607b64e890eb76d61ca71997eeacf8863745e553742ac032c8e02ba02d03dd66490d7f46c07cbd50e9d9c0f0a0af1d3a60adfcbc1d5545af8fb274eb4d816e56471d135bf25d84d6e9c7d3ad248b1014ae8fe941a2e800b045a95db4e811d48c60a44604e3907681bbe38442c0a11aee48bae2b7e0ee29dc359624623ef38d69000ef2d486b75d8764bb5570e4731ff19ae5bb7923a68f643d65602b2096d2fcdb048bf9be331d7094c536ece66036642e2d913866d0836c50229d3262846340cd2f47ee73596371f60c9f844cee848a26686f48c3953d4e2fe348abd417a9cae1 Hey, password is required here."},{"title":"【Python】编码之encoding","date":"2023-05-03T16:05:10.000Z","path":"posts/3RHEW9J/","text":"ASCII, gb2312, unicode, utf-8 Human use text. Computer speak bytes 不论什么字符, 在计算机中都是按照bytes存储. 区别仅在于 encoding 和 decoding 的规则. encoding: text -> bytes decoding: bytes -> text ASCII American Standard Code for Information Interchange (since 1963) /ˈæski/ ASCII 使用 7 bits 来表示计算机键盘上的所有字符. 一共定义了 128 个字符, 描述范围是 0 ~ 127. 95 x printable ch. : a-z, A-Z, 0-9… 33 x control ch. : EOF, LF 为什么 ascii 是 7bit 而不是 8bit? 第8个bit 要么用于校验位: Parity bit 奇偶检验位 要么用于扩展 ascii 到 256个: Extended ASCII ANSI: 泛指最早每种国家语言各自实现的 ascii 扩展编码方式,各个编码互相之间不兼容 GB2312 Guo Biao 2312 号标准 (since 1981) 由于 1个byte 的ascii不能表示中文, GB2312将编码拓展到 2个byte. 但是 GB2312 只收录了 7000多个 中文汉字, 不包含 生僻字、繁体字和日韩文字, 后来推广到 GBK(Guo Biao Kuozhan) 得以收录完毕. Unicode GB2312 是显示中文的编码, 不同国际不同语言都维护了这样的编码, 管理起来就很混乱 (如 \\ux8\\ue2在中文和俄文编码中的含义就不同). Unicode 通过构建一个全世界通用的字符集解决了此问题. 具体的Unicode编码需要查表: Unicode, UTF-8, ASCII 在线转码的网站 UTF-8 ⭐ Unicode Transformation Format-8. UTF-8 是 Unicode 的一种编码方案, 它是不定长的(1~4字节), 可以显示 ascii、中文、繁体及其他文字. encoding 规则如下: 单字节符号 (ascii): 第一个bit 为0, 后7位即 ascii码, 这与 Unicode/GB2312 都是兼容的. n字节符号 (如 汉字): 第一个byte 的 前n个bit 为1, 第 n+1个bit 为0, 后面byte的前两位一律为10, 剩下的都是Unicode编码. decoding 规则如下: 若第一个bit是0, 则为单字节的ascii. 若第一个bit是1, 则连续多少个1, 就表示占用多少个字节. 根据Unicode范围决定字节数: 1 byte: 0000 ~ 007F: 0******* 2 byte: 0080 ~ 07FF: 110***** 10****** 3 byte: 0800 ~ FFFF: 1110**** 10****** 10****** 4 byte: 0001 0000 ~ 0010 FFFF: 11110*** 10****** 10****** 10****** 以汉字 “鲁” 为例, 解释 UTF-7的编码规则: “鲁”的unicode编码是 0x9C81, 二进制为 1001 1100 1000 0001. 首先查上表得知占用 3字节, 因此选择 1110**** 10****** 10******. 从后往前依次填入 Unicode, 不足的补0: 得到: 11101001 10110010 10000001,十进制为 0xE9B281 Python编码问题 为什么首行添加 # -*- coding: utf-8 -*-? 因为Python默认编码是 ascii, 如果文件中包含如中文, 会报错: SyntaxError: Non-ASCII character '\\xa3' in file ..., 即 超过 127的ascii范围导致解码失败 BOM头 Byte-Order Mark 文本文件如何确定自己的编码方式? 通过开头几个字节即BOM来辨别, 同时包含大小端信息 FE FF: 大端 FF FE: 小端 EF BB BF: UTF-8 … 下面这段 python2 程序: a = u'El Niño' a.encoding('utf-16') # '\\xff\\xfeE\\x00l\\x00 \\x00N\\x00i\\x00\\xf1\\x00o\\x00' # fffe 即代表 小端存储 Escape Escape通常用来打印彩色输出等. wikipedia: Escape Character 站内博客: Python之优雅输出 Python与编码 传送门: json Python3.11 与编码打交道最多的是 json库 Python2 中允许传一个参数为 encoding Python3 干掉了这个参数, 需要手动编码 有一个 ensure_ascii参数, 默认是True, 它允许对 ascii 不进行额外的编码. 工作中遇到过一个小bug, request请求得到的unicode编码为: \\\\u6789..., 问题出在多了一个斜杠, 原因是重复的json.dumps, 需要避免 资料 Unicode, UTF-8, ASCII 在线转码的网站 Stackoverflow: What’s the difference between ASCII and Unicode? 阮一峰: 字符编码笔记 ASCII,Unicode 和 UTF-8"},{"title":"【日志】2023年5月","date":"2023-05-01T14:26:12.000Z","path":"posts/2023-5/","text":"b3dc07a81f6459d120ce338ccca55046116de9af7135eccfa9c61a82756948fc387d14cd325776592b49d3f1481915c7af940bfd801627f44561689b79cae0dc10d51e8b38f6abff19d282ea8f2ce4a1a7b21476ff60f0802fae4d4cf990cfa67f839215902bc1fd50e5aac32fc2f2c2501b22d895d776f73872ff39db9d4f0336d04723fffacce349547dbbfbc9bb3aea7a10e6685fc3aec57a4155b3009c0e109261f3ea9bd9ab466dd4c12bc32b47e77c0dc457085e5a6efaa6c9b8c236e377017b6c2b20bce3aeb1f710c5761130afbf733859958cfcf1bde5fbf0c13fb8cb206af3a3d0dd204f780e217c1fedd66c4227b6aac9b918474e0e3a0ac3df383d6a7f727c8a22b4c1f66c9298a02b34b44eb90c7473dc9243ca8cd8fe6f2abe429eed206e886a403067d8815ccd0a3a52dd5c2013b00b33453658ab181c7f90f99246d3311605b748704be75865fe09706269709e8fde2bcc3eae83d48869bc8fa8d6155ca00b8efdbeb22fff1bcbcaa5acdc5c34b1dc3fa4db78fdb663d62811aac50072a07ed19c8d785b9b7c560e83ca857443e4634b445dc999e6608b6ed048516050ab25255dd2f932d6695fdeb5b0fe0ef588553ca601bd4ca13037039f55b14cf80b2f53a9cdc8a945e045148acf1c14116299553df9b1d2c70e7339ffc0bc53a3d0c89bef9c77b897d7073812a94d21d434c4e4508a3b5bb5250a99ecc194614da541edfdb5d35e6c7a0150a4183e3595ec62f2a86d26e3906bd2226b9e12d240d7161dd8e86ba8a4c90688873b6526ea74cad40c2eb81bc2a7ec319f9d1f7d1ecff15e01a2edddcee4eaad12012058d55c68d75520057847766e5439a92b5ca5e0e9df4ffd8ac091735a9c75ebcd755849ed32c5a3e537dd6ccc69a74cb9e57971ff1b0bf326b171cf1b73c75e8681a47a38be4932dda31c80240e7fa5a1dbb68093f40741d794ea9b171df98b34945c567230f76070836e025a0a00334fc61ce3bf194d9dda4ef9d494e9c73bda899050ec5901e4c62a33b85e6461be8548ccd0ab0fe9f19662f936f5bb725a5fb1ed973fbcbb7d4dabf09f5ec0533979bf203ca9606581a3ed38a6989f176cde8a9a16f4b5173fd1e221e3e69861d221c307d021c1f3f78fe6ff05ce248c31dbdddd33d2d5619525d521abe18109ec7a1e5930329efc36e6267574bde629b0a080c9cb3a45a0f83f36b7b599c36c3bae734f2b711c29c16fa95bfe62936c2d3d5039655e9077b351b039a8e33527b6ed48d074450bdd1a9609d2a07bd87332ed66899c294ac44d22d946f71a2c148f4b5096ad8a37b8203b6a11b1a430f4932ee7c4362118ba3456aa5a670f62331ff2c755c8dc5932712d73fd45bf89d4a9e97420b04c55c55f5384fe13dde1f9f5fecc73a9dfedbbbf7dc1406b66747fd43a52d0b7efcb717d8d39890f3bfe809b75c90edd83bd81529fc1e62d1bb96ba287bacab0a91775df61ca59f5077ac81eb642796507855a6a47a26a205c60250630ceef5986ed3b27e14fbd8fbbf6298c049e7d2680adcc2c1cbf9d580c2948ca7a57ceb9c16d4e3a5ab063bcbefc7f2bd0911ffcd854dbcc17b32a21d382821ad48d8795c20b7abd62a52e545f216f48d1e0bff63da7aa2fa813617a1416d4fe5d1f0d6d3245518633ac00ea2c61722f085af79adbdeca87675200a505816b2825ed07a2d4d42a7f9966f6e569bac85eb16d6ae483459a179b558288067008e899a9c7ac02f6ca3a0fbadf3c52f1857dde68186a26120e510b813e8af4daea3b6b0e16c4f4d2c3196580cca97cf30e872fb8fd1a664033c1676c0bbf63a0aa02b0ab16e5522aa314a3b08496f6e5c3c2ec617eaa0763f9f8f850d0c6490f3f64612545a5400dad40797b4962c8bc2fd3c0a821f56c37dcd940caf323ea42c4c75fea51e83b9336d3932a3fb895e5e2f12d78ad910bdff50d233c187768cf0827eabf7a7f7eb551ca293ee788fec75eb37f60cc3d8829662fed9d03cd93a1f5cde87bb78333ca1182c60e17ac6c98f0c6888aaaaee952be6cf1027160e0626a2e027531dac6d3cabebb688cca80cb425186679305f211ed6e93403d466e9905d31b08f27c09e44df25405402c279bfabc28ee3a2d6ffa28023c7fb20a24fdcf74e24652002d487eebb237b8602d77a4d42fd85a05eca18b22482c12939f09743c4a0d9d83d0d1796e6ab6ca85c9d705073c24d197d63743d1f5cb5211e8dfb4a91345b62a5b2e5349f9759b534a757e2a00c11f8188f4a385ba18c74f79c2982c9c658c722037591ea9c606f9eed0d60fd6ca21073a53c4dba47d211093d43cb3cb024e4566c8e56c0f71d7c6066cf95855c5d36f5fe3c5407391fbdf9c5f166e2e80c9c6341eabcd498d31fe1349380b09f46acfe2908e5d602f1a0f3aab9176d2d256050417647e7def66ba0bd0e52873ebe838bea753d136c14fc952088fd2725db7d42db185d735876df2f099516b469f3e3dab80a4383aa91e5fc1d33bd4ebb2d19fcf6e01b45216e4d9e7d7fa2736a7bc3607882675117aa6b3309d69fbb8c90d5861b1044fffc516d150618523121322d51715a85a23ed606732fa5504afda287e0001ff8196be950cc7ec0fc63fa67d1690b443d0e241cce8dab60ac74b81418bdad07a0f776a27fc2679837b3676ee517b1fb53f49f35ab0c2db40c2ce3541db1c782830663a4d95fc16a7d49e7fb55a2d1bfd4b663efbedddc0923cab5a2da56fd3f158ca683744563ce8063c221eee1f47e597b20e55d150f28ea06d21d40cb0a4d08c4dec7d14eca9aa83931b5bfcbacf08b44bd108e24130a45b9ac1430fb3eb1415d2b35886ed66085b0919253d9a23cbca8067444ae6cf8f1125143fb589eede24d40e33a1bf685fb1cd90285d9a95e01e0edb5653abca4d63a31a78138481ae7788011885fea28073e164c79e9ff5d2c800ed4c5cb383cbf7204805ace01717f8c2372f72e75b18536d0fa84c99f8021be13b526518f189e17ae376e3b041b92797e428bba8ccd261168c5d42a98f04f81c237ed7b11ddbf3b47bcddf6cd4a573a877d38271dc0d24491df738912b40f3d0f23346f7fbdd271558b3429381c20c6c542c84b484c86acab5675f7ee7f78fa549f3a3adc1c0be44bcc07c8837731c50c5986517690821c509f6658f642e405366ec245b9308cd08a9218163a0297fda672c2846a49fa31446f87d806b457a360c9b3956dde2086f04003e5a62fa81901fb96ac85d4abdac89e4b761b54520238457966a6fa5852683ce182b79718d3ee9d7e77e435dd5b8b9bf8673a488e1846ee473c9a6aece281a95aebe74d77f04d0ad5f05780734e7293d09aacbae2c5a99a49316d88b67d0501aeb4f79064764f20b32c5a001bca3fc0f2d583b139cc1dbf9ffbe0acfeedd55f85f79cc03c31b351ec92f54cd9e5e2878087bac0521e00d19e4d14b37b5386a03188b690a5a1ce759ee417ebdbdd482d611efdfbd102782c487b1738f9aeccfde623c0bd511b138704a5986c5d914a402703e9e9604e4cf767a5e2c99d6b204d4d0ce2d6c9b4686a065893885f6f3657307863f57b0aad1cf7e3dedd8370dc13f2616aa46bd4050f1c5070b619d56ed75bc3f14faea904ebe399b00ca923c53795c36cedcadd5b4eaa0888beeb89da5fc9209e7bf716f79eb8615d5353feff27b35be050bc97ad666e70e991b16308f5143afffc39a5d878f771d9c7e4f0124cfb9a5b68479531d783827e91720d08f427c7a13178b0d32caf20fe6a0f17f9a59b04cbbfb937c1ac0d059b6bb013de86972fcbf4cbe85b82ff97dbce3692bc4b8ff6f66176332282d47eaef10db48c14e5d04ec3c6577a209868d4b2deea139ba312c40dc8fba8eb5bba9250e529d158dfc61c66004ae0a11a8ca6df5ddf98ae4e90570b755c9044420863f1f69607905dd917a6b20310c6a42ef87035e1dd453413ab40d183034afed3fce92f46d5d1e1af7136e2a2730125cb238daca79b36b36d7e28412a17e2607bea153955cf4ddebb58a6684d454f392141be1633da15410bf9c8117a0619befa13eb21447d51a60ecedbd6fc15b7ef2623afe8e3661024e9b25af88263c77475f338a412a0ff608aab216d51d79a2aecf40c3c25f273e09d7f6a405393d0c67608a75e40895b2657a2c02681d842c8888ee09ca3edcb74ce0f474f55aca7325b043ae4144a5bd5ddcd3473035be6df26d21ea7ec23a451155a622a841fa47bb136f951f06983024d46120fbcb0a121a7341fdd7298f2b223c6b4e4b15b14d6c499f01ed0892424ff097d564fecef94edfecb6d0559443139dea475209125b1e4ae0554cad8c47c6c97dc8cd7ec629ccb14d10a47b7aac1f784a338f18ad4d132d80b796e7d023953a579c8c8c1014d3b6fa35c89af7aa86f87f2a928d5b965fbcecc6aaa9468db1751ece62fcfb938ab049564fa7cdae2540c41ebf930025a9a342dd8731b01b5c431cd50ed130f5def04d18b7ce6e4ac58530c22a5a356c7bd87295103a4175028cae3da360b9fc8599e7cc85c96ee63ed4f8e9458212432bce8f0caf6973ecea91e130e889acc30ec2b77d0f34a6fa56ff236b85fbefe98ab6baa36a1408fcae4e443ead45d8308a3855c876cc08954dc8f7f579aa429b057d35ed1846626b98675accd106b76dcc865c95cc58d8e40b8ee1df1bde165c068c19da8ffae4b8ed6d73d83d3c839739e48e95014ff4bb05af38c2e4b67428a067dc29242d488594a5996ec1bb284f7e6708242f86fe3cf13eed62d01e109bda51c8c429377ea54e4515f0e56719ae0395b520fa9f49c3f66b44dfe24d016177df73d1378e7f778e70e658b0112de762cd1fb01c8fe07cea37ea13057dc75ec8a453535cd00dea731db058243e65edc325d7d70af364d24ee9dba5b32a35c0128ed406b4e4798755ee242e45480dd51f2d64f5532c2cbda90f431b852c7af54a56d63ab43bce0c11bcd363974e5228307430c28db64a9aa2d6eca73f0608e9c9f6b1102256e3ffba4f6febb0c803a3b386dd8a64b5b896dcf53e411b9ef61d363a7586a68e82936fee9b76410b8582e792e897dea12ac5469e4f284a7c0f9605ed4ecea756344a4a199f935904368c219e4a581c0a2a7ba6304eb5c2b43485d03c0bc2102c263d57dad0011f9a4a41db42297bdaa0bf0dd4c0cbca41f134657d27feb8df1dd7f7d3ed54d8509949e619367291e16bb4761aa8bb82003b61416004bbe8257840cb94c82edcbaf09bda9035a94396cf012f1e778cf12a5325b8d3e7f9628a732e070c10c5a075564f82bc7665167f9a7d0f87d0402779771f272ef3de3c647a7bac08626e91f7046efe6b3fbe89218e4b74362b3434f37129c513889234773cf22efba855251f270259247c6995e2a509861b462521a91eb2256c8b0a06895171d6c0cbedc34c76740e9e09031c90106cb97360fcb42f4ae4f3cab5fcf7d723f0b1011720986ff1e5105d139ccac0207c12a84d4783c871a8602835edfdd6bb97d9c66fe0cc001154aecd14ebb4f603f613f277425a43509806b91c48336ce15dbee4cc0286d3871250e0c0f65d52e2119b5a7ee9d77a7f9458361e933b30ef553ede51b5cb2e13a594b8700d4dae0d38282c5c88fdc7d9249e41c6c4c3605b2fa7b5f82006ac1436c9204138d4ab3c91f97e2e4b6c4c5b13b71e1a4742511742c9ebac165abd9cd13d7b7c03a713c79afc70a884f3470d0a39e4b4c771caf212f71e00b23044940a0b7c7a42932944b74abe80e088d53fb4ef24ab591f0eee9f80d300f09a6c515b9f7606602c42f90b2922347762427230d0ae7c2271b24346d15a5b3b7594edfb40104f0849eeda0b6563cdedb05f0e6dcae3b90cf0fb242e8c922ee03449966fefe3d63abd8c2b358d570ef579a22ab410aca029c3c04655a204458bf44d1528b8873e676a56ba65fc18d1dfe02bb523bc854deac1cf5aa32f4a92e92ce980599ded7336d42912d321fbf0dd381f64b1f952eca7992db3980df0d3aa31fad8833840b61d52ffff4cbe5d7dcbfcef4f4a40125e7be753476cdc690875f418d0de5fd1f50ff847c0c2650fbdab39aea9ffc731c31e9db61101d22641d2378c532d9201e31c33675cce5cbbcf5902b438c70492c30c4adff9c634ecbd8da97c836b273285eee757b785611817374ab42cfd691f8d9575e33339c1fb4519779be16d38a039bbc049061bf2470de0923f2bd0b9170313f1890ed5c81ab74d8802c4f28d9bd3ffe6d6f7a5f4b835ddd6556edf967a6e4a724366271697cb79bddc0a9df380ac304d91f11ab489f6c866bb017275b718f3d9e9bb98e30c22a0489b002ee55d2f062078e361ce1ed51b3bea2985c1f7a9b006e647705d970b08bbed3c730c3f39c94281868d3c9795575694c5dd05fdc7493947d73fd5331d6d3cc11ab4c7b9adc6d14f54817b9fd7dea83dfb2aaf2f69241c1a6edb591ac718b262acd5122806ee11c9a619d25a1e8cbafdd75d9953f88dc5a193c00509f58185adb7166d514e9abe0e2bcc87c15fa0d1b2864ae6e320d154ee17d50df4291b271e1cb42869e5c1d396091af829eff8ae349ddf4c49648d767a4286bb51f9aeaad5c5c47085b35406ff4d45b1d646f1b8f1e4641f44ffcdcb8f79267e94e145522acd730c59d9a6eef459e456a1377e4f3292ab586d047d28a51370f38cd2dcb57c5ef15cf9241b0b7d1c50f9f9e2a827b49bd084525dd530a440ab53810d8794e1bfcac4830f2fa9d28b45ce50b4d471a650ec5ced240bdca5ff17fb23a0011cade29b4749135788f39f2f90cc41ecf24ac39a9e57f91fb9709cb9139b266fecf8846f3342f3491859ab0542a5748f09b73cef07c31f88f8353ce7da017f7dd40508b9dbb1b26245d9ab461f78ee5542974ecd4a369a847620654588e6b3c9e60a05632f2a7e63bff05aad09465300abc60d4b378542db9c195359f93c727c8fadd71ff254dec98b9f257055900b8439d4b20345bf0474588efad9d2a8796e4349985fd1e1791f0fc75a25d47185dd9063b594975c725ec67c497a7efb036e1f17c0a42a194742e1fb6d062b42ca6bae088f756325fe2caf12b2101bf1b4d58e05d52d8b3592893a054c24d63a0322e20141a231dc3581419347ca6f1c97b7efd197c11bbeace06c3620d8a1e72216fb6c19ecac5dd4ec6c3527ad72360c9dc2c3b3bfcb1d702a0643e1fe90fda934037735d832ee36a404f21977630daf23ede143eae5cca722c5f01273a246e59d1bdf92749d0ee182e68ee0426e8489bfd5adfa1e2820bddc10c9d4cc4dfcc0db397ac01ed820063ddcf53c25a28fff37a548b7e61627ba0f6208c26eb6814d707b0b30b2e86f442dcc77ae0b4c03c742feba4191c0f6fe3b94538ee6b0db09e325eaad9ec378ac45c57d12117284c19fd8f3263e55db49deb5a1766d369034c618e4a4c6f58c1685e08f418c33f03375967ed2a7f34d5ac2f26b209d9fbe657c8c400c2a7834a0771a36bcaa66a06fb9632e94147b71f0746090685aae17bd149f5e3ce229216c923a757673c03e6bd11d572fa3f6d72ed49cfe111e1226910ad1694c7e6ab7905ac050d42917613f97a1876a88d7034ce49d384202d41bb22d42490ef8e4fb16a4ff8cecb27578028c114e53da70653bbad7eafa48a3c6fa68f3f7087345b5777e9c1f6986e646c0f701ff570c01d7115d12b8181b72783fd8b92cf460cc2b0faa6d92626f40047a23b737b4c0c8fd6b3b2d0eeb2038ba51bc5ff0ed27a6e85fc2f4f84faaa166be0f5a6e50feebea542eeb4937e25a4400cc3c796aeabbddb9bbac1730e5e6e5ae70a75233196f5ba7fce2fa1fe485d4d93fc09cdc2f560637aba9a7bda0c1b46231cb46392d2c02f745b1fcd09cd011e5c9c02a064b8f584ab7e09bdff439589ffea0e1b9c1fd35cf828819c485ac6d9c24e7377358bfc368b0a7b6e787a6b96db52756dd8a03dd8499d17d55d4e571d6764adf56e46d7dcc142e8581191e089b35810a5fc21938787605b9125e5feafd0702e48cb669e8176aa66c0b4cc587f3cdb51ecae939026b16c4c1ce1686eb75d141f51e08a9bd1a7d8328215f47105aab8fe16d315ee0433807a2911bb1b5c20085cc2cad0267830519f06fe73a1265c7a04c9a75d9e3a9b44cc0239a81ca81edebbf987df1628d238097ce5b289f4c64a7f1ce84d4abb554132d66d8a8425dd7c8011a8239563505d08601c100dda54d36860e38ec9a7163bf6ada16fbefaf3474940d0f78d8bc695e57aba37c34a8b68740234067ea135de11f7af41db7b9f773fabf3ff9c64be10e78ee9588d83fb376048c43f3778bd1adbcb5e63bd18dacb523e3dbd275d397a04a8dfa6b876bbabb94351164f43a36a73d086f247ff5b7e7a40f4557bc562149a6e9d8a15f673e1cd3c2b7e096c95b24be38ce89aa32948eb6ecc69d51fc7dab4750795343d6e4ebd8d3b89f153b3282122986a55573f54ca5559a6a4a3893d980e23ad1acc372eae6601b91396df91f96fe04a8bbd5f48d7b6afc5089ebb637fa5789da351c1bffdaa89bbbe4299c774be2a64c411d37fb98b3b987834e7bdef9827f768afed83375f4303d6abdffcc18170aa4840f140e8ac61fe524b26e8fa9383c9c10d77a93ba1249581cb380a9d263b6f10df5e88a773ec7dbb3c0c9a251b3f2d2d54cb0776833bac28ad2f1db95526f61aea66a576be06268ab241d25bdef338629897dc96c9e770d85d3d83d25b7e2f9c542ab8b86480efc7d017612f4e4c7ac6389046e9df86a97733c5bf96a0efdc8c9489663d6c4e6ff4fc8be0b96d8d0f71841a06541731b13da94bb1fa3272ff43a386d2f41e1a489362fd264b88588145abad341c7b8972a72bab5daad65148e916839c658e1b7092afde6897d50682a0ce7b5c30f7c19c170d21f7d7e2eb15f41c71a806db5a43343c35ef124bb17b1fb0de3e74e209b54e4bef72c89e5aab9e5a71904aaa793e9ba315faaee6d4e652fc4cd2f5a8db703fbc598b87b59968c8705719f97df552636e5dcfc96470ebd39f1b79b7b02fead29373f93ffa544f117dbc3568d57c91c2f0fd6afe5829ecc49e505f39507b8b0062b9bec28844a5b8566caee9ad8589445eba20e238ffb2cf5ed488638f2e69b330fd1fa32cea40b6eb85364b75fae13f04b4c098269011f173a987883affa3c44c4b366e45ae04fe3890e82bcd88559a7f396341ab631722f8980e587904029785c8449219d63b6e45b2d01c4f2c8346976c0adbaef3f21be46273c6bd496ce64952c5f429c900275e30d3b89defc342bf1ab9472bf05223dab95901b6ebe84e57118a826bb28b2edf7d1abd3ffe691b0619d25a3a41816bcc7e840f954de65572c5a64b40525f92a78da0cd6755a7fb3be25be7cb4a08ca626698314e37bc1e47eb86165e3011dce51dc6272172616b40c87862e511dc79f9ead6257af568398f9e8535276299eaaccdd7c4036858c3f8f68508d8ef843a8c2cc691ff567828bf5c3492ab80f4cc86a8e7f9253b0faf4d6ea68f6b6f5ca519055c9849d7a4ee2318ebc74ea81facb5baa1f902863ba0fd7feb0e77807604c798ac1dca05b9a7900d5f989329882020ee701056613755b724223df5f7351bf193bcd5a2efe0db994c51689ba3c1da6a11ac8b60ccf2a86666f942a228dcc62483da3b93666685c24d436f97a386365d3f699da11459af0c9050a0372b5acafbed04dfe4695b6a9d597dedbc03044fed4c1ae4848822c2c3ffb5cd31abe1da62cfa1764cdf065118d386617664eb97484e7a70c252af3c85345629229428b38f3bc511ea177169d46519451536e14a2dbc9ddc0d1c7497f9174 Hey, password is required here."},{"title":"2023上半年总结(写在五一)","date":"2023-04-30T19:16:24.000Z","path":"posts/2023a/","text":"b3dc07a81f6459d120ce338ccca55046116de9af7135eccfa9c61a82756948fc17f3721d690036660e1ca319b1de7ea5abee2b21d3b2ea15225dc942382fb2c6a01d15f403303ea35415a8b95d29bb66ef379a601361db61ad440174f24415743f6c6f9156bfeeaf0e6657aec44167f87bf27d9768494353a067b87b00a8c76676a1351ebacf5d195df6fa9b57ce808c501fa2aa03b645d2393067c862c33671b06654a4787ea1d45a0123005205bec8cea6476970d45126eb65e8e41c2204755ee61b772cfc80df1938c72d56dd1171efe1683b3a3e8225dfdcf8e999ab2bc72d9c078c46414a2a1ec88978f9207b13046bbb4152fa7c5c912771ac4081deffca6235bfed3cfcccd86776aeb2e247975da6d8d8c5f97217c495d5f55645b82fda52bc29b6114c3c612e47d09f6f7bd6df69366238caddb4f2e0f75a06adfda300037feef6a9e6426651f58b448bea63e6c19e5de7941243ebbf477fd0fdff187250aea12f29fb37bed75737c26615effd83715a69856556d0d7caa8ace3af0b697e5756c306654f2f3c2fa5569ffb2857ad59ff567047f989d51bbfbcf7ec6873fefb563576bb875354666c54ff7dc85524e5fbb3d0f89ceecb3cdb74abfcd2388266307d1caa091555781377025fe5b22396aa85d41793338e3db1fe30eb6a993e70ef79933ca100cee264aabdb13a7170a4565c364d12d0599160d5b4899fd4be011688b95398d2400b6330dc25b4e33d754a9ca8c27ad29a0fb8637129b6a8c85cf3d64aae7a79da926e99f7850a9b3d601f7c8cb0da06a14560e17c19eb45ed171f9e8ee924f4e1e49e06b9355e9233faf5aff88c09da9542b9b90d86c95e29fd0e9f85444a4b1ca681504d28dd142e7cff06cd226adcb6ccf12bd3d3ebb38acfc9059ba34ff82478f4e21a148982f2ffbb0b1d02674b850fb02cc1c92e1ef9d05347f15f4f64033f0dded2725d0d44a668e508a7c774eed0bfa85f8583a3a4a5e838db20c7ab8f2853ed080d789cc9d4f85be2b5b69552fc104069cf4f2e1ccbbb81cde2a09dd18dfca376ca1bb0794c9fb2ec71c11a1fcaaa8176255d9077fb86e66e08ae8bab5ff4fa1dd3bf874298c6e18887bad76d2649968d114ce2c3cd5a3c7be7b4abf5273e1085a1bccc567d8a37370c9afeb0a64bbbf9022a48db59365068675bce2de12b96100c4e11adb4071ff9a102dce16f2fbd48eba54c64298ae4ddf256c9e3b62a8392a8ae4b74df9a463ccf0bf5a2fe005ba508a520f5c6ab641800e34482eec8f13abc390d39e715dff34268e04a8dbf484816f755a578d502cabd819b27f40a4c59fd3ccdef61d045ef3059653482fa47318d385d0500904d924f175c1977dc337a854ef864eb24ed831ecd2678a99245de31475720585c390e5a7ed145b33c36d69027c7f7e1ae9ed74d1c4a0776d9e7d5aaa7339734835ec2d1b45692308a7e91f03170a586d86a8d68f785cb7f862e7345b121042a8fdfa42c32c999e7b14cc4f78cf433f818331d14b9edefd8ee81467e20972c5feeb7655100861d9e75bfacc02c24800bce75e824071f6ba480696afdc9be8a95a9d37344f74c9d2f20683ee1dfedb0ccbe2c871e7f35ecef96de4c674fdfb7dbe2af9678e45177c20f6fb3eec48d11ff50998a251978e816dae9307d28beeb9718ba6c11f94b1891613324224d36a5a0c4bdf0d8248ec2c6425a4c32712d6f34966800b283b127ff4f3c65414bfff3a187ad7a4cd6156d599992a0cd24041d258777b2a1db4660171a8137c071ae53025d28c8c2892250876516418433431dcd099cd87c339b90e7b4beb8db8385be58f49403820c3dc07110e581a7db180c174fa35020afc38a66b8f16c1aa7ede367eb98c619d831fba5bf94ddd6ff9941f90abbcb969aaad58622c9d2cd179c2dbaee78ea0f9f642f41c3a137f5f8ce59714c66acac56ca80948194ffe566d4c2d612cd2c7b9ec3ee2f1231e3620459b2eb79dd7b543cecb1f9d67673b4abdd22242e02f79451dd60f057b125fe420618ef3338a82c42b9196043be45ef43369da4887b0ddab80edcef2242b94ad9b0bccb50a745841b07175cde913ca4de6df1e31e2763e172a14e2c88fb29bef3398ce153a851f475706209307f0786e45562128f58ed2fee5b4a78330442ceeeecc51d78736696a3a2e00faf613eb3dac0bee0a5b7f256de770db74d3fabc06a9a7ba2417820df126eb0fa17ef4eaf1335ebd53686246b8c5255d5bc35c5b5beeeed19a840bf962abb10e74175b7c49d7a169ef72c2ff405f22ed09d518e78adb90f709be4c5914c0dd9df2f8cfdf03d132401308c41df6c85671f19f76720ef328bc5b1e1402b293898d2c1f46dc958433abfd2441ec9f5208e62643a6d5a4228b70973e81506fbc1f213c66703983a3fd7a35e72df84f010bedef9f95b54970c6122477c1c905300f290f0317515434350f9d749f60b55c06215d0b6f8a53f11579d3503990fab5eb95f779153ef32906d745ad6d5ad6fb498a9048bd33bf3292f0cc53b6537c7985cd72e071960d7c572561bdfcbf2b95011351a237dd9ca4d65771e5ff8bd91ee4ba1b71951d6ab211b6f5b10c15ab9e2970c87e5b0b852343d29fca6c0e3d54732bc9f2c7a971e7cd6586fcc719b104a206748dd2b2ff4ad6163ca121a1e62ba80aa91acc29763f4b164bfa9b52a392ee175435283bdced0b2210235074ebfa0a52b2b2389c8d90f3fe13fbd1b44e6534a2de358efae5eeb51d935325026c964209fa5e918c6f2c158ab412ff0342d1d7319b230c5ac880fc6bc53328d42f0fde9057180c7f3aa55990dc19a25a057f1afe05d31396e2d60dd72baa7044b758732f5b466bd7243d51e71cb3801c3fc6a4084be623cc36329ef25b4be7f2374423beb3fb48a7c35fd2d0961546dc605f25a260c50e791c25291c8191ce55b16965b7caf2b43ea2cd69fff7d71a17e713d1fc7df0f5ab55b503085cb30b0edb5c7d14bc48cdc31772706357ec2648b374790333497f7f2798cbf951c8e2f42c497d472c85b141f859800d210d655f2acf1a6fd8d259f0662be33000f22fcb8d6300cd41400ebbabbf0d42ea457213f548e6a71312d62f5aeb5ca2bc1b6fc11690f17be8ee0f872cbcc7323c8b7765134ab70ca8a72ebad4d9e5db12e207aee53e43d9a42e5fb21461e221f7986435e1f0f2b75edd6c89488dffd055fa8f7e2bf86e5906dc7b5f19e1ba6bbc22ba3983504d03e1c0889649b3bfbde88d8eb148eeb783a4cbb8b199ef58965f1f3a31f0559507640211d8c62de1020142456500e0aaa4f3da82f36daae0288597fca7b48dca12c2b2f6d54e654c70c83fb5bf999e27da7af4a98f80911652854b1fde14a7390761d4a873ab53396b0d34ef21a36035954664465e8c52c7172edc0ea305fa7344d57d3a51ff801a8c7afd86952a108982101aeb737a29111534a31c5989be365ccf5d8f9f1de8a7c106d582572241db3e8eed380c2e2819660f651d9fe082d5a4eb79a59a60cf62b15f435d241050cbaec0f0ba1c029ca5565a98a48a19de1db963a46101747e3919978ccf6f945f9c7fb383a0398643e3cbe88e34db77407a1c008534b2e5f14f4579bc22bb082d5786695d81e67b2ce9f6aa9cb445812a3b2a0e01ca47ac385640b88e18fa9cfe760ba6328253c781cb963a8cd8434fbd51abb8798a9299966941d31c08445b23ad9e8dff95c54e6c8791cb3e85b2733cefa62e41bf17e22fb7148811b196088f7f00e9aa404a539386809df25ebefca1eb52d0de4d48da335e2625bd6456034a384a454b32e07db056557e4b4bbbcf55efc0802e65fbd86fc70281d3fcd03cbbd14c93e3f8f77cba91668235ed98c5a141ccad4d0b5aad84f18f12f07aaafdc65f5d70f8c8be3021eb60c2c82d23ca3aff1a2ffb0bf86a75e698063776c7bd848ad9e819aa61a25813a214e7bda5aadf182ab983732af3af07597ec92c48ebc5e72c84bd8b0115228d1db868e5adb209371943bd41d1cf5caa839db2ab5273ea558a924a4c185539a0df2f8e900f526145934dd794c0ea31a476ba74b7bdb4b457bd2fc1a3501c6267fd9070640ce635848fa9c0848f3c006eba0d6e54af1c9da67bfe494bb33fb726f379d5396ebf68d826e609e15f56807adb72c170c993ee8f336a9453e7b8c622ea7e64b351f7494cf1bb1f68751e05ef9bfb6a7eff9ddf96e253626e030ed56aed15e15886be150af573f833efaf3b41fe281f8e8f599e2dec7df8dcfbb6d408e3358415d46ce7420ca658a1dcb7361e289043e27f05839af88ce810cea8ee19989da86a10584b70eb46330cd5292d89eeb8fc11ff5aa32a0f98a481aec10f580b8f0a972bd6e5e1f71d9a199feb23fab9aed6f9da39150cc77098d7a38344f286de76db18ba11bf0c3d467640a870a8f4477b1c177aecfa812a92fb020f6e969be9638033bc4e054234abab4211dd063a9d53c7b12a009880338ea6cacf4068ce4021e63123bb469239f1178255e8e195d97f241a9dbd9243a25690b817a02e0edd7e4512a235332850b836e64fa92e2aadd51296231bd7f9b10510952acefa6e27fa411da0466add8fb92c5ec48a1f436e1e3b8effbaf6b5efcc038e419c2300fd9d07013d804a869029001dfa50513a1e1dd2d57fdca2213f3ff92740ddd5bd37422a7eb64a75700c37c9e1176 Hey, password is required here."},{"title":"【引擎】基础架构","date":"2023-04-30T01:23:57.000Z","path":"posts/2TKCV1V/","text":"提要 很多技术点只是埋了个坑, 等以后学到了再回头补充 游戏引擎 Engine Types Games UE ⭐ 商业引擎 PUBG Unity3D ⭐ 商业引擎 原神 CryEngine 商业引擎 孤岛危机 RAGE 自研引擎(R星) GTA5荒野大镖客 NeoXMessiah 自研引擎(网易) 阴阳师明日之后 Orge 开源引擎 天龙八部 Godot 开源引擎 … Cocos2D 2D开源引擎 … … 核心难题 1. Complexity 游戏引擎设计的计算机技术非常广泛, 涉及模块不限于如下: 渲染 物理、动画 Gameplay 网络 … 图形与物理依赖于数学, 跨平台依赖操作系统的兼容性, 在线游戏又依赖于网络同步, 实时运算要求高效的cpu/gpu优化… 游戏引擎的开发, 可以说是计算机技术中的地域级难度 2. Realtime 区别于电影行业的离线渲染(off-time rendering), 游戏要求在30ms内完成多个模块的运算; 对于某些要求高的FPS/动作类游戏,甚至要求16ms(即60帧) 3. Collaborate 一个3A游戏至少上百人的工作团队, 难题在于多职能间的协同合作. 涉及的职能不限于如下: 程序 美术 (模型,动作,特效,场编…) 策划 (数值,关卡,运营,战斗…) … 引擎架构 ⭐ 1. Tool Layer 工具层; 接触任何引擎的第一印象, 都来源于 编辑器 2. Function Layer 功能层 让整个游戏的功能丰富起来, 如角色控制、如物理碰撞、如特效渲染等 tickLogic(); // 逻辑tick tickRender(); // 渲染tick 单线程 -> 多线程 ⭐ Fixed Thread: MainThread, RenderThread, LogicThread… MainStream: fork, join Job System: …? 2. Resource Layer 资源层; Assets: 将外部的资源导入为 游戏中的场景,模型,动画,声音… Composite Assets: 索引其他资源(xml), 如场景索引模型、模型索引贴图… (Assets) Manager: 运行时资源管理很重要! 频繁的加载与释放 垃圾回收 延迟加载 3. Core Layer 核心层 Math Library: Quake’s Fast Inverse Square Root Data Structure: 如八叉树管理场景 Memory Managerment: 内存管理, 减少cache miss, 处理垃圾回收 4. Platform Layer 平台层 需要兼容PC, Xbox, IOS, Android等多个操作系统 游戏对象 Everything is a Game Object 游戏中一切对象都是一个 GO, 根据是否改变/运动可分为: Dynamic GO Static GO 如何描述一个 GameObject? Property: 位置, 大小, 其他属性… Behaviors: 移动, 攻击… class Character: public GameObjectBase { public: // Property Vector3 position; float health; // Behavior void move(); void die(); } Component ⭐ Every GameObject could be described in a Component 将游戏对象的所有功能拆分为 Component, 例如: transform_comp: 移动的组件 ai_comp: AI的组件 … UE, Unity等主流商业引擎基本采用如下的设计: class ComponentBase { virtual void tick() = 0; } class TransformComp: public ComponentBase { Vector3 position; ... void tick(); } class GameObjectbase { vector<ComponentBase*> comps; virtual void tick(); //... } Tick ⭐ 游戏世界将所有 GameObject 的 Component 全部 tick一遍, 即模拟整个游戏世界的运行 Object-Based tick: 按照对象tick ❌ Simple and easy to debug. Component-Based Tick: 按照系统tick ✔ Parallelized processing. Reduce cache miss. Events GameObject interact with each other via events todo Scene Management GameObject are managed in a scene with efficient ways 多个GameObject如何管理? Uid Position 当地图上的一个角色开枪, 是否要遍历所有的GameObject以查询伤害情况? 二维 xz 划分: Quadtree 四叉树 三维 xyz 划分: Octree 八叉树 TODO Bounding Volume Hierarchies? 搞懂 BVH 的原理 知乎: BVH相比八叉树有什么优劣? Wiki: Bounding volume hierarchy 学习参考 GAMES104-现代游戏引擎 Wikipedia: List of game engines CSDN 2016: 游戏行业内部主要几款游戏引擎的技术对比"},{"title":"初读 周易八卦","date":"2023-04-29T04:58:03.000Z","path":"posts/3GXTS7K/","text":"先说为什么叫 周易? 据传 \"周的易经\", 系周文王所创 昭示 周期性的规律, 世间万物的推演和变化 理解周易, 核心是理解下面这段话: 道生一,一生二,二生三,三生万物 道生一 “一” 是世界上划分物质、时间的基本单位. 一壶酒、一竿纶… 物质的划分是简单而具体的,但如何划分时间呢? 古人通过观察时间的变化规律: 根据日出日落划分为 “一日” 根据月亮阴晴圆缺划分为 “一月” 根据太阳位置变化的周期 划分为 “一年”. 这就是 “道生一” 中的 ,一,而它是根据自然界的运转规律得来的,因此称为 “道生一”. 一生二 古人观察到一年中总有两天很特别: 一天白天最长 (夏至)——> 阳 一天白天最短 (冬至)——> 阴 这就是阴阳两极的由来,即 “一生二” 中二的含义 古人继续观察发现,世间的任何东西都可以通过阴阳来划分: 日为阳、月为阴 男阳女阴 上为阳为阴,外为阳内为阴… 为了方便记录,古人创建 爻(yao二声)来代表阴阳: 即 —— 表示 阳爻,- - 表示 阴爻 阴阳爻与二进制 阴阳即二进制中的 0 和 1 古人认为阴阳是组成所有物质世界的基础,这与计算机的组成原理是一致的 阴阳转换 古人观察发现,夏至和冬至间,白天长度一直在发生变化 这代表阴阳两极之间也会相互转变 它在当时对农业生产起到极大的推动意义 这就是八卦的由来: 阴阳拓展 由于阴阳,夏至冬至的划分不足以满足 农耕农时的要求(理解为计算机中的精度不足); 古人提出了 春分秋分 的概念 那么怎么表示呢? 聪明的古人将 “一爻” 推广到了 “二爻” (即计算机中1bit到2bit) 这样就能表示4种组合:春分、夏至、秋分、冬至 二进制的理解 对于上图用二进制表示,阳为1,阴为0 即 春(10),夏(11),秋(01),冬(00) 二生三 古人将 阴阳爻 扩展到 三爻,即创造了8种的排列组合,这就是 八卦 的由来! 三生万物 古人的 两仪(阴阳)、 四象(春夏秋冬)、 八卦(天地水火山雷风泽),就是自然世界的基本组成,它的确可以表示任何物质. 用现代计算机的解释就是,3 bit的组合可以推演出任何可能. 六十四卦 古人将 3bit 的八卦,衍生到 6bit,再拿去占卜算命,就是所谓的 “64卦” 解64卦 以下图的 “否卦”(乾上坤下)为例: 俗话说 “否极泰来”,显然否卦是不好的卦象,但是 “乾上坤下” 又很符合自然界的规律,这如何解释呢? 为什么 “否卦” 是不好的? 易经的核心是 “易”,即万事万物是寻求变化的 因此 “乾上坤下” 这种与自然界一致的卦象,属于 “不易”,毫无变化内部无限熵增,最终会乱作一团 卦象赏析 64卦 吉凶 释 象 乾,乾为天 乾上乾下 大吉 诸事顺利,名利双收 坤,坤为地 坤上坤下 吉 精守安顺,妄动招损 屯,水雷屯 坎上震下 吉 宜守不宜进,蛰伏 蒙,山水蒙 艮上坎下 凶 蒙昧忧愁,缺乏果断 需,水天需 坎上乾下 大吉 等待时机,收成在后 云团等待下雨 讼,天水讼 乾上坎下 凶 诸事不顺,避免树敌 天水相隔,事物相背 师,地水师 坤上坎下 中 包容别人 容纳江河的大地 比,水地比 坎上坤下 吉 求助聚力,方能谋事 水依附大地 小畜,风天小畜 巽上乾下 中 受人牵制,蓄养实力 风云积蓄但雨不曾落下 履,天泽履 乾上兑下 中 谦虚自重,不可攀缘 上天下泽尊卑先别 泰,地天泰 坤上乾下 大吉 万事诸顺 否,天地否 乾上坤下 大凶 诸事不顺,凡事忍耐 同人,天火同人 乾上离下 吉 与人共事,谋事有成 大有,火天大有 离上乾下 大吉 事事亨通,大有收获 火在天上明烛四方 谦,地山谦 坤上艮下 吉 谦虚忍让,步步高升 豫,雷地豫 震上坤下 吉 诸事吉祥,可得长辈助 雷鸣催发大地万物 随,泽雷随 兑上震下 吉 – 20. 观,风地观 巽上坤下 中 处于变化中,观机行事 34. 大壮,雷天大壮 震上乾下 吉 强盛壮大,切忌骄傲 55. 丰,雷火丰 震上离下 中 如日中天,谨防盛衰无常 63. 既济,水火既济 坎上离下 事情已成,谨防变故 水浇火熄 参考 B站: 一个视频就能讲明白的《周易》底层原理 B站: 如何解读周易64卦"},{"title":"【C++11】lambda","date":"2023-04-27T17:04:43.000Z","path":"posts/87V2YJ/","text":"Lambda lambda 表达式是C++11最重要的特性. 中文可以翻译为 匿名函数, 在此之前 C# 和 Python 都已经引入了 类似lambda 的概念: C# Func<int, int> square = x => x * x; Python square = lambda x: x * x 经常使用Python的同学应该能体会到lambda的好处, 避免了繁琐的函数定义, 提高可读性(避免跳转阅读)… 参考Python的语法, C++的 lambda语法基本一致, 只是多了其特有的 值传递 和 引用传递: auto func = [captures] (parameters) { /* func body */ }; 一个计算求和的简单Lambda函数: auto sum = [](int a, int b) { return a + b; } Parameters 这部分就是普通的参数定义, 不赘述; 重点提一下 auto lambda(C++14), 即模板传参, 写起来非常方便 auto lambda = [](auto node) {}; Captures 与局部函数不同, lambda函数无法使用外部的变量, 因此需要通过 捕获(Caputres) 来获取外部变量. 同时捕获也分为 值捕获 和 引用捕获 两种: Value capture 等同于参数中的值传递, 会伴随一次拷贝, 无法修改原变量的值 Reference capture 等同于参数中的引用传递, 避免拷贝, 能够修改元变量的值 Implicit capture 隐式捕获, 主要如下几种写法: []: 无捕获 [&]: 都引用捕获 [=]: 都拷贝捕获 [=v1, &v2]: v1拷贝, v2引用 关于捕获 [=] [&] 不是捕获所有 local 里的变量, 而是 \"对lambda内所有使用的变量, 采用 值/引用捕获\" 避免对 很大的结构体 使用拷贝捕获, 例如 vector<int> m_huge(10000) Examples 看了一圈发现微软的文档示例(Examples of Lambda Expressions), 写的最好, 以后多读读微软的文档… 这是微软对 lamda 的介绍: Lambda expressions in C++ related: Modern C++ Tutorial Ch.3: Lambda Expression 中文 lambda代码示例"},{"title":"Fluent Python","date":"2023-04-24T16:06:47.000Z","path":"posts/3K47Y0N/","text":"《fluent python》前部分阅读摘录 NOTE 英文原版pdf链接: Fluent Python (2nd Edition) 上书随时可以拿来翻一翻,很多地方一目十行带过了 前景 精通Python? Python作为一门脚本语言, 上手门槛很低, 但是自以为 “精通Python后”, 往往导致自己再难跳出舒适区, 不会花费时间去学习跟高效、更科学的特性;4月25日晚, 花了半小时初读第一章 Data Structure 的部分内容, 收货非常多! 阅读计划 个人以为该书充当两个重要的作用: 随时查阅的手册 (Manual) 编程习惯的养成 (如何Pythonic) 章节划分 很多小节应该过得很快, 实际不需要1天的开销, 暂时先排到五一结束吧. 阅读这本书的收益还是很高的, 尽快读完~ 大节 小节 排期 进度 Data Structure 1. The Python Data Model ⭐ 4.26 ✔ 2. An Array of Sequences ⭐ 4.28 ✔ 3. Dictionaries and Sets 4.29 - 4. Unicode Text Versus Bytes 4.30 ✔ 5. Data Class Builders 5.1 - 6. Object References, Mutability, and Recycling ⭐ 5.7 ✔ 1. The Python Data Model 书中以实现52张的扑克牌为例(不包括大小王),讲述了若干Python的特性. 这段代码推荐反复阅读… import collections Card = collections.namedtuple('Card', ['rank', 'suit']) class FrenchDeck(object): ranks = [str(n) for n in range(2, 11)] + list('JQKA') suits = 'spades diamonds clubs hearts'.split() def __init__(self): self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks] def __len__(self): return len(self._cards) def __getitem__(self, position): return self._cards[position] 涉及Python特性如下: namedtuple collections.namedtuple('Card', ['rank', 'suit']) 它创建了一个 Card 对象, 拥有 rank, suit 两个属性. namedtuple 可以替代 dict 和 class 的繁琐创建, 大幅提高代码的简洁度. meta programming 重写各种 内置函数( __len__ , __getitem__ ), 合理搭配 len, [] 等特性使用. list comprehension 列表推导式, 看第二节. TODO 找时间过一遍所有Python的内置函数 例如 __getitem__, __dir__... 2. An Array of Sequences Sequence 先看Python内置的容器 根据存储的数据类型可分为: Container sequences 允许不同种类型的对象: list, tuple, collections.deque Flat sequences 仅允许同一种对象:str, bytes, array.array 作为对比, Flat-seq 在内存上更为紧凑和节省, 因为 Container-seq 会存储 ob_refcnt、ob_type、ob_val等 C 对象, 这导致其内存占用也更大. 根据是否可修改可分为: Mutable sequences 可修改类型:list, bytearray, array.array Immutable sequences 不可修改类型: tuple, str, bytes Tuple 尽可能的使用tuple类型(在某些不可变对象的场景下), 因为它有两个优点: 防止数据被错误修改 (如误传引用) 比list节省内存 tuple 不可变, 是意味着被tuple直接引用的对象不可变. 以下图为例, tuple中嵌套一个list, 该list仍是可变的. List comprehension [str(i) for i in range(10)] 这是一个最简单的 Python List Comprehension, 即列表推导式, 它可以替代简单的 for 循环. 性能上, 推导式 往往优于 for循环, 主要有两个原因: for循环中的 append 等操作很废. 推导式的 for 是C层面的 for, 它要快于Python写的for循环. runtime 优化 当不需要一次性产生所有数据时, 使用(str(i) for i in range(10)) 它是一个generator, 每次迭代时才会从中取出数据 filter 函数就是一个generator, 使用list(filter(...)) 以将其转化为列表 Unpack and * 使用unpack来赋值 a, b = \"1 2\".split() 使用*来存储溢出的数据 a, *b = \"1 2 3 4\".split(), 其中b是一个list 3. Dictionaries and Sets 略 4. Unicode Text Versus Bytes 写在这篇文章: 【Python】编码之encoding"},{"title":"【问题求解】Hash Map","date":"2023-04-23T14:59:09.000Z","path":"posts/7BBSYB/","text":"概要 《Introduction to Algorithms (4th)》: Hash Tables Python dict的算法原理 Hashing A hash table, also known as hash map, is a data structure that maps keys to values. 哈希表, 就是一种将 key 映射到 value 的数据结构 哈希表实则为列表的进阶应用, 那么为什么需要哈希表? 先看列表的增删查改开销: 增删改: O(1) 查: O(n) 因此遇到需要密集查询的场景 (如通讯录, 如飞机航班…), 列表就会遭遇查询的性能瓶颈 (数据量很大)… 此时 Hash Map 就是应对高效查询而提出的数据结构 Hashing 主要由三个部分组成: Key : 可以是任意的输入类型 Hash Function : 将input 转化为Hash index的函数 ⭐ Hash Table : 存储所有Key, Value的数据结构, 类似Python Dict Hashing的性能几乎完全由 Hash Function 的好坏决定, 因此本文将围绕 Hash Function 的选择展开论述. Hash Collisions 最朴素的Hash结构, 即用一个大的数组表示 (即直接地址法), Key即Value. 缺点很明显, 随着数据量的上升, 该数据结构的内存开销很大, 且存在空隙的浪费 这在实际应用中显然是不可能的, 任何一个Hash Table, 其大小肯定是小于数据量的大小. 因此根据抽屉原理, 总会存在两个Key 哈希到同一个Index的情况, 这就叫哈希冲突 (Hash Collisions). 如下图所示: 如何理解 哈希冲突? 班上出现两个同名学生, 老师第一次点名时, 根本无法识别到哪一位. 因此需要额外的特征(身高、衣服、别的外号等)才能识别 如何处理 Hash Collisions? 🏅 Stackoverflow: How do HashTables deal with collisions? 1. Chaining 算法导论书中的方法是, 在哈希冲突的槽位中, 引入一个链表(如下面所示). 这也是 Java Hash-Map 中解决冲突的方式(jdk1.7). 0 -> 40 -> 27 -> 53 // Hash Collision 1 -> 88 // OK! 2 -> 16 -> 42 // Hash Collision ... 2. Double hashing 第一次哈希冲突时, 使用另一个备用的哈希函数, 循环下去直到冲突不再产生. 缺点是哈希的计算时间复杂度变高. 什么是 Load Factor (负载因子) 负载因子 = 总样本数 / 哈希表容量 对于列表来说, 负载因子为1, 永远不会出现 哈希冲突的情况. 负载因子越大, 说明发生哈希冲突的概率越高. Hash Function 此时我们得到 衡量一个哈希函数好坏的标准: 运算 快 内存 小 (产生的index少) 哈希冲突 少 ⭐ 负载因子 小 常用的哈希函数有如下几种: 1. Division Method $h(K) = k \\space mod \\space M$ 最朴素的哈希函数, 取模. 其中k是Key, M是哈希表的大小. 示例如下: k = 1278 M = 11 h(1276) = 1276 mod 11 = 2 这里的关键是 M 的取值, 显然它不能是 2 的任何次幂. 如何选 M 的取值? 2. Multiplication Method $h(K) = floor(M \\times (kA \\space mod \\space 1))$ 乘法哈希, 其中k是Key, M是哈希表的大小, A是一个 $(0, 1)$之间的常数. 示例如下: k = 12345 A = 0.357840 M = 100 h(12345) = floor[ 100 (12345*0.357840 mod 1)] = floor[ 100 (4417.5348 mod 1) ] = floor[ 100 (0.5348) ] = floor[ 53.48 ] = 53 Application 哈希如何用作加密? 以SHA举例, 用户输入自己的密码后(比如长度为5~12的字符串),系统会利用SHA Hash将其映射为 一个256bit的 字符串,并存储在计算机本地. 当用户再次输入密码, 只需要对比生成的SHA Hash值是否前后一致即可. 同时, 由于Hash的不可逆, 还能有效起到防盗的作用. SHA SHA-256(Secure Hash Algorithm)是最广泛的加密哈希算法之一, 任何长度的信息都会被映射到一个256位(32字节)的字符串. 详细可以看这篇: 知乎: SHA256算法详解及python实现 hash(\"hello\") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 hash(\"hbllo\") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366 MD5 MD5(Message Digest 5) 是一种消息摘要算法, 同时也利用了哈希函数. 本质上, MD5就是把任意长度的数据生成一个128位的字符串. 详细可以看这篇: 稀土掘金: MD5算法的应用及原理 游戏Patch经常会使用MD5哈希算法, 来做文件下载校验. SVN检查本地哪些文件作了修改, 也使用了MD5. SHA-256, MD5等哈希算法是否可逆? 显然是不可逆的, 因为这些是摘要算法, 本质是 无穷信息 到 有穷信息 的一种映射 知乎: 为什么说 MD5 是不可逆的? 王小云: 破解出一个高效的MD5哈希碰撞算法 Paper Wiki: Hash table Geeksforgeeks: What is Hashing Hashing"},{"title":"【Linux】如何阅读man手册","date":"2023-04-22T03:34:01.000Z","path":"posts/man/","text":"NOTE man-pages for web 程序员工作生涯三年, 至今恍然醒悟, 发现自己特喵的甚至不会读文档... 搜索能力、阅读能力、表达能力, 是容易忽视却重要的技能 TODO: man如何在多个标题之间快速跳转? man如何显示行号? man man man, 利用man查看man指令的手册. 基本使用: man 是完全按照 vim 的操作方式, 推荐阅读站内博客: 【VIM】Vi Improved 比如: q键退出, hjkl翻动等 下面总结几个重要的概念: sections 为了处理 linux 不同模块手册中重名问题, 利用section将其区分. 通过 man -f [cmd] 可以查看对应的section. 以 passwd 指令为例: man (1) passwd: changed user password (可执行指令) man (5) passwd: the password file (文件格式) 通常情况, 使用 man [cmd] 已经足够满足需求. 下面列出常用的section: 可执行程序或 Shell 命令 ⭐ 系统调用(内核提供的函数) ⭐ 库调用 ⭐ 特殊文件(通常位于 /dev 目录) 文件格式和约定(比如 /etc/passwd) 游戏 杂项(包和一些约定) ⭐ 系统管理命令(通常是 root 用户执行的命令) 内核相关的文件 headings man手册的输出, 一般有如下几个标题组成: Name: 指令名 Synopsis: 指令的格式概要 Configuration: Configuration details for a device. Description: 指令的详细描述 ⭐ Options: 指令接收的参数 ⭐ Examples: 指令的使用样例 Commands: 指令内置的操作键, 如htop有一个简单的UI界面. Defaults: The default functions of the command and how they can be overridden. Exit Status: 指令的返回值. Environment: 指令相关的环境变量. Files: 指令使用到的文件. Authors: 开发者和维护者. History: 发布历史. man -k 但你不确定要搜索的指令/模块名称, 使用 man -k 可搜索所有的手册全文. colored-man-pages 原生的man输出, 没有彩色的样式区分, 可以利用 colored-man-pages 实现. 阅读站内博客: 【Shell】oh-my-zsh k Quote How to Use man in Linux What do the numbers in a man page mean?"},{"title":"【OS】多线程之pthread","date":"2023-04-20T14:02:05.000Z","path":"posts/XT9Q9N/","text":"大学时期写了一篇 CSDN: 进程和线程的深入理解 阅读量接近4w, 回头看很多概念理解比较肤浅… 这篇文章会更注重代码层面的理解 pthread man pthreads Linux c 提供 pthread 库用于实现多线程, 可通过 man pthreads 查阅手册. 阅读一遍筛选部分有用信息: Description pthreads 是 POSIX threads 的简写, 它维护了线程相关的若干接口. These threads share the same global memory (data and heap segments), but each thread has its own stack (automatic variables). 这句很关键, 线程之间共享内存(堆), 但不共享栈. 看到没有, linux man 已经给了详细的答案和描述. Threads share a range of other attributess: 线程间还共享如下属性: - process ID - parent process ID - controlling terminal - open file descriptors - signal dispositions - ... Thread IDs Each of the threads in a process has a unique thread identifier (stored in the type pthread_t). This identifier is returned to the caller of pthread_create(3). 这就是俗称的pid (可以通过top指令看到), 它的类型是 pthread_t. pid是由 pthread_create 第一个参数给出的. Thread-safe functions A thread-safe function is one that can be safely (i.e., it will deliver the same results regardless of whether it is) called from multiple threads at the same time. 重点+1, 这句直接解释了何为线程安全, 这不比CSDN上的一堆废话来的清晰直接吗? 线程安全, 即多线程下随便怎么调用, 结果都是唯一和确定的 后面接着列出了非线程安全的C函数… Compiling on Linux On Linux, programs that use the Pthreads API should be compiled using cc -pthread. 在linux下使用该API, 需要加上 cc -pthread 的编译选项! 直接 include <pthread.h> 以为就万事大吉了? 所以还是得看文档呀~ LinuxThreads 这里逐渐将如何使用 linux 多线程, 总结几点如下: 1.使用 pthread_create(3) 创建额外的线程, 这里自觉 man 3 pthread_create 查阅手册. 2.剩下的实践中再补充吧 pthread API 下面尝试利用pthreads实现 c 的多线程 pthread_create 这是创建线程的接口, 查看man文档可知: #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); args: thread: 即pid attr: 创建线程的相关参数, 传NULL即使用默认 void*: 线程调用的函数 void& arg: 函数的参数 return values: On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined. 即创建成功会返回一个0, 有点像http请求的返回值规则. 下面给出一个示例: #include <stdio.h> #include <pthread.h> void *entry(void *arg) { char *ret; printf(\"thread() entered with argument '%s'\\n\", arg); } void main() { pthread_t pid; if (pthread_create(&pid, NULL, entry, \"thread 1\") != 0) { printf(\"pthread_create() error\"); } } 通过 gcc main.c -lpthread && ./a.out 编译运行, 发现没有任何输出… 这里得出猜测, pthread_create 创建的线程不会直接运行 查阅文档得知, pthread线程的运行时不确定的, 进一步查阅 pthread_join 相关手册. pthread_join int pthread_join(pthread_t thread, void **retval); 作用是等到一个线程运行结束, join函数才会返回. 对于上面的 C代码示例, 只需要加一行: if (pthread_join(pid, NULL) != 0) { } 然后编译运行, 就可以看到 entry 函数中的正常输出. 另外, pthread_join 的另一个参数 retval, 可以获取线程的返回值. pthread封装 Linux pthreads接口不是很好用, 且缺少一个全局的thread管理器. 我们可以尝试封装一层: thread.h 此时我们只需关注 create join这两个函数. 后面的所有代码示例都将使用 thread.h 展开. #include <stdlib.h> #include <stdio.h> #include <string.h> #include <stdatomic.h> #include <assert.h> #include <unistd.h> #include <pthread.h> #define NTHREAD 64 enum { T_FREE = 0, T_LIVE, T_DEAD, }; struct thread { int id, status; pthread_t thread; void (*entry)(int); }; struct thread tpool[NTHREAD], *tptr = tpool; void *wrapper(void *arg) { struct thread *thread = (struct thread *)arg; thread->entry(thread->id); return NULL; } void create(void *fn) { assert(tptr - tpool < NTHREAD); *tptr = (struct thread) { .id = tptr - tpool + 1, .status = T_LIVE, .entry = fn, }; pthread_create(&(tptr->thread), NULL, wrapper, tptr); ++tptr; } void join() { for (int i = 0; i < NTHREAD; i++) { struct thread *t = &tpool[i]; if (t->status == T_LIVE) { pthread_join(t->thread, NULL); t->status = T_DEAD; } } } __attribute__((destructor)) void cleanup() { join(); } Shared Memory What is Shared Memory? 进程间存在共享内存 这个很容易解释, 想象一下windows的任务管理器, 或者Linux的top指令等. 他们一定可以读取其他进程的若干内存信息 (?). 线程间不共享内存(local), 但共享进程间内存(global) 线程的局部变量, 是存在栈中的, 之前看pthread man手册, 明确指出线程之间的栈是相互独立的空间. TODO 关于共享内存先埋个坑, 看到相关的再补充. Atomic 从一个经典的问题入手: sum.c 两个线程分别对一个全局变量++, 最后的结果是多少? long sum = 0; void Tsum() { for (int i = 0; i < 1000000; i++) { sum++; } } int main() { create(Tsum); create(Tsum); join(); printf(\"sum = %ld\\n\", sum); } WHY? 基本介于100w~200w之间, 偶尔会出现小于100w的情况. 先给出结论: sum++ 不是一个原子操作, 即非线程安全. 为什么++非线程安全? stackoverflow: Can num++ be atomic for ‘int num’? 看汇编代码, num++ 实际会拆分为3句汇编指令: 这三条指令在多线程中其执行顺序是不确定的. 即线程1在read时, 线程2可能在write… mov eax, [num] // read inc eax // add mov [num], eax // write 什么是原子操作? Atomic operation 即原子操作在执行期间, 没有其他指令同时能够读或写. 正因为num++ 不是一个原子操作, 才会出现上面的多线程bug. 多线程引发的支付问题 下面是一段模拟支付宝 扣除100元 的多线程代码: 当时尝试运行它, 会发现账户中瞬间多出用不尽的money… 这里的问题有两个: blance -= amt: 它不是一个原子操作 unsigned long: 非整形导致的溢出 #include \"thread.h\" unsigned long balance = 100; void Alipay_withdraw(int amt) { if (balance >= amt) { usleep(1); // unexpected delays balance -= amt; } } void Talipay(int id) { Alipay_withdraw(100); } int main() { create(Talipay); create(Talipay); join(); printf(\"balance = %lu\\n\", balance); } Paper 如何阅读man手册? pthreads(7) — Linux manual page Microsoft: About Processes and Threads MIT: Concurrency Three Easy Pieces: Concurrency and Threads Stackoverflow: What is the difference between a process and a thread?"},{"title":"Node.js","date":"2023-04-12T17:13:53.000Z","path":"posts/1T7TWH8/","text":"涵盖Node.js, NPM, html, TS/JS… NOTE WHAT is Node.js? Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. WHAT is NPM? NPM is the standard package manager for Node.js. WHAT is TypeScript? TypeScript is Typed JavaScript at Any Scale. Node.js linux 默认安装比较老旧的 Node.js 版本,需要如下操作以升级: sudo npm install -g n sudo n stable TypeScript TS默认使用非常严格的静态语法检查,通过如下途径屏蔽它(不推荐): 单行忽略 // @ts-ignore 忽略全文 // @ts-nocheck 取消忽略全文 // @ts-check nvm https://github.com/coreybutler/nvm-windows/ nvm 是一个管理多 Node.js 版本的工具, 就像python conda. 使用: nvm -h nvm list: 查看所有Node.js版本 nvm install xxx: 安装指定版本 (win10需要管理员权限) nvm use xxx: 切换到指定版本 install 遇到报错, 是因为无管理员权限 Error extracting from Node archive: open ...\\node-v18.15.0-win-x64\\corepack.cmd: Access is denied. Problems 1. npm如何打包发布到html? 借助 browserify: 如何在浏览器使用npm包? 2. html调用js方法总结 CSDN: HTML如何调用JavaScript 3. html中js的相对路径问题 使用 \" {{ assets/extra.css | url }} \", html会自动生成相对路径"},{"title":"【VSCode插件】function decoration","date":"2023-04-09T06:55:34.000Z","path":"posts/vscode-func/","text":"🏠 vscode-function-decorations 原始需求 某个项目需要通过全局的Env属性来控制函数的可见性, 如下: Env.py # Env.py Feature_A = True Feature_B = False Feature_C = True # ... xxx.py if Env.Feature_A: def f1(): pass if Env.Feature_B: def f2(): pass 痛点 当python文件很大, 函数定义很多的时候, 你得往上翻文件并通过对齐来确定函数是属于哪个if的作用域, 这是非常痛苦的. 因此希望通过vscode插件解析其作用, 在不同的函数后面显示样式来区分, 如下: python语法解析 先借助 python ast 解析整个语法树 只需关注ast.If节点, 将它的body中是ast.FunctionDef的子节点都拿到即可 注意, 为了匹配else 还需要拿到其orelse对象 整体实现思路很简单, 见 pyast.py VScode添加标注 DecorationInstanceRenderOptions 的 after属性, 可以改变尾部的值和样式 代码示例: // 新建一个样式实例 const myRenderOptions: DecorationInstanceRenderOptions = { after: { contentText: ` # Feature_A`, fontWeight: 'bold', color: '#FF00FF', }, } // 控制样式位置和范围 dividerRanges.push({ range: ..., renderOptions: myRenderOptions, }) // setDecorations, 切换文件会失效 editor?.setDecorations( dividerDecoration, dividerRanges, ) 效果展示"},{"title":"【OS】实现指令pstree","date":"2023-04-08T12:28:46.000Z","path":"posts/3AGXVNM/","text":"提要 pstree 是os-2022的一个mini lab: M1: 打印进程树 (pstree) 主要内容是模拟linux下的pstree的输出 $ pstree systemd─┬─YDLive─┬─YDService─┬─sh───13*[{sh}] │ │ └─23*[{YDService}] │ └─14*[{YDLive}] ├─acpid ├─2*[agetty] ├─barad_agent─┬─barad_agent │ └─barad_agent───2*[{barad_agent}] | ... 思路 先通过 strace pstree 查看其系统调用, 发现其通过读取linux下 /proc/xxx 的文件实现 (这源自linux everthing is file的思想) $ strace pstree # 这句调用pstree的脚本 execve(\"/usr/bin/pstree\", [\"pstree\"], 0x7ffe85396ce0 /* 40 vars */) = 0 ... # 这句读取了/proc下的文件 openat(AT_FDCWD, \"/proc\", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 fstat(3, {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 ... # 这句读取/proc/1/stat的文件 openat(AT_FDCWD, \"/proc/1/stat\", O_RDONLY) = 4 ... 所以该实验考察的核心内容是: strace 系统调用 c 读文件 树 数据结构 代码 无脑写完的, 没有什么技巧, 等过完os-2022的课程再回头review一遍此代码, 看是否有提高吧 缩进不一, 应该是因为混用tab和space导致, 这个有空处理下Hexo的编码 #include <stdio.h> #include <assert.h> #include <dirent.h> #include <string.h> #include <stdlib.h> struct Proc { char name[255]; unsigned int pid; unsigned int ppid; }; struct Proc proc_list[255]; int proc_count = 0; int isNumber(char *str) { return strlen(str) == strspn(str, \"0123456789\"); } void walkTree(int ppid, int tabs) { // printf(\"%d:%d\", ppid, tabs); for (int i=0; i<proc_count; i++) { struct Proc tmp = proc_list[i]; if (ppid == tmp.ppid) { for (int i=0; i<tabs; i++) printf(\"---------\"); printf(\"%s(%d) [%d]\\n\", tmp.name, tmp.pid, tmp.ppid); walkTree(tmp.pid, tabs+1); } } } void walkDir() { DIR *d = opendir(\"/proc/\"); struct dirent *dir; FILE *fp; while ((dir = readdir(d)) != NULL) { if (isNumber(dir->d_name) == 0) continue; char path[256] = \"/proc/\"; strcat(path, dir->d_name); strcat(path, \"/status\"); fp = fopen(path, \"r\"); if (fp == NULL) continue; char buff[256]; struct Proc proc; while (fgets(buff, 128, fp) != NULL) { char *ptr = strtok(buff, \":\"); if (ptr) { char *val = strtok(NULL, \":\"); if (strcmp(ptr, \"Name\") == 0) { strncpy(proc.name, val, strlen(val)-1); } else if (strcmp(ptr, \"Pid\") == 0) { proc.pid = atoi(val); } else if (strcmp(ptr, \"PPid\") == 0) { proc.ppid = atoi(val); } } } proc_list[proc_count++] = proc; fclose(fp); } closedir(d); } int main(int argc, char *argv[]) { for (int i = 0; i < argc; i++) { assert(argv[i]); printf(\"argv[%d] = %s\\n\", i, argv[i]); } assert(!argv[argc]); walkDir(); walkTree(0, 1); //printf(\"%s: %d\\n\", \"12.3\", isNumber(\"12.3\")); //printf(\"%s: %d\\n\", \"123\", isNumber(\"123\")); return 0; }"},{"title":"【问题求解】Recursion","date":"2023-04-04T14:29:58.000Z","path":"posts/20ER2MC/","text":"TODO 理论: 英文教材 《Chapter 2 Recursion: The Mirrors》 Leetcode: 递归专题 | 递归blog Recursion | Devide-and-Conquer | DP Recursion = Mirrors Recursion breaks a problem into smaller identical problems. 想象两面镜子正对着放在一起, 其中会无限产生倒映, 并且越来越小以至于肉眼看不见. 递归的本质, 就是将问题逐渐拆分成更小规模的问题, 以至于能被简单解决. How to search a word 书中以 “在英文字典查词 vademecum” 为例, 阐述了两种方法: Sequential search 从字典第一个单词开始, 按顺序逐个查询… Binary search 翻到字典一半位置, 根据首字母确定在前半部还是后半部, 然后接着翻到一半位置, 循环下去… 直到翻到只剩一页, 就停止循环, 在此页查找 这就是一个经典的递归思想. 2 x Features ⭐ 递归思想解决问题, 符合如下2个特征: base-CASE: 递归会终止于一个简单的问题 降维: 每次递归都会调用自身, 并解决一个规模更小的问题 Recursion Problems Factorial N 先以阶乘为例, 阐述上面的特征: $factorial(n) = n \\times (n-1) \\times (n-2) \\times … \\times 1$ $f(1) = 1$ 这是一个base case, 因为它是最小的正整数, 是终止条件 $f(n) = f(n-1) \\times n$ 每次通过调用自身缩小了问题规模 python程序如下: def fact(n): if n == 1: return 1 return n * fact(n - 1) String Backward 字符串倒置问题, 这是递归中稍难的问题, 因为没有返回值, 它不再像 Factorial那样直观 以此为例, 阐述解决递归问题的思路: 找到base-CASE: 长度为1的字符串, 倒置即是本身 找到降维方式: 已知n-1字符的倒置, 如何求n字符的倒置? 即S[n] + S[1…N-1] python程序如下: def write_backward(s): if len(s) == 1: print s else: print s[-1] write_backward(s[:-1]) Binary Search 二分查找, 这是计算机最经典的问题, 生活中也经常用到这个类思想. 任何情境下的二分问题,只需要想清楚如下2点: 什么是base-CASE? 它确保二分能停止 选左半边? 还是选右半边? 例如对一个有序数组作二分查找: base-CASE: array[i] == target 或者 left >= right 选择: 根据大小关系很容易选 python程序如下: int binarySearch(const int& Array[], int left, int right, int target) { if (left > right) return -1; // not found int mid = (left + right) / 2; if (Array[mid] == target) return mid; // find it else if (Array[mid] < target) // find right return binarySearch(Arrary, mid, right, target) else // find left return binarySearch(Arrary, left, mid, target) } Find Largest 二分查找每一半的最大值 def findLargest(arr): if len(arr) == 1: return arr[0] mid = len(arr) / 2 return max(arr[:mid], arr[mid:]) Find $k_{th}$ Largest Combination of $C^{k}_{n}$ Hanoi Tower ⭐ 这有个 网站演示demo WARNING这些算法待实现, 存在一定难度, 尤其是汉诺塔 Recursion Complexity ⏳ 递归算法的效率往往不高, 因为任何编程语言的函数调用开销都较大. 可以通过使用迭代避免, 以Faborial为例: def fact(n): ans = 1 for i in range(1, n+1): ans * = i return ans Leetcode 递归专题 这里选取了几个最经典的算法题: 斐波那契 反转链表 509. Fibonacci Number F(0) = 0, F(1) = 1 F(n) = F(n - 1) + F(n - 2), for n > 1. 计算F(n), 0<=n<=30 递归 int fib(int n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); } 尝试用主定理分析一波复杂度: $T(n) = 2 T(n)$ 等比数列求和得到复杂度: $O(2^n)$, 空间复杂度: $O(1)$ 尝试优化一下: fib(n-1)和fib(n-2)中间有很多是重复的运算, 可以作一个存储 递归 -> 缓存 int fib(int n) { // n <= 30 int cache[31] = {0, 1}; for (int i=2; i<=n; i++) cache[i] = cache[i-1] + cache[i-2]; return cache[n]; } 通过数组存储, 避免了重复计算, 这里复杂度很容易分析: 时间$O(n)$, 空间也是$O(n)$ 进一步优化 那么是否有办法优化掉$O(n)$的空间呢? 尝试使用多个局部变量即可 即a, b, c, 三者分别代表$fib(n)$, $fib(n-1)$, $fib(n-2)$: int fib(int n) { if (n < 2) return n; int a = 1, b = 0; int c = a + b; for (int i=3; i<=n; i++) { b = a; a = c; c = a + b; } return c; } 他是复杂度为: 时间$O(n)$, 空间$O(1)$ 206. Reverse Linked List 反转链表, 并返回链表head 递归 先尝试递归的解法, 按照递归的两个特性尝试理解: base-CASE: 链表长度为1, 此时反转即本身 降维: 长度为n的链表, 反转为 $R_{n}$ + $R^{'}_{1 - (n-1)}$ 实现的技术细节不是很直观, 因为利用了递归中函数调用的栈特性 如下 next_node 其实会一直调用下去, 直到最后一个尾节点, 然后才会执行head->next->next=head那两行代码. 即真实执行的顺序, 是从尾部到头部 ! 这个leetcode题解有动画演示, 跳转链接 ListNode* reverseList(ListNode* head) { if (head == nullptr || head->next == nullptr){ return head; } ListNode* next_node = reverseList(head->next); head->next->next = head; head->next = nullptr; return next_node; } 照例分析复杂度: 时间$O(n)$, 空间$O(1)$ 双指针 其实是单指针的方法, 利用head->next来更新节点, cur当做指针移动: 这个leetcode题解有动画演示, 跳转链接 ListNode* reverseList(ListNode* head) { ListNode* cur = head; while (head && head->next) { ListNode* next_next = head->next->next; head->next->next = cur; cur = head->next; head->next = next_next; } return cur; } 复杂度和递归的方案一致"},{"title":"问题求解与算法","date":"2023-04-04T13:02:53.000Z","path":"posts/3HZHSCW/","text":"理论书籍 《Data Abstraction & Problem Solving with C++》 目录 专题 Blog 备注 Sorting 【Algorithm】Sorting Recursion 【问题求解】Recursion Hash 【问题求解】Hash Map String-matching 【Algorithm】String matching Binary-tree 【Algorithm】BinaryTree Graphs 【Algorithm】Graphs 时间复杂度分析 递归和分治法, 可以通过主定理计算: 假设每次递归, 拆分为 $a$ 个规模是 $\\frac{n}{b}$ 的子问题, 额外计算量是$f(n)$ $$T(n) = aT(\\frac{n}{b}) + f(n)$$ 则时间复杂度为: 参考链接: wikipedia: Master theorem 主定理的证明及应用举例 附录 算法随想录 leetcode刷题攻略 Data-Structures"},{"title":"【日志】2023年4月","date":"2023-04-03T13:08:58.000Z","path":"posts/2023-4/","text":"b3dc07a81f6459d120ce338ccca550463faa708b9b4d89df9ab14ba4edd809f60b32d15b5672ce84c5d84744f7f475de0b1305a637ea913974dff7410a14ed4dbbb40a5278eb14bbac34e59e12a284c7643238e28174e788020e96a39239fbb7a1c76b116f6e38dc468b0c7cac9c50048e68c3dccb238a37e6d426c67296e44e624d0263ecbb1ee2ce6233952e63f68c54518300883aa91a56cb1ceea99ee02984fbd13056cf47fe0fd747697d84c93d835cf6f85ae3fef03ffe45ee23440aa039585c6ec0cbf30f39b3a857ce58c38f217729c7ec0c60e9bf63b162f3949f75b4548da5a65844a0bc0ed6329c52beceed7c96d178b24d5f7e92f673c17dd2f65c345f37961af7dee00bd990c7fc8ca4d5c0e4652d6d0313ddddf539200625354abbc375769ce7c5b3cce5aea73d281f4550dec5fdaf522abd2ca7b8deb0d9ea6c597736e09c84d18612210d09dafc7bfa4c134291a742e7627455b4b9883f85834fc8b42aa8bd86a5267a5595e235f3a2d43d079ff3e2554eb2e5d45370b7d46bfee4b35b6a89bb4161cd3e682ab74d091edf5a91939934ea6828deb895afc4bfdf0af86b435a2880bd8135110dc0f005a511f6b287e132c5031c05e8914e9d26f844cb26829a7d5c3c9cc64db2c2297fc35a8276e6f707ea808e5d83188fe0b9b31217c5c40f96f2bc55ed958db73b80aa28bd914c9aabda4ae50bbd92b45fc282114327b82c2d63a8a091bb0e0c1355e167457ce0638af83ec60396f3b1e714bfbcfd5fbb0ea9d25177b9876fd43403bb03db84052ea8e3fe9d998d819e6acf7433e431fd011e6a16d73ea835affc3dfe57c325036b3a905daa71ae851ba00ff7d2ed45f0ff999d368748f4ab57b8c9cb42bdaadef682c49de8c76f08613583208effcd82af9e5bb3b8843d46d348c4eea23cf50e4cc77bcb213da0dbbf3c964f2b00c70b9a51bd6e379ce65052185f6ddf25e40ad78df75342d8d529c9e2a4dee16e76d5b6e767452792eea3a78eced48fa7aebe768469f554bb29d3a26b45e676b7f1802179126ba0b5eeaebf314bb15cb18ee3a38a5f22d7b830c3b6cd5bf6c7b88fc0ab34d0cde0bc8fd13ab0a30219268c64488a44cb6fc24a8c6da1e5a5258bea003cf27452fce7c8a278cdbcccafab0874f9fe1dd35df6a9c5c5c9791557a59597466ac3fdb206ac43967ed09f6e9d03cec82142acaefe9397284812d51eee079393a92379cca42da4d044a189fbbe79fdc8a71d20c4566da54434eaeb6b7b947ddeea09e4c6c3813121795978ac2e2a6ac37e6923cf523881f8107cb5c1f74b60ef1c64502b8ffa625334760436e0fb541c55603f0c8703001790ca62c21ad1ff78c3d9078857492f6208aeb9956c02e277693987d84dacfb12c1889fb210da092b7a625051b2145de42cf1f3ecabe516402eff729b97479c39d0b3863a181ee41504fdca415abbd13659cb26535b16f0f1a53ac9805fe942ee5bcfabc61bf26b9175d10d56a104bed007bfebea691fd232481f2c7924679923b705334b6022f17ea6c5ff72571cf89eeb095a6dbaf77c190989d2804d8a1f7204a83d4d5a15777e3f7ea699029b6f5dfc31762b1751a52a738929435689823408d077906b5732028cd7ccd55f835ced93a1e95505e0e8518c53f26230da7226290f1bfc4094bd0ca2e31f6fa8300bacc4cf782b7fb3c6b7b64d23ba610c2b7d41c0c4e62d4c25f0aa96d616c09e371480c732b511f565ae06541634ca2170ce0eac117fa286a38f4db2f82c5379dfd273ef048fbb6019a8feb7437072263cc7bffa397e2a9d487a861f9d47f84aa0c7a5c1c9b7ed48e99485914a41feb32c57256fd04541511c9744c95c10f8ff74e2107f8ee6050594743c7ddd8c4e637ac67e1d515c2588de0fb08eb09f627080cd6cc1a308bc0ba5eac653b412b9eecb81751abec1d8ff98ae9803876457f586b4237285c3722363ac856a4118c53fe2725109627b02e0cc5d9326b40cb329c6ab74a8b9e1e616a389a8b9aa26bab69af4e4c9b688ec03f27474c283c2720b157a5081d8db41c5e5e5688b1a210a23d2577d48634e0e30ce3ab3e6f72fd8572fcdec59a6ca7e7631c11b3bc71ee23ded0881338671f60d8bd96bee283d3daccb34c5243b04795ec06b52dba190c85df0b0a8b8f26f9cffd4bc6bbf9fac76d313a5dee8c692a440ce26064d098b9a61f9a9c083a79dbd94baf0b6349f3f189677001ffc117520362ffd7962689d95d09720cf749bfd684cc708819053d68b0c0e2bd086ce4fcb962ccbce8a4fe87c9b23f885006f50c81d78ee05eecf1827b499caac7c6c4716f166ff2d24f6cdb4f505491ac74ff57455419b28ed82cfd484466fd1e34664cf015599aadabaafac3f3ca60399c3e5201aa84f81404b60cee265125e49e2965c90131eaa9c516556de738c73c834e9416bc33f1817e89c5691cd16094ae32a4c8484b57bef9ec2dfcfa4958a80a3da810faae1f1e9e893d4939b5d50f15bc4467d225ebc4578e2130e6e80708f4c909731851fbc62f690ddb403798e067cd8fc75a4ffdbefcb440a97872c0a8b0003d2076cff5ed39349590c5df318e6303454dd82ac3651a618a8729c5f848f2aedfe2e033d11aa4dc1d77a720be95609b8ae17f269eafce6fcd2133fb95789638aa3ff296f49a89de7c90174dd362abbca8d77a428b8352f389bd8fea81346093117ce89150ee938ec353a34dc587f96e5a1282af23b52030f616d237ce4472739b43e09effa8f468a76ce91b9ba04f479ef8f5215e8241e20df89b4c4257899ae013571b6eac5c7580f09cc075881f3eb59666834df5c3fcfc5dfae926e8e0a6bcab8eb187f5583a1cbcbe61efe8e0df7cd0d21d5df22602951840faf054ccf71cc069479dcbc5e3655f686ae62f84283075ed674c73fd372b7ec33a8da0a7f8094dfdedfe3149e6f9e197080f67d67e19fa1bfad62693cdaf6f527b6af9a7f01d9d0a4a62cd4d4788f8d36c1e80427328beb621504c18df5f672ff82842deae4e0c55758fce30fcfc2a752f69e06f179d97089237a7e1e1bd8da7b92acd39118092f6084a1b9956047c4c861a8f5b31825316f644772fbf5d690485974a4beacdcad5fa942cf5e1663ec241091b2eab691e7917647c9b7a66488caa4ae535a008a1424cb7a78bba3940d97a4ca29cd6ef7f256ca904c02df6f1199e85827b2f27f8f304fd60891666758a60b58291f35642051df38b6667fb6b3ae7ff21c1a3c4c8fa7013523818706aaf56497bb5989b0a51e410db312c22fe7ed84abdc3becc750e0e66d117120b467ccd65ea98f724430ccaee0c934c03aa6c830aae34f95c8c7eab5b74eb6fe903df3a7b088dea8714c5a20206261b3a4d1c7d9bfe01b373eefec634d80a6fbc826a9932d54fe1097a273083a29bfa6214ca40d6247342c6efda5d1bedb62125fff1ab5ec69c6ab62af161f05114b8b918dcc7d4bd2071d3cd1f380fc950b2a01975d81ccf708ed207452bf714b720b60f2061bb8c0f1c2d9076d2e2da3949b2296ffe23fb247b1ec05113372d7633c9a31210281abd97826aee4a159bfb83ed34b6ae3fef5032c27390248b861a22ff8b83471a6783162a359568c20d55b5e131153690bdfc4b23b4f776d0f71f87b4d3bc21ff03f6c2e7bf1179056993ea2a237a1f3a5eba2c8c2548c1dbb53377cc050918af21398be6fc0fecfdebb351a1e5d4789cde897cc9cd53a430f745bfc3c698203868d8a6fbc6f9ca6bb2a28991093d3dec6be898668c76b6b711f54c96a7b04e0589b436baab99b889791115fbfe6a9a95a859e9f921b516eb604911fc927b05e8c8701bd4414418b11694bba30aa2557ae860d42257dcba5642e6fa54ba7eb06a1a44dced955a2dc742f45cdf82a0b76ff1d0ec855aaea0dd33234c6ce00b223224a5287c059b7c159a0de028ba0cd8eb181936f42b8fbc2061ee5597fb4d993b0f307f392d22a411ced9e5511480fb56fbb92bad0d3ffd5c71fe7aa49a51ccf1968b84de2928c150f8a0aec8d2f11540ffbcdd5e74ea0b3eb6b73635d2f207ac5cba0bcac647c76f2e0cd49502ebdb7fc13785515cad8e8c90f0ff38f156eed1ec0dddf0f7082c5154620a2c377cf518d66e411dd742fb764808bdce9d5586c2d397033a90461cc3fc3163024f4f2a1be40f908e87dbafa28903fdc34f93be04aa36baac7163d1bada1be7f369ddab76852c03c0b6a59dbd10333b0459f450c68a63c86f6de68d724e5fdc05db3638335e99c4ca39093412b6a4e8ae587fc202e2324ccdadc93057f295da94204ff515b1de712dddec26b330cd8c08b4494b3509cf9df7872ace352ad66892e4c3814a6d3648284f43a91984d9acd95f99c8a5e930c64e480a5236657e8fff59dc0bb3af779dcce1722116c5f7e7fce65d8b87f7f240b122a353626fb67e5904baa6fef53ab8a4494fea1ff0bd0a0c2523472f5a987e7d5d8232bd3c4f605d29f2705931d4c19b4e7baa7b3b3d3b583a517559c5dac895d2ffbcd29dc5f9c6767ffce08dfb047ad03cdd504dcfaf8d8f5befc27b5eb0d93b53a039e7edb950310f379d3d074a87444dea4960e08bf5c28de126d4c87d8ac04048013673a5c006c755af4da7a33d18ebb40d5aec6cb3f6362ad96abe340f233d6c7dccc2ae0ee0c2b7dff380fc340ccfd085d9cf78b701f3a6b45410911f3dc9e6e3fee195ad76fcc084b698a42528fb460998d51bd70b037f50d53ce86d49d9b5198bfe6ff49c442fef2ace9def4beb230fb5664214e08ff7e239e9805a691b06550cd4211331399fcc53669f642c16fe6f3ceae673ae0e60ba980db05edc42ab19d2f0198858969ebc6046d52c03a2cb041c2a6ee1f513336d08ca54ee8a1877cc40d98c406219f42a8f9142ba4ac9fca5adea13bfd23bd53f0742c74082c00bd66ffb4eb8b0acdda9ee02172d790d7129d86175ba8cf332d239e7a9e7314d53c0c290a753e39f76f6fd87442c7ca656acb4c5736e1d244d45ae30617bed99775e3ec4b2e054813f09a52d6f1c35bba138b9d9e2308096901e715696f9dd9e480cfe38b9a16f24c810a70a507863712d1bd94d2bbf348dc222c80517532f9e649dd263edf76bb8bbffeb863025de7e9dcacb61131d25d8c15b8a1fff13c3e89dbeb5881ab629a3df14a8ba1323ca672b6d3a2e7b6c2ac619ab122a7fc921a0cf1334fd8529ed74adbe491d0db65b2fecdbc5b1f04b54577f51593028da29ac61197b6635c24b217339346e723589c70c35d5ed24b7265f041908c60d2325d47efbc9b3f90272bd321a2b042c39898ac5e0b85a59d137dc7a376dbee5672884227252fda1dbdb4d7c3ec1d776905eccd13fe4ec251579fb4caacccf414078a9966b3414ded41c1e3b03b0e21573540641aff48a2399b0a0427ee25e5da11091bd90f9b86547eb32ebe3b3d6104ecdff9ee25bd9d7c27c9938cadfe75b0d02274f518a50bb68149df05b8877d1e8de43d915063a3d75018d40e6c7be894ef445b2eff9e3f771e962070898b821a089d317f3c1e4e1c2f65858bc7948b5ed9403ae5e5c08c3521b67f561bfbf433a4ab02a8ea2bba3d10f8b3639c15e6137adf2ec05c2ab4e6c5772f614c052e8f646f8533f3d2ea6ac3fc6797cb36e803bbc21eb1a946a3b08e9e22ac8b3a54ba463578bd4585f14308848d8880fc00337a42bfbe05def1cc95b1bdfaed29ac60a88f5220cb5b9edcaa1295573e99eec3e09ab794342f749967222f992cb7c75593adf43fb37696d807aa2befdead293cbff99dad2a719fd7d6229c3b50ad88be6f4d01349e9a79784748c7fefd4666fbf6792b4824761a9a9b8a1a23fdb1112c98890e60b8fdbc96e0e20c1ec29336194839837f0b403167c6e112e55f84d5a3ea819c77f3c0c3068dc8938064cc6ae43ffc7d51a8eb5cfa023a0e1ef6e6c663bfea7635c2c1814a9aff3b25cec087ebb647008f4f3a1bf04431a226aef836e410e4ef6ecd90ecf6d16e72e19511e708963cfb24acddd81cd3fd2524ba05b1bca042f24ddb2739cc06a5e10b96ee41f62a55196eb23a79b93675d903187ce23e59708a6390101ab3f27e900cbe296958e90abebdb9049c64ce83c32fd755e11aeed53b332cbddf91375475e83f874aec215c25d7669bfae30644fb91923e70f6be060df7722e4a4e36fa0e329c11b443d78114a03b9231a0b9864b2ff0937d3ab3e9360befe8c1dfd503dbe946353f101e00769d518ef37a03d7c834e4753cd41e457cd5fc8a3b4c5e125472660f18e6200f6a7913fd48b35097e04500a2196109f8f27f3d1b3820c12ef16a3136d745e97e984fa56ae74d7ab85cb737bc9670a907f9101c591a31b4b804af14736be2959a356a7a23bc103e4606e9bddb0bb95767821a7f64af230c0739963768ebb4b4eee750b37d92e5d4aad00114ca491a88e13d79ea834e45c06e7061fbd4bffb8ab7a575f418ea75762f7b22b88ad8e8b4acde203319629a22e8adfacb2449b19a2fa26978e3f9457939c06e7cddabc79b7d0c0717748a5aff007980f4ddedd0b1dc44442d4afef1f2e070f92f340b0a488fdd50f2df1be00e8ef792679fdc2c7171fdae57f36a012badc922e70972dae00dfd0044fb3a9fd41b55eae9e44c672c72a5ae3c0c6723385175b2b0efd71f712e7ffe4420bfd8bdc953cf61f7a351cf43e5a962247854a1e3790f1cc9fcb281a460e6c2e074dfa23d6f16d1d05cd6d077bfa869aaf9701063c4f2dd0a0d90b56b05893ce653f8953d05eed96d2036bbc802278757b9c0f2da7d38a934afb7c9221a716c30b849d5d352d408ea450e2199feae9158d4a0672181a05a60c210e079b9fb941b5017637587bacffb99a0ea69584f64084943b6f1c21cee89cc06070ca947c3f3312adc6cb5d264398def825ebfe65112a7c1b401a4fc69e20eb35c58ff8e7704452f5b96a7d72a92679d116847429fa3941b9f5bc5e9033ba551505728e308e9c91f042ed4d3e904332015e6f4ccb837455670f42ea5ac535b0c279bfdf34ab8ba8c660a3dbc4b723b14291e1872fbd444a1cdb4e69030712fa60187bb0b635ef8ae0b00b52e4b00e09653a0239847a11fd9bb93befcc68d466b4c92533d83d9e987bc24fd3a3ecc294e194c23c329bda099934ea695e316da492fdcf329979324405ba709080ed628e6b88aa9933e4d948c9d911b525d63d96db55c839df78368c07288e2653fe5a44f00b33069bdae3514c993488ffae7207147a833366bf1f1befa4804ffa7dc29b62d3732575af990809438d7109f8b3a4440f48399bba568fb24ef942aa8aa00bbbf15a1e6a850f85ad96ec9ea836bcc09298ce6c2106aec488c35724ddd18b4ac57dd1d16e7703e005dc03b2e8bb65afa483877b53b7680b0b0ed459d3fa80cd6532cfaffb4c3a6d61f6678077d4b1d258630c9ae183629494f08456c63975d2508f72546f071311f29d373e2d0856dfea4d7dc44bfec38ca6891cedee6145e23c46f42c218c34d83b0a4f8b295a8a9e01ab248720407d669dea3393025e71b4e52dff37d7ad3e03895e5662fa5cb074c6f0d8d4ab5f01dc40bb01252707fe7c86446ef0af605ef07cbd9394e62cbb0bb0ef4d8a9c81e0250d6940e2133c5e6ed57462384073938a9b4b210295c1c8e3fe8451280c01284ae8ded609e9f194c8d262f6a2bd3a057684418cc11e33508bcebb5b554eca0ffecf29f66be7203ddb5a524513f9cf6ee91117555b3e0b709e58a8d8171e5a02c064fea90a91a83742051e19c7c0ae361e5d976bed60cbc8866a07c2cfb19d5eafd9bb78bbdf9415174cfc1033b19aeb26a54960246860556e4e83a1d30a18308b8fe6a5b0d5c487dea481afc21a938f1f1269173cff27cfdf9a4836dedb4b66a3366c524cb5bedfdf98df797903623707a00f8ba7358294b2dda6ba48db91246d427d4e6f49c0c45750f3da027699d026100c02b76275d743f147d30dc978364c42d725837595c32dc95b7b44311bd58517c31f6ff78e66ebf475f574a304363b82a57640de9f233f9700634beafcc2c6628ece15126f779fc993a3b7c6ab21dd8adcd2e830e5214ce9cf5357eb6b4fb55f60ff0f3c7f02c64ca1ea8ee60a338c4f9610e304080e8fc763549f35071ce6cede3da0739d3a4f8001232c397d231e805a8c1dd1b111e9079209d21e5617942768616fee6a640b8d4d9f7bc3c52663e5ecfbfc97cb649e0cd075efae20ea8a7eb9a8ee1570bf788fc713785da5adf625092a813e7027ec9332d813017d2d5d6bd3fe8ea2786297e0082595fb3625419a38c221e2599ba3735d57730ca43c96fba84d0ed297b13fc7c7efe2daba31b1e78007085edff1aa3ddb107b3d867535e52bad508a636c932972972fa8fef45061cce581e967ec08b5d75de36784f92bfa99cfb5853898b530e60e44b2e29076bf85e6fd9d5ae78d4978f21a1e551264aadf2c59a8c71493e33757c667c3b9a1f8b5d8e8d9a1b96eaed61e3ed1872d6a1d9a52674c96863b095447983ee8fd0bf7031b155738def2ddb694de86b8f80323031639d72ffd954e3f42144d71641bbb50e3a22b6e1ca321d5ba095ccd397b9a2ad01d510c14ea2e64134e6b3c8073c585015397dcf17bf51407fbec2b68a2cca8676f470ba80153d32d7c3816bc922e4731e434eed4314c759df2fc7dcbc0ca88b61b426c032b19017cf663022042e5adbf8072652461381b82b9f47052df41aea42e29b2f7bb6d2cb3d09588904249768bbf3d7979ba265659cddee29a6841641a453032a2e29e1e44bd226c542488b9f5bfd513e213b72fbb3294ea8617c857810e4b0a938373139b6d580b28364dec3af722e09506ad70f75d80fe8600d8b6b33fd94aa57990f990d3e25e97a94a624fe1d541bcfd26a33ae2f9086d2f1522f173a51fa541b36c5c19f3c0a1edfb4682947a8f3de8cdc14438736dbef0a3f6f208b012afa01ed1d7990bcc40693b13fd8d4be5add25ecd38b9e262c335be3362db170c2912978b309632d7f706f035fa74167f2f2d6d7290b0eb6069dd803209789026a432d48d4675dbe2f9801c294d140bcac1d60fd4e6bfd79ba1fc5a970c874e0675aae884754d52c63a16ac5d5ef3baf96934090584541cf7afff2797e3e9225aee8f3612ac81c690a55248dba8f13c2a53d2b647bf405e5e24e627a4e527adbc1fb3d6c38a8d9eddefc196be0070b1f9955752a21af691434099ece2a5ebca0f50615b5b8a33e83214f32c9dc9f2a0f8e86aa91cfb138dff8dc77071463126178b9829b77cfe3c0e143f87701c45cb4c37386024ed34f717575f4577998491e3e3d0e6864085295378277b009f8f23a4a06d16ca179c4b7aa0d391fd2b71066a915a1e46ceedb6a4121abbffe2c622d06e96223d744ba3375c2892ddbbbc3e5110e1162ec20147a2533542f0cf40609a58c668c2a458b3b03abad45ae408e029dea44f74cabc4184c7d3b1fe91b3822be30987996480eb75e5d09632d6a158d78648b2a00e14ea4b655f581cec7660f6f42bf730e9e7af691ab79d8698e067b36fb0b5255034a4ee3339860a82080f60d4db4ece88fff29fc9f090e654329adfbc42ea1db64f72069a4f3b4f53b1bdf69e729357bf9d1fd14d92ac4ae408b92b1bdc047a7241e9c6c7036bd73665083d0dba830f9ab1156d906ba96eb35658db3f726ff15b611e078dfb0cd1b3d94250f4c8c6ff4192afc95316f196955bd492f2cc319499099ed32a92ebbaf786073d457e04f55a9994eb13be9e4d07125374e57aa22b73fe9149912e1f95278b32e9e2e3b2308ce3e79a20d762d27ec02fe0e243b7a5a34bd985d2fe459f9ac140efdd4c79d51fa19523854c210770bedb676dc3728eba8e8d952abaf327ec172991c601fcde6b724203d115c1824ad41138240b1b9ac3957b0a72290e6de97604cc03eb523798f22fc64d4b00b2d19a942e2839edd97a90f4ab7b6d7538e0d528d7d5a897131a3c5e9d84756276ebf4eae39f5145d433e0297fe9b046e54f7ee11679506300cc76f332095adb1f105a57c2bebf1c7e08afa504a5994a3e225ced61fbd7950860d668dba5045c41d4a6f12b2bad61b7fd5cbc397b991d4dd135fbef3b378e4a9e3ced7d3705fb8345afa708c79e24d4af77b5623202fd6b3d46ffc2ffb1d74d92fe45a47b29f70d25d62820bf8f44d47aa692b92c58e8e09e94a5db1d444ac540cc565c33e0513c5cb75bcf4ec3851f12142f87109f656cac8187ebb4b4427f5ca1ea79925f7717586c0087f76ffca6ff2eaa57c0f4ea319700ce2377f4ab5181718b03a59055dbe00a012a2a2c58d492b97ef0eea7149d25f346368c5152d6b0c3c77639a99ca1d6a17208f564f1650953d01a043b252d3ea8930b8e012175ea9e734e9e932bb803fc35bdde65180c1d7c1d9ace27e090934977d3031b51fe187e790d51306e20107c30a4b27bc98991a81192669371b4da9afe9fc06b6651421252fd914003ef76bb2cb8c98a49ae090461c5bb3d04fe7009abf1acb2b51b9f4762500c2da26ca95e002d9c06a80853c7c2ee241fb993693d2b51451d17aa8fb3f76cc69eedc5d426fff5e24252c1a39a7079379d007a63f17bf781352d3ae04d510d97ac3a22799f0c3bd7710b366fefe1473a61ee18836060bece1bb2da87536bdadb2f71cb795001d071ba27349f9eed726b0dc217d7992659128686b62332da0991f198f44c6540f1eb11f47088cbe44a2cd6da4747415edc8db2c39b2a55ced7c6dc33b0a267b03c535813b8d98f7928b5838d995cec9db82405fff68f2fdc603a1d8e4d428f57d79fa442beb82490c859ee86d108c4d7a091c8c945d92b655069e4908930db4e14d61a013f8b7614dfff39276c4f65fe826d1e2a62305e1fbbc8ae1d80493e3dca4a114fca6efac773aa311d1b6f7159cad0f8e49cca6f78139c8604fd371d373f98bc56dd9088f22d9deb0faa17ab9ac1cd4b7b6dd6757aaaf31aceaad55d38ef27d190be76775b6eac81ed8290facf367da39532cca453201e288bcd72fed12a863ec1b5fabe7abfa66504f9d42530a811a3bb66b36142b0b502fdcc87171330deba44cb179aee041fd95b19c0e2e93e248d5b7eb741efe4e25812382c26fe577ff813bed23869261d3f985141156fb1a2605f847af853d0c8dfc432c1c25c5 Hey, password is required here."},{"title":"静态网页搜索引擎","date":"2023-03-29T17:00:55.000Z","path":"posts/2F3J6A9/","text":"背景提要 在使用 python-mkdocs 生成内部文档时, 其内嵌的搜索非常难用, 奇卡无比且不支持中文 因此萌生了写一个简易静态搜索引擎的想法 搜索方案 常见的搜索有两种实现: 接入 Baidu, Google 等搜索引擎的爬取, 然后利用他们的搜索接口去搜 适用于ip公开的网页, 好处是搜索快且功能全 坏处是, 不太好做输入的实时预览? 静态搜索网页内容, 适用于个人博客、内部站点 本文讨论的就是这种方案 实现步骤 解析markdown语法树 按照一定规则将解析结果写入Json 实时搜索时调用fast-fuzzy搜索 html前端展示搜索结果 markdown语法树解析 借助 python-commonmark 实现 import commonmark \"\"\" MdParser: 解析一个md, 返回所有正文 \"\"\" parser = commonmark.Parser() class MdParser(object): def __init__(self): self.visited = set() self.content = defaultdict(list) self.all_content = [] self.head = '' def entry(self, path): with open(path, encoding='utf-8') as f: string = f.read() root = parser.parse(string) self._walk(root) def _parse_children(self, node): if node in self.visited: return child = node.first_child while child: if child.literal and child.t != 'code_block': self.content[self.head].append(child.literal) self.all_content.append(child.literal) child = child.nxt self.visited.add(node) def _walk(self, root): for node in root.walker(): if node[1]: node = node[0] if node.t == 'heading': self.head = node self._parse_children(node) def dump(self): content = '.'.join(self.all_content) # fix special character content = content.replace(' ', '').\\ replace(',', ',').\\ replace('!.!.!.', '').\\ replace('\\t', ' ').\\ replace('img.', ' ') return content 生成数据search.json Key Value title h1标题 text 正文 link 跳转链接 如下遍历一个目录下的所有md, 并将结果写入一个json, 供搜索用 import os import json \"\"\" MdWalker: 解析路径下所有md \"\"\" index_output_path = '' output_path = './search.json' class MdWalker(object): def __init__(self): self.output = {'all': []} def entry(self, folder): # read index f = open(index_output_path, 'r', encoding='utf-8') index_json = json.load(f) f.close() # walk *.md for root, _, files in os.walk(folder): for _file in files: if not _file.endswith('.md'): continue path = os.path.join(root, _file) md = MdParser() md.entry(path) file_name = _file.split('.')[0] index_info = index_json.get(file_name, {}) self.output['all'].append({ 'title': index_info.get('title', file_name), 'text': md.dump(), 'link': index_info.get('link', '.'), }) # dump to output_path self.dump() def dump(self): with open(output_path, 'w', encoding='utf-8') as f: json.dump(self.output, f, ensure_ascii=False, indent=4) fast-fuzzy模糊搜索 借助 Node.js: fast-fuzzy 实现 使用方法直接看文档 html展示搜索结果 考虑复用mkdocs原有的前端框架, 或者找一个带滚动条的容器 采用这个html样式: swipeout-overswipe 其他问题 参考这篇: Node.js (站内文章)"},{"title":"【OS】调试理论与实践","date":"2023-03-20T14:50:57.000Z","path":"posts/2F2QH5R/","text":"牢记两条定律 机器永远是对的 未测代码永远是错的 Bug产生原理 代码错 Bug -> 程序状态错 Error -> 可观测错 Failure 代码错,通过阅读代码发现是比较困难的 程序状态错,是可以通过状态机(断点调试)一步一步定位出来的 可观测错,如Segmentation Fault、如Wrong Answer,是发掘Bug最主要的途径 因此debug最佳的方式,是 让Bug尽早暴露出来 二分 使用assert 使用print (注意是debug环境下…) gdb 汇编(四):gdb调试 为什么gdb可以暂停程序的运行? 一般的debug都是利用 软中断,即将系统调用INT 3 注入汇编代码 明白此原理,可以自己实现一个gdb调试程序! vscode断点调试原理 现代编辑器(&IDE)为每款语言都实现了一套自己的debug工具 如 ptvsd 就是vscode对python的debug工具 如下main.py: import time while 1: time.sleep(1) 运行时需要加入ptvsd选项,默认端口是5678 python -m ptvsd --host localhost .\\main.py 对vscode的launch.json加入如下配置: { \"version\": \"0.2.0\", \"configurations\": [ { \"name\": \"Client: Python Attach\", \"type\": \"python\", \"request\": \"attach\", \"port\": 5678, \"host\": \"127.0.0.1\", }, ] } 此时启动vscode 的f5,就可以愉快的断点调试了!"},{"title":"【Shell】oh-my-zsh","date":"2023-03-20T13:01:52.000Z","path":"posts/2N8EPP4/","text":"NOTEoh-my-zsh 是一款具有高亮和丰富插件的跨平台终端 已亲测在Linux、Win10、MacOs上使用 安装 需要先安装 zsh:sudo apt-get install zsh 官方文档给的是: sh -c \"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\" 但是这个链接可能会被墙,因此使用国内代理: sh -c \"$(curl -fsSL https://gitee.com/mirrors/oh-my-zsh/raw/master/tools/install.sh)\" 接着执行install.sh,如果速度仍然很慢,修改其中内容: # 这是旧的配置 REPO=${REPO:-ohmyzsh/ohmyzsh} REMOTE=${REMOTE:-https://github.com/${REPO}.git} 更改为: # 这是新的配置 REPO=${REPO:-mirrors/oh-my-zsh} REMOTE=${REMOTE:-https://gitee.com/${REPO}.git} Linux的默认shell一般是/bin/bash 可以通过echo $SHELL查看环境变量 设置zsh为默认shell, 重新打开shell后生效 sudo chsh -s /bin/zsh zsh插件 zsh插件基本是通过.sh实现的, 通过编辑~/.zshrc修改: 通过source ~/.zshrc使其生效 plugins=( git zsh-autosuggestions colored-man-pages # ... ) zsh-git 这个是默认配在plugins的, 会显示当前的git branch, 如图: zsh-autosuggestions git clone https://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions source ~/.zshrc 会自动根据历史、剪切板、推荐等补全shell输入,如图: 注意,右键是补全 colored-man-pages 彩色显示man,查手册非常有效"},{"title":"VSCode","date":"2023-03-16T04:52:31.000Z","path":"posts/vscode/","text":"涵盖vscode源码、插件、使用技巧… 参考链接 github: microsoft/vscode vscode 如何配置断点调试 源码 Windows build 限制node.js版本最好使用: v16.19.0 安装yarn: npm install -g yarn git clone [email protected]:microsoft/vscode.git .\\scripts\\code.bat yarn watch: 任何源码修改会自动reload Linux build 还没 build 成功,缺一个带桌面环境的 Linux… vscode 插件 推荐插件只有如下几个: Remote-SSH 远程连接服务器开发必备, 太爽了! 建议在其config中配置所有常用服务器的ip, 别名, 然后直接在Remote-Explore中快速连接 Bookmarks 快速在代码指定行添加标签, 方便跳转 推荐快捷键改为: ctrl + →: 添加mark ctrl + ←: 取消mark ctrl + ↑↓: 跳转 Live Server 网页开发必备, 快速其一个本地的server 自研插件: vscode-function-decoration 博客 | Github 为Python函数增加标识, 以区分是哪个if包围. vscode-markdown-snippet 博客 | Github 提供markdown的常用样式插入 使用技巧 0. Tasks 自定义vscode执行任务 相当于快速执行某个bat/shell脚本 1. 区分顶部窗口颜色 当work-space项目较多, 可以在setting.json下作颜色区分. active代表选中, inactive代表未选中的颜色 \"titleBar.activeBackground\": \"#583e63\", \"titleBar.inactiveBackground\": \"#583e6399\", 2. editor和terminal切换 作为VSCode terminal的重度使用者, 经常需要在其与编辑器界面切换 推荐绑定ctrl+, 在Keybord Shortcuts配置如下快捷键即可: { \"key\": \"ctrl+j\", \"command\": \"workbench.action.focusPanel\" }, { \"key\": \"ctrl+j\", \"command\": \"workbench.action.focusActiveEditorGroup\", \"when\": \"panelFocus\" }"},{"title":"【OS】程序与状态机","date":"2023-03-14T13:27:35.000Z","path":"posts/22MGG22/","text":"先理解两个概念: 程序是 状态机 程序 = 计算 + SYS_call 如何模拟一个数字电路? 假设有两个寄存器,X和Y,取值分别为0和1 每次执行下面的动作: X′= ¬X ∧ Y Y′= ¬X ∧ ¬Y 则对应下面三种状态的转移,每个转移对应cpu一个时钟周期: X=0 Y=0 X=0 Y=1 X=1 Y=0 循环... 这就是一个简单程序的数字电路视角,下面是C代码的实现: gcc -E main.c 可以展开宏 #define REGS_FOREACH(_) _(X) _(Y) #define RUN_LOGIC X1 = !X && Y; \\ Y1 = !X && !Y; #define DEFINE(X) static int X, X##1; #define UPDATE(X) X = X##1; #define PRINT(X) printf(#X \" = %d; \", X); int main() { REGS_FOREACH(DEFINE); // 展开: static int X, X1; while (1) { // clock RUN_LOGIC; // 展开: X1 = !X && Y; REGS_FOREACH(PRINT); // 展开: printf(\"X\" \"=%d;\" X) REGS_FOREACH(UPDATE); // 展开: X = X1; putchar('\\n'); sleep(1); } return 1; } 示例代码: logisim.c seven-seg.py C程序的状态机 状态 = stack frame + 全局变量 初始状态 = main(argv, argv) 转移 = 执行pc处指令,pc++ 就是这么简单!结合上面的代码更容易理解。 模拟一个C程序的函数调用 hanoi-r.c hanoi-nr.c 二进制程序的状态机 状态 = M(内存) + R(寄存器), 所有能存储数据的东西 初始状态 = ? 转移 = 执行pc处指令 程序 = 计算 + syscall 最基础的main函数都是通过syscall系统调用实现的 假设没有syscall,你甚至都不能让程序返回or停止! 如下是一个最简单的没有syscall的C程序 // main.c void _start() {} 可以通过gcc -c main.c && objdump -d main.o && ld main.o的方式编译链接它 但当执行a.out时,得到Segmentation fault 尝试用gdb去调试这个程序,得到汇编如下: 0x401000 <_start> push %rbp 0x401001 <_start+1> mov %rsp,%rbp 0x401004 <_start+4> nop 0x401005 <_start+5> pop %rbp 0x401006 <_start+6> ret 下面将打印每步执行后的寄存器和堆栈的值: 汇编 rsp rip 栈顶 初始 0x0001, 0x0000 push %rbp - 8 0x0000, 0x0001 mov %rsp,%rbp pop %rbp + 8 0x0 0x0001, 0x0000 ret + 8 0x1 0x0000, 0x0000 最后执行ret时,rip的值是0x1,这意味着要取0x1地址上的指令去执行 但是0x1的内存是无法访问的! 因此报错 segmentation fault 课程资料: NJU-OS2022 jyy ppt: 操作系统上的程序 b站: 南京大学2022操作系统-P2"},{"title":"【Slidev】基于Markdown的Web-PPT","date":"2023-03-13T05:08:37.000Z","path":"posts/2CQGWAY/","text":"NOTE slidev官网 slidev在线编辑器 slidev demo slidev是一款基于markdown语法,基于vite/vue3搭建的开源powerpoint工具,它的最大优势是可以部署在任何网站上,以实现快速、轻便访问目的 使用 WARNING下面这部分看官网文档... npm install @slidev/cli @slidev/theme-default npm init slidev: 初始化一个slidev工程 slidev build: 项目默认发布到./dist目录 slidev: 默认以localhost:3030的域名进行预览 演讲者模式 使用http://localhost:3030/presenter 可以查看注释,并且能同步操作另一处ppt的显示 todo:此功能无法用于发布场景 VSCode快速开发 插件:antfu.slidev 如何编写自己的theme? 所有主题开发遵循npm包规范,且存放在./node-modules 通过md中theme: default来指定选择的主题 为了适应公司内分享的ppt需求,特维护了一个slidev theme: 🔥 slidev-theme-beautiful 如何部署到服务器? slidev build会在当前./dist下生成所有的依赖资源,./dist/index即为访问的ppt主页 需要将./dist拷贝到对应服务器下,就能通过浏览器在线访问 slidev build --base './' 防止以下的路径使用绝对路径 <script type=\"module\" crossorigin src=\"/root/hexo-workspace/source/assets/index-aaad8f94.js\"></script> <link rel=\"stylesheet\" href=\"/root/hexo-workspace/source/assets/index-43f0699a.css\"> 编写规范 全局配置 相关配置可以阅读源码:💡 config.ts 官方推荐的主题:themes 合集 theme: seriph # ppt主题 background: https://source.unsplash.com/collection/94734566/1920x1080 # ppt背景图 class: text-center # 字体对齐方式 highlighter: shiki # 代码高亮格式 lineNumbers: false # 是否显示行号 drawings: persist: false transition: slide-left # ppt跳转方式 矢量Icons 借助 pictogrammers 实现ICONS自由: 先在官网查到icon名称, 如unity, 然后在md中插入: <mdi-unity/> transition 跳转 fade, fade-out - 淡出 slide-left, slide-right - 左右移出 slide-up, slide-down - 上下移出 class 对齐方式 text-center: 居中对齐, 一般用于首页 代码 // ```ts {2-3} // 表示从2-3行高亮 // ```ts {2-3|4-5|all} // 每次显示不同的高亮, 2-3 -> 4-5 -> 全部 // ```ts {monaco} // 可以运行编辑器 主题示例 一个具备图片轮播的主题 github: https://github.com/godkun/ppt-template 示例: https://ppt.godkun.top/#/4"},{"title":"【Apache】搭建文件服务器","date":"2023-03-09T16:17:12.000Z","path":"posts/5S5T02/","text":"概要 文件服务器: http://110.42.228.178/ 旧服务器地址即将到期(2023.6.24),已经迁移完毕 apache2 介绍 Apache2 又称为 httpd,它是一款开源的网页文件服务器,由于其安全性和可靠的跨平台性,被广泛使用。 安装:apt-get install apache2 启动服务:systemctl start apache2 停止服务:systemctl stop apache2 重启服务:systemctl restart apache2 Apache2 配置: 配置文件:/etc/apache2/apache2.conf 字体配置:/etc/apache2/conf-enabled/charset.conf 文件服务器的根目录(默认):/var/www/html 默认显示 Apache默认展示的网页是 index.html,需要将其删除。 之后添加文件就能在服务器上看到 内容规划 该文件服务器主要用于存储一些文件,以方便在不同机器随时访问、以及起到备份的作用。 大概分为如下几类: pdf:一些书籍、资料、ppt等 tools:一些工具的安装包(支持正版) … 疑难 软链目录 /var/www/html 目录藏得太深,我们希望软链一个外层的目录,这样更容易修改和获取它: 先删除 html 目录:rm -rf /var/www/html 新建一个软链 到 ~/apache:ln -s ~/apache . 改回默认的 html 名称:mv /var/www/apache /var/www/html 如果此时文件服务器仍无法访问,这是因为需要为root加权限: chmod +x ~/apache 加密 为用户 luhao 创建一个密码,写在passwd中,后续会提示你输入密码的 htpasswd -c /etc/httpd/passwd luhao 接着在/etc/apache2/apache2.conf中维护你要加密码的路径: <Directory \"/var/www/html/to_lock\"> AllowOverride all authuserfile \"/etc/httpd/passwd\" authname \"luhao\" authtype basic require user luhao </Directory> 中文乱码 todo,这个还没解决,文件名显示正常,文件内是乱码 美化 默认的apache网页风格实在太丑,推荐如下一个GUI框架: Github: simple-apache-directory-listing-theme"},{"title":"【日志】2023年3月","date":"2023-03-07T14:54:46.000Z","path":"posts/2023-3/","text":"b3dc07a81f6459d120ce338ccca55046116de9af7135eccfa9c61a82756948fc2a685033db839f342fbe3644f374a32778c751268c2722fcafa2a8f38511b56b78650e2cce932aa4eec25b1ef8bafcbfabf65bccd0fd450c8916126348c073c8da6394e16dabb4b5f1d7d49bcb675c654780966c4fc5f7ccbab6211ca668430e1ce67aee0ad4ef92fccefd194f2db6a6c81f7f130b9319631be3985720bf86b43a699748bf0896a9723e84f1f705e194b36b64e5a798155d83cb93a3433d15b33d9b3110bb38a7ad6c993db506d0a32ca10552f04fbadcce3fc8d2741850632243eaab632965c432e22f7c1a4916290b50a01b46b61cafac5873b9130a52daa3356fa546e113bc5f4a89970b1534b3bf425a14a9015e051ade2835d72d57a3c10c4031ee129fe4b3489fb22cadf33f65f40c8deb3db456ca911940431d3176302c5043f2c0c1c3c70902bdf9748e24df71fcd9accee6accfe9fc323ac423a4bf290452d768408e68dc140a28665e7b7b74bb5f6c1c5d9a94bfb53e01810866d8e7d27640b9e5bc86bd4308d7eae057e70df0faea1bfb25113f2f78404e67dc1ff1c8e44e2d757951b0e3382f7bc9307bcce71e3ea6fa2bad92ce1f239c3d2d5d355972490e0ce67f5fe4f548369ee756a347196266e8d56dc5e128e05b59fd1d843bcd1e045a2fa4e6abbccfa88f586e305b6690f9315dd4f74ad3f6ff7ae4bcf3a2a03d18700a396d40b38a513cce6afffc5ccd4c38bbd4428780fa06ac7397d0650450abf305ed91b8acc6a111c881148cb17adc2772b9dadfb3f4e791afcf88ee948f0d55c5b91056fb5e9ef19837518e97ddf8981c2c3c37eede0446a40875d509bdb1ed0f26b051903129e874d14514d8e18d64421a20e786db32e205ce9338fa88869960ba2512b515fd395b2cfc1f72ccfe768f532307de62863f496d6190b19815699cfaacf0a9d3eee9e28c396a9e5bd7991cb5a71e3d5efc250cf32aa19c82e6ae5c850a4d94c6570301a8b9e2186c620a808912da319f8496b2d6acc6418c9d79d8d008a2209c901fd19f3fb927d7e07c6696c9bd55d1196de36ad8a4db4cfee2acc33b85c3b53ca20725db7b0c358f3927dac7e75d5f48e82e6116eb3425b3bf284ea9fb60ffc43b171995ff8139551d93cc1cf2d1b3c98f166219644c699cc22662e766a0ecdaa42ec956e1f48debc0b48526dcf6db5ddc025931f1317a8c522f6d45017e36bfdadc5183e3467c960f3db80dc70f10b6dc3667c6c7ed0b2a3e03f0c4d3300edb5c2024cf66c90391338c0d7321fe73d17386583e31bca4a838aca381358604062e5fb26f8123550a1125a61a979c253a3a317efa22726b43142073eeae5126432f1dc4b3f5193415e691beb32b071305a7dc5d93562dd908997364813849976d8e7ca46801a870d90b9711cb065109b00f6c6d6fe4379f735f8d0904a786a187c247249b72e367d822fcfc97e17c0e3fb853fd8478bb404ab1a6a62414fa644ff1a661298473cfcc51ee729e1a5fc989c0f4a354442efc89cfc492643c6e5cbbc234404a7d5673bd0a7cda0617ceb9772189ed5658f025b7a781d3504d2aba758a5c29e7d5f473f0588e46c6a903bb068a384e6fc153e3d2163ac443f4066a913a8dd8c32daa088d0668abfec75b242d90dde0b97bb2a3a6683ed25c8aa695b551957baf638c5f7c682e1e2d2e51a48312789d7fbc64ae2e42323bc6899cd8bbbe0dca54dd4244e37797133a90836284e1e2ae547e316c0c0b333eee20d837f8d5abf3f7440e96ebde8219f2b34322284f9593dd2e5b22f1db74a103ae4f9e70f08b1ad1f64a1a448df8c768bc9867ba06e6dee4db18282ff447d08f4a38867145645beda234be1b4edcf6af90f5226d67ef8eb9b53eb197334634f3e0ae02ef4c97bd0965c173c1d6dfcd5955e9a027f279b86c5e36485620537877c5b33c1c917c13045c903943243034aa3bd744ecd9a726726e90b1c776fc0e5f2a4e412ceff0eed662ae057f02d584641d4ec931b4f28bbf2b809e0902259665cfe71258ba80f77a4d54907f5aa1723b07c3bbb9e5c5b968f587da18c9eb0ff58fe448165776bb1747c0ce1750f09ba3a355a1c54acec5ab3f70a351ffdafbc816b50a23596bc6a9fe8bd3b928c055d2c7e0d49cf8336deb86a35586345f68b0fd0c2cc71c85c57f64d5f11bca6db134fd68891977e79f8131d707d2bc210493bce0a5ef30818d388cae4953616b06bd2aeb06c8c4770588582eb488d9a6e51c7e85291f969f7fc45ba87bb73d7d0b0797453d4ba718efab30f408e7081f37c8f26f28e3feb4addabd050aa26922f7f747b0ef5a2a188c3cf5e16c9a7b725157e98e2e74d552924f8dfe6226f1d2f9b38ce9c4e7acf86a160a29f326be89b36f8c082dc517b0e0a5bf9202481b5d2ce14ec14da4b48a2b12681eafd363491bbc06e3ff9e25f01c828446ccc6633afa6f89fe2e474cc53777a0a9a65ead9217169824923b8b5e2bf73fa7da63516b542f84a996524c9f213399dc3254001c01df1d1729c7f451c1ad8558d1913f2c035e6a28a2870a502e09b6fa92181cce3f8b437bc993fb9998d35a4d1142282552c1f215a60e74ea9833be1ab34e4736ed6e6d9af67e2021890500e32dcefc4610b33cf8f2ede76bca09ca8a3b3779b78082da4f384b80b149a018bc11f3ccacb2de14a366d4e9bced5054d534f2fd774eb9df5ca2380af77f545b67d64fd8afc1e7db4b4a2130784cc1958c4f5795943f1fa21c3c0c555f1f17fdcbf71a185fa1bddb229dca738900661cf9df76842e428b74f45f47c707c4a3935f67f017f13deea1c8125da3e5b6692a6e9dc4dd72697c572f471e983e0e0d911bba75fdab2aa00e86286b3852798bb7f1ed9021531488913da4c4eb633e08722fe7a8c5cd3bdab3c7e38e21156453ed0d41452d2bfc10964e62c39e7defdd50598d7c77c53a3560710c6061440cf38d301cd89d5dcf21cdf441b59d68f75cbd3c30277b4dc2b91043d5c776599cb07f7eb1f379215ce40326a0173b2b59935d7e9bbae5e97232f255e4a280ae367995e76e91b9f0aac2cfe31f190844d13c745229a66d3073da7143ce33c0f3019c8bb0e3af209a817e0d619aa679cd358d16f39a2e4eaeb66ab66062b0ac1691cd275da2bfa11abb471c28d984f581c02c156dae243fa4aa6b09d53e7b13c4521fa7d0a9f914ad3995147b0ae239535dbe4e94caf2dc9cf2bd4a9aac6db36caaece65d695b3f55cae60505865de06be7cf43984e3eae0855afced8a1eba18fecc206e0589ffb3f8e25d43f9fb67f3336bfc39ce9b686b800d18299c0506c4aa414a3b89312f0b30d2539b698053e00ccedb7b4923836a7afcb87b7efb88f80e5362c8fbd75f4e0a6cbaf6e8840ed27d002be7ab3fc06084efaed7cb430ecfae1b2079d20c6432d21efb2d2d208c8317f66ae0f9c3d0f08bfd4680eb1b2135272d633dbf61873f0f84f2577ebdfbb5242dae268776174e570ce541bb8815fdca426085849cffe76d8c33357c3ecf47ad5079fbc7b125f8e6da5cf4eea0677a4f6369d7cd9d9d9b799401746751a55f72cd3d507f56e5520e8e1136df016c94faaca7c8b6e469a183aa2807818701dca2654deeb39f9363a6066abc89f06f075fce3fb3d25df744af1000edd5c02ed3f3516043b1afb71422b086fdc9a7de284270c9e28d525dfbc455b318bb6f2f4525573bf1e30d05cfb313d1886e5614bed6ca0139906900e0014553d3ab215b5bf13c089a0840354752ba3b963f5cbb8c95cb7aadb49e399b4f816aeb3a55d292f077046671c683bab21a0235bfc242a33acc5bad603ce94895d6e31d61f99fcad5143fd469d20b4bb30b8a7dea62bb64b9bb6bb51bb15c54989e60cf8f3b96a27111d08b24cd47f9e0f4068d46c506393001144c6e7487cc36d990e380c1e5de790b45a3d1bbcddf3d61be5fcece5dfea14765e4eac5d796d4fa376ab448337f909f7f7c3061a83a885d2dc11d5308f5ac2f5bd0cdd8f2cf2d92814d53c2ee8324969e60399dd6d39b79a9cc93f4b1cc1131bebc9302d83010b850e3da81a9e43e9dd13e22e944eb033c7abed65778b2634945078cdbc8137aa43b3049cbc3945f2521d750451982b1e22bc4f9990ce90541e22625b2cc8e7e647c6cc84ea9699f81a6d208f8b64d4d6ac2a6b68ead48706711a4cf40ea6fc0061a9eb58ed092fe703aee6e83df638138e02fe06d1e0b2cf4167aa65a5fc96dfb585a885bae094098dc7b92d7509290c46f27c75f7d7f2f75a819e312627e1ff38839ddc1d1fb97e308eedc335719dc55fa2f340d5ceed0e48f42d3cde44ed38e347063aa79d789817a59eb768bbd75abc9ed721bc8e2c7c3987582078da920cd82fee57f8cff1ed250aceba336a413aeefe0e3c9b71294b2346bdfd0932f69a95923364e9dcdbbe26633132736936364c72b30a229810ef6483a3b8621768b6e2d138452945fb57227772d10a6e7f363de532a25f01bd8b4eab5bc963a8ebd2816aadf83bd8f33e662183292bc9bead5b0a8bf42feac4df957defba50380c957243ee48598e7d13f7c2d4ef71a2cd4e223808493857a7ebcc5eb10a26baf09c306d0e3697e02048bf76330ad34adb464f5956ccd2bcf562a6c09664b467394b2066e4910e3e2fbcb4f1ab7b1c50223ef90acac5a881c6a7b76605b5724af032503cb4ed3e56ad28072afd7495ae9983e2c97bca6127cb767bf7f86d1944445cbc0074051811570c8cd386a68d154a1eed49ab8b1705677964bfc5c0d9ab10e0f27cf484a7cde722febda3f0b0e38d003e26311c294313ad5cccd64babfaf6184d43c19e75ac9c72ce9d49b3c47a199347e73cea06f0e639eac0a6e09f98b7686d07424883572561be7908960ac01593e8a59f3fb5916b343e9d76467c628fd0f6f3895a78e7b8ab9581a8a1c92b61a466f83d18442f91fc07f7ea2294b0be16cbc527ea4f90120cc50d791f28be374babf1e7729884a22f18690fe92509ad8aaf944a8eca19aa674892906dc1cb8ae41e4cbf1055e94840e71eb2f5992298d45bd32d78aefcb678221f1f5542176f8d76949bed21c8b2ebb2e0139128155dc96e5bb99b828416d8470cf8ab79b87ef5c162640c9e0b1c971b4b4d4383a46dbdfe3838b9d719480c37f696c1eb9c08c586dad326190c2708a3686503762ee5ca613006efc6a33eb57482f8339ce3ffd0596634943d8e3269243a806b48ec3eac7d3ef2f11c97a968c3feb9e634021edbff6a09d55748af4845fec96618e3fd4bc2c1ba4ce14399c14796addd1c9d820e714ad16d5d32597de20838d3b354d19269fb6ea586ea1bdb43e8f727867e87b5d7cf60d7dc74221721420d3c65931d292514b5157c2e63a570133ae2ba1b6cb6677c709c6be3650998fb49be85adadd0ef5fa5e1dfffbbb82ef83c316f576553c28b4322f30469dd41fd811354da276ca78715fe414ad38eda1cd86cc300338f014e6dcfb164bd1754e17e12c507466041af96384c770c77dc7b6d46a22e42b9f06df9a10c4e9459fe0533cd7d5dddf1bf2614e448a5d23139c74461fe72f14a98bb487dd57f73f29eb0c5e521a1ea44c09dfc5c23f12c871b2fc04b6da9b80e8ed13e3901539fc891fafa1eab0f5746dd69f6313bb288ea4eec3fdb95a5db7f9fef7247508c2fd8d7ae5abd80a6b3731abc4b05e3d9d19525eb9339c1c33336b640030caf8c28722814c67b104c6a6a0b05067e2fb6fd1976e1eb68c08caba75f9a24cf1d5c84e3cb82656e6a84ce09a0542605558b765bdf54e6a4aaae4414118ded3718b61ea492f0fbb664ad96af9563dd6a8abac5611a069f6beda5a27ef31545844adbd66b1f493c30dba3ce9f4e1175bc41a2828e9fd27277be5a3d37014bd41bdbd6fc7ade9be91e02f6b4e2680880301a03537cd7c74e0f6123c03131a2eb8de0ca0bf457f9c1ecf737b66af42998cd9d4f9f587bae317638057ef1870315be2f298a99837ccf9285e5597d69cf52e904143cb16bbb0630892f0e84ffda1a8a39dbd7abedd72d562df3085b6c5161371dd3244faae7df584fb9bf7147de739c8b96e47e939c414b5fda9cbcaf3692f4eae861d9c89747eca88fa46fb99acd4e1319c6e62f393fc680137b76fb436813ccea42ba185cb4bcbe5f99cf270bc99bff20ab5ccf4b3c6b709e78f2f04be946a798c54a8ab8b9d475c85e2ed4c1afdcbce2374ddc9e155f853fffe0bd1e1fdd01f6ec5e2925e12f3278dafd4550e4f179c5551278fc66d84682b2720f1aa26f01f012360e384068978df7a64fc96f3e4f8b1b81d59f1e4e63aae604b5069055d66cd2c9ad5fa8185f2587a2a4a38784c5fd161f25a12dc58fec2600d5d3342c2b6fe308c99672705c9e4281af62df2b4a4281bc87503a561835fd8fdf20b80ad2bbe9204da3f6faffdd4f3dff95cc113ff3bad3eb1e86f88f817770823c8b632ab918f5952f4300f10117d34808213c8238fb6edf0ae93eeefae112006d990c1bf9d2eccc81054b5c8694729bb3ca3d3bcc5e52cd6edae35efa9a177be42657b637d0d3bc3de23fd983a60f05e5e41ba212112cb7ca484ac1c54c58d915348c691c81c40290bd6c881ba8ed438a5375b0510361cb5a2ba8ba4ec1233873a669a89fb8b67daffb4ec2fa2e053afeca94bd2017f76c8ea5728e6158ee1db25bbbd7d317a20ee8fe87e635cb53783bd82f5bbec4465f002017688ed9a9b5fd051b366fdef1eebcd2eeedd5820d8339adff87bc1c5ad4b29e2d90bc9f4907dcdcb653e4b60d9737d560a76962f1fad2b834fb0dd90663e49ddafa0b1e7a495429aac7597f72bdef2c1d30b15f0325df918c1779cdb370dd62cd4b8a15b2a2394a28fd9b2d8efa79b6905e7ec2896d02a56d4e502657be8ac434d84f01cc336dcdb2ba3a2776738a16f48feba21a39cc17c84dd5ddc533874a9fa2fb510e18c6468a055b2593967d89dc13da1a20acabb6f31cd62f68aa1a2271b7f082ff23e93bf2cd217d6d14a6b80694055c06808c1556355c615339d5a67d5068e042adfaef0395869fa3ed245447fc0a31205bea8026a53317490227a2a8ed5036b5931171832de1c52e65157ddffbb4d5df57de2afebb453f9606f97845544c7de33807812afcf1395ec510665a70a8075d8ddd75e21b37816bb4478a3c3270c7d73448efac43a599f6178a64fe3265673aff3a380c6102c0620ab4a43957d8041064bb67ed7303b28fbf8d24c1970666be1adca34d11f1660444f881053d037a9019b9f476da48186ba02f2bcbcf770256245a85bdb1e048d4a3aa4c7fbf284b315a2add757ca00e5e60dff1d530e3edcdacacb9ef9c36910d87640417723fcfba8c1138c7713291a22348646ed2dfce33365f8385dcfc812607857e74ba833d5bfdebb91108dcb686be47ae038a5fd6f294f8f895c8f9e41f21edf91af6ad189d9d0a26e82e71ea88e0ade2d42803e27fe43ce1e39183f7b585b11428b7a5604512bb394be9f63ca91189c902d33cac5f06c8853ae61381d9cb05f00dfd78944e12f8d09195ff9061970c6f4e445a5b00ec6984ab7c6e62b497ff4cfb1bbd465f0998b8d24368ae00c06fa9ec60f6e0f6e3477222642fd7f78c1774cae7cad8df11069503d36ec2499bce4a0e6578cece39f176c39056701c21fb8b57107a234ac5e092096ed789fdd4cd65f6ce4a54e9fb31fad6862120f5fd6535c48404c25650105ec77f26eb1b9e21463c33181dfde995eb031aa3e229922f14d8c3637f78fda88db3640b50a5348aaf049489193067c53980bdcdf9cb88422f38c30532a8a2f3c5a0ed89fe7a0ed564623fbd0029452eae39e026aa6822214e93bebb90da7d18b497f8b89088a19a6235d86518a21b0a778b42581da018cf50eeb938d826c0bb65f5a147e64074611d5abbdac03f1221ddc6dbb2221ece9634a710224dbdaad01abee03db0797414b9de51f2240a293587fb01062e117a47b37fdfca68eb31ae778cc581b6554b8add2e43d20be9276a8cde78a6d81259952d5e9b03ef46f10f9f771fc2fe398af8f3b207fa0d1dfa0219de21db908af468becf9f2eb5c191669f51c547f6025f7c5d0a94b121bfa39cdba02aec49a6624d3dc40a00b374d713f654fdc1ff9f48784b077872459445fc89770c98bf070a8bf824e35875f672f5e75e83df8246bc0461136644f10e25c6c472187e1ef2e915231fe72f1b0668b09f8a585c2531328e1dcffc2dd5617b579b7e3622b1f36966589a9f5e6367eaa46425d8ddd050 Hey, password is required here."},{"title":"汇编(四):gdb调试","date":"2023-03-06T04:42:48.000Z","path":"posts/3DJGDAE/","text":"GDB: The GNU Project Debugger gdb cheat sheet godbolt (C/C++, python等在线转汇编) online gdb (在线gdb调试) godbolt 推荐这个网站 https://gcc.godbolt.org,在线写 C/C++/Python 等代码,并查看整理后的汇编码,非常清晰。 Todo"},{"title":"汇编(三):基础AT&T汇编","date":"2023-03-05T07:10:01.000Z","path":"posts/1QNHHCN/","text":"Read the Fucking Manul x86 Assembly Guide Linux System Call Table godbolt (C/C++, python等在线转汇编) AT&T与intel汇编风格 汇编的编写,离不开寄存器和基础指令,前置知识需要阅读 👉汇编(二):基础寄存器 汇编分为AT&T与Intel风格:Intel vs. AT&T syntax Intel AT&T 注释 ; // # 指令 mov movb是byte, movw是word, movl是long 寄存器 eax %eax 立即数 0x12 $0x12 汇编 mov eax, 0x12 mov $0x12, %eax 移动0x12到eax寄存器 编译器 windows masm unix as AT&T汇编示例 各个字段的含义,请继续阅读后文 .data # 定义数据段 t1: .int 0x1230 # 定义int型的t1, t2变量 t2: .int 0x4 .text # 定义代码段 .globl _start # 定义_start函数入口 _start: mov t1, %eax # eax: 0x1230 mov t2, %ebx # ebx: 0x4 add %eax, %ebx # ebx: 0x1234 # 0x80系统调用sys_exit, 退出程序 mov $0x1, %eax int $0x80 编译与链接 利用汇编器将.s转化为目标文件.o as hello.s -o hello.o 利用链接器将.o链接为可执行文件hello ld hello.o -o hello 为什么需要一个链接的步骤? 链接器(linker)的作用 内存划分 .section .section 将代码划分若干个段,程序执行时,每个段会被加载到不同的内存地址 .section 含义 作用 .data 数据段 存放已初始化的全局、静态变量 .bss 数据段 存放未初始化的全局、静态变量 .text 代码段 存放只读的代码 .heap 堆 动态分配的内存 .stack 栈 函数局部变量等 内存划分如图所示: 其中除了堆和栈是动态分配的,其他都是静态预先分配的内存 调用size [target]查看各个段的内存大小 # 示例见上面的汇编代码 size hello text data bss dec hex filename 23 8 0 31 1f out \"\"\" text: 代码段大小 data, bss: 全局/静态变量大小 dec: 总结大小 (hex是十六进制) \"\"\" 统计代码段的内存大小 0x401000<+0>: 0xb8 0x34 0x12 0x00 0x00 ---> mov $0x1234, %eax 0x401000<+5>: 0xb0 0x12 0x00 ---> movb $0x1, %el 0x401000<+7>: ... \"\"\" mov和movb都是操作符, 占1个字节, 分别是0xb8与0xb0 因为mov的操作数是16位,因此占2个字节,而movb占1个字节 eax和el寄存器也是,分别占2个和1个字节 \"\"\" Linux上可以查看所有虚拟内存的分配:/proc/[pid]/maps 可以通过 man proc 阅读linux文档。 观察到:内存 heap 位于低地址,而 stack 位于高地址,没有 pathname 的一般是通过 mmap 分配的匿名内存 而读写属性有如下几种: r = read w = write x = execute s = shared p = private (copy on write) address perms offset dev inode pathname 561155768000-5611557b5000 r--p 00000000 fe:01 410132 /usr/bin/python2.7 5611557b5000-56115594a000 r-xp 0004d000 fe:01 410132 /usr/bin/python2.7 561155ad9000-561155afc000 rw-p 00000000 00:00 0 561156b9f000-561156c7d000 rw-p 00000000 00:00 0 [heap] 7f7265abe000-7f7265da5000 r--p 00000000 fe:01 393252 /usr/lib/locale/locale-archive 7f7265da5000-7f7265f1b000 rw-p 00000000 00:00 0 7f7265f1b000-7f7265f3d000 r--p 00000000 fe:01 431545 /usr/lib/x86_64-linux-gnu/libc-2.31.so 7f7266278000-7f7266279000 rw-p 0001c000 fe:01 442692 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so 7f7266279000-7f726627f000 rw-p 00000000 00:00 0 7f72662b2000-7f72662b3000 rw-p 0002a000 fe:01 431541 /usr/lib/x86_64-linux-gnu/ld-2.31.so 7f72662b3000-7f72662b4000 rw-p 00000000 00:00 0 7ffd4c51b000-7ffd4c53c000 rw-p 00000000 00:00 0 [stack] 7ffd4c59e000-7ffd4c5a2000 r--p 00000000 00:00 0 [vvar] 7ffd4c5a2000-7ffd4c5a4000 r-xp 00000000 00:00 0 [vdso] 数据类型 文本字符: .ascii 整型: .byte(8位) .short(16位) .int(32位) 浮点: .float .double 指令后缀同时代表精度: b, w, l, q: 分别代表8,16,32,64位 _start函数 就像C++的main函数一样,_start是整个汇编程序的入口,你必须显示的定义它 且需要通过.globl _start,来告诉链接器如何查找_start的符号地址 系统调用 Unix下通过int $0x80进行系统调用,其中参数存在%eax等寄存器中: 查阅对照表: Linux System Call Table %eax 名称 %ebx %ecx %edx 1 sys_exit - - - 2 sys_fork struct pt_regs - - 3 sys_read usigned int char * size_t 4 sys_write usigned int const char* size_t ⭐ TODO push/pop 栈相关 loop 循环 系统调用不够细 PA实验"},{"title":"汇编(二):基础寄存器","date":"2023-03-04T03:27:04.000Z","path":"posts/14G1VW3/","text":"约定如下原则: ax与AX都代表同一寄存器,不区分大小写 0xAA、0xaa、AAH都代表十六进制的写法 本文汇编基于Intel vs. AT&T syntax 什么是寄存器? 寄存器是cpu中负责数据存储的电子器件 它的传输速度最快,大于l1、l2 cache,远大于RAM和磁盘 一个16位的寄存器,可以存储2字节的数据,最大可表示0~65536范围的数据 寄存器一般用约定俗成的命名,来区分其具体用途 通用寄存器 存储一般性数据的寄存器,如AX, BX, CX, DX… 汇编 作用 mov ax, 18 将18写入寄存器ax mov ax, bx 将bx的数据写入ax add ax, bx 将ax,bx的值相加,并写入ax sub ax, bx 将ax,bx的值相减,并写入ax 寄存器数据溢出时,会自动舍弃最高位,例如: 8位寄存器:ax(0xff), bx(0x01) 执行add ax, bx后, ax中的值为 0x00 段寄存器 根据 《汇编(一):计算机架构入门》:物理地址 = 段地址 * 16 + 偏移地址 因此,CS存储代码段的段地址,EIP存储代码段的偏移地址! 代码执行地址为:(CS) * 16 + (EIP),且每次执行一行后,EIP会根据指令长度偏移若干字节 计算机每次运行,会根据如上计算出的物理地址,取出对应的指令去运行 详解下图过程: 1.取出CS,EIP的值,并放入地址加法器,得到结果是0x20000 2.将物理地址0x20000送上地址总线 3.读取到该处指令为mov ax, 0123H,并送入数据总线 4.CPU ALU获取到该指令,将其执行(涉及AX, BX的读写) DS存储要访问数据的段地址 当我们要将al中的数据写入0x1000的内存时: mov bx, 0x1000 mov ds, bx: 将bx值写入到ds,注意,不支持直接往ds寄存器写数据! mov [0], al: [0]代表读取以ds为段地址,以0为偏移的内存地址上的值 [ax]的特殊用法⭐ : 表示以寄存器ax上的值,为偏移地址(段地址在ds中),此时去对应物理地址寻值 栈寄存器 什么栈(stack)? 后进先出的操作规则:LIFO(Last in First out),就像往枪膛里填子弹 程序在运行时,会将一段连续的内存空间划分为栈,这里的操作原则符合LIFO SS存放栈顶的地址,SP存放栈的偏移地址 汇编 作用 push ax 将ax值写入栈顶,即写到SS+SP,并将SP位置下移 pop ax 将栈顶值写入ax,并将SP位置上移 什么是Stack Overflow? 因为程序中的系统调用,都是用堆栈实现的,但默认分配的大小为固定的; 当你写如下一个Python无限递归时,就会因堆栈溢出而导致RuntimeError而程序退出 def f(n): return f(n-1) f(100) # RuntimeError: maximum recursion depth exceeded 内联汇编 TODO 关于 内联汇编(inline asm) 可以单独开一篇学习之 GCC-Inline-Assembly-HOWTO C内联汇编 写汇编经常遇到 movq %0, %%rsp 的写法,其中的 %0 是什么含义? 它一般用于 C++内联汇编: int a=10, b; asm (\"movl %1, %%eax; movl %%eax, %0;\" : \"=r\"(b) /* output */ : \"r\"(a) /* input */ : \"%eax\" /* clobbered register */ ); \"=r\"(b) 表示把 %0 寄存器的值输出给 变量b \"r\"(a)\" 表示分配一个寄存器 %1 保存变量a 的值 因此这段代码的作用是:将a赋值给b 进阶示例: 下面这段代码,能够切换到指定的堆栈(sp),并执行对应的函数调用(entry arg) static inline void stack_switch_call(void *sp, void *entry, uintptr_t arg) { asm volatile ( #if __x86_64__ \"movq %0, %%rsp; movq %2, %%rdi; jmp *%1\" : : \"b\"((uintptr_t)sp), \"d\"(entry), \"a\"(arg) : \"memory\" #else \"movl %0, %%esp; movl %2, 4(%0); jmp *%1\" : : \"b\"((uintptr_t)sp - 8), \"d\"(entry), \"a\"(arg) : \"memory\" #endif ); } 输出修饰符: a: 使用 eax/ax/al 寄存器; b: 使用 ebx/bx/bl 寄存器; c: 使用 ecx/cx/cl 寄存器; d: 使用 edx/dx/dl 寄存器; r: 使用任何可用的通用寄存器;m: 使用变量的内存位置; 这段代码一共使用了3个寄存器: \"b\"((uintptr_t)sp): 使用 ebx 寄存器(%0)保存 sp 的值; \"d\"(entry): 使用 edx 寄存器(%1) 保存 entry 的值; \"a\"(arg): 使用 eax 寄存器(%2) 保存 arg 的值; 此时再去理解汇编代码,其作用是: movq %0, %%rsp: 将 sp 的值传递给 rsp寄存器(函数堆栈); movq %2, %%rdi: 将 args 的值传递给 rdi寄存器(传递参数); jmp *%1\": 跳转去执行 entry 函数; 对照表 寄存器 作用 英文 AX, BX, CX 通用寄存器 Register CS (代码段)段寄存器 Code-Segment (E)IP (代码段偏移)寄存器 (Extend)Instruction-Pointer DS (数据段)段寄存器 Data-Segment SS (栈顶)段寄存器 Stack-Segment SP (栈顶偏移)段寄存器 Stack-Segment"},{"title":"汇编(一):计算机架构入门","date":"2023-03-03T14:15:32.000Z","path":"posts/3RBTN4W/","text":"介绍CPU、寄存器、物理内存、寻址等硬件架构与概念 架构图 这张架构图清晰的解释了计算机各个硬件之间的关系与作用 第一次看不懂,先理解下面各个器件的作用 箭头连线代表电信号的传输,从计算机角度就是数据的传输 存储器 存储器是存放计算机数据的器件,根据读写属性分为两类: ROM(Read-Only-Memory) 只读,一般是显卡、网卡上的默认系统,如BIOS RAM(Random-Access-Memory) 可读可写,显卡上的RAM又成为显存 数据总线 Data-bus 数据总线是为CPU与存储器提供数据传输的 1根数据总线(宽度为1),一次能传送1位(2bit)的二进制数据 数据总线的宽度,决定CPU与外界传输数据的速度 地址总线 Address-bus 读写操作时,CPU通过地址总线来指定存储单元的位置 宽度为10的地址总线,寻址能力为2^10个内存单元 控制总线 Control-bus 代表CPU对计算机器件的控制能力 如有一根“读线”控制发送内存读取的信号,有一根“写线”控制写信号 CPU 寄存器负责存 对于一个16位寄存器,能存储的最大数值是2^16 逻辑算数单元(ALU)负责算 控制器负责控制各个器件 ? CPU也有自己的内部总线, 同上 主频用来衡量CPU的执行性能,如2.0GHz的CPU,每个时钟周期是0.5ns 可以通过时钟周期衡量各个器件的传输效率 器件 消耗cpu时钟周期 (<1ns) Register 0.5 L1 cache 3 L2 cache 15 L3 cache 45 RAM内存 300 SSD固态盘 300000 HDD机械盘 >>SSD 延伸阅读: 为什么 SRAM 读写速度更快? 存储技术SRAM详解 物理地址 计算机的内存单元是一个线性的地址 假设总内存大小1M的计算机,(1M=2^20=16^5),可以理解为[0x0, 0xFFFFF]这样的一维数组 当内存较大,但地址总线较小时,通常采用寻址的方式: 物理地址 = 段地址 x 16 + 偏移地址 因此通常将内存分段, 但物理意义上内存是连续的 《汇编语言》(王爽第三版)P23,列举了一个学校、图书馆位置的例子,很好解释了寻址的思想"},{"title":"【C++】编码规范","date":"2023-02-23T14:05:16.000Z","path":"posts/WTFJWR/","text":"Linux kernel coding style 🔥 聊聊 C++ 的优雅写法 Names and Order of Includes 当一个cpp存在非常多的 include 时,请合理地对其进行排序。这个编码规范在很多语言都值得引起注意,如 python、JS/TS。 顺序逻辑如下,不同模块间请用一行空格隔开: self header C header C++ header libraries project Preincrement and Predecrement 使用 ++i/--i 代替 i++/i--,因为后者会产生一个临时变量的开销。 分割线 1. 缩进 Linux指定缩进为 8 个空格 python pep8 使用 4 空格缩进 ✔ 一些 C/C++ 开源框架式用 2 空格缩进: leveldb 当缩进层数较多(n>=3),应该考虑代码的设计问题!👍 2. 单行长度 限制在80字符内,利用\\换行 (可以借助VSCode提示监督) 3. 大括号 {} 对于函数定义,大括号单独换行 int function(int x) { return x; } 4. 空格 对于二元、三元运算符,左右都留空 int a = b + c; 对于一元运算符,右侧不留空 s = sizeof(struct file); 5. 命名 全局变量,使用详细的命名 g_info ❌ g_send_msg_info ✔ 局部变量,力求短小精悍 is_decoration_always_open ❌ is_open ✔ 避免种族歧视的命名 ⚠ master -> primary blacklist -> denylist 6. 函数规范 声明函数原型时,需要包含变量名(尽管编译器允许只写变量类型) __init void * __must_check action(enum magic value, size_t size, u8 count, char *fmt, ...) __printf(4, 5) __malloc; .h的函数声明包含默认参数的,.cpp中使用注释注明 // a.h void action(size_t a, size_t b = 10); // a.cpp void action(size_t a, size_t b /* = 10 */) { // todo } 7. 注释 注释是解释What,而不是解释How What -> 注释 How -> 文档、Readme 多行注释的格式如下: /* * This is the preferred style for multi-line * comments in the Linux kernel source code. * Please use it consistently. * * Description: A column of asterisks on the left side, * with beginning and ending almost-blank lines. */ 8. 宏定义 常量的宏定义,应该大写 宏定义 如果使用操作符,务必使用括号扩起 #define CONSTANT 0x4000 #define CONSTEXP (CONSTANT | 3) 函数的宏定义,应该小写 跨行函数,应该使用do-while ❓ #define macrofun(a, b, c) \\ do { \\ if (a == 5) \\ do_this(b, c); \\ } while (0)"},{"title":"Forward Lighting的技术优化","date":"2023-02-22T02:37:09.000Z","path":"posts/2KDTR1T/","text":"Foward管线下实时光照计算的若干优化技术 复杂度分析 谈优化前,先分析一波复杂度 Forward Lighting 遍历场景所有的mesh和光源分别计算光照, 复杂度是O(mesh*light) for mesh in scene for light in scene color += Calc(mesh, light) 当场景的光源数量上升时,GPU计算开销会骤增,显然吃不消; 题外话,为什么Deferred管线下,能驾驭多光源的实时计算呢? Deferred Lighting 在包含SceneColor, SceneNormal的GBuffer上进行光照计算, 复杂度是O(light) for light in scene color += Calc(Gbuffer, light) 而Deferred管线的弊端,网上很多没说清,因此提一嘴: 硬件不支持MRT 不用考虑,根据2020年移动端白皮书,gles 3.0下的占有率已接近0,海外市场具体问题具体分析 不支持透明渲染 代价低的方案是,在Deferred结束后,新开一个ForwardPass去渲染透明物体,缺点是透明物没有光照 不支持MSAA 一方面是对GBuffer做AA无意义,另一方面Multi-Sample会使带宽问题雪上加霜 带宽瓶颈 移动端硬伤, 标准的GBuffer4最低就是128-bits的带宽 优化方向 对于Forward lighting 已知:复杂度是O(mesh*light) 问:如何优化? 答:要么降低Mesh数量(也可理解为Gemotry数量), 要么减少计算光源数量 1. 降低Mesh Vertex Lighting 正常的光照计算是在pixel shader, 但如果在顶点做光照计算, 复杂度就很可控 但是画面表现很差, 推荐在低画质使用 Pre-Z 因为Pixel-Shader是在Depth-Test阶段前进行; 那么某个Mesh即便经过光照计算,最终也未必呈现在屏幕上,所造成的算力浪费,就是OverDraw问题; Pre-Z可以在Pixel之前拿到DepthBuffer,就能只对通过深度测试的点做计算: for mesh in scene if (DepthPass(mesh.depth)) for light in scene color += Calc(mesh, light) 2. 降低光源数 如何优化光源数量? 一方面,光源较多时,适当阉割几盏,对整体光照效果影响不大; 另一方面,光源范围都是有限的,适当做一点剔除,也能起优化作用; Overlap 如上图,一个像素接受了8盏光源的影响,增加计算量的同时,最后呈现的效果又不明显。 不妨先根据距离、强度等信息对光源排序,对应A~H 8盏: 方案一:只计算前几盏光源; 方案二:根据贡献度混合计算,如: 近(ABC)用Pixel-Lighting 中(DEF)用Vertex-Lighting 远(GH)用球谐光照 此时复杂度就下降为O(k*mesh), 且丢失的光照细节很有限 Tiled Based Lighting Clustered Based Lighting 对于每个像素, 当我们无脑遍历每个光源时, 思考一个问题: 光源的影响范围真有这么大吗? Tiled的主要思想, 就是把屏幕划分多个区域, 记录每个tiles能影响的光源id, shader阶段只用计算受他们影响的光源 Clustered是Tiled的三维划分版本, 将视锥空间沿xyz划分成 24*8*16 个cluster 接下来cpu负责计算,对于每个cluster影响到的光源id和分布, gpu负责计算剔除后的light shading 根据光源类型如下,需要做如下相交测试: Light Intersection AreaLight AABB & AABB求交 PointLight AABB & 球求交 SpotLight ⭐ AABB & 圆锥求交 严谨的相交检测,在物理引擎中使用较多,可以尝试学习: 🔥 Separating Axis Theorem (分离轴定律) 🔥 Realtime Rendering: Static Object Intersections 圆锥&AABB求交优化: 在做Spotlight的视锥剔除时,传统方案是, 用圆锥的AABB包围盒,或者是包围球,与和视锥的AABB求交。 这样就转化为AABB&AABB 或 AABB&球体 的求交问题,相对比较简单,但从剔除率堪忧,因为圆锥占其包围盒的体积比很小,不足三分之一,这里就是我们的优化点 我们改成用圆锥与视锥的包围球相交,方案如下: 首先圆锥由2个部分组成:侧面、底面,若两者都未与球相交,则说明无相交: 底面相交: 只需计算锥顶到球心的投影(如图红线),范围在(-r, r+h)之间,则判定相交; 侧面相交: 只需计算球到圆锥侧面的最短距离(高中数学题3D版),若小于半径r,则判定相交; 光源信息的建表和查表 首先需要一个32位int存储如下信息,以及一张光源信息表 struct ClusterDataDesc { uint16_t light_idx; // 对应去查light信息表 uint8_t pointlight_count; uint8_t spotlight_count; } 查表过程比较繁琐,原理不细讲了,对着下图去看代码会更清楚: 原理看这里: NeoX Wiki: Clustered Shading 3. 球谐SH 这篇KM写得很清楚,就不再赘述 基于球谐的动态点光优化方案 引用 NeoX Wiki: Clustered Shading (NeoX Clustered实现方案) DOOM(2016)- Graphics Study (DOOM Clustered实现方案) Practical Clustered Shading by Emil Persson (正当防卫引擎的Clustered实现方案) A Primer On Efficient Rendering Algorithms & Clustered Shading (好文, 系统且详细地讲解了大部分lighting方案及优化) RealtimeRendering: Static Object Intersections (realtimerednering文档,含游戏中常用几何体求交的算法及数学证明) Optimizing spotlight intersection in tiled/clustered light culling (利用AABB的包围球,近似的算AABB&Cone求交) Cull that cone! Improved cone/spotlight visibility tests for tiled and clustered lighting (同利用AABB的包围球,近似的算AABB&Cone求交)"},{"title":"Echarts可视化库","date":"2023-02-11T14:12:01.000Z","path":"posts/M53XVE/","text":"pyecharts Apache Echarts shawshank/ChartManager.py 写这个目的是不是搬运代码和图片,而是封装一个便于自己常用的绘制库. echarts简介 echarts是百度开源的一款图表绘制库,包括且不限于折线图、柱状图、蜡烛图及3D图表 pyecharts是移植于python的三方库 安装方式: pip install pyecharts 应用重点: 性能分析数据: 如帧率折线图、flamegraph火焰图 量化金融数据:如K线图 装B应用:地图、词云等 代码示例 0. 基础代码 写法一:python风格 from pyecharts.charts import Bar bar = Bar() bar.add_xaxis([\"衬衫\", \"羊毛衫\", \"雪纺衫\", \"裤子\", \"高跟鞋\", \"袜子\"]) bar.add_yaxis(\"商家A\", [5, 20, 36, 10, 75, 90]) bar.render() # 默认是./render.html 写法二:链式调用 (推荐👍) from pyecharts.charts import Bar ( Bar() .add_xaxis([\"衬衫\", \"羊毛衫\", \"雪纺衫\", \"裤子\", \"高跟鞋\", \"袜子\"]) .add_yaxis(\"商家A\", [5, 20, 36, 10, 75, 90]) .render() # 默认是./render.html ) Fake库:生存随机数据,方便测试 from pyecharts.faker import Faker Faker.choose() # 生成x-axis Faker.values() # 生成y-axis is_smooth: 曲线平滑 is_symbol_show: 显示数据详情 1. Line-折线图 按年度生成600519贵州茅台的日线价格走势: timeline = Timeline() df = G.dm.parse(file_name) code = file_name.split('/')[-1] if split_year: min_year, max_year = df.index[0], df.index[-1] for year in range(min_year.year, max_year.year + 1): _df = df[str(year) : str(year)] line = ( Line() .add_xaxis(list(map(lambda x: x.date(), _df.index))) .add_yaxis(f'{code} {year}', _df.close.to_list(), is_smooth=True) .set_global_opts( # NOTE: y轴分割线 yaxis_opts=opts.AxisOpts( splitline_opts=opts.SplitLineOpts(is_show=True), split_number=10 ) ) ) timeline.add(line, str(year)) timeline.render() 2. Kline-K线图 按年度生成600519贵州茅台的日K线: timeline = Timeline(init_opts=opts.InitOpts('1500px', '500px')) df = G.dm.parse(file_name) code = file_name.split('/')[-1] if split_year: min_year, max_year = df.index[0], df.index[-1] for year in range(min_year.year, max_year.year + 1): _df = df[str(year) : str(year)] oclh = _df[['open', 'close', 'low', 'high']].values.tolist() kline = ( Kline() .add_xaxis(list(map(lambda x: x.date(), _df.index))) # open, close, low, high .add_yaxis(f'{year}年度', oclh) .set_global_opts( xaxis_opts=opts.AxisOpts(is_scale=True), yaxis_opts=opts.AxisOpts( is_scale=True, splitarea_opts=opts.SplitAreaOpts( is_show=True, areastyle_opts=opts.AreaStyleOpts(opacity=1) ), ), datazoom_opts=[opts.DataZoomOpts(pos_bottom=\"-2%\")], title_opts=opts.TitleOpts(title=\"贵州茅台 600519 日线\"), ) ) timeline.add(kline, str(year)) timeline.render() TODO 搞懂opts每个配置项对应的图表含义 MarkAreaOpts: 标记区域 增加按钮, 在页面打开html flamegraph火焰图"},{"title":"Linux Crontab定时任务","date":"2023-02-02T14:45:01.000Z","path":"posts/15G1D76/","text":"资料 🔥 菜鸟教程crontab 在线验证 crontab 技术背景 服务器经常定时执行任务(如每日9点开盘),自己维护定时器又容易出Bug,还得做日志模块… Windows Server 推荐 Windows定时任务(有GUI和命令行) Linux 推荐 Crontab(纯命令行) crontab 基本使用 crontab -e: 编辑配置 service cron restart: 重启cron服务 service cron reload: 重新载入cron配置, 修改配置后需要reload 定时语法 # Part 1: * * * * * task - - - - - | | | | | | | | | +----- 星期 (0 - 6) (周日为0) | | | +---------- 月份 (1 - 12) | | +--------------- 日期 (1 - 31) | +-------------------- 小时 (0 - 23) +------------------------- 分钟 (0 - 59) # Part 2: */5: 代表每5分钟 1-5: 代表第1-5分钟都执行 如果不需要任何输出: xxx.bat > /dev/null 2>&1 附上服务器上的crontab量化相关配置 Q&A crontab没有日志 /etc/rsyslog.conf: 取消crontab的log注释 service rsyslog restart: 重启syslog /var/log/cron.log: 接着就可以看crontab的log! 没有 service 指令 使用 /etc/init.d/cron reload 等代替"},{"title":"2022年总结(写在除夕夜)","date":"2023-01-21T14:35:32.000Z","path":"posts/19KZ887/","text":"b3dc07a81f6459d120ce338ccca5504661c2b6d199f8abfa2402c5b16901e347024de2c78f361d0ef47e1cde1303c3a8822026a64881b4638ef155338ea76cb1ccbcc2fbbf742a4f3070a7d9b42e431dcbe95d92f1417cbb887e9e54af2d88a5a74520860d80486fcb86fc73e942bf7941a9773e852aedb8200286cb2ab906047f86456675c6bde6c0093bea5239620ad245c2a7ec1b9400de4c95235d9104a6ddc0446748fb69b6a1aa305dfbef999715b3b1ac87d1bab79af8437885d4c9b329e8c8a813b335cdf221b6b663d6c00890b0744d6e159f8a7068da64977cbde3d4d1401637450aa42464b2e281778f55feae741b3c097361af34421b519372dd5a8b7746dc61ea4b32c7f4bea23222785afd438726ff91feb0ef36aba4254fb27b9a63d01ad7e6ea981f479f9e6ec4451625982918852962ef58ed53f9330592f7bf685f81e01f04d76b06e9ddf6926adf50fdcd88aa613666e097f169cf84a95604ad4f25f68b2a116a41f0df70c3befcc81e646b095525205bf86a72b2eb303219e01bb38c361effc1b5cb8afb26e83363549511f6734cb3383abeffd5dcff07f4b6ccf93eb452106d17843bf1f914ce7905f5413eda38f5f2836fa58f194557a356afab190ed393e8b724dbd498f495b8ba5f7c98ad748b1175b35233c91593d4c3f61ad88b9a035d039f9ed1a5c9efea66d4d25da3f152b3ca6da335eddb4e20ab7e226dc390a458a291698af63b9a13755af75ea9cca27893a5aa82591bea44c53d0d154c97c839f627d1b4432801bec0d7d5c0e2337f72e3213e4824321f75f08d64bca7980c9ebe4c06e3c187bb1966858cad410f2089ca030eb0a3a03a87d5d64279eb1f200554e8eac027aa72b1dfb4c6538d3a4dff03ebef5b7a3acf547b6c58849716a5d9f296fc941c5cfe5158c93e7397007a7ac07c2021ef1648594a0caebbf35b5c98147e8353bbaab8eb98bc042e36e4ba143abc072d2f5dad3b6fa06e0909a3ae70da5472a6916588ce97990a6ba87e09cb4cad834b91f3ec0913bb00ebf96561c2c1393939ebbee8bf840ce33d1785f44e1f762e1eae1e58322b80766a61f53cf8b7f854c7a2938c58ee846ce6744fc3038e01a86ae9c43b83683087ab08a73b30a79c8bb7abdd06ba8c558771a3c3455b6c8a6e00ae579d52cacaabc7d9e55cdc7c4c4b756f7d6ebb29faa160243b0268a8c65b76e06a858a324ddfb63c4fe354783e7e876e67ae7e4d682752880eaf589f5be9375c526216f51eb6f1a82a01b4d4fc9f1684fca005eb2e098e35b17fc8fdf45717a7d3ed2d4136ee100ff4263acdc6e8f883b84a894a63d69de30ebe7cda09c4d3a3004cd8e45ec0c04a49ad8c3fc076f6736474cbf50f92f6c1696dcbcebbbb459425970a6be01f93e5b3f2d7b30ceaa79c80c4ede7bc003d91c0f5727bfe25c70b825b0ce263f0f05c57c1ed72d818c9559657a9e3463aca1f95c7589bc39f914bab887fd8aa124379803207b31c344e3a82814c964a0802d00b323ad1cc77c6ffd30255d9629a2b9730657f236f9520eb338d5392b6860f209502f3caac9da96a99f6a2c36543ca6e6e8e63eaac0224a84de69b67d0214aef4b961e9474574049aaf374e78721f0e03693e4d1ceb07c5e8008a632ee4882ddae973edd56059225658172572dbaa6f9eee2825eeab4b3f7189d6cc7fd5428e23a55c4d843a46a52d6a494da2213d844eb1d7bf035c103ee9bb87d7218b11e8208f2aa25a436fe436fbefb18e6596a6786f3d55118207063ceb868366f2dcceab03743286181d4d5937b969bba9b98c29792217dcf01ab51a14b9834d07eb4daae0ea73757bf2419425fac6c695bd88f38b3a546582369255212371fa7466d4607ad4ab1f46e9b4a74d27b1110ccb1e1bcdba65bc1fb6f927fc144f8afd31a208854a61a5f0c0ade394cac9d97c6600eb2bccfff2321d983e7e4b9627f769d4e49e7d01efe7712bb89643bb4c3c435c92bc45a25fc1b884dda77a42f2ec1e21b22b9159b9cb453cde0e5a301f3bf8d85844bf81225dbe1767d75d881be2e636eaa6450bc5f3eed4a8453f7c3632c9eef82a2e0f27c1b34187c047622d6f7f4f5d240138861be413ca74911e7d0558c2d92d5d9e9989828364fb3aaddd143a5fdea53d5e4508c5a307bec84753e5d09370a3e807c3043467a01ee5cd2f855236eb2903f1f126a32d3540db886fa679e73417bdd61f9196d302e45777d0676ab26183c5d9984485a2d2e0c4cca536b76dce3cc8bdade92dad936f58f6c62e8c9a400e0541d40ea1f1658d31a5a94037b320acf85c9b544628bed424214739d0fe52bd2b3ee52305ef9c69fb8fbde13598b09eee99c0c2485a46478d3e6458696f09f3ae5bb60b5895c7a68b8c08f5e68ad1bfd6f64d8e9769c92ce5a8362c27a14fea013aaba724a13a84715cae254748e4f6811d1877f714808d46871893831af89489765d9cc25a7b9272ecf2f20044f92aed2d0c7645d681fbc72e8c980cfd1c733bc519604bfb4feca7779b6b358d277484b63959ec3a4fd86e9c12d4bbe8853461a05d32df6399257df2f2599d65b5b0873e3d24d9f02ed87cc28e6152929b4f920550abe4086dfffe9f306b66b9398545c4a84a46738582bde79c411f8844eb53d5136911fbaadd907d64266144b716dbe3d6b9c240e0e7457e4f1d9c3de0bcd492653f98e18bd47b46132c78ce6504442e629dd6795ea064477d5cedb8f280b3f5d94587991400905e089d9346a063f7b8674fe9ee640a49b0626ee183106da7606575ec9e845a0f998c8e3946bd8721cfd06bbca7870b0e4bacdfa7bf49532c42976b1f87e87b6310907da874f448543dc628aa991053df6608752c2d3b1e3bd1c2ec1c5f04a0cd56f4d615a11af5db83229a103d4581a24b8e846c01c9eba5784ef6ddd165ed553c37041f9ca7c49e6c26ef9432c7514e96983a22b49d28dca8ac8a6d6488c5009e13773c42175dd68afdde5d4e380ddf6e8d69b21827201ed24c34139a571c322f78a3e1f9bdb5000f5674b24c2ef0069a8bce1f0504591b0ae81f060861cb6d4586aa7b16549dcea5219c329b9779d9aa3ba27767f04eea1411e602fc265ea77b5bbd861fb70201bb1c861cc51240195685454366ed9d07ef4a289156c7e6fb8c2454d222572a1fe0bbfbb7916f9e6705ab9f6ec40fd180a04d7d3417ac3eb0dc5ce92b29c559702bf12b922552582e1cdf68fa6207abf5ef763d8168cd372f0c1ad75ff40daa90af561aa00c40bd102fa1b0f0ce80befbb96845ad7d1d7e765076588fde40dad6b83a8cf9dda68926a637e86422a5b6f038630f50f72f0887517100c6f2e1f45cab53018145a2036632c4dd46dbb47c4c3aaa3ab73ff1f5882e034e51c86cf042bf95ceceb5f3d9a305b5993e036deb6e1aedddb9fa55ed76d847090ee72c56bd0f055a05f37d1a621409e9ea927ffb670610e09edc5fb Hey, password is required here."},{"title":"火焰图flamegraph","date":"2023-01-20T17:52:34.000Z","path":"posts/38ZNGYA/","text":"什么是火焰图? 首先火焰图, 是一个性能排查的可视化工具, 广泛应用于函数调用开销、内存占用统计等… 如图长这样: 它有两大优势: 1.可以看清调用关系, 2. 一眼看出性能瓶颈; 如何生成火焰图? 常见的开源flamegraph都是基于perf结果解析出的svg, 再用浏览器打开就是火焰图了; 比较经典的开源是这个: 🔥 brendangregg/FlameGraph 但比较可惜,作者基于Perl写的,windows下运行比较麻烦(因为你得为每台工作机装个perl环境吧…) 推荐一个在线看火焰图的网站: speedscope.app"},{"title":"C++ 汇总","date":"2023-01-01T11:00:38.000Z","path":"posts/2N6K1PK/","text":"语言特性 时间 特性 C++98 1998 the original standard C++11 2011 almost a new language C++14 2014 some improvements C++17 2017 new features & library extensions C++20 2020 game-changing new features & libraries ISO Standard C++ since Got nullptr c++11 ✔ constexpr c++11 ✔ auto c++11 ✔ decltype c++11 decltype(auto) c++14 lambda c++11 ✔ shared_ptr c++11 unique_ptr c++11 weak_ptr c++11 make_unique c++14 constexpr 阅读材料: cppreference: constexpr Microsoft: constexpr constexpr和const有什么区别? stackoverflow const simply means that value cannot be changed constexpr creates a compile-time constant 核心在于, constexpr在编译期就已经完成值计算. compile flag 阅读资料:Important GCC Flags in Linux,Gcc Option-Summary compile flags,使用合理的编译选项来让你的程序更健壮和友好,掌握如下重点: -Wall:打印所有的 warning -Werror:所有 warning 当做 error 处理 -o:指定输出文件 -l:链接共享库,如-lpthread -g:编译包含debug信息 -std=c++11:指定C++标准 -O[0|g|1|2|s|3]:优化级别,-O0不含任何优化 Value or Reference? 引用自《C++ Templates》,总体说使用类对象时传递值,使用基础类型时传递引用。 quote Modern C++ Tutorial https://changkun.de/modern-cpp/en-us/01-intro/ cpphacking https://hackingcpp.com/index.html 强推这个网站 fmt库 https://github.com/fmtlib/fmt 基于C++17的python解释器 https://github.com/blueloveTH/pocketpy/blob/main/src/main.cpp 搭配这个看, python写的py解释器 https://aosabook.org/en/500L/a-python-interpreter-written-in-python.html 工具 学习 Makefile 学习 cmake 学习 编译选项"},{"title":"【Linux】命令行大全","date":"2022-05-31T15:57:22.000Z","path":"posts/2G7VBZ2/","text":"记录常用的Linux指令、踩坑经历 导读 查阅 man 手册是最根本的办法,但某些用法要牢记! The art of command line explain shell,解释任意指令 重定向 Linux 的宗旨是 everythin is file,对于进程、IO、设备等,都是以文件的形式存在,这些文件都可以用文件描述符(file descriptor)来表示。 Linux 默认有三个文件描述符,你可以始终默认它们是存在的: stdin(0):可以理解为键盘设备的输入信号 stdout(1):标准输出 stderr(2):标准错误 符号 重定向操作的格式: shell命令 + 重定向符号 + <文件、文件描述符、设备> “>” 默认重定向 stdout,会覆盖原内容 cmd > file,等价于 cmd 1 > file “>>” 是追加,不会覆盖 “<” 重定向 stdin,可以从输入设备、文件内读取 wc -l < file,会统计文件的行数 “>&” 符号实现 stdout、stderr 之间的重新绑定。 cmd 1>&2,会将所有 stdout 输出到 stderr 忽略 /dev/null 是linux下的一个device,相当于黑洞,任何输出重定向到这里都会被吞掉(即忽略)。 cmd > /dev/null,会忽略所有的stdout,但会保留stderr,想要忽略任何输出,可以使用 cmd 1>/dev/null 2>&1 pipe管道 管道(“|”) 和 “>” 的区别在于,它仅仅支持将一个(前面)指令的 stdout 传递给 下一个指令。 cat file | grep luhao,即将file的内容传递给grep,以匹配luhao字段 理解pipe:将上一个指令的stdout,重定向到下一个指令的stdin 阅读材料:linux管道和重定向区别 here document command << EOF document EOF here document 是 linux 一种特殊的重定向方式,它会将两个EOF之间的字符重定向给 command(或者一个程序)。 一种常见的用法是,结合 cat 实现多行字符的输出。 xargs 前面说到 管道 可以在两个指令间传递信息,但很遗憾的是一些指令并不支持管道,例如 echo。 这个时候就需要 xargs (eXtended ARGuments)发挥作用! xargs [-options] [command] xargs 本质是 将stdin转化为command的参数(结合管道使用) $ xargs find -name 回车后会等待用户输入,然后按下 ctrl+d,会执行 find -name [input] $ echo \"test\" | xargs mkdir 实际上等价于 mkdir test,如果不加xargs会报错 xargs -d xargs默认根据 “空格” 进行分割,可以用参数 -d 调整其分割策略 echo \"one two three\" | xargs mkdir 实际会创建名为one, two, three的三个目录 echo -e \"a\\tb\\t\\c\" | xargs -d \"\\t\" 即按照\\t分割,实际输出a b c NOTE -p:解析完参数,请求用户确认 (当你不确定时请用 -p) -t:打印出参数,直接执行 xargs + find 这两个指令搭配好为例无穷,举个例子,搜索目录下所有 *.txt 中是否包含 luhao 的字符: find . -name \"*.txt\" | xargs grep \"abc\" ✔ find . -name \"*.txt\" | grep \"abc\" ❌ 再例如,删除所有以 test 开头的文件:(-print0是处理文件名含空格) find . -name \"test* -print0\" | xargs -0 rm 高级应用,统计所有 .cpp 文件的代码行数: find ./source -type f -name \"*.cpp\" | xargs wc -l 管道和xargs区别? 管道 将上一个stdout一股脑丢到stdin执行 xargs 会将上一个stdout根据空格等分离,再一个一个丢进来执行 匹配 linux下有两种匹配规则,分别是 RE正则表达式 和 Glob通配符。 Glob一般在shell指令下使用较多,因为较多的指令不支持正则表达式。例如 ls、find、sed… regex 正则表达式 glob 通配符 linux关于匹配的指令也有多种,使用最多的是 find、grep… 推荐查阅man手册来学习 grep grep 会将匹配的部分高亮输出到 stdout,需要牢记如下几个参数: -i:ignore-case -o:only-matching,只打印完全匹配的部分 -v:invert-match,只输出“不匹配” find TODO:用到时候再来学习 watch linux cat 指令会将文件所有的内容输出到 stdout,当文件很大的时候会占用很大的缓冲区,并造成卡顿和等待。因此学会如下几个指令: less:打印部分,借助vim指令上下滚动 head:打印头部 tail:打印尾部 可选参数常用的有两种: -f:follow模式,当文件增长时会刷新stdout -n:打印指定的行数 监测日志时,使用 tail -f *.log | grep xxx 非常有帮助 curl curl 是用来请求web服务器的命令行工具,其名字是 client & url 的结合体。Postman跟它的作用类似,但是带有GUI界面。 curl https://www.google.com/:无任何参数的curl,就是调用一次 GET 请求。 -d:发送 POST 请求 --head:发送 HEAD 请求 -o:将服务器的返回保存为文件,等同于 wget -X:指定HTTP请求方法,如 curl -X POST ... apt-get debian如何换国内源? sudo sed -i -e 's/deb.debian.org/mirrors.aliyun.com/g' -e 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list sudo apt-get update 分割线,下面内容要整理重写 port Unix: lsof: 打印所有端口的占用情况 lsof -i:4000: 查看占用4000端口的进程 Windows: netstat -ano | findstr :6000: 查看占用6000端口的进程 taskkill /PID 12345 /F: 杀死进程12345, /F表示强制关闭 sed: stream editor sed -i: 直接替换源文件 sed '4a newline' [file]: 第4行新增 sed '1,5s/a/b/' [file]: 1~5行的a替换为b $: 表示末行 scp 上传/下载 scp [local file] [email protected]:/root: 上传到服务器 scp [email protected]:/root/ [local_path]: 从服务器下载到本地 scp -r: 文件夹操作 scp -P 32200 ...: 带端口的scp disk df -lh: 查看磁盘剩余空间 du -lh: 查看含目录的大小 rm -f *: 删除目录下所有文件 l --color: 彩色格式显示 ll: 显示详细信息, 如最后修改时间 ls | wc -l: 统计当前文件数, wc表示word count, -l是行数 nohup 看这篇cnblog就够了: nohup和&后台运行 compression zip unar xx.zip: 可以自动解码中文 vim xx.zip: 查看zip压缩文件结构 tar # .gz ------------------ tar -czf x.gz [file] tar -xzvf x.gz # .bz2 ------------------ tar -cjf x.bz2 [file] tar -xjvf x.bz2 # .rar ------------------ rar a x.rar [file] unrar e x.rar # .zip ------------------ zip x.zip [file] unzip x.zip shell脚本 如何写一个自己的脚本? xxx.sh: 如/luhao/bin/demo.sh ln -s /luhao/bin/demo.sh /usr/bin/demo: 加入环境变量, 可以直接demo访问 自定义start, end, restart function start() { } case \"$1\" in start) start ;; *) esac ps, kill, pkill 进程相关 ps -e: 所有进程 ps -u: 按用户打印进程 ps -x: 没有终端的进程, 后台python应用需要! kill -9 [pid]: 杀死对应pid的进程 pkill -f [pattern]: 杀死制定名称的进程 挂载云硬盘 fdisk -l: 查看新磁盘名称, 如/dev/vdb mount -t ext4 /dev/vdb /data: 将/dev/vdb挂载到/data目录 df -lh: 查看对应硬盘"},{"title":"《直击本质》书摘","date":"2022-04-23T16:54:46.000Z","path":"posts/3819NVV/","text":"概要 《直击本质:洞察事物底层逻辑的思考方法》,专注于“思考方法”的书 本文旨在记录书中有共鸣、有感触的语句 理解事物本质 思考事物的根本属性,是学一样东西、论述一个原理、解决一个问题、判断一个趋势的基础; 当你做到以下中的任何一件,你就理解了事物的本质: 给出清晰的定义 “商品”是用来交换的劳动产品; 做出简单、准确的类比 “谈判”就是“找交集” 切忌用一件晦涩的事物去解释另一件事物,容易将简单的问题复杂化; 升维思考 跳出眼前问题的限制与常规解法,通过层级、时间、视角、边界、位置、结构的变换,重新思考问题及其解决之道的思维方式 爱因斯坦:“我们不能用制造问题时的同一水平思维来解决问题。” 1. 层级思考法 上三层: 愿景层:人生使命,比如灭霸为了维护宇宙平衡; 身份层:我是谁,我想成为谁; 价值观层:好坏对错之分; 下三层: 能力层:技能、状态、策略; 行为层:具体的行动,每天做了什么? 环境层:学校、职场、家庭的环境影响 上层对下层拥有指导作用。当你懒得背单词(行为层),想象你想考上怎样的大学(身份层),背单词就有了驱动力,正是这个道理; 面对下层的现象,从上层介入思考会更直击本质。这也是为什么通过一个人的交友圈子(环境层)、言语交谈(行为层)和处事策略(能力层),你基本能推断出其三观与身份特征。 如果我们只在下层做出改变,而上层的意识没有变化,下层的改变常常是难以持久的。 2. 拉长时间轴 消除痛苦与烦恼。站在更远的时间线外看待当下,你会觉得,一切的痛苦都会随时间烟消云散。 3. 上帝视角 为什么你能教别人做事,却过不好自己的生活?居高临下,跳出剧本,才能拥有上帝视角(这我想起了罗永浩的\"大局观\") 上帝视角让我们摆脱自我情绪和感受的束缚; 上帝视角利于拥有全局观、大局观; 普鲁斯特:“真正的发现之旅不在于发现新风景,而在于获得新视角。” 4. 第三选择 我们经常陷入非黑即白、非此即彼的二选一思维模式,即“点状思维”。以至于条件反射般地将事物一分为二或两极对立:男人与女人,原告与被告、资本家与工人等等… 只有机器人才会只拥有0和1两种选择,试着寻找“第三选择” 5. 无边界思考 哲学家卡斯将人类所有活动比作两类游戏: 有限游戏:制定一系列机制与规则(如政治、法律),以取胜为目的(如战争、官司); 无限游戏:没有任何规则,以延续为目的,拥有无限可能与结局(如文化、艺术); 我们总是习惯性给自己限定框架,进行“有限游戏”。如“我只是大专毕业,怎么找得到好工作”;如“我是女生,我一定会受到歧视”;一切身份限制、性别限制、输赢限制等等,都在无形中束缚了我们的发展。 所谓无边界思考,即打破人生得到种种限制。关键包括三点:消除时空边界、消除角色边界、修改规则"},{"title":"VPN搭建一条龙","date":"2022-03-12T17:28:31.000Z","path":"posts/2R7AP5Z/","text":"Windows/Mac/IOS/Android通用的VPN搭建教程,收费的 1. Shadowrocket安装 美区App Store利用 $2.99 购买 shadowrocket app 🔥 注册美区 Apple ID 帐号的终极指南 美区app如何充值?移步万能的淘宝,搜索AppStore美区礼品卡,买$3的就行 2. shadowsocket配置 认准这个网站: 🔥 v2ww.com 里面有详细的Shadowrocket配置教程,和梯子价格清单,🔥建议购买月¥9.9 的10GB套餐"},{"title":"游戏记录:ARK方舟生存进化","date":"2022-02-20T14:21:16.000Z","path":"posts/160HQCD/","text":"概要 方舟:生存进化是一款开放世界游戏; 方舟听宇网 方舟资料网站(英文) 说几句 这个游戏晕死我了,两个月来玩了200小时,主要是孤岛和焦土DLC。 基本玩两小时就恶心发晕,我就是个菜鸡。 玩了100小时才知道这游戏有主线和BOSS,不过感觉没啥意思。 觉得最大乐趣是探图、建家、抓恐龙,并乐此不疲,像是虚拟世界的一个家园,一片净土吧… DLC介绍 游戏包含付费DLC和免费DLC, 按照时间顺序: 付费DLC 孤岛(The Island)★ 焦土(Scorched Earth) 畸变(Aberration) 灭绝(Extinction) 创世纪一二(Genesis) 免费DLC 中心岛(The Center) 仙境(Ragnarok)★ 瓦尔盖罗(Valguero) 水晶岛(Crystal Isies) 迷失岛(Lost Island) 本体+全dlc安装,硬盘空间 400G 闪退白框修复 【方舟问题】解决方舟所有白框问题。网络问题,驱动问题,地图问题 方舟这个破游戏经常会各种UE的Crash,本人遇到最多的是 NetConnection 的闪退,俗称 4096。 搜寻网上各种外行和玄学的解决方案,没有一个百分百有效的。 总结如下: 使用UU加速器(没用) 插拔路由器(没试过) 清空所有浏览器缓存(没用) 修改本机配置 ...\\steam\\steamapps\\common\\ARK\\ShooterGame\\Saved\\Config\\WindowsNoEditor 如何修改恐龙等级、刷新规则? GameUserSettings.ini: 修改恐龙等级、刷新规则等 可以订阅这个调等级的MOD: Custom Dino Levels 配置文档 其中官方设置的难度表示一个level的等级间隔(如5表示5级), MinLevel=30 表示最小等级限制为 难度 x MinLvel # GameUserSettings.ini [CustomLevelDistrib] MinLevel=15.000000 MaxLevel=30.000000 WantsEqualLevels=True # 刷新概率 TinyWeight=1.000000 # 5-25 LowWeight=0.3500000 # 30-60 MediumWeight=0.250000 # 65-100 HighWeight=0.250000 # 105-150 重置地图请删除: ARK\\ShooterGame\\Saved\\LocalProfiles 方舟私服 如果在本机玩,方舟就是个单机游戏,想要和别的玩家一起玩,就得找服务器。(推荐去听宇网找) 一般服务器的介绍是 “PVE15通 采3驯10…” 分PVE/PVP两种服务器 15通: 表示共有15个地图可以互通 采n驯m: 表示游戏加速的倍率 实用MOD MODs 作用 Awesome SpyGlass A+镜,观察恐龙的等级、晕眩度、加点等 Ultra Stacks 可以堆叠物品,节省背包的容量! Automatic Death Recovery 死亡物品不掉路! Pet Finder 恐龙丢了用这个找! Kraken’s Better Dinos 修改刷新恐龙的等级、比例 Upgrade Station 可以升级鞍具、工具的工作台! Baby Premium Care 100%留痕药、飞龙奶等! 游戏资源推荐 B站 老司机hot《方舟生存进化》 游戏配置(备忘)"},{"title":"TypeScript概览","date":"2022-01-03T14:55:04.000Z","path":"posts/286HTVQ/","text":"Note 在此之前,先看看什么是JavaScript? JavaScript 中文介绍 | What is JavaScript? TypeScript其实就是Typed JavaScript at Any Scale. ts基本特性 特性 解释 Coding Staticlly Typed 静态类型 let foo = 1; foo.split(' '); => 编译报错 Weakly Typed 允许隐式类型转化 console.log(1 + '1'); => 打印11 Type Annotations 允许类型注释 let foo: string = '1'; import, export Import statements in TypeScript: which syntax to use 代码 解释 import * as path from 'path commonjs模块 import fs = require('fs') commonjs模块, export=xx const xx = require('xx) 无类型声明 var、let、const TypeScript - Variable 使用场景 var 全局作用域 let 块作用域 const 块作用域、只读 function 下面是一个求和函数的示例: function add(a: number, b: number): number { return a + b; } class 下面是一个类声明的示例: class Point { // 成员变量, 可用readonly, 不可用const x: number; y: number; // 构造函数 constructor(x = 0, y = 0) { this.x = x; this.y = y; } // 成员方法 getScale(): number { return this.x + this.y; } } // 实例化一个类 var point = new Point(1, 2) 参考 TypeScript官网 一个中文的TypeScript入门教程"},{"title":"【Python】优雅输出","date":"2021-12-16T14:17:59.000Z","path":"posts/1MJH73F/","text":"前提 Linux命令下, 见识了如gcc, apt, pip之流的优雅输出; 这篇博客想仿照之,借Python实现一些优美的输出与交互; 何为优雅? 鄙人低俗, 仅想从样式与外在谈论优雅 清晰的输出、高亮的样式… 这些就是开源软件的优雅! 1. pip 2. oh-my-zsh 3. VScode Extension 4. ipython 何为优雅? 彩色文字区分 如SUCCEED,ERROR,都应该有专门的色彩,起到警示和强调的作用; 进度条 对于耗时较长的程序,应该打印一个进度条,让用户对完成进度有一个直观的概念; 条例清晰的LOG 每一个指令,都该包含指引(-h)、运行中和运行结果的Log提示; 二、ANSI实现丰富的终端样式 ANSI escape code ANSI是一种能想终端打印彩色输出的标准编码,你可以理解为富文本! 这是一段python示例代码,我们需要关注0, 30, 42这三个数值: print '\\033[0;30;42m' + '...' + '\\033[0m' 内容 图示 作用 Style 0 字体样式 Bgcolor 30 背景色 Color 42 字色 下图罗列了一些字色和背景色: 1. Style 下面是常用的字体样式 作用 编码 示例 Normal \\033[0m Hello World Bold \\033[1m Hello World Underlined \\033[4m Hello World Changing \\033[5m Delete \\033[9m Hello World 2. Color todo下面汇总一些常用颜色 颜色 编码 示例 black \\033[30m Hello World red \\033[31m Hello World green \\033[32m Hello World yellow \\033[33m Hello World blue \\033[34m Hello World white \\033[37m Hello World 3. Cursor 如何控制箭头(终端的闪烁符)移动? 下面{n}中表示移动的单位数 作用 编码 备注 Up \\033[{n}A 上移箭头 Down \\033[{n}B 下移箭头 Right \\033[{n}C 右移箭头 Left \\033[{n}D 左移箭头 Next Line \\033[{n}E 移到下一行 Prev Line \\033[{n}F 移到上一行 Set Column \\033[{n}G 第n列 Set Position \\033[{n};{m}H 第n行m列 Clear Screen \\033[{n}J 清屏 Clear Line \\033[{n}K 清行 对于清屏和清行: J = 0: 清除光标到末尾(行位) J = 1: 清除光标到开头(行首) J = 2: 清除整个屏幕(整行) 我们可以借此实现一个百分比的进度更新: for i in range(0, 100): # 10秒跑完 time.sleep(0.1) sys.stdout.write(u\"\\u001b[100D\" + str(i + 1) + \"%\") sys.stdout.flush() 下面模拟一个实时滚动的进度条,且只停留在最底栏 n = 10 for i in range(n): sys.stdout.write(u\"\\033[-1;0H\") sys.stdout.write(u'\\033[2K') sys.stdout.write(u'\\033[100D') print i print 'Processing: [' + '#' * i + '.' * (n - 1 - i) + ']' + ' %d%%' % (i+1), sleep(1) 参考 ANSI escape code How to print colored text to the terminal"},{"title":"【Python】性能分析","date":"2021-12-14T15:31:37.000Z","path":"posts/2NFFW5M/","text":"NOTE推荐最快的Profile方法: python -m cProfile -o tmp.prof xxx.py snakeviz tmp.prof Profiler工具原理 性能分析的原理是什么?有哪些常用的profile方法呢? 1. Instrumentation (插装) 这应该是最容易理解的方式,看下面代码 import time t1 = time.time() # do something t2 = time.time() cost_time = t2 - t1 # s 插装的本质就是:统计运行时间或次数 常见方法: 1.首尾手动记录时间/次数… 2.profile工具自动插入源码; 3.profile工具修改汇编码/中间码; 海森堡Bug 由于修改了源码,插装有概率会使程序发生不一样的结果,尤其是多进程的情况下; 2. Sampling (采样) 每隔一段时间中断进程,获取当前堆栈信息,并从统计学上估计每段代码的运行时间; Visual Studio Profiler 就是基于采样实现 几乎大部分Profiler都是采样实现,因为它有如下几个优点: 1.不需要修改代码,不需要重新编译; 2.可以动态修改中断的interval,控制采样精度和开销的平衡; 3.对程序的性能影响小,结果可靠; Python Profiler 下面介绍几个常用的python profile库: [1. cProfile](https://docs.python.org/2.7/library/profile.html) 这是一段官方文档示例 import cProfile, pstats, StringIO pr = cProfile.Profile() pr.enable() # ... do something ... pr.disable() s = StringIO.StringIO() sortby = 'cumulative' ps = pstats.Stats(pr, stream=s).sort_stats(sortby) ps.print_stats() print s.getvalue() 下面是cProfile的结果,很清晰: totime:函数本身花费时间 cumtime:函数累计花费时间(包含子函数递归) [2. snakeviz](https://jiffyclub.github.io/snakeviz/) snakeviz其实是解析了cProfile的结果,做了个网页端的可视化界面; snakeviz使用步骤: 先使用cProfile生成prof文件: python -m cProfile -o res.prof demo.py 再解析prof生成网页: snakeviz res.prof 得到如上精美的profiler火焰图; Profile注意点 1. 多进程 不要把cProfile放在父进程,不然profile结果只有acquire; 这是因为父进程只起了调度和fork的作用,要在子进程中 profile; 🔥 Stackoverflow: 90% of the time is spent on method ‘acquire’ of ‘thread.lock’ objects 参考资料 CPU性能分析工具原理 How can you profile a Python script? Python程序性能分析 穷人的程序性能分析器"},{"title":"【Python】多进程multiprocess","date":"2021-12-12T06:52:20.000Z","path":"posts/2A7ENQS/","text":"概要 多进程(并发)是现代计算机的基本组成之一; 介绍了进程、线程、协程是什么,以及相互关系; 1. 一篇CSDN博客 这是我大学操作系统期间写的一篇CSDN博客,看到有3w+访问和700+的收藏,这是我意想不到的; 回头看这篇文章,几乎没有专业词汇,而是用类比的方式解释了进程相关的基础知识; 摘取片段如下: ===================================================== 单CPU:一台单核处理器计算机 = 一个车间; 多CPU:一台多核处理器计算机 = 一座工厂; 进程:一个车间 = 一个进程; (即一个运行的程序) 多进程:一座工厂可以同时运行多个车间; CPU和进程:单CPU只能同时运行单个进程,多CPU可以同时运行多个进程。 线程:车间内一个工人 = 一个线程; 进程与线程关系:一个进程可以包括多个线程。 ===================================================== 什么是进程 (Process) A process is a program that is running on your computer 单核机器为什么给人可以同时处理一堆程序 的错觉?似乎和多核差不多? 如下图,这些任务其实是依次执行,这叫并发 (concurrency); 如下图,对于多CPU机器,很多任务可以同时执行,这叫并行 (parallelism); 进程间通信 (Process Communication) 进程间是互相独立,不共享任何数据的,那么跨进程如何交互呢? 🔥 What is Interprocess Communication? 进程通信的例子 linux grep、windows任务管理器... 什么是线程 (thread) 🔥What is the difference between a process and a thread? 先问一个问题:为什么需要线程,只靠进程不够使用吗? 假如OS中只有进程... 处理input的进程,每当用户敲击键盘,系统就要给出相应处理; 想象当你以飞快的速度敲击作输入,而进程又处理不过来呢,电脑是不是就巨卡无比? 多线程的意义:某个任务会阻塞,但又不希望影响到别的任务进行下去; 如图所示: 线程存在于进程内,且多线程共享进程内存; 多线程间有自己的寄存器和堆栈,互不影响; 下图是windows的任务管理器,可以清楚得看到进程与线程之间的从属关系! 什么是协程 (coroutinue) 通俗讲,协程是对线程的一种更小粒度的划分 (这里就不多介绍了) 扩展阅读 借助 C setjmp.h 实现简单的协程 2. Python? 写到这里突然扪心自问,这与python有何关系😅? 本来是使用Python2的multiprocessing和subprocess踩了坑,想写篇文章记录下; 但随着白天查阅资料,大多问题已然解决;💪 等以后有缘再来补上吧 参考资料 进程和线程的深入理解 What is Interprocess Communication? Threads What is the difference between a process and a thread? What is a coroutine? Building Coroutines"},{"title":"资源汇总帖(效率、专业知识)","date":"2021-12-06T16:06:30.000Z","path":"posts/FYK74B/","text":"效率工具 Listary 全局文件搜索,类似于everything Snipaste 截图神器,支持桌面悬浮、历史记录 Directory Opus Windows资源管理器 神器! Beyond Compare 代码比对神器,看Diff Traffic Monitor CPU、内存监视器 网页工具 jsoncrack json在线可视化 godbolt 在线看python、cpp汇编 speedscope 在线生成火焰图 m3u8-downloader 网页m3u8视频下载 专业书籍 《汇编语言》(王爽) 交叉:计算机系统架构 + 汇编 + Unix操作系统 《Data Abstraction and Problem Solving With C++》 交叉:算法/数据结构 + C++11语言特性 + 设计模式与思想"},{"title":"Windows Batch脚本","date":"2021-12-06T14:48:15.000Z","path":"posts/2R1N3XK/","text":"概要 Batch(批处理)是 Microsoft 提供的一门基于cmd.exe解释器的脚本语言; 客观讲很不好用,但是windows下开发尝尝难以避免; Batch常用语法 🔥 传送门: Windows Batch Scripting 1. 基本指令 语法 作用 示例 echo 输出字符 echo hello world rem 注释 rem 这是一条注释 pause 暂停命令 pause call 调用指定bat call demo.bat start 调用exe start demo.exe goto 跳转到:xx goto 1 :1 call demo.bat set 设置变量 set path=C:\\ @ 屏蔽回显 @echo off > 重定向 echo test > a.txt | 管道,前面的输出重定向到后面的命令 echo /Y | start demo.exe 2. 高级语法 1. 判断某个路径是否存在 if exist C:\\Python27\\python.exe ( ... ) 2. 寻找python的sys path路径 for /f %%p in ('where python') do( set PY=%%p exit ) 3. 关闭batch指令的打印(回显) @echo off 4. 利用管道回到命令行的输入请求 可以将/Y作为输出传给后面的指令 echo /Y | xxxxx 3. 建议 Windows Batch不是很好写,建议用Python脚本或Linux Shell…"},{"title":"Regex:正则表达式","date":"2021-12-02T15:00:24.000Z","path":"posts/3SAT4K7/","text":"NOTEregex:regular expressions 本文介绍了正则表达式的基本常用语法 这篇文章有一些进阶regex应用 【vscode插件】function decoration Good Resources 下面是一些介绍Regex的网站: 这些内容很优秀,我写博客仅仅是为了自我记录、及筛选对自己有用的部分; learn-regex (github) Regex Online test RegexOne RegexLearn Regex在线调试: regex101 推荐一个在线Regex做题的网站,难度很高! https://alf.nu/RegexGolf 这是一个在线regex测试的网站: https://tool.oschina.net/regex/ Regex Grammar 1.完全匹配 1.区分大小写、空格等,严格匹配每个字母的顺序; 2.实际用(xyz)表示完全匹配; (The) => The fat cat sat on the mat. 2.基本字符 字符 用法 示例 . 匹配换行符外的任意字符 .ar => The car parked in the garage. [] 匹配[]内的任意字符 [Tt]he => The car parked in the garage. ^ 反义符 [^c]ar => The car parked in the garage. * 匹配$\\geq0$个字符 [a-z]* => The car parked in the garage #21. + 匹配$\\geq1$个字符 c.+t => The fat cat sat on the mat. ? ?之前的为可选 [T]?he => The car is parked in the garage. {n, m} 匹配$n\\leq k\\leq m$的重复次数 [0-9]{3} => The number was 9.9997 but we rounded it off to 10.0. (xyz) 完全匹配 the => The fat cat sat on the mat. A|B 匹配A或B字符 (c|g|p)ar => The car is parked in the garage. \\ 转义,表示[]().?等 \\. => a.b 3.特殊匹配 字符 用法 等同于 \\w 所有数字or字母 [a-zA-Z0-9_] \\W 所有非数字or字母 [^\\w] \\d 所有数字 [0-9] \\D 所有非数字 [^\\d] \\s 所有空白字符 [\\t\\n\\f\\r\\p{Z}] \\S 所有非空白字符 [^\\s] Regex进阶 /g /m /i: Flags Flag Description i Case insensitive: 忽略大小写 g Global Search:全局匹配 m Multiline: 匹配多行 ?: lazy matching 默认的正则会匹配尽可能多满足条件的字符 (greedy matching) 通过使用?, 可以在第一次匹配到的时候即停止 (lazy matching) MODE grammar result normal /(.*at)/ The fat cat sat on the mat lazy /(.*?at)/ The fat cat sat on the mat"},{"title":"Depth testing","date":"2021-11-30T14:37:50.000Z","path":"posts/2M66594/","text":"概要 深度测试,决定了投影空间下的物体是否被遮挡,即是否需要绘制; 一些关键的技术,离不开精确的深度测试结果; Depth-test and Depth-buffer 先阅读这篇文章: 🔥 Occlusion: 可见性与剔除 depth buffer (z-buffer) depth-buffer存储着每个pixel的深度信息(可以理解为一个0-1范围的float值); 而depth-test就是写深度(write depth-buffer)的过程 depth-test是在pixel shader后进行 为什么是这样的次序? 如果在pixel shader之前做深度测试,有什么后果呢? OpenGL Code glEnable OpenGL是默认关闭深度测试的,因此需要手动开启它; 这句标志着,depth-buffer是可写的,且会在每一帧不断写入它; glEnable(GL_DEPTH_TEST); glClear 每一帧绘制完,需要手动清空这一帧的color-buffer和depth-buffer; 不然会残留上一帧的深度数据,导致显示Bug; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDepthMask 当某一帧不想写深度了怎么办? 它用来标记depth-buffer是只读,还是可写; glDepthMask(GL_FALSE); glDepthFunc 深度测试,会将每个pixel新的深度信息,与旧的比较,如果满足条件则通过(写入并覆盖旧值); 下面一些参数是不同的通过规则: Function Desc GF_ALWAYS 永不通过,后画的在前面,与远近无关 GL_NEVER 与上面相反 GL_LESS ✔ 小值通过 GL_EQUAL 等于的通过 GL_GREATER 大于的通过,远的反倒画在前面 … … Depth precision z-value,也就是深度值,是怎么计算的呢? 线性方案: $$Z = \\frac {z - near} {far - near}, Z \\in [0, 1]$$ 缺点: 近处和远处的depth,拥有一样的精度; 是不是很浪费?游戏中我们更关注靠近相机的物体! 非线性方案: 不如把$z$取个倒数,这样是不是近处的精度就更大了呢? $$Z = \\frac {1 / z - 1 / near} {1 / far - 1 / near}, Z \\in [0, 1]$$ Debug输出z-buffer TODO 调试输出,需要转化到NDC空间,感受下z-buffer的变化 多截图,多说明 Z-fighting 这是一个常见的artifact:当两个三角面离得足够近(coplane)时,做深度测试时由于精度问题,导致两个三角面的计算结果总是反复横跳的,表现上是奇怪的锯齿花纹: 解决方案 避免物体摆放过近; 增大近裁面的值,相当于提高了整体的z精度; 提高z-buffer的精度,比如从24bit -> 32bit,但是带宽消耗也变大了; 参考资料 Depth-testing Depth Buffer and Z-fighting"},{"title":"Model and Mesh","date":"2021-11-28T10:52:52.000Z","path":"posts/2WF6HFQ/","text":"概要 实际的游戏开发中,(模型)美术会制作fbx,max等资源给程序使用; 因此我们要讨论如何导入,以及如何渲染这些3D模型 3D Formats 美术使用的3D软件很多,如Blender、3DMax、Maya等等; 不同软件有自己的3D模型格式(虽然大都提供了互相导入导出),所以了解这些常见的格式很关键; 格式 特点 FBX 含动画、材质、骨骼等 glTF 含动画、材质、骨骼等几乎所有信息 OBJ 仅模型和材质信息 FBX .FBX 是MotionBuilder使用的格式,它是一个动画制作平台; FBX支持动画、材质、贴图、骨骼、灯光、摄像机等信息; FBX支持法线和贴图坐标,可以写入贴图路径; Wikipedia: FBX glTF .gltf 是OpenGL和Vulkan背后的3D图形标准组织Khronos所定义,它具有最标准与精简的格式,可谓3D模型的JPG; gltf支持动画、材质、场景、摄像机等几乎所有,甚至可以包含shader程序; gltf采用json的格式,因此可读性和兼容性很高; gltf可以编译成二进制:bgltf; wikipedia: GLTF OBJ .obj 是Wavefront公司推出的跨平台3D模型格式; OBJ是纯3D模型,不包含动画(区别FBX)、贴图路径、材质等信息; OBJ支持法线和贴图坐标,但需要手动制认贴图; Wikipedia: Wavefront.obj 这是一篇介绍OBJ的好文章: 🔥 Tutorial 7 : Model loading OBJ格式分析 这是一个简单的Cube的obj文件: # Desc xxx 这里都是注释 mtllib cube.mtl # mathlib或usemtl记录了使用的材质 # v是vertex v 1.000000 -1.000000 -1.000000 v 1.000000 -1.000000 1.000000 ... # vt是texture uv vt 0.748573 0.750412 vt 0.749279 0.501284 ... # vn是normal vn 0.000000 0.000000 -1.000000 vn -1.000000 -0.000000 -0.000000 ... # f是face, 描述每个三角面的v/vt/vn s off f 5/1/1 1/2/1 4/3/1 f 5/1/1 4/3/1 8/4/1 f(face)怎么理解 假设对于某一行:f 8/11/7 7/12/7 6/10/7: 8/11/7是三角面的第一个点; 7/12/7是三角面的第二个点; 6/10/7是三角面的第三个点; 对于每个三角面: 8是指第8个vertex,找对应的v; 11是指第11个vertex texture uv,找对应的vt; 7是指第7个vertex normal,找对应的vn; 以上利用了索引(indices)的思想,本质是用时间换空间,节省了内存! 动手尝试 这么看下来,是不是自己也可以写一个obj的解析器了呢? Mesh 既然Model是由很多Mesh组成,那么什么是Mesh? A mesh is a collection of vertices, edges and faces that define the shape of a 3D object. mesh是一些顶点、边、面的组合,它们定义了一个3D物体的形状. 我们通常用triangle mesh来代表物体,为什么? 几乎任何图形都可以用多个三角形表示,比如一个圆,可以是无数个三角形模拟而成; 所以三角面越多越复杂,往往意味着模型的面数越高、精度越高;(如下图) Assimp Open Asset Import Library: 一款支持多种模型格式导入与加载的标准库 下图是Assimp中一个model的架构: Scene scene包含一个RootNode指针,以及所有的Mesh和Material对象; RootNode RootNode对象中的Children[]中,递归包含了所有Model的指针; 它还包含了所有的Mesh对象; Mesh 什么是Mesh?它包含了所有Rendering需要的信息:vertex(顶点), normal(法线), texture(贴图), materials(材质)等… 问题汇总 “由于找不到assimp-vc140-mt.dll,无法继续执行代码。重新安装程序可能会解决此问题。” 参考资料 3D graphics file formats What is a mesh Tutorial 7 : Model loading"},{"title":"博客写作规范","date":"2021-11-27T08:56:28.000Z","path":"posts/docs/","text":"导读 使用push指令代替hexo,一键生成、发布、更新config 常用文章自己指定 abbrlink 较长文章开头写 思维导图 大纲 为什么写博客 📄 随时查阅的手册 ✏️ 锻炼写文档的能力 💪 收获感,满足感,正向推动学习的坚持 💡 反复の增、删、该,是一个逐渐 “加深理解” 的过程 🎉 博客网页的不断打磨,是一个满足 “强迫症” 的过程 虽然 www.luhao.wiki 公开了ip,但这是一个完全 “面向自己” 的博客 汇总一些优秀的个人博客: lianglianglee 👍 酷壳 - CoolShell BOT Man (Tecent) 云风的 Blog 博客特性 Config todo: true: 标记未完成, 文章首页显示 📌TODO top: xxx: 置顶的序号, 文章首页显示 ⭐置顶 thumbnail: /images/xxx.png: 首页的文章icon, 使用 100x100 的像素 password: xxx: 加密, 但愿不会被破解 Description 描述xxx<!-- more -->: 对应封面描述 当文章标题不够清晰时, 请在 “箭头处” 添加额外文字 Heading 文章内标题以 ## 开始, 样式会标注为棕红色 更低级的标题以 ###, **** 等开始 Latex latex失效看后面的问题汇总 $sin \\alpha + sin \\beta = 2 sin \\frac{\\alpha + \\beta}{2} cos \\frac{\\alpha + \\beta}{2}$ Code 要标注代码的语言, 才能正确开启高亮 Emoji 表情仅用作视觉区分 or 强调,不要滥用 ✔ ❌ 🔥 ⭐ 🎉 💡 警示框 Admonition 是一种基于html的警示样式, 规定语法如下: !!! NOTE/ERROR/TODO/WARNING title paragraph ... Admonition示例 This is a demo for admonition. 链接 基本的链接格式是: ,其中两个部分要严格按照规范: 图片 所有png、gif资源都放在 ./themes/pure/source/images/ 目录下,因此链接的格式为  文章锚点 所有markdown h1, h2, h3都会插入html锚点,链接格式为 [text](#html-id),注意最好填入 “html源码中的id值”,因为html源码会把空格、特殊字符等作一个替换 图表 其实是内嵌html文件, 示例如下: <iframe src=\"https://luhao.wiki/html/render.html\" 替换这里的html height=500 width=100% 修改高度即可 frameborder=0 scrolling=yes> 支持滚动条 </iframe> 视频 利用html语法内嵌视频 (资源来源于w3school) <video width=\"400\" controls><source src=\"https://www.w3schools.com/html/mov_bbb.mp4\" type=\"video/mp4\"></video> 更新日志 🎉 备注: html报错 Uncaught ReferenceError: $ is not defined? 确认 *.ejs 中填写的路径不要是绝对路径 错误: /images/a.png 正确: images/a.png 3月, 首页文章新增 thumbnail 缩略图 修改 themes/pure/layout/_partial/post/title.ejs,根据post对应属性来添加html样式 所有缩略图在 icons8 网站使用 100x100 的png图标 4月7日, 修复Latex失效问题 确保安装的包是 hexo-renderer-mathjax 需要将node_modules/hexo-renderer-mathjax/mathjax.html中的script替换为: <script src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML\"></script> 4月30日, 优化代码高亮样式 弃用 hexo原生的 highlight.js, 转投 google/code-prettify 的怀抱, 目前功能如下: 支持所有主流语言(带自动识别) 支持行号 字体编程友好 Consolas 准备将 inline-code 仍然沿用之前的 橙色加外边框 todo 参考:Hexo 接入google高亮的教程 5月5日, 集成hexo指令到 push 命令为 . push, 点号表示在当前路径运行 push 考虑添加 git 提交次数, git rev-list --all --count 6月4日,新增html 红色下划线样式 效果预览:int *p = *a 借助html代码实现: <u class=\"error\">text</u> 6月11日,主页左侧新增分类栏 实现方式为在 header.ejs 中遍历添加 site.categories 中的元素。 同时关闭了 “所有分类” 的按钮入口。 通过 _config.yml 的 enable_left_categories 字段控制开关 6月13日,上传图片脚本 windows下通过一个 push.bat(放到环境目录)实现png上传到对应hexo目录 同时将 id_rsa.pub 拷贝到 ~/.ssh/authroized_keys 下以避免密码输入 scp %1.png [email protected]:/root/hexo-workspace/themes/pure/source/images/%1.png hexo-blog-encrypt 加密文本 此加密插件会在首页的缩略文字加上 “Here's something encrypted, password is required to continue reading.” 的文字,需要修改如下代码去除: node_modules/hexo-blog-encrypt/index.js 将 data.excerpt = data.more = config.abstract; 修改为 data.more = config.abstract; 2023.6.18 迁移到新服务器 必要组件 zsh : 【Shell】oh-my-zsh Node.js: Node.js hexo: Hexo:轻量的博客框架 迁移服务 hexo 个人日志 ✔ apache 文件服务器 ✔ crontab ✔ 迁移ssh私钥和公钥 直接拷贝 id_rsa 和 id_rsa.pub 到新机器的对应目录下 ssh-add id_rsa 如果遇到报错,执行 chmod 400 ~/.ssh/id_rsa 支持思维导图 hexo-markmap Github: hexo-markmap npm install hexo-markmap --save 通过缩进控制归属层级,示例如下 {% markmap 300px %} # Testa ## test1 ## test2 # Testb ## test1 ## test2 {% endmarkmap %} 修复源码 hexo-markmap/lib/template.js 中自定义css的bug一处,配置的css路径为 /html/[email protected],主要是修改了字体样式 有空尝试另一种 minimap:https://hunterx.xyz/use-mindmap-in-hexo.html 分类页切为首页样式 修改 category.ejs,将生成html的代码直接替换为 index.ejs 同款; git commit 网页自动刷新 接入 browsersync 实现改动md后,网页预览自动刷新,提高开发效率 先全局安装 browser-sync ,再安装 hexo 插件 hexo-browsersync,然后 hexo s 即可使用kk。 npm install -g browser-sync npm install hexo-browsersync --save 文末自动留多个空行 为了编辑预览的时候,方便阅读,通过 ejs 在文末自动插入 8 个空行 themes/pure/layout/post.ejs"},{"title":"Colors and Materials","date":"2021-11-18T15:15:17.000Z","path":"posts/SZGATA/","text":"概要 自然界的颜色可以看做是由Ambient, Diffuse, Specular三种组成; 不同的材质(Materials),决定了颜色的不同显示性质; Colors 物体的颜色是如何产生的? 观察下图: 太阳光包含所有可见色(7种),当照射在物体上时(如红色),只能反射大部分的红光,如下图所示: 用公式表示简单的反射颜色: $reflectColor = lightColor * ObjectColor$ RGB颜色表示: $red = (1, 0, 0)$ $green = (0, 1, 0)$ $blue = (0, 0, 1)$ $black = (0, 0, 0)$ $white = (1, 1, 1)$ 为什么黑色是0, 白色是1? 从能量看:黑色能量最低,白色能量最高 从反射公式看:黑色不反射任何颜色(0),白色会反射任何颜色(1) Phong lighting model 了解了Colors原理,我们学习如何表示现实中的lighting; 三个组成: ambient + diffuse + specular Ambient 环境光: 各种复杂光源反射的颜色,通常表现在较暗的环境下; 通常会取一个环境光强度(ambientStrength),乘以光照强度,以计算ambient: $color = ambientStrenth * lightColor * ObjectColor$ Diffuse 漫反射光:直接光照反射的颜色,受光照角度的直接影响; 既然考虑角度,所以引入normal(法线)的概念:垂直于顶点所在平面的向量 当光垂直入射平面,反射最强,平行入射则最弱; $color = max(dot(normal, lightDir), 0.0) * lightColor * ObjectColor$ 怎么算顶点法线? todo Specular 高光:光泽表面反射的颜色, 如下图所示,当视角与光的反射角度越来越小时,我们看到的高光也会越来越强烈 (假设这是一面镜子); 显然:高光强度是受 光入射角 + 面法线 + 视角 共同影响的; 与Diffuse的计算有一点区别,我们使用pow来让越接近反射光线的区域,反射强度越大; $color = pow(max(dot(reflectDir, viewDir), 0.0), 32) * lightColor * ObjectColor$ 32的系数是什么? 用作pow的系数越大,表示高光影响的范围越小,高光强度也越大; 下图可以看到不同系数的效果; 最后如何叠加三种颜色呢? $color = ambient + diffuse + specular$ Materials 什么是材质? 自然界中,不同物体对光照的反应不同; 木头和水泥不会对光产生很强的反射,他们几乎没有高光;但水晶和镜子就会显得闪闪发光… 它们本质是各种反射属性和数值的组合,我们将它定义为Materials(材质) #version 330 core struct Material { vec3 ambient; vec3 diffuse; vec3 specular; float shininess; // pow的指数, 衡量高光区域 }; uniform Material material; 对应的,我们也可以维护灯光的简单属性: struct Light { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular; }; uniform Light light; 下面看一个使用材质和灯光属性的示例: // lighting properties lightingShader.setVec3(\"light.ambient\", 0.2f, 0.2f, 0.2f); lightingShader.setVec3(\"light.diffuse\", 0.5f, 0.5f, 0.5f); lightingShader.setVec3(\"light.specular\", 1.0f, 1.0f, 1.0f); // material properties lightingShader.setVec3(\"material.ambient\", 1.0f, 0.5f, 0.31f); lightingShader.setVec3(\"material.diffuse\", 1.0f, 0.5f, 0.31f); lightingShader.setVec3(\"material.specular\", 0.5f, 0.5f, 0.5f); // specular lighting doesn't have full effect on this object's material lightingShader.setFloat(\"material.shininess\", 32.0f); Lightmap 想象在实际工作中,我们怎么制定一个真实物体的Materials呢? 这就是美术同学的工作啦,在编辑器中指定diffuse、specular、normal等贴图,这样就赋予了它复杂而美丽的纹理; Shader Sampler shader提供了sampler2D,它能够读取各种格式的图片; struct Material { sampler2D diffuse; sampler2D specular; float shininess; }; in vec2 TexCoords; uniform Material material; 感受一下带了diffuse和specular贴图的效果: 参考资料 LearOpenGL-Colors LearOpenGL-Basic Lighting"},{"title":"第一次来家的小边牧","date":"2021-10-24T15:07:42.000Z","path":"posts/25374YZ/","text":"b3dc07a81f6459d120ce338ccca5504603780459e0306826441972681af56d62e5c8df8a8c8b786c064334936495b3bf83228f3b2ead8556cc3cb8048ae26847c8e914621b496b7004fe6882233afe537494e11018b92db83a2f4c03c19d166f76764fdb814b267512d2d6e4026cc811864d6b643ae3817aee6547cc32ead4dd496a2f28d62bb419f88839721b8233b941a1c7fb7d0f042ef8f4e8c92ddc75fd7b65eb068a403695b4afe49cd7c3aaae3b586f0c2fd99159ff9c09e3d550e0d4398482f1bc3384ef0639ddf258d4387a71b8fe757ff782efe3e5c904a3a214239fe86669fb0288138093a8a11f76fe549bb6d4725a5511f5cd0e62e1feaf3ad9203be23aa3a20c41b2618b75969ce09cc3f0726dec899707c307ed13b915305c78829934d65839666f8cb9f769033b153c57a5593fd8cf5853ebc6867b57d5484f868115fa0fd2c416d01a64e3417fa67f46425e35d6d87840951f6e5c2f2240032417879cdcfa7de182ff2b4ec9d177296ac6d46c3e6255786540bee4601cf6c1aa6e6e4ee909e0a01262a994e56907ee890acd0160386de31a58b0281898d3560e5ae1dd77cc3c1e7ea5dd1ec282fe4538cabcf086a2a5cb8bba9f6514e0840016b9223bf2a258f201496283c451623cac5bcf33b557593680cbc6a5bd08684285d45c65bcd850c034cc2fde8b0de9d0822eed3f5395294e1e7dd7c5ee374234e1d701c5f9dc4f40e46e4646e4f4eeb429a9d53f1558ec964ccec4a7ef608f7b575433e71180c24c3e33d73012ffdc41564af8aaaf0e257d614c0522430c645e7136f84ea7e8b5439b7194d13c9bc476767fc06ae7fce531e75fafaee455dda42a7f7dae979ee009d37e99f717b5181f1c16c3982ae60ae86c4af92eda6c916fa0de353b5c6ad91f564f55f818a3a543a63ebe6cadb1f45314e130480d6e6b85155b46dbd564207640f0c21ac0d616d9538e77c8ae958c34bebb6dc82ad1aa1b56f8d852059c00fdd7e587cbd30bfcab27fa5498fa49f8f62bdb4054cc31f96a132fde6b3b652d875d4b7a4f41dead3b7a9408beeeb175ad640ec1cd717ef90838b79fb8871bbc0b314369b14a756b9a1fdb343fe9d50eb2024da450cff88a93fa63d22da3b65b502c8b62b2cab55bb9508fdb88f84c9a78637549529881bcf547a2bf6a81960b365074d6e105dcf0a3b2258bf8e3da7733549d4c1f6d80cd38f022bd7953464020f5bcf8b354b8fa Hey, password is required here."},{"title":"Shaders","date":"2021-10-20T14:51:51.000Z","path":"posts/3GZKBFK/","text":"概要 Shader, 理解为一种GPU Program 不要尝试用中文翻译它! Graphics Pipeline The process of transforming 3D coordinates to 2D pixels 我曾尝试去理解什么是“渲染管线”? 这其实是一个很抽象的翻译,不仿用造房子来比喻。 建筑工程,其实是从设计图纸到高楼大厦的过程,其中有画设计图、打地基、该楼层、装修,等等。 渲染管线恰恰与他相反,是一个把高楼大厦复原到图纸(即屏幕)上的过程。 渲染管线,即从三维模型处理到最终二维的屏幕输出。 从上面图片可以看一个大概的流程 (当然实际应用复杂的多!) Shaders: small programs running on GPU. vertex and pixel shader is neccessary in OpenGL! 1. Vertex Shader Takes a single point and can adjust it. 这一步是为了处理顶点数据,对Vertex坐标做各种变化,然后再输出给Geometry/Pixel Shader; FAQ Vertex Shader会处理颜色吗?是否存Color属性呢? 2. Geometry Shader Takes each transformed primitive (triangle, etc) and can perform calculations on it. 处理图元数据,输入可以是三角形等,可以输出多种三角形 3. Rasterization maps the resulting primitives to the corresponding pixels on the final screen. 光栅化负责将所有Vertex坐标,映射到不同分辨率的显示屏上; 这里有很多研究领域,比如**采样、抗锯齿(AA)**等等; 4. Pixel Shader (Fragment Sahder) Calculates the colour of a pixel on the screen based on vertex shader, textures and others (shadow, lighting…). 这是渲染管线比较重要的一步; 常见的贴图、光、阴影等各种着色都在这里计算; 因此,Nvidia又称之为Texture Shader 5. Alpha test and Blending alpha test: blending 透明材质、混合材质等,由于其特殊性,会在渲染管线比较靠后的位置;不然怎么表现透明呢? Pratice: 第一个Shader Vertex data NDC: Normalized Device Coordinates. vertex shader的输入,就是定义在NDC坐标下. 如下我们定义一个三角形: float vertices[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f }; 接下来,我们要将这些数据传给GPU,用于Vertex Shader的计算! VBO:Vertex Buffer Objects,存放以上数据的GPU Memory OpenGL有很多VBO的类型:GL_ARRAY_BUFFER是比较常用的; unsigned int VBO; glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData: 将顶点数据真正的喂给GPU glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); Vertex Shader 后面会介绍一些简单的语法: #version 330 core layout (location = 0) in vec3 aPos; void main() { gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); } Compile Vertex Shader (run-time) 先对char[]编译,后续会扩展开发! const char *vertexShaderSource = \"#version 330 core\\n\" \"layout (location = 0) in vec3 aPos;\\n\" \"void main()\\n\" \"{\\n\" \" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\\n\" \"}\\0\"; 如何编译VS? unsigned int vertexShader; vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); Pixel Shader 同理,ps更关心的是color,vs则关心位置 const char *fragmentShaderSource = \"#version 330 core\\n\" \"out vec4 FragColor;\\n\" \"void main()\\n\" \"{\\n\" \" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\\n\" \"}\\n\\0\"; unsigned int fragmentShader; fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader); Link and Combine Shader编译完,需要链接并整合起来 unsigned int shaderProgram; shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram); 渲染线程中开始绘制: glUserProgram() glUseProgram(shaderProgram); // 记得回收 glDeleteShader(vertexShader); glDeleteShader(fragmentShader); 到这里我们通过vs+ps实现了一个三角形的绘制! EBO: element buffer objects 假设我们需要画一个正方形, 那么需要两个三角形,即如下6个点: float vertices[] = { // first triangle 0.5f, 0.5f, 0.0f, // top right 0.5f, -0.5f, 0.0f, // bottom right -0.5f, 0.5f, 0.0f, // top left // second triangle 0.5f, -0.5f, 0.0f, // bottom right -0.5f, -0.5f, 0.0f, // bottom left -0.5f, 0.5f, 0.0f // top left }; 是不是很浪费? 因为其中两个顶点可以复用,所以其实4个Vertex就够啦! 尝试用vertex + indices,用4个vertex画出一个正方形! float vertices[] = { 0.5f, 0.5f, 0.0f, // top right 0.5f, -0.5f, 0.0f, // bottom right -0.5f, -0.5f, 0.0f, // bottom left -0.5f, 0.5f, 0.0f // top left }; unsigned int indices[] = { // note that we start from 0! 0, 1, 3, // first triangle 1, 2, 3 // second triangle }; unsigned int EBO; glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 这里指定用EBO+索引的方式,省了2个Vertex glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); Shaders语法 先看代码示例 #version version_number in type in_variable_name; in type in_variable_name; out type out_variable_name; uniform type uniform_name; void main() { // process input(s) and do some weird graphics stuff ... // output processed stuff to output variable out_variable_name = weird_stuff_we_processed; } 1. Types vec类型 vec2 vect = vec2(0.5, 0.7); vec4 result = vec4(vect, 0.0, 0.0); vec4 otherResult = vec4(result.xyz, 1.0); 2. In and out #version: opengl版本 in: shader的输入 out: shader的输出 Vertex Shader需要 layout关键词来获取vertex data location = 0 代表采的indice #version 330 core layout (location = 0) in vec3 aPos; // the position variable has attribute position 0 out vec4 vertexColor; // specify a color output to the fragment shader void main() { gl_Position = vec4(aPos, 1.0); // see how we directly give a vec3 to vec4's constructor vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // set the output variable to a dark-red color } Pixel Shader的输出必然是一个vec4的Color #version 330 core out vec4 FragColor; in vec4 vertexColor; // the input variable from the vertex shader (same name and same type) void main() { FragColor = vertexColor; } 3. Uniforms uniforms是一种重要的从CPU->GPU传数据的方式 uniforms是global变量,任意stage的任意shader都可以访问! 换个思路,它是不是很费很耗? Shader通用框架开发 ★★★ 痛点1:咱们总不能每次写个Char*去编译Shader吧? 痛点2:PC(dx11)到android(gl)到ios(meta)的shader互不兼容,我们每个平台都要写一遍shader吗? (fuck) Writing, Compiling and Managing Shaders Github Page: 🔥 gshader.cpp ./shader目录:存放所有vs/ps源码 问题汇总 1. OpenGL的VAO、VBO、EBO区别? VBO: 存储大量顶点信息,给Vertex Shader用 VAO: 告诉GPU如何使用VBO,使用哪个VBO EBO: 利用索引节省Vertex的内存开销 2. Vertex shader与Pixel shader的区别? Vertex shader:对顶点坐标作运算; Pixel shader:对每个像素的颜色作运算;(Nvidia又称之为Texture Shader) 参考资料 Stackoverflow: What are Vertex and Pixel shaders? LearOpenGL: Hello-Triangle"},{"title":"Git操作指南","date":"2021-10-19T15:47:07.000Z","path":"posts/git/","text":"概要 Git使用指南; 汇总了使用Git时遇到的一些坑! Git原理 🔥 这篇文章讲得非常棒:我用四个命令概括了 GIT 的所有套路 Git本质是:三个分支之间的状态转移与维护: work dir:自己的工作目录 stage:本地暂存区(缓冲) history:本地历史区(永久保存) Git操作 man git git [--version] [--help] [-C <path>] [-c <name>=<value>] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path] [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare] [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>] [--super-prefix=<path>] [--config-env=<name>=<envvar>] <command> [<args>] Git问题 fatal: unable to access https://github.com/xxx.git/:Failed to connect to github.com port 443: Timed out stackoverflow 尝试 git config --global --unset https.proxy Git Error: OpenSSL SSL_read: Connection was reset, errno 10054 … Git插件 VSCode推荐使用 GitLens 插件, 支持逐行实时Blame, 但是似乎收费?"},{"title":"【Algorithm】String matching","date":"2021-10-16T15:51:06.000Z","path":"posts/1RJC6HF/","text":"概要 linux grep的效率令人称奇! 字符串匹配的常用算法与原理; 字符串匹配算法 问题描述 在父串$(String, len=n)$中寻找一个合法子串$(Pattern, len=m)$; 例如,abcdef的一个合法子串是abc,但ac就不是 warning不要尝试写代码和图解,侧重算法思想和推导过程! 如果连一个非CS专业的都能看懂,那才叫棒; 算法 时间复杂度 精髓 Brute Force $O(nm)$ 有脑子就行 Robin-Karp $O(n+km)$ 字符串Hash,$k$ 是hash hit但不匹配的次数 KMP $O(n+m)$ LPS数组 Boyer-Moore Sunday 暴力匹配 两层$for$循环的遍历: 父串$S$中共有$n$个长度为$m$的子串,复杂度$O(n)$ 将长度为$m$的子串与$Pattern$逐字符比较,复杂度$O(m)$ 暴力的复杂度是$O(nm)$; Robin-Karp算法 暴力中逐字符对比很浪费,尝试Hash算法,可以从$O(m)$ 降低到 $O(1)$; 但考虑到hash collision,即使hash hit,也需要逐字符对比来验证; 时间复杂是 $O(n+km), k$ 为hash hit但不匹配的次数; 缺陷 假设每次都hash hit但又不匹配,那Robin-Karp就退化为了暴力算法;(显然不可能) 因此越选择精确的Hash算法,Robin-Karp的效率就越高; KMP算法 如何更好地理解和掌握 KMP 算法? Knuth-Morris-Pratt Algorithm KMP的精髓在于,如何跳过一些明显不可能的匹配? 如上图,当字母d匹配失败时,暴力法会回到字母b处重新开始对比; 这显然是效率低下的;因为回到字母ab处匹配会更好; 为什么回到ab处呢?因为ab是$p$的一个前缀! LPS数组 longest prefix and suffix. 最长公共前后缀 对比abcab,它的LPS就是ab,长度为2; 这就是为什么,上图中我们要移动到ab处; 如何计算LPS数组? 利用了递推的思想,有空详细解释下~ LPS[m] # 构建的数组 i, j = 0, 1 LPS[0] = 0 while j <= m: if P[i] == P[j]: LPS[j] = i + 1 i++; j++ else: if i == 0: LPS[j] = 0 j++ else: i = LPS[i-1] 复杂度 时间复杂度$O(m)$,$m$是子串P的长度; 利用LPS数组做匹配 i = 0 # 父串S下标 j = 0 # 子串P下标 while i < len(S): if S[i] == P[j]: i++; j++ elif j > 0: j = LPS[j-1] else: i++ if j = len(P): # Match Success pass 这一步时间复杂度是len(S),即$O(n)$ KMP算法的复杂度即$O(n+m)$ FAQ 笔者python测试对比如上三种算法(随机字符n=1000w, m=10),时间如下: BF: 5.6s Robin-Karp: 1.18s KMP: 1.41s 为什么KMP稳定比RK算法慢? 虽然KMP严重依赖Pattern的性质,但不至于稳定差 参考资料 如何更好地理解和掌握 KMP 算法?"},{"title":"【Algorithm】Sorting","date":"2021-10-16T12:43:21.000Z","path":"posts/1CKQG3J/","text":"这是大学时期写的 排序算法的深入分析和实现 1.1 排序的定义 对一序列对象根据某个关键字进行排序。 1.2 术语说明 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面; 不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面; 内排序In-place:所有排序操作都在内存中完成; 外排序Out-place:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行; 1.3 算法总结 排序算法 时间复杂度 空间复杂度 冒泡排序 O(n2) O(1) 选择排序 O(n2) O(1) 插入排序 O(n2) O(1) 归并排序 O(nlogn) O(n) 快速排序 O(nlogn) O(logn) 堆排序 O(nlogn) O(1) 捅排序 O(n+k) O(n+k) 1、冒泡排序(Bubble Sort) 相邻两个元素比较大小,一次外循环比较(n-1)次; 如跑完第一次循环,最大的元素被移到最后一位; 内循环跑(n-i)次,因为最后i个元素已排好序; 时间复杂度分析: 比较次数:不管怎样,冒泡排序都要比较(n+(n-1)+…+2+1)次,即n(n-1)/2 ,O(N^2); 交换次数:有序不需要交换,逆序交换n(n-1)/2次;O(N^2); 稳定性分析: 相邻两元素大小一样,自然不会多此一举去交换,因此稳定; C++代码实现: //冒泡排序 void bubble_sort(int *a, int n){ for (int i=0; i<n-1; i++){ //外循环n-1次 for (int j=0; j<n-i-1; j++){ //内循环找出前n-i个中最大元素,不断往末尾移 if (a[j]>a[j+1]){ int tmp = a[j]; a[j] = a[j+1]; a[j+1] = tmp; } } } } 2、选择排序(Select Sort) 每次选择第i小的元素,把它放在index为i的位置上; 一共n次外循环,第一次选出最小元素,放在第一个;第i次选出第i小的元素; 每次内循环要比较(n-i)次,最终选出后(n-i)个元素中最小的,放到i位置上。 时间复杂度分析: 比较次数:选择排序同样比较(n+(n-1)+…+2+1)次,即n(n-1)/2 ,O(N^2); 交换次数:有序不需要交换,逆序交换n(n-1)/2次;O(N^2); 稳定性分析: 因为涉及相隔较远的元素交换位置! 例如3 2 3 1,第一次循环结束,3和1交换,破坏了稳定。显然选择排序是不稳定。 C++代码实现: //选择排序 void select_sort(int *a, int n){ for (int i=0; i<n-1; i++){ //外循环n-1次 int min = a[i], index = i; for (int j=i; j<n; j++){ //内循环找出后n-i个中最小元素,放到第i位置 if (a[j] < min){ min = a[j]; index = j; } } a[index] = a[i]; a[i] = min; } } 3、插入排序(Insert Sort) 类比平时打牌时插牌,拿到新元素,把它放到已排好序的元素中的适当位置; 外循环n次,第i次外循环结束,则前i个数已排好序,第一个默认排好序; 内循环为执行(n-i)次,将新元素和前i个排好序的依次比较,是一个不断往前插的过程; 时间复杂度分析: 比较次数:同O(N^2); 交换次数:有序不需要交换,逆序交换n(n-1)/2次;O(N^2); 稳定性分析: 插入排序是稳定的;例如 1 2 3 3,前三个已经排好序,最后的3显然不会再往前插; C++代码实现: //插入排序 void insert_sort(int *a, int n){ for (int i=1; i<n; i++){ //外循环n-1次,第i次外循环结束前i+1个元素排好序列 int index = i; for (int j=i-1; j>=0; j--){ //内循环将第i个元素往前插 if (a[index]<a[j]){ int tmp = a[index]; a[index] = a[j]; a[j] = tmp; index = j; } } } } 4、归并排序(Merge Sort) 采用分治法,将序列分成两个n/2长度的子序列,合并时依次按大小输出到新序列; 占用额外空间,非原址排序; 时间复杂度分析: 每次递归复杂度O(n),递归层数O(lgn),所以复杂度为O(nlgn); 稳定性分析: 归并排序是稳定的,合并过程左右两个序列的比较大小保证了这种稳定性; C++代码实现: //归并排序 void merge(int *a, int *b, int p, int r, int q){ //合并下标为p到r 与 r+1到q 这两部分 int write = p; //写入b的下标 int i=p,j=r+1; //分别标记左右序列正要读的位置 while (i<=r && j <= q){ if (a[i]<=a[j]) b[write++] = a[i++]; else if (a[i]>a[j]) b[write++] = a[j++]; } //此时左、右序列可能有一个未写完 while (i<=r) b[write++] = a[i++]; while (j<=q) b[write++] = a[j++]; for (int k=p; k<=q; k++) a[k] = b[k]; } void merge_sort(int *a, int *b, int beg, int end){ if (end==beg){ //1个元素直接返回 return; } if (end-beg == 1){ //2个元素,排个序 if (a[beg]>a[end]){ int tmp = a[beg]; a[beg] = a[end]; a [end] = tmp; } return; } merge_sort(a,b,beg,(beg+end)/2); merge_sort(a,b,(beg+end)/2+1,end); merge(a,b,beg,(beg+end)/2,end); } 5、快速排序(Quick Sort) 从数列中挑出一个元素,称为 “基准”(pivot); 重新排序数列,所有比pivot小的摆放在基准前面,所有比pivot大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序; Partition操作实现: pivot选择末尾元素,维护i和j,i指向头,j指向pivot前一个。i不断往后搜索直到找到第一个大于pivot的元素;j不断往前搜索直到找到第一个小于pivot的元素。i,j都找到时交换i,j上的元素,如果j<i,退出循环,此时交换pivot和i位置,满足pivot(即i原来位置)左边都比它小,右边都比它大; 时间复杂度分析: 每次递归复杂度O(n),递归层数O(lgn),所以复杂度为O(nlgn); 稳定性分析: 快速排序是不稳定的; 因为间隔元素的交换,很可能打破原有相同元素的顺序关系; 例如3 3 1 1 2,选择2为pivot,那么第一次循环,3和1就发生了交换,打乱了稳定性; C++代码实现: //快速排序 int partition(int *a, int p, int q){ int pivot = q; //选择最后一个元素作为pivot int i=0,j=q-1; while (i<=j){ while (a[i]<=a[pivot] && i<q ){ //i左边都<=pivot i++; } while (a[j]>a[pivot] && j>=p ){ //j右边都>pivot j--; } if (i<j){ //i不会等于j int tmp = a[i]; a[i] = a[j]; a[j] = tmp; } } //交换i和pivot int tmp = a[i]; a[i] = a[pivot]; a[pivot] = tmp; return i; //partition结束,满足左边<=它,右边>它 } void quick_sort(int *a, int beg, int end){ if (beg>=end) return; int pivot = partition(a,beg,end); quick_sort(a,beg,pivot-1); quick_sort(a,pivot+1,end); } 参考资料"},{"title":"Occlusion: 可见性与剔除","date":"2021-10-14T14:10:29.000Z","path":"posts/2AR5BC4/","text":"概要 光栅化决定如何将三维场景画到二维屏幕上; 但以什么样的顺序绘制?(Visibility) 是否所有的物体都要绘制到屏幕上? (Occlusion) Occlusion 一叶障目,不见泰山 想象你的视线,远处的物体总被近处的遮挡,这就是Occlusion! 将“看不见”的物体剔除掉,是图形学优化的重要方向之一! Painter’s Algorithm 画家算法:由远到近绘制物体,远处总被近处的遮挡; 因此又称Depth-sort Algorithm; 算法分析: 需要对n个物体排序: $O(nlogn)$的复杂度,对$n$面数; 不能处理多面重叠的情形; Z-Buffer 对于每个三角形面片中每个Pixel$(x,y,z$),不断取其$z_{min}$,并赋值位z-buffer上该pixel的值 # init zbuffer for +∞ for (each tri T): for (each pixel (x,y,z) in T): if (z < zbuffer[x][y]): framebuffer[x][y] = rgb; # also as color buffer zbuffer[x][y] = z; # also as depth buffer else: # occlusion now! 算法分析: 省略了排序,因此是$O(n)$的复杂度,对$n$面数; 不能处理两个三角形,存在深度值z相同的pixel的情形;(虽然这种情况很少发生,因为是float32) 什么是framebuffer与zbuffer? 通俗的说,它们都是一块内存(RAM); framebuffer:存储rgb的颜色值 z-buffer (depth-buffer):存储深度信息 图解z-buffer的生成过程 可以看出,计算z-buffer就是一个不断取$min$的过程! TODO补充一张Nsight抓帧的frame-buffer, depth-buffer截图~ Futhermore… 常用的culling方案 hlod"},{"title":"关于开发理念: Simple is Good","date":"2021-10-10T10:03:53.000Z","path":"posts/1ZMZBDW/","text":"希望这个理念,能够伴随我所有的开发生涯 优秀的开发,是大道至简,而不是简单问题复杂化 在这之前 大学身边有一帮要好的文科朋友,经常会观察到这些现象: 理工科出身的同志,喜欢提枪就上,埋头苦干,就算没思路也要把电脑敲的噼里啪啦响; 文科出身的同志,喜欢事前构思、冥想,甚至磨半天也挤不出几个字; 如果要双方取长补短的话, 我希望程序员的世界,多一些主观性的理念的东西, 就是在每次敲代码之前,有个尼采一样的诗人在你耳边朗诵一遍: “1.少用宏; 2.做好封装;3.保持代码整洁 …” 程序员喜欢review,把问题留给Bug和Review阶段, 但为什么不学文科生, 脑海中树立一些宏观的、虚头巴脑的、理念的东西, 每次动键盘前,有个“社会主义核心价值观”在脑子里过一遍: 虽然玄学了些,但我觉得是个好建议。 这就是我写这些随笔的原因。 Simple is Good 我尝试用一些观点来论证why simple is good 运行更快 Bug几率更低 维护成本更低 有两种软件设计方法: 第一种是使得软件足够简单以至于显然没有错误; 第二种是使得软件足够复杂以至于没有显然的错误。 (最难的是第一种) - Tony Hoare (1980图领奖得主) 如果你说如何让一个复杂的功能保持simple? 试试 封装 优化经常是一个不断删除代码的过程 simple不是无脑删除,合理的解耦与拆分也是一种simple 你可以尽可能地控制“少”,但你永远无法控制“多”; 想象一万个人对同一个需求的不同实现,总是千奇百怪。 Simple 拓展阅读 🔥 代码整洁之道 🔥 知乎: 一百行以下有哪些给力代码?"},{"title":"中英文档","date":"2021-10-10T08:06:11.000Z","path":"posts/XPRTBH/","text":"NOTE 罗列了常用、令人困惑的英文专有词汇,可结合站内 右上角的搜索 速查! 本篇已停更一段时间,尽量使用 “英语阅读,中文写作” 的方式 好的翻译多重要? 先祭出CS领域的经典垃圾翻译: socket: 套接字 robust: 鲁棒性 handle: 句柄 再看看什么经典优秀翻译: context: 上下文 (通俗) process/thread: 进程/线程 (进行中的程序) garbage collection: 垃圾回收 (直译就够了) 欢迎移步知乎话题: 为什么handle会被翻译成句柄? 哪些专业名词翻译得特别烂? 中英对照 关于对照表 1.Wikipedia的解释准确且全面,但可能不易理解; 2.某些社区(如知乎、博客)会有一些精彩而巧妙的理解,因此贴出来; 3.个人备注栏,添加了一些自己的理解; ❌意味着远离中文! 英文 中文 官方 个人备注 socket 套接字 Wikipedia robust 鲁棒性 Wikipedia 功能健壮, 具有高扩展性 handle 句柄 Wikipedia 对一段资源的引用, 像pointer? Cartesian coord. 笛卡尔坐标系 Wikipedia Homogeneous coord. 齐次坐标系 知乎 n+1维表示n维 我的博客 Viewport 屏幕 Projection 投影 我的博客 Orthographic 正交 我的博客 Perspective 透视 我的博客 rasterization 光栅化 Wikipedia 三维场景渲染到二维屏幕 anti-aliasing 抗锯齿(AA) Wikipedia framebuffer Wikipedia GPU memory to store Z-Buffer/Depth-Buffer Wikipedia 我的博客 painter’s algorithm 画家算法 Wikipedia 由远到近排序后绘制,近的覆盖远的 NDC Normalized Device Coordinates VBO vertex buffer object Wikipedia 我的博客 VAO vertex array object EBO element buffer objects vs vertex shader ps(fs) pixel (fragment) shader uv 纹理坐标 Texture filtering 纹理过滤 Wikipedia Artifacts ❌ Wikipedia 凡是不接近真实的图形表现 Mipmap LearnOpenGL Phong lighting model ❌ Wikipedia Colors and Materials Ambient 环境光 Colors and Materials Diffuse 漫反射光 Colors and Materials Specular 高光 Colors and Materials Lightmap ❌ Colors and Materials directional light 平行光 Lighting point light 点光 Lighting spot light 聚光灯 Lighting hover 鼠标悬停态 CSS hover Assembly-Language 汇编 AT&T Syntax AT&T汇编格式 Syntax versus Intel Syntax Intel汇编格式 Syntax versus | socket | | Wikipedia | |"},{"title":"TypeScript数据类型","date":"2021-10-08T16:13:59.000Z","path":"posts/3YE7XY8/","text":"Note🔥 Type Challenge 这是为TypeScript打造的Online Judge, 可以考察对其类型的理解 Builtin Primitives Types string 单引号与双引号皆可 (同python); ${expr}用来嵌入表达式; number 没有int, float之分; 0x, 0o都是ES6的进制表示法; boolean true, false 空值 undefined, null: 变量赋值 void: 函数返回值 Object Types Array … Any 当不确定类型时,使用any,这样后面可以赋予它任何类型 let foo: any; foo = true; foo = 'abcd'; let arr: any[] = ['John', 88, true]; arr.push('luhao'); any对象可以调用任意属性与方法! let foo: any; console.log(foo.name); // OK foo.setName('luhao'); // OK 没有申明类型的变量,等价于any // 两者等价 let foo; let foo: any stackoverflow: 申明any与否的意义? tsconfig.json中有如果对\"strict\"定义为true,那么对所有any的非法访问都是错误的! Inference 类型推论: 对于没有指定类型但已经赋值的变量,TypeScript会推断出一个类型: let foo = 'luhao'; // 此时TS已推断foo为string foo = 100; // Error // error TS2322: Type 'number' is not assignable to type 'string'. Union 联合类型使用 | 表示几个类型的并集: let foo: string | number; foo = 'luhao'; // OK foo = 100; // OK foo = true; // Error 联合类型一旦被赋值,其类型就是唯一的: let foo: string | number; foo = 'luhao'; console.log(foo.length); // OK foo = 100; console.log(foo.length); // Error, 此时foo是number类型 interface 它就像C中的struck: interface IPerson { name: string; // 必选属性 age?: number; // 可选属性 readonly gender: boolean; // 只读属性 [optName: string]: any; // 任意属性 } let person: IPerson = { name: 'luhao', // age: 24, // 可选 gender: true, school: 'NJU', } Assertion: 类型断言 !!! todo"},{"title":"【VSCode插件】Markdown-snippet","date":"2021-10-07T07:45:37.000Z","path":"posts/vscode-md/","text":"NOTE 实现了一款用于Markdown代码补全、CSS样式插入等功能的VScode插件 开发环境:VScode, TypeScript 🏠 VScode-Markdown-Snippet TODO 这个是若干年前的项目,有空捞回来完善下; 支持fontawesome 支持骚的html语法 支持表情的检索 支持size/color快速自定义 2023.12.10 补充: 已经专用 Obsidian,放弃 VSCode 开发环境 插件功能概述 支持Markdown基本语法插入与补全:表格、超链接、图片… 支持对选中内容:加粗、下划线、划去线… 支持内嵌多种css样式:改变颜色、左右对齐、字号… 支持图片直接复制粘贴到VScode; W1: 环境搭建与Demo 直接上文档: VScode API npm install -g yo generator-code : 安装依赖环境 yo code : 进入开发环境, 如下图 观察一下开发目录: extension.ts: 插件的脚本逻辑: TypeScript package.json: 所有的环境配置选项: 插件名、版本号、command… README.md: 发布后的文档 第一个Demo: 对选中内容加粗显示 **这段文字是加粗显示的** #这是markdown加粗语法 下面是实现的ts逻辑 showQuickPick API insertSnippet API // 当surround插件被激活时,会进入activate函数 export function activate(context: vscode.ExtensionContext) { // 输出一行Log console.log('Congratulations, your extension \"surround\" is now active!'); // surround.sur 定义在package.json中 let disposable = vscode.commands.registerCommand('surround.sur', function () { interface CommandQuickPickItem extends vscode.QuickPickItem { command: () => Promise<void>; } let items: CommandQuickPickItem[] = []; items.push({ description: '对选中的字体加粗', label: 'Font Bold', command: surroundWithBold }); vscode.window.showQuickPick(items, { matchOnDetail: true, matchOnDescription: true }).then(selectedItem => { if (selectedItem && typeof selectedItem.command === 'function') { selectedItem.command(); } });; } // 对选中的内容进行加粗 function surroundWithBold() { let msg = \"**\"; let snippet = msg + '${TM_SELECTED_TEXT}' + msg; vscode.commands.executeCommand('editor.action.insertSnippet', {'snippet': snippet} ) } VScode插件打包与发布 本地打包测试 这里推荐打包为visx,并使用VScode安装 npm i vsce -g: 安装vsce打包工具 vsce package: 在插件目录打包 xxx.vsix: 在VScode的插件右上角选择Install from VSIX 发布到VScode Marketplace 使用Azue注册一个开发组织 vsce login <publish-name>: 登录自己的开发者账户, 需要tokens vsce publish: 发布到marketplace vsce publish patch: 使发布的version自增, 会自动修改package.json中的版本号 vsce unpublish <publish-name>.<extension-name>: 下架 这是我的VScode Marketplace的个人主页 已上架VScode Marketplace 参考资料 Building your VScode extension TypeScript VScode插件开发 中文文档 VScode LaTex Snippets开发教程 VScode插件开发全攻略十篇"},{"title":"【VIM】Vi Improved","date":"2021-10-01T07:56:55.000Z","path":"posts/vim/","text":"vi/vim is the best editor in the world! 骚操作 终端输入 set -o vi,在命令行使用vim模式(set -o emacs以回退) gim配置vim:git config --global core.editor \"vim\" 1、Vim: Vi improvment 先从一个stackoverflow经典问题看起: How do I exit the Vim editor? 这是回答: Hit the Esc key to enter “Normal mode”. Then you can type : to enter “Command-line mode”. A colon (:) will appear at the bottom of the screen and you can type in one of the following commands. To execute a command, press the Enter key. :q to quit (short for :quit) :q! to quit without saving (short for :quit!) :wq to write and quit … 到这里,你至少不用窘迫的不知如何退出Vim界面了! 技巧:所有Vim Operation,都源于英文的缩写 (例如 q 表示 quit), 因此下面的所有Vim快捷键,我都会尽可能列出其英文含义,已帮助你更快得记住它! 2、Vim modes 什么是mode? 模式,几乎所有的编辑器只有一种模式,即insert 插入 但是,Vim有起码3种重要的模式:normal, insert, visual 引自3中一句话:Programming大多数的时间消耗,不是在Writing上,而是review和Editing。 normal 模式 如何移动光标? h - ← move left j - ↓ move down k - ↑ move up l - → move right 更高效得,如何以单词为单位移动? w - move to beginning of next word (word) b - move to previous beginning of word (back word) e - move to end of word (end word) just type 'vim' in terminal, and check these with your hands! insert 模式 使用i来进入insert模式,使用Esc来退出insert。 i - insert text before cursor (insert) a - insert text after cursor (insert after) o - Start a new line below cursor, insert text (??) visual 模式 你一定猜到了,使用v可以进入visual模式。 此时可以批量成段的选择、编辑文本! 3、Vim commands 直到阅读这篇文章前,我都不曾理解vim command的真正要义: Vim Text Objects: The Definitive Guide 在Vim的眼中,一切内容分为两种:text和objects 试想一下,一串无规律的字符串,它在编辑器眼里是text,这不无道理。 但如果是一个word,一个sentence,甚至是argument,那vim会视其为一个text object,以实现个更高效的操作。 <number><command><text object> 这构成了几乎vim 90%的操作。 daw – (delete a(around) word) 没错,它执行了删除一个单词的操作,并取决于你光标所在的位置。 diw - (delete inner word) inner于around有什么区别?around删除了周围的空白符,但是inner只删除了单词! vf) - (visual(select) until(find) first )) 选中直到)的所有字符,并进入visual模式! (需要稍加理解) 下面罗列了几乎所有的command和obejct,标星的表示常用! Command d - delete ★ y - yank (经典ctrl c) ★ c - change (delete and insert) ★ v - visual (选中并进入visual模式) ★ r - replace (delte and insert) Object w - word ★ s - sentence p - paragraph (其实对不同于语言的识别不佳) a - argument ★ (编辑函数很有用) \" - quoted ★ (引号内) ), ], }, :… 一切皆可 4、vimrc ~/.viminfo是vim的cache,包括历史操作等 vim的配置是在~/.vimrc, 比较多且难记, 可以查看 Vim 配置入门 例如: set number是显示行号 5、vim pro 多行编辑 v键盘进入visual block模式,然后选中要编辑的所有行 I进入多行编辑模式, Esc退出生效 快速光标移动 %: 配对括号、引号等 {、}: 上一个/下一个 空行 [[、]]: 上一个/下一个 代码段 +、-: 上一行/下一行 非空白字符 Enter: 下一行 非空白字符 f+单字符: 行内快速查找并移动, 按 分号键; 可以继续查找 运用数字表示重复 3j: 表示往下3行 3igo: 结束按esc, 即输入3遍go vim-surround:使用某个符号包围对象 ds<sign>: 删除包围符号,如 ds\" 删除引号 ysiw<sign>: 添加包围符号,如 ysiw\" 添加引号 这里有个潜规则:左符号(会默认添加空格,右符号)则不会 不熟练操作 ~: 转大小写 O: 大写O在上方插入新行 c: 改写操作 cc: 删除当前行, 并进插入模式 cw: 改写下个单词 5、关于Vim的更多 vim 杂谈 - 关于快速编辑 StackOverflow: How do I exit the Vim editor? Introduction to Vim Modes VimGolf"},{"title":"写给三年内的技术成长目标","date":"2021-09-30T22:52:16.000Z","path":"posts/goal-2024/","text":"以下 写于2023年7月 继续惊觉时间飞逝 有收获,但也有走弯路 剩余一年时间,保持现有节奏之余,坚持两个核心 核心一:提高效率 当追求细化文档、扩散式学习(遇到陌生的点就深挖)后,工作的效率(指做完主线)明显降低。 中短期来看,这是不好的迹象,期望通过 区分任务主次 来改善。 核心二:攻城略地 期望 “染指” 更多中大型模块,快速弥补经验上的欠缺。 同时,攻坚 modern cpp、linux kernel、network、algorithm… 等领域,计算机的 “万丈高楼平地起”,恰恰依赖这些基础知识。 以下 写于2022年5月 惊觉半年已逝; 回看曾经的目标,宏大却不够具体,因此缺乏坚持的动力; 这次来细化下目标吧! 坚持阅读、写作 每周 1+ 篇博客, 不限题材(算命的、风水的都行) 每月阅读 1+ 本书籍 (技术 or 思维 …) 每月做 1+ 篇陌生技术的积累(前沿的、其他行业的) 尝试捣鼓一些小玩意,如破解插件、工具脚本,并将这些成果发布在开源网站上! 以下 写于2021年国庆 希望在三年后(2024.10),能够达到如下的水平 精通2+ 脚本语言 python 2/3 ➜ 使用层面 ✔ JS/TS 精通1+ 静态类型语言 C/C++ ➜ C++ 11/14/17 精通3+ Gameplay模块 熟悉常见 Gameplay 模块 ✔ 手写一套基于OpenGL的渲染管线 基于引擎实现具体中小模块的 增删查改 Shader (compiler and manager) ➜ cross-platform shadercompiler Texture ➜ texture-budget Camera Lighting ➜ Cluster Lighting Shadows ➜ CSM Model/Mesh ➜ Culling Instancing Postprocess ➜ SSR Animation … Editor (Image, Scene, Model…) ✔ Debug Tools ➜ ImGUI、Tracy、RenderDoc… ✔ 图形引擎其他 良好的数学基础 图形学基础 ➜ GAMES UE4/Unity的学习借鉴 前沿技术的积累 Learning and Coding Reading Paper (GDC…) Reading Document (API…) Code review ➜ MR ✔ Blog Recording (Markdown!) ✔ Learn from open-source"},{"title":"【Algorithm】BinaryTree","date":"2019-09-22T16:00:00.000Z","path":"posts/1E2J1Y0/","text":"这篇文章是大学写的 二叉搜索树 1.0 定义 每个节点包含属性left,right和p,分别指向左右子节点和父节点 二叉搜索树的性质: x.left<= x <= x.right; 左子树<父节点<右子树 基本操作与树的高度有关,即O(lgn) 数据结构 查找 插入,删除 数组 O(n) O(n) 有序数组 O(lgn) O(n) 二叉树 O(lgn) O(lgn) struct Node { int val; struct Node* left; struct Node* right; Node(int x): val(x), left(nullptr), right(nullptr) {} }; //定义二叉树类型 typedef Node TNode; typedef Node* pNode; class AVL { // 平衡二叉树 public: pNode root; AVL() {root = nullptr;}; ~AVL() {delete root;}; int empty() {return (root==nullptr);}; int maxDepth(pNode p); // 该节点的最大深度 int minDepth(pNode p); // 该节点的最小深度 int findMax(); // 二叉树最大值 int findMin(); // 二叉树最小值 pNode search(int x); pNode _search(pNode p, int x); void insert(int x); pNode findParent(pNode p); // 返回该节点的父节点 pNode successor(int x); // 返回节点x的后继节点 void Delete(int x); // 删除值为x的节点 void levelOrder(); // 层次遍历,用换行表示层数递进 void preOrder(pNode p); // 前序遍历 void inOrder(pNode p); // 中序遍历 void postOrder(pNode p); // 后序遍历 }; 2.0 查找二叉树的最大最小值 最小值一定在树的最左节点,最大值一定在树的最右节点 不停向左(右)子节点搜索直到NULL即可 int AVL::findMax() { pNode p = root; while (p->right) p = p->right; return p->val; } int AVL::findMin() { pNode p = root; while (p->left) p = p->left; return p->val; } 2.1 返回二叉树的最大、最小深度 深度Depth定义:根节点root到叶子节点的距离 最大深度:即不断返回左右子树的深度中的较大值+1,递归实现 最小深度:即不断返回左右子树的深度中的较小值+1,递归实现 特殊情况:根节点只有一个子节点的时候,最小深度不是1,因为根节点不是叶子节点! 处理方式:只有一个孩子,其最小深度就是该子节点的最小深度+1 int AVL::maxDepth(pNode p){ if (!p) return 0; return max(maxDepth(p->right), maxDepth(p->left)) + 1; } int AVL::minDepth(pNode p) { // 深度的定义:p到某个叶子节点的距离; // 因此当p只有一个孩子时,它的深度并不是1 if (!p) return 0; if (p->right && p->left) // 有两个孩子,返回较小的深度 return min(minDepth(p->left), minDepth(p->right)) + 1; else // 只有一个孩子,返回此孩子的深度 return minDepth(p->right) + minDepth(p->left) + 1; } 2.2 二叉树的查找 search(x):查找key为x的节点,返回其指针 先和根节点比较,小的话去左子树搜,大的话去右子树搜,等于则返回 pNode AVL::_search(pNode p, int x) { if (p->val == x) return p; if (x > p->val) return _search(p->right, x); return _search(p->left, x); } pNode AVL::search(int x) { return _search(root, x); } 2.2 二叉树的后继节点 x的后继节点(successor node): key值大于x的节点中key最小的一个 x的前驱节点(prior node): key值小于x的节点中key最大的一个 查找x的后继节点,要对x进行讨论: x有右子节点,那么后继节点是右子节点的最左子节点 x没有右子节点,再分两种讨论 x自身是左子节点,那么后继节点是它的父节点 x自身是右子节点,那么后继节点是根节点,且必须满足x小于根节点,否则x没有后继节点 pNode AVL::successor(int x) { // 后继节点,大于x中最下的那个 pNode p = search(x); pNode tmp; if (p == root && !p->right) // 根节点且没有右孩子 return nullptr; if (p->right) { // 如果p有右孩子,那么后继节点就是右孩子的最左孩子 tmp = p->right; while (tmp->left) tmp = tmp->left; return tmp; } else { // p没有右孩子 pNode par = findParent(p); if (p == par->left) { // p自身是左孩子 return par; } else { // p自身是右孩子, 返回层数最靠近p的有左孩子de节点 if (p->val > root->val) return nullptr; pNode ppar = findParent(par); while (ppar) { if (par == ppar->left) return ppar; par = ppar; ppar = findParent(ppar); } return nullptr; } } } 2.3 二叉树的插入和删除 insert(x):插入新节点,同时维持二叉树的性质 若树空,直接插入root 若树不空,通过不断和左右孩子比较,往下移动直到找出合适的位置 void AVL::insert(int x) { pNode p = new TNode(x); if (!root) { root = p; return; } pNode tmp = root; pNode par; while (tmp) { par = tmp; if (tmp->val < x) { tmp = tmp->right; } else { tmp = tmp->left; } } if (par->val > x) { par->left = p; } else { par->right = p; } } delete(x): 删除已存在的某个节点x,同时维持二叉树的性质 若x是根节点,直接删除 若x只有一个孩子,用该子节点替换x 若x有两个孩子,找到x的后继节点nex并替换x 先用nex的key替换x的key,接着删除nex节点即可 若nex是它父节点的左孩子,那么把nex的左孩子顶上来 若nex是它父节点的右孩子,那么把nex的右孩子顶上来 void AVL::Delete(int x) { pNode p = search(x); if (!p) { cout << \"Not Existed\" << endl; return; } // case 1: p 没有孩子节点, 直接删除 if (!p->left && !p->right) { if (p == root) root = nullptr; else { pNode par = findParent(p); if (par->right == p) par->right = nullptr; else par->left = nullptr; } } else if (p->left && !p->right) { // case 2: 只有一个左孩子,替换即可 if (p == root) root = p->left; else { pNode par = findParent(p); if (par->right == p) par->right = p->left; else par->left = p->left; } } else if (p->right && !p->left) { // case 3: 只有一个右孩子,替换即可 if (p == root) root = p->right; else { pNode par = findParent(p); if (par->right == p) par->right = p->right; else par->left = p->right; } } else { // case 4: 有两个孩子, 把它的后继节点替换到该位置即可 pNode suc = successor(p->val); int key = suc->val; Delete(key); p->val = suc->val; } } 二叉树的各种遍历 遍历方式 遍历顺序 应用和意义 层次遍历 按一层一层遍历 符合人的直观感受 前序遍历 根结点 —> 左子树 —> 右子树 打印目录结构:如每个文件夹下的文件 中序遍历 左子树—> 根结点 —> 右子树 编译底层:实现表达式树 后序遍历 左子树 —> 右子树 —> 根结点 回溯法: 实则就是采用后序遍历的形式 层次遍历:1 2 3 4 5 6 7 8 前序遍历:1 2 4 5 7 8 3 6 中序遍历:4 2 7 5 8 1 3 6 后序遍历:4 7 8 5 2 6 3 1 层次遍历 通过队列实现,每搜索一个节点,将它的孩子按先左后右的顺序加入队列; 这样保证了 1:层数越低的越先搜索; 2:左子树先于右子树搜索 void AVL::levelOrder(){ if (empty()) { cout << \"empty Tree\" << endl; return; } queue<pNode> Queue; queue<int> Level; Queue.push(root); // 记录节点 Level.push(0); // 记录层数 pNode p; int h; int last_h = 0; while (!Queue.empty()) { p = Queue.front(); h = Level.front(); if (last_h < h ) cout << endl; cout << p->val << ' '; if (p->left) { Queue.push(p->left); Level.push(h+1); } if (p->right) { Queue.push(p->right); Level.push(h+1); } Queue.pop(); Level.pop(); last_h = h; } cout << endl; } 前序遍历 搜索顺序:根结点 —> 左子树 —> 右子树 算法实现:1.访问根节点 2.前序遍历左节点 3.前序遍历右节点 void AVL::preOrder(pNode p) { // 先序遍历:根->左右节点 if (!p) return; cout << p->val << ' '; preOrder(p->left); preOrder(p->right); } 中序遍历 搜索顺序:左子树 —> 根结点 —> 右子树 算法实现:1.前序遍历左节点 2.访问根节点 3.后序遍历右节点 void AVL::inOrder(pNode p) { // 中序遍历:左->根->右 if (!p) return; inOrder(p->left); cout << p->val << ' '; inOrder(p->right); } 后序遍历 搜索顺序:左子树 —> 右子树 —> 根节点 算法实现:1.前序遍历左节点 2.后序遍历右节点 3.访问根节点 void AVL::postOrder(pNode p) { // 后序遍历:左右节点->根 if (!p) return; postOrder(p->left); postOrder(p->right); cout << p->val << ' '; } 3.0 扩展概念 满二叉树 顾名思义:每层铺满的二叉树,n层满二叉树有2^n-1个节点 完全二叉树 即倒数第二层以上都铺满,最后一层紧左边铺的二叉树 平衡二叉树(AVL) 递归地满足:左右子树高度差不超过1的树 为什么需要AVL? 因为当二叉树形似链表时,查找效率从O(lgn)变成O(n),所以想办法使得树的高度尽量平衡 平衡因子: 某节点的左右子树高度之差 二叉树的整个插入过程,分为四种形态:"},{"title":"【Python】pyautogui","date":"2019-07-24T05:27:11.000Z","path":"posts/2KGMM94/","text":"Python纯GUI自动化工具 备注 这篇是大学于CSDN所写,移植过来 借助 pyautogui 实现鼠标键盘控制,实现一些自动化操作 pyautogui是一个可以控制鼠标和键盘的python库,类似的还有pywin32。 pyautogui的安装 pip3 install python3-xlib 依赖库 sudo apt-get install scrot 依赖库 pip3 install pyautogui python3下安装pyautogui库 import pyautogui 引入该库 pyautogui的使用 保护措施 为了防止pyautogui夺取了鼠标的控制权导致我们无法关掉该程序,它提供了一个保护措施,即把鼠标移到最左上角,此时程序报错退出; 默认FAILSAFE=True,保护模式开启 FAILSAFE = False 关闭保护措施 获取屏幕信息 size() 获取当前屏幕的分辨率,如(1920,1080)二元组 注意:屏幕左上角是原点(0,0),整个屏幕相当于第一象限 position() 获取鼠标当前坐标 onScreen(x,y) 判断点(x,y)是否在屏幕范围内,如负值一定返回False 移动鼠标 moveTo(x,y,duration=0.25) 用0.25s的时间将鼠标移到(x,y)位置 moveRel(x,y,duration=0.25) 以鼠标所在位置为原点,将鼠标移动到(x,y)处 下面代码,让鼠标在指定位置绕一个正方形转动10圈 import pyautogui for i in range(10): pyautogui.moveTo(300, 300, duration=0.25) pyautogui.moveTo(400, 300, duration=0.25) pyautogui.moveTo(400, 400, duration=0.25) pyautogui.moveTo(300, 400, duration=0.25) 下面代码,让鼠标绕当前位置绕正方形转10圈 import pyautogui for i in range(10): pyautogui.moveRel(100, 0, duration=0.25) pyautogui.moveRel(0, 100, duration=0.25) pyautogui.moveRel(-100, 0, duration=0.25) pyautogui.moveRel(0, -100, duration=0.25) 鼠标事件 点击鼠标 click(x, y, button='left', click=3, interval=0.5) button有3个选项:left,middle,right,不加则默认点击鼠标左键 click表示单击次数 interval表示每次单击之间的时间间隔 click()函数实际由mouseDown()和mouseUp()组成,即按下和松开; pyautogui.doubleClick() 鼠标双击,其实就是执行两次click()函数。 pyautogui.rightClick() 右击 pyautogui.middleClick() 中击 鼠标滚轮 scroll(200) 控制鼠标的滚轮,正值上滚,负值下滚 拖拽鼠标 dragTo() dragRel() 按下鼠标,并拖拽到指定位置,用法同moveTo(),moveRel() 窗口截图处理、寻找目标按钮 截图功能 im = screenshot( region=(x,y,width,heigth) ) 截取以(x,y)为左上角且指定宽高的区域,不加参数,默认截取整个屏幕 im.getpixel( (x,y) ) 获取指定位置的像素,是一个三元组(注意输入格式) pixelMatchesColor(x,y,(R,G,B)) 判断(x,y)处的像素是否等于RGB im.save('xx.png') 保存为x.png 寻找按钮 locateOnscreen('xx.png') 寻找屏幕中和xx.png一样的图标位置,是一个四元组 click( center( locateOnscreen('xx.png') ) ) 点击该图标的中心,center用来获取图标中心点的坐标 比如把网易云音乐的应用存为music.png,上述就执行自动点击网易云的图标"},{"title":"【Python】tkinter","date":"2019-07-24T05:25:01.000Z","path":"posts/1AMJ55J/","text":"python + tkinter 实现绘图板 备注 这篇是大学于CSDN所写,移植过来 背景是课程作业 python+tkinter实现绘图板 github项目地址: 593413198/DrawingBoard 创建时间: 2019/5/10 搭建环境: Ubuntu 18.04 + python 3.6 + tkinter 使用指南: 上方菜单提供了用户界面的所有操作; 下方文本框提供了命令行接口,按\"执行命令\"按钮即可执行指令 最底部显示canvas上的所有图元,包括 “类型”+“ID”** 实现功能: 重置画布 resetCanvas width height 删除canvas所有元素,然后重置窗口大小即可(canvas是始终铺满窗口的) 保存画布 saveCanvas name.bmp 因为linux下通过截屏实现,所有保存的画布实际是整个屏幕 设置画笔颜色 setColor R G B tkinter内使用16进制,要转换 设置画笔粗细 setWidth width 绘制线段 drawLine id x1 y1 x2 y2 algorithm Bresenham DDA 绘制椭圆 drawEllipse id x y rx ry 中点圆生成算法 绘制多边形 drawPolygon id n x1 y1 x2 y2 … xn yn 通过Bresenham绘制多条线段即可 对图元平移 translate id dx dy 对图元旋转 rotate id x y r 对图元缩放 scale id x y s 代码架构: 第三方库 from tkinter import * import tkinter.messagebox as messagebox # 弹窗 import pyscreenshot as ImageGrab # 截图功能 for linux #from PIL import ImageGrab # 截图功能 for MacOS and Windows 窗口及菜单 window = Tk() # 主窗口window canvas = Canvas(window, ...) # 主画布canvas menu = Menu(windows) # 顶部主菜单menu entry = Entry(..) # 接受命令行指令的输入 button = Button(..) # 读入指令并执行 全局变量 Type_draw # 记录画图的类型 1:直线 2:点 3:椭圆 Flag_draw # 记录是否允许画图 0:不允许 1:允许 Color_pen # 画笔颜色 采用16进制表示 Width_pen # 画笔粗细 鼠标事件 onLeftDown() # 鼠标左键单击,允许开始画图 onLeftMove() # 鼠标左键拖动,开始画图 onLeftUp() # 鼠标左键松开,停止画图 绘图算法 Bresenham() # bresenham算法画直线 Draw_ellipse() # 中点圆生成算法画椭圆 相关函数 toHex() # 将RGB转化成十六进制色彩表示 rotate() # 旋转指定点 execute() # 读入命令行并执行"},{"title":"【Algorithm】Graphs","date":"2019-07-24T05:23:40.000Z","path":"posts/2W1G2XP/","text":"备注 这篇是大学于CSDN所写,移植过来 图算法的总结和实现 1.0 图的表示 图通常用两种数据结构表示:邻接矩阵->稠密图、邻接链表->稀疏图 对于图 G = (V, E) ,V是点集,E是边集,|V| |E|分别表示点、边的数目 稀疏图:边数很少的图 稠密图:边数接近|V|^2的图(一个图边数最多是点数的平方,只考虑单边图) 邻接矩阵 维护一个n*n的数组,n是图的点数|V| 根据图的性质,数组对角线都为0,上下半角都对称 矩阵存储的特点,不论边数|E|多大,永远都开|V|*|V|大小的数组 因为矩阵的大小取决于|V|,所以当边数足够多的时候,采用邻接矩阵表示对空间的利用率最高 邻接链表 每个节点维护一个链表,储存所有与它邻接的点 所有链表的头部储存在一个数组中 数组空间O(|V|),链表空间是O(|E|)、最坏空间是O(|V|^2) 因为链表大小取决于|E|,所以当边数足够小时,采用邻接链表表示 python代码如下: class Graph(): ''' 无向图,点下标从0开始 G[x][y] = 1 存在边xy ; G[x][y] = 0 不存在边xy ''' def __init__(self,n): # 声明一个有n个点的图G self.n = n # |V|点数 self.node = [i for i in range(n)] # 所有点 self.G = [[0]*n for i in range(n)] # 邻接矩阵 self.d = [0]*n # 记录宽搜中每个点到起点的距离 self.order = [] # 记录深搜的顺序 def add(self,x,y,w=0): # 添加一条(x,y)的无向边 # 不输入边权重的情况默认为1 self.G[x][y] = 1 if not w else w self.G[y][x] = 1 if not w else w def remove(self,x,y): # 删除一条(x,y)的无向边 self.G[x][y] = 0 self.G[y][x] = 0 def neighbour(self,x): # 返回与x邻接的点 # rtype: 列表 ans = [] for i in range(self.n): if self.G[x][i]: ans.append(i) return ans def isEdge(self,x,y): # 判断点x,y是否邻接 return self.G[x][y] 1.1 图的宽搜和深搜 BFS:广度优先搜索 采用优先队列,先将起点x入队列 不断从队列中取出元素,访问其所有的邻点,再将其中未访问过的邻点加入队列 当队列空时搜索结束 DFS:深度优先搜索 深搜的顺序不是唯一的;从任一节点开始都可以 对某一节点,只要找到一个邻点,就对邻点继续搜索邻点的邻点 理论上的深搜,是对所有节点执行一次上一步骤,这样保证了所有节点都能被搜索完,实际操作中可以维护一个set来记录已经访问过的节点 以1为起点的BFS路径: 1 2 5 3 4 以1为起点的DFS路径: 1 2 3 4 5 python代码如下: # 广度优先搜索 def BFS(self,x): # 从点x开始宽搜 vis = [0]*self.n # 记录节点是否搜索过 Q = [x] while Q: v = Q.pop(0) vis[v] = 1 for i in self.neighbour(v): # neighbour表示v的邻点的集合 if not vis[i]: Q.append(i) self.d[i] = self.d[v] + 1 vis[i] = 1 # 深度优先搜索 def DFS(self,x): # 从点x开始深搜 vis = [0]*self.n # 记录节点是否搜索过 def dfs(node): vis[node] = 1 self.order.append(node) for i in self.neighbour(node): if not vis[i]: dfs(i) dfs(x) for i in self.node: if not vis[i]: dfs(i) 1.2 图的最短路径算法 单源最短路径问题:求出某一节点到其他所有节点的最短距离 Dijkstra算法 Bellman-Ford算法 所有节点对最短路径问题:求出每个节点到其他所有节点的最短距离 Floyd算法 Dijkstra算法 给定某一起点x,计算它到所有节点的最短距离 dist[v] 记录起点到点v的最短距离,即最终返回结果 S 一个集合,存放已经处理过的节点 初始状态:令起点的dist为0,它的邻点的dist就是两点距离,其他节点的dist赋为无穷大 循环过程:不断取当前dist最小的节点v出来,对它的邻点i做松弛操作,当前最小节点视为已处理节点,放入集合S 松弛: if dist[i] > dist[v] + G[i][v] { dist[i] = dist [v] + G[i][v] } 终止状态:所有节点都已放入S,即都处理完毕 # dijkstra算法的python实现 def dijkstra(self,x): # dijkstra算法求x到所有节点的最短路径,采用贪心策略 dist = [99999]*self.n S = set([x]) # 记录已经找到最短路径的节点 for i in self.neighbour(x): dist[i] = self.G[x][i] dist[x] = 0 while len(S) != self.n: # 找到当前距离最小的点,这里用到最下优先队列 MIN = 99999 MIN_i = 0 for i in range(self.n): if dist[i] < MIN and i not in S: MIN = dist[i] MIN_i = i # 松弛relax操作 S.add(MIN_i) for i in self.neighbour(MIN_i): if dist[i] > dist[MIN_i] + self.G[i][MIN_i]: dist[i] = dist[MIN_i] + self.G[i][MIN_i] return dist"},{"title":"【CSDN】进程和线程的深入理解","date":"2019-04-24T16:14:40.000Z","path":"posts/2TKZZE3/","text":"备注 这篇是大学于CSDN所写,移植过来 回头看有点肤浅,但是这种类比学习的思维还是很难得呀 下面是抽象类比: 单CPU:一台单核处理器计算机 = 一个车间; 多CPU:一台多核处理器计算机 = 一座工厂; 进程:一个车间 = 一个进程; (即一个运行的程序) 多进程:一座工厂可以同时运行多个车间; CPU和进程:单CPU只能同时运行单个进程,多CPU可以同时运行多个进程。 线程:车间内一个工人 = 一个线程; 进程与线程:一个进程可以包括多个线程。 线程间内存共享:车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。 一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。 内存安全:可是,每个车间容纳大小不同,有的最多只能容纳一个人。车间人满的时候,其他人就进不去了。 一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。 互斥锁:一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。 这就叫\"互斥锁\"–Mutex,防止两个线程同时读写某一块内存区域。 信号量:这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。 这种做法叫做\"信号量\"(Semaphore),用来保证多个线程不会互相冲突。 锁和信号量:不难看出,互斥锁是信号量的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。 操作系统的资源分配与调度逻辑 以多进程形式,允许多个任务同时运行; 以多线程形式,允许单个任务分成不同的部分运行; 提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。 下面是严谨的解释: 进程 进程是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间,至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。 线程 线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。 进程和线程的关系 线程是进程的一部分 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程 进程和线程的区别 理解它们的差别,我从资源使用的角度出发。(所谓的资源就是计算机里的中央处理器,内存,文件,网络等等) 根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位 开销方面:每个进程都有独立的代码和数据空间(程序上下文),进程之间切换开销大;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行) 内存分配:系统为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源 包含关系:线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程"}]}