[{"content":"给定一个整数数组 coordinates ，其中 coordinates[i] = [x, y] ， [x, y] 表示横坐标为 x、纵坐标为 y 的点。请你来判断，这些点是否在该坐标系中属于同一条直线上。\n示例 1：\n输入：coordinates = [[1,2],[2,3],[3,4],[4,5],[5,6],[6,7]] 输出：true 示例 2：\n输入：coordinates = [[1,1],[2,2],[3,4],[4,5],[5,6],[7,7]] 输出：false 提示：\n2 \u0026lt;= coordinates.length \u0026lt;= 1000 coordinates[i].length == 2 -10^{4} \u0026lt;= coordinates[i][0], coordinates[i][1] \u0026lt;= 10^{4} coordinates 中不含重复的点 最开始我想的很简单嘛，单纯的一个数学公式就秒了\nclass Solution { public: bool checkStraightLine(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; coordinates) { int r = (coordinates[0][1] - coordinates[1][1]) / (coordinates[0][0] - coordinates[1][0]); for(int i=2;i\u0026lt;coordinates.size();i++)if((coordinates[i][1] - coordinates[i-1][1]) / (coordinates[i][0] - coordinates[i-1][0]) != r)return false; return true; } }; 交上去发现 devide by zero（太经典了）所以得换种思路：\n解决除以零（以及整数除法精度丢失）的最佳方法是将除法转换为乘法（交叉相乘）。\n斜率公式为：\n$$ \\frac{y_1 - y_0}{x_1 - x_0} = \\frac{y_i - y_0}{x_i - x_0} $$转换为乘法形式即可完全避免除法：\n$$ (y_1 - y_0) \\times (x_i - x_0) = (x_1 - x_0) \\times (y_i - y_0) $$修改后的代码：\nclass Solution { public: bool checkStraightLine(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; coordinates) { int dx = coordinates[1][0] - coordinates[0][0]; int dy = coordinates[1][1] - coordinates[0][1]; for (int i = 2; i \u0026lt; coordinates.size(); i++) { int cur_dx = coordinates[i][0] - coordinates[0][0]; int cur_dy = coordinates[i][1] - coordinates[0][1]; // 交叉相乘判断斜率是否相等 if (dy * cur_dx != dx * cur_dy) { return false; } } return true; } }; 时间与空间复杂度： 时间复杂度为 $O(N)$，空间复杂度为 $O(1)$。\n","permalink":"https://blog.muwinds.com/archives/401/","summary":"\u003cp\u003e给定一个整数数组 \u003ccode\u003ecoordinates\u003c/code\u003e ，其中 \u003ccode\u003ecoordinates[i] = [x, y]\u003c/code\u003e ， \u003ccode\u003e[x, y]\u003c/code\u003e 表示横坐标为 \u003ccode\u003ex\u003c/code\u003e、纵坐标为 \u003ccode\u003ey\u003c/code\u003e 的点。请你来判断，这些点是否在该坐标系中属于同一条直线上。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e示例 1：\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://assets.leetcode.cn/aliyun-lc-upload/uploads/2019/10/19/untitled-diagram-2.jpg\"\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e输入：coordinates = [[1,2],[2,3],[3,4],[4,5],[5,6],[6,7]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e输出：true\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e示例 2：\u003c/strong\u003e\u003c/p\u003e","title":"leetcode 复健 1232. 缀点成线"},{"content":"说来感慨，大一那会儿这题都是有手就行的，现在手撕都费劲了，有个思路就了不得了。\n给你一个 m 行 n 列的矩阵 matrix ，请按照 顺时针螺旋顺序 ，返回矩阵中的所有元素。\n示例 1：\n输入：matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出：[1,2,3,6,9,8,7,4,5] 示例 2：\n输入：matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 输出：[1,2,3,4,8,12,11,10,9,5,6,7] 提示：\nm == matrix.length n == matrix[i].length 1 \u0026lt;= m, n \u0026lt;= 10 -100 \u0026lt;= matrix[i][j] \u0026lt;= 100 这个题还是大模拟，但是你需要考虑怎么样别重复遍历，这里我死活没想到可以加个 visited 数组……\n代码：\nclass Solution { public: vector\u0026lt;int\u0026gt; spiralOrder(vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt;\u0026amp; matrix) { int dire[4][2] = {{0,1},{1,0},{0,-1},{-1,0}}; if(matrix.size() == 0 || matrix[0].size() == 0)return {}; int rows = matrix.size(),columns = matrix[0].size(); // 设置 visited 数组存放 vector\u0026lt;vector\u0026lt;bool\u0026gt;\u0026gt; visited (rows, vector\u0026lt;bool\u0026gt;(columns)); // 存放遍历顺序 vector\u0026lt;int\u0026gt; order(rows*columns); int row = 0,column = 0; int dire_index = 0; for (int i=0;i\u0026lt;rows*columns;i++){ order[i] = matrix[row][column]; visited[row][column] = true; int nextRow = row + dire[dire_index][0], nextColumn = column + dire[dire_index][1]; if(nextRow \u0026lt; 0 || nextRow \u0026gt;= rows || nextColumn \u0026lt; 0 || nextColumn \u0026gt;= columns || visited[nextRow][nextColumn]){ dire_index = (dire_index + 1) % 4; } row += dire[dire_index][0]; column += dire[dire_index][1]; } return order; } }; ","permalink":"https://blog.muwinds.com/archives/395/","summary":"\u003cp\u003e说来感慨，大一那会儿这题都是有手就行的，现在手撕都费劲了，有个思路就了不得了。\u003c/p\u003e\n\u003cp\u003e给你一个 \u003ccode\u003em\u003c/code\u003e 行 \u003ccode\u003en\u003c/code\u003e 列的矩阵 \u003ccode\u003ematrix\u003c/code\u003e ，请按照 \u003cstrong\u003e顺时针螺旋顺序\u003c/strong\u003e ，返回矩阵中的所有元素。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e示例 1：\u003c/strong\u003e\u003c/p\u003e","title":"leetcode 复健：54螺旋矩阵"},{"content":"在无限的平面上，机器人最初位于 (0, 0) 处，面朝北方。注意:\n北方向 是y轴的正方向。 南方向 是y轴的负方向。 东方向 是x轴的正方向。 西方向 是x轴的负方向。 机器人可以接受下列三条指令之一：\n\u0026quot;G\u0026quot;：直走 1 个单位 \u0026quot;L\u0026quot;：左转 90 度 \u0026quot;R\u0026quot;：右转 90 度 机器人按顺序执行指令 instructions，并一直重复它们。\n只有在平面中存在环使得机器人永远无法离开时，返回 true。否则，返回 false。\n示例 1：\n输入：instructions = \u0026#34;GGLLGG\u0026#34; 输出：true 解释：机器人最初在(0,0)处，面向北方。 “G”:移动一步。位置:(0,1)方向:北。 “G”:移动一步。位置:(0,2).方向:北。 “L”:逆时针旋转90度。位置:(0,2).方向:西。 “L”:逆时针旋转90度。位置:(0,2)方向:南。 “G”:移动一步。位置:(0,1)方向:南。 “G”:移动一步。位置:(0,0)方向:南。 重复指令，机器人进入循环:(0,0)——\u0026gt;(0,1)——\u0026gt;(0,2)——\u0026gt;(0,1)——\u0026gt;(0,0)。 在此基础上，我们返回true。 示例 2：\n输入：instructions = \u0026#34;GG\u0026#34; 输出：false 解释：机器人最初在(0,0)处，面向北方。 “G”:移动一步。位置:(0,1)方向:北。 “G”:移动一步。位置:(0,2).方向:北。 重复这些指示，继续朝北前进，不会进入循环。 在此基础上，返回false。 示例 3：\n输入：instructions = \u0026#34;GL\u0026#34; 输出：true 解释：机器人最初在(0,0)处，面向北方。 “G”:移动一步。位置:(0,1)方向:北。 “L”:逆时针旋转90度。位置:(0,1).方向:西。 “G”:移动一步。位置:(- 1,1)方向:西。 “L”:逆时针旋转90度。位置:(- 1,1)方向:南。 “G”:移动一步。位置:(- 1,0)方向:南。 “L”:逆时针旋转90度。位置:(- 1,0)方向:东方。 “G”:移动一步。位置:(0,0)方向:东方。 “L”:逆时针旋转90度。位置:(0,0)方向:北。 重复指令，机器人进入循环:(0,0)——\u0026gt;(0,1)——\u0026gt;(- 1,1)——\u0026gt;(- 1,0)——\u0026gt;(0,0)。 在此基础上，我们返回true。 提示：\n1 \u0026lt;= instructions.length \u0026lt;= 100 instructions[i] 仅包含 'G', 'L', 'R' 我最开始思考的是要不要用 set（大脑短路……）于是脑淤血代码如下：\nclass Solution { public: struct P{ int x; int y; }; bool isRobotBounded(string instructions) { int dire=0; P pl = {0,0}; set\u0026lt;P\u0026gt; s; for(int i=0;i\u0026lt;instructions.size();i++){ if(instructions[i] == \u0026#39;L\u0026#39;){ dire++;dire = dire % 4; } if(instructions[i] == \u0026#39;R\u0026#39;){ dire--; if(dire\u0026lt;0)dire += 4; } if(instructions[i] == \u0026#39;G\u0026#39;){ if(dire == 0)pl.y++; if(dire == 1)pl.x++; if(dire == 2)pl.y--; if(dire == 3)pl.x--; } if(s.find(pl) != s.end())s.insert(pl); else return true; } return false; } }; 这玩意儿肯定过不了编译的啊，后来思考了一下只需跟踪终点坐标和方向即可，时空复杂度均达到最优（后来看了一下，还是没读明白题，我以为的是只要有环就返回 true，实际上应该是有(0,0)的环才返回true……）\n代码如下：\n#include \u0026lt;string\u0026gt; class Solution { public: bool isRobotBounded(std::string instructions) { // 方向向量：0: 北, 1: 东, 2: 南, 3: 西 int dx[4] = {0, 1, 0, -1}; int dy[4] = {1, 0, -1, 0}; int x = 0, y = 0; int dir = 0; // 初始朝北 for (char c : instructions) { if (c == \u0026#39;G\u0026#39;) { x += dx[dir]; y += dy[dir]; } else if (c == \u0026#39;L\u0026#39;) { dir = (dir + 3) % 4; // 向左转 90 度 } else if (c == \u0026#39;R\u0026#39;) { dir = (dir + 1) % 4; // 向右转 90 度 } } // 满足条件：回到原点 或 方向改变 return (x == 0 \u0026amp;\u0026amp; y == 0) || (dir != 0); } }; ","permalink":"https://blog.muwinds.com/archives/392/","summary":"\u003cp\u003e在无限的平面上，机器人最初位于 \u003ccode\u003e(0, 0)\u003c/code\u003e 处，面朝北方。注意:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e北方向\u003c/strong\u003e 是y轴的正方向。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e南方向\u003c/strong\u003e 是y轴的负方向。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e东方向\u003c/strong\u003e 是x轴的正方向。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e西方向\u003c/strong\u003e 是x轴的负方向。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e机器人可以接受下列三条指令之一：\u003c/p\u003e","title":"leetcode 复健：1041"},{"content":"一面：\n面试问项目啥的直接跳过，说说问的技术问题：\n浏览器里除了会保存 cookie 信息以外还会保存什么其他信息吗？ 这个问题我当时答得磕磕绊绊的，只说了有个 local storage 然后还会保存一些 css 这种信息用来提高多次加载的速度以外没回答出什么。正常的回答如下：\n除了 Cookie，现代浏览器在本地存储的信息非常丰富，涵盖了网页应用数据、用户浏览偏好、网络缓存以及安全凭证等多个维度。\n按用途和技术架构，主要可以分为以下几大类：\n1. Web 存储机制（Web Storage \u0026amp; Databases） 这些是前端开发者用来在本地存储应用数据的技术，存取容量远大于传统的 Cookie（Cookie 通常限制为 4KB 左右）。\nLocalStorage（本地存储）： 特点： 容量大（通常 5MB~10MB），永久保存，除非手动清理或通过代码删除，关闭浏览器也不会关掉。 常见用途： 存储用户的界面主题设置（深色/浅色模式）、未提交的草稿内容、用户偏好设置等。 SessionStorage（会话存储）： 特点： 容量与 LocalStorage 类似，但生命周期仅限于当前标签页。关闭标签页或浏览器后数据即被清空。 常见用途： 存储单次访问的临时状态，例如多步骤表单的中间数据、单页应用（SPA）的临时页面状态。 IndexedDB（索引数据库）： 特点： 一种在浏览器端运行的 NoSQL 事务型数据库，支持存储大量的结构化数据（甚至是二进制大对象 Blob），提供索引和高效查询，容量通常按磁盘剩余空间的百分比分配（可达几百 MB 甚至 GB）。 常见用途： 离线 Web 应用（如 Google Docs 离线编辑）、复杂的前端数据缓存、Web 游戏数据存储。 Web SQL（已废弃）： 旧版浏览器使用的关系型数据库存储方式，目前大多数主流浏览器已停止支持，全面转向 IndexedDB。 2. 缓存与离线文件（Cache \u0026amp; Offline Assets） 为了提高加载速度、节省网络流量，浏览器会把静态资源保存在本地。\nHTTP 磁盘缓存（Disk Cache / Memory Cache）： 保存网页的 HTML、CSS、JavaScript 文件、图片、字体文件以及音视频。 Memory Cache 保存在内存中（随标签页关闭而释放），Disk Cache 保存在硬盘中。 Service Worker 缓存（Cache API）： 现代 Progressive Web App (PWA) 的核心组件，允许脚本拦截网络请求，并将特定的静态资源和 API 响应手动缓存到本地，实现完全离线运行。 3. 用户个人数据与自动化填充 为了方便用户日常使用，浏览器自带的管理器会保存大量个人信息（通常经过加密）。\n密码与凭证（Password Manager）： 保存的账号密码、Passkeys（通行密钥），通常会自动同步到用户的云端账号（如 Google 账号、iCloud）。 自动填充数据（Autofill Data）： 姓名、手机号、电子邮箱、收货地址等。 支付信息： 保存的信用卡/借记卡信息（通常敏感安全码 CVC/CVV 不会保存，只保存卡号和过期时间）。 4. 浏览历史与用户行为痕迹 这些用于支撑浏览器的日常功能，如历史回溯、书签同步等。\n历史记录（Browsing History）： 访问过的网址（URL）、页面标题、访问时间及频次。 下载历史（Download History）： 通过浏览器下载的文件列表及本地保存路径（不包含文件本身内容）。 书签与收藏夹（Bookmarks）： 用户保存的网页地址和分类层级。 恢复会话（Session Restore）： 上次关闭浏览器时打开的标签页列表及历史前进/后退栈，用于“重新打开上次关闭的标签页”。 5. 权限管理与网站配置 网站权限设置（Site Permissions）： 每个网站是否被允许使用地理位置、摄像头、麦克风、通知、剪贴板、弹出式窗口等。 站点偏好（Site Preferences）： 针对特定网站设置的页面缩放比例、默认字体大小、自动翻译选项等。 6. 安全、标识符与底层凭证 TLS/SSL 客户端证书（Client Certificates）： 在某些高安全要求的网站（如网银、企业内网）使用的身份认证证书。 HSTS 列表（HTTP Strict Transport Security）： 记录哪些网站必须通过安全的 HTTPS 连接访问，防止降级攻击。 浏览器指纹与特征标志（Device/Browser Identifiers）： 某些特定的 DRM（数字版权管理）凭证（例如播放 Netflix 高清视频需要的 Widevine 凭证），以及浏览器 Flags 设置（chrome://flags 等实验性功能状态）。 在浏览器 F12 开发者工具中，你可以看到上面有几个选项 打红圈的 Fetch/XHR 具体是什么呢？\n这问题当时问的我也有点懵，我点开看了一下，直接说这块我也不太熟，不过我看 Fetch/XHR 这里面都是一些后端的接口数据。\n正解：\n在浏览器的 F12 开发者工具 中，Network（网络） 标签页下的 Fetch/XHR 选项卡，是专门用来监听和展示网页在后台与服务器进行数据交互的请求列表。\n简单来说，这里展示的就是网页“偷偷”在后台发送和接收的数据，而不需要刷新整个页面。\n1. Fetch 和 XHR 分别代表什么？ 它们都是前端 JavaScript 用来向服务器异步请求数据的技术手段（通常被称为 AJAX）：\nXHR（XMLHttpRequest）： 老牌技术。早在 2000 年左右就已诞生，是早年实现“无刷新更新页面”的功臣。 它的语法相对冗长和古老，但在很多老旧项目或底层封装库（如著名的 axios 库）中依然被大量使用。 Fetch： 现代标准。是 HTML5 时代推出的新 API，用于替代传统的 XHR。 基于现代 JavaScript 的 Promise 机制设计，代码更加简洁、优雅，是目前新项目开发的主流选择。 在 F12 的 Fetch/XHR 筛选器下，浏览器会把通过这两种 API 发出的网络请求集中在一起展示。\n2. 它们和普通请求（如 Doc、CSS、JS）有什么区别？ 当你在浏览器地址栏输入网址并按回车时：\nDoc（文档请求）： 浏览器去下载主 HTML 文件。 CSS / JS / Img（静态资源请求）： 浏览器解析 HTML 时，顺便去下载引用的样式表、脚本和图片。下载完后，页面就渲染出来了。 而 Fetch/XHR（动态数据请求）：\n触发时机： 通常是在页面加载完成后，由用户的操作（比如点击“加载更多”、提交表单、切换 Tab）或定时器触发。 传输内容： 它们通常不传输大块的页面样式或图片，而是传输结构化数据（最常见的是 JSON 格式，也有 XML 或纯文本）。 页面体验： 拿到数据后，JavaScript 会局部更新页面的某一部分（例如刷出来 10 条新的评论），而整个浏览器页面不会闪烁或重新加载。 3. 常见的使用场景 开发者前端调试： 检查调用的后端 API 接口是否正常、入参（Payload）有没有传错、服务器返回的数据结构对不对。 网络爬虫/数据采集： 很多动态渲染的网站，其真实数据都是通过 Fetch/XHR 接口返回 JSON 格式的数据。直接抓取这里的接口比解析 HTML 页面效率高得多。 接口逆向/分析： 分析网页前端与服务端的通信逻辑、身份验证方式（如 Bearer Token、自定义 Header）等。 哎！你别说，虽然这俩问题我都答得稀烂，不过和面试官整体聊项目、聊其他的都很愉快，一面结束10分钟HR告诉我一面通过准备二面。\n二面：\n二面主要问的是人生理想，然后就是为啥选择我们单位，我人还是比较懵圈的（因为我啥都玩，所以我也没啥想法，有啥单位面我就过去了）不过面试官说挺好的，20岁的年轻人懵懂转圈是很正常的事情。\n问的唯一的技术相关的问题就是 Python 基础：1.Python 有几种数据结构？你记得啥说啥就行 2.既然你说到了 dict 和 list，讲一下他们的区别\n这俩问题都太ez了，答出来之后就没啥了，二面通过一小时之后回家的路上就HR就电话通知我稍后发offer聊待遇了。\n","permalink":"https://blog.muwinds.com/archives/390/","summary":"\u003cp\u003e一面：\u003c/p\u003e\n\u003cp\u003e面试问项目啥的直接跳过，说说问的技术问题：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e浏览器里除了会保存 cookie 信息以外还会保存什么其他信息吗？\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e这个问题我当时答得磕磕绊绊的，只说了有个 local storage 然后还会保存一些 css 这种信息用来提高多次加载的速度以外没回答出什么。正常的回答如下：\u003c/p\u003e","title":"Inagora 爬虫实习面试记录"},{"content":"最近开始使用 pi 的 coding agent，发现没法通过 shift+tab 来调整 effort level 到 xhigh 和 max，pi 的 settings 里也没有\n让 pi 自己解决，很快就好了\n直接把 thinkingLevelMap 放在模型对象下，并且在 max 键上也做映射（否则 max 级别不会出现）：\n{ \u0026#34;id\u0026#34;: \u0026#34;your-model-id\u0026#34;, \u0026#34;reasoning\u0026#34;: true, \u0026#34;thinkingLevelMap\u0026#34;: { \u0026#34;minimal\u0026#34;: \u0026#34;high\u0026#34;, \u0026#34;low\u0026#34;: \u0026#34;high\u0026#34;, \u0026#34;medium\u0026#34;: \u0026#34;high\u0026#34;, \u0026#34;high\u0026#34;: \u0026#34;high\u0026#34;, \u0026#34;xhigh\u0026#34;: \u0026#34;max\u0026#34;, \u0026#34;max\u0026#34;: \u0026#34;max\u0026#34; } } 然后 /reload 就好了\n","permalink":"https://blog.muwinds.com/archives/389/","summary":"\u003cp\u003e最近开始使用 pi 的 coding agent，发现没法通过 shift+tab 来调整 effort level 到 xhigh 和 max，pi 的 settings 里也没有\u003c/p\u003e\n\u003cp\u003e让 pi 自己解决，很快就好了\u003c/p\u003e\n\u003cp\u003e直接把 thinkingLevelMap 放在模型对象下，并且在 max 键上也做映射（否则 max 级别不会出现）：\u003c/p\u003e","title":"pi agent 不显示 xhigh 和 max 的解决方法"},{"content":"给定两个字符串 s 和 t ，它们只包含小写字母。\n字符串 t 由字符串 s 随机重排，然后在随机位置添加一个字母。\n请找出在 t 中被添加的字母。\n示例 1：\n输入：s = \u0026#34;abcd\u0026#34;, t = \u0026#34;abcde\u0026#34; 输出：\u0026#34;e\u0026#34; 解释：\u0026#39;e\u0026#39; 是那个被添加的字母。 示例 2：\n输入：s = \u0026#34;\u0026#34;, t = \u0026#34;y\u0026#34; 输出：\u0026#34;y\u0026#34; 提示：\n0 \u0026lt;= s.length \u0026lt;= 1000 t.length == s.length + 1 s 和 t 只包含小写字母 最开始没看清楚 length 的条件，看到随机重排想当然就直接用 unordered_sets 了\nclass Solution { public: char findTheDifference(string s, string t) { unordered_set\u0026lt;char\u0026gt; st; for(int i=0;i\u0026lt;s.size();i++){ st.insert(s[i]); } for(int i=0;i\u0026lt;t.size();i++){ auto it = st.find(t[i]); if(it == st.end())return t[i]; } } }; 后来发现不对，这可以是重复的字母……\n那就想到计数法，每碰到一个字母就计上去，最后一减，多出来的就是加上去的。\nclass Solution { public: char findTheDifference(string s, string t) { vector\u0026lt;int\u0026gt; cnt(26,0); char ans; for(int i=0;i\u0026lt;s.size();i++){ cnt[s[i]-\u0026#39;a\u0026#39;]++; } for(int i=0;i\u0026lt;t.size();i++){ cnt[t[i]-\u0026#39;a\u0026#39;]--; if(cnt[t[i]-\u0026#39;a\u0026#39;] \u0026lt; 0)ans = t[i]; } return ans; } }; ","permalink":"https://blog.muwinds.com/archives/387/","summary":"\u003cp\u003e给定两个字符串 \u003ccode\u003es\u003c/code\u003e 和 \u003ccode\u003et\u003c/code\u003e ，它们只包含小写字母。\u003c/p\u003e\n\u003cp\u003e字符串 \u003ccode\u003et\u003c/code\u003e 由字符串 \u003ccode\u003es\u003c/code\u003e 随机重排，然后在随机位置添加一个字母。\u003c/p\u003e\n\u003cp\u003e请找出在 \u003ccode\u003et\u003c/code\u003e 中被添加的字母。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e示例 1：\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e输入：s = \u0026#34;abcd\u0026#34;, t = \u0026#34;abcde\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e输出：\u0026#34;e\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e解释：\u0026#39;e\u0026#39; 是那个被添加的字母。\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e示例 2：\u003c/strong\u003e\u003c/p\u003e","title":"Leetcode 复健2: 389"},{"content":"之前面一个中小厂不太顺利，回过头来发现我 vibe coding 太多导致基本的数据结构已经还给院长了，自己手写一个费劲的很，打算从 easy 难度的恢复一下，重走长征路\n1768. 交替合并字符串\n给你两个字符串 word1 和 word2 。请你从 word1 开始，通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长，就将多出来的字母追加到合并后字符串的末尾。\n返回 合并后的字符串 。\n示例 1：\n输入：word1 = \u0026#34;abc\u0026#34;, word2 = \u0026#34;pqr\u0026#34; 输出：\u0026#34;apbqcr\u0026#34; 解释：字符串合并情况如下所示： word1： a b c word2： p q r 合并后： a p b q c r 示例 2：\n输入：word1 = \u0026#34;ab\u0026#34;, word2 = \u0026#34;pqrs\u0026#34; 输出：\u0026#34;apbqrs\u0026#34; 解释：注意，word2 比 word1 长，\u0026#34;rs\u0026#34; 需要追加到合并后字符串的末尾。 word1： a b word2： p q r s 合并后： a p b q r s 示例 3：\n输入：word1 = \u0026#34;abcd\u0026#34;, word2 = \u0026#34;pq\u0026#34; 输出：\u0026#34;apbqcd\u0026#34; 解释：注意，word1 比 word2 长，\u0026#34;cd\u0026#34; 需要追加到合并后字符串的末尾。 word1： a b c d word2： p q 合并后： a p b q c d 提示：\n1 \u0026lt;= word1.length, word2.length \u0026lt;= 100 word1 和 word2 由小写英文字母组成 最开始的思路是既然是交替，那就直接交替 insert，最后看哪个长就把剩下的插进去就行，脑淤血代码如下：\nclass Solution { public: string mergeAlternately(string word1, string word2) { string ans = word1; for(int i=0;i\u0026lt;word1.size();i++)if(i\u0026lt;word2.size())ans.insert(i*2+1,1,word2[i]); if(word2.size()\u0026gt;word1.size())ans.append(word2.substr(word1.size(),word2.size()-word1.size())); return ans; } }; 提交上去，嘛，虽然AC了，但是不尽如人意：\n说实话，这个时间复杂度初看以为是 $O(N)$，后来思考了一下 insert 的问题发现不对劲。在 C++ 中，std::string::insert 在中间插入字符是一个 $O(L)$ 的操作（$L$ 是当前字符串的长度），因为插入点后面的所有字符都必须在内存里往后挪动一位。因此，这个脑淤血的循环部分的总体复杂度高达：$O(M \\times (M + N)) = O(M^2 + MN)$\n其实改成双指针就好很多了\nclass Solution { public: string mergeAlternately(string word1, string word2) { int m=word1.size(),n=word2.size(); int i=0,j=0; string ans; while(i\u0026lt;m || j\u0026lt;n){ if(i\u0026lt;m){ ans.push_back(word1[i]); i++; } if(j\u0026lt;n){ ans.push_back(word2[j]); j++; } } return ans; } }; ","permalink":"https://blog.muwinds.com/archives/385/","summary":"\u003cp\u003e之前面一个中小厂不太顺利，回过头来发现我 vibe coding 太多导致基本的数据结构已经还给院长了，自己手写一个费劲的很，打算从 easy 难度的恢复一下，重走长征路\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://leetcode.cn/problems/merge-strings-alternately/\"\u003e1768. 交替合并字符串\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e给你两个字符串 \u003ccode\u003eword1\u003c/code\u003e 和 \u003ccode\u003eword2\u003c/code\u003e 。请你从 \u003ccode\u003eword1\u003c/code\u003e 开始，通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长，就将多出来的字母追加到合并后字符串的末尾。\u003c/p\u003e","title":"Leetcode 复健1: 1768"},{"content":"我们在海光 Z100 DCU (gfx906) 上对 vLLM 推理框架的 CUDA Graph 功能进行了完整的测试验证。本文记录开启 CUDA Graph + torch.compile 的具体配置、性能数据和关键发现。\n测试环境 项目 详情 服务器 scnet GPU 1x 海光 Z100 DCU (gfx906), 16 GiB Python 3.10.12 PyTorch 2.10.0+das.opt1.dtk2604 vLLM 0.18.1+das.3266200.dtk2604 Triton 3.4.0 模型 Qwen3.5-4B (BF16) 两种配置对比 Baseline：enforce_eager=True（禁用 CUDA Graph） compilation_config.mode = CompilationMode.NONE cudagraph_mode = CUDAGraphMode.NONE 启用 custom fusions（norm_quant、act_quant） 模型加载时间：~125s 显存占用：~13.25 GiB CUDA Graph + Compile：enforce_eager=False compilation_config.mode = VLLM_COMPILE（Inductor backend） cudagraph_mode = FULL_AND_PIECEWISE compile_ranges_endpoints: [8192] cudagraph_capture_sizes: [1, 2] 禁用 custom fusions（与 compile 不兼容） 模型加载时间：~172s（编译开销） 显存占用：~14.5 GiB（多出 ~1.3 GiB 用于 Graph 缓冲） 关键：CUDA Graph 模式不要传 \u0026ndash;enforce-eager，去掉该参数后 vLLM 自动启用 VLLM_COMPILE + FULL_AND_PIECEWISE CUDA Graph。\n性能对比数据 输出 Tokens 统一为 128，测试单卡单序列吞吐：\n输入长度 Baseline (eager) CUDA Graph + Compile 加速比 1024 9.6 tok/s (13.3s) 17.2 tok/s (6.3s) 1.79x 4096 6.9 tok/s (18.5s) 9.8 tok/s (13.0s) 1.42x 8192 4.8 tok/s (26.7s) 5.5 tok/s (23.1s) 1.15x 16384 2.3 tok/s (56.8s) 2.2 tok/s (57.9s) 0.96x 24576 1.3 tok/s (98.3s) 1.2 tok/s (109.0s) 0.92x 按技术方案档位分析 档位 权重 输入范围 Baseline CUDA Graph 档内提升 4k-8k 20% 4096-8192 6.9-\u0026gt;4.8 9.8-\u0026gt;5.5 +42%-\u0026gt;+15% 8k-16k 50% 8192-16384 4.8-\u0026gt;2.3 5.5-\u0026gt;2.2 +15%-\u0026gt;-4% 16k-32k 30% 16384-32768 2.3-\u0026gt;\u0026lt;1.3 2.2-\u0026gt;\u0026lt;1.2 -4%-\u0026gt;-8% 加权平均吞吐量提升：约 6%\n关键发现 1. 短上下文收益大 CUDA Graph 的核心优势是消除 kernel launch overhead。在短上下文下，kernel launch 时间占总耗时比例高（约 30%），取消后效果显著。1024 tokens 时达到 1.79x 加速。\n2. 长上下文 prefill 主导 当输入超过 8k tokens 时，GDN prefill 计算时间占主导，kernel launch overhead 占比下降到 5% 以下，CUDA Graph 优势消失甚至略微劣化。\n3. compile_ranges_endpoints 限制 默认只编译到 8192 tokens，超过此范围回退到 eager 模式。尝试扩展 compile range 到 16384 未能生效（环境变量未被正确识别）。\n4. Gated DeltaNet 的特殊性 该模型 decode 阶段每步耗时约 100ms（与上下文长度基本无关），但 prefill 随长度线性增长。在 24576 输入时，prefill 耗时约 95s，decode 128 tokens 只需约 13s。\n5. 显存权衡 CUDA Graph 需要额外约 1.3 GiB 显存用于图缓冲，需要将 gpu_memory_utilization 从 0.90 降低到 0.78 以避免 OOM，这减少了可用 KV Cache 空间。\n启动命令 Baseline (eager) 模式 python3 -m vllm.entrypoints.openai.api_server \\\\n --model /path/to/Qwen3.5-4B \\\\n --tensor-parallel-size 1 \\\\n --dtype bfloat16 \\\\n --max-model-len 32768 \\\\n --gpu-memory-utilization 0.90 \\\\n --enforce-eager \\\\n --max-num-seqs 1 \\\\n --skip-mm-profiling CUDA Graph + Compile 模式 python3 -m vllm.entrypoints.openai.api_server \\\\n --model /path/to/Qwen3.5-4B \\\\n --tensor-parallel-size 1 \\\\n --dtype bfloat16 \\\\n --max-model-len 32768 \\\\n --gpu-memory-utilization 0.78 \\\\n --max-num-seqs 1 \\\\n --skip-mm-profiling 注意：CUDA Graph 模式不要传 \u0026ndash;enforce-eager，去掉即可自动启用。\n总结 在海光 Z100 DCU 上，CUDA Graph + torch.compile 在短上下文（1024 tokens）下可带来最高 79% 的吞吐提升。但由于 compile_ranges_endpoints 限制、显存开销增加以及 Gated DeltaNet prefill 主导长上下文耗时，加权平均提升约 6%。后续优化方向包括动态策略选择（按输入长度切换 eager/CUDA Graph）、GDN prefill Triton kernel 优化、KV Cache FP8 等。\n","permalink":"https://blog.muwinds.com/archives/380/","summary":"\u003cp\u003e我们在海光 Z100 DCU (gfx906) 上对 vLLM 推理框架的 CUDA Graph 功能进行了完整的测试验证。本文记录开启 CUDA Graph + torch.compile 的具体配置、性能数据和关键发现。\u003c/p\u003e\n\u003ch2 id=\"测试环境\"\u003e测试环境\u003c/h2\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e项目\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e详情\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e服务器\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003escnet\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eGPU\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e1x 海光 Z100 DCU (gfx906), 16 GiB\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003ePython\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e3.10.12\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003ePyTorch\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e2.10.0+das.opt1.dtk2604\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003evLLM\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e0.18.1+das.3266200.dtk2604\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eTriton\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e3.4.0\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e模型\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eQwen3.5-4B (BF16)\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"两种配置对比\"\u003e两种配置对比\u003c/h2\u003e\n\u003ch3 id=\"baselineenforce_eagertrue禁用-cuda-graph\"\u003eBaseline：enforce_eager=True（禁用 CUDA Graph）\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003ecompilation_config.mode = CompilationMode.NONE\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ecudagraph_mode = CUDAGraphMode.NONE\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e启用 custom fusions（norm_quant、act_quant）\u003c/li\u003e\n\u003cli\u003e模型加载时间：~125s\u003c/li\u003e\n\u003cli\u003e显存占用：~13.25 GiB\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"cuda-graph--compileenforce_eagerfalse\"\u003eCUDA Graph + Compile：enforce_eager=False\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003ecompilation_config.mode = VLLM_COMPILE\u003c/code\u003e（Inductor backend）\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ecudagraph_mode = FULL_AND_PIECEWISE\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ecompile_ranges_endpoints: [8192]\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ecudagraph_capture_sizes: [1, 2]\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e禁用 custom fusions（与 compile 不兼容）\u003c/li\u003e\n\u003cli\u003e模型加载时间：~172s（编译开销）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e显存占用：~14.5 GiB\u003c/strong\u003e（多出 ~1.3 GiB 用于 Graph 缓冲）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e关键：CUDA Graph 模式\u003cstrong\u003e不要传 \u0026ndash;enforce-eager\u003c/strong\u003e，去掉该参数后 vLLM 自动启用 VLLM_COMPILE + FULL_AND_PIECEWISE CUDA Graph。\u003c/p\u003e","title":"Z100 DCU 上 vLLM CUDA Graph 推理优化实践"},{"content":"本文记录了我们使用自研微基准测试工具对 4× 海光 Z100 DCU 进行的一系列测试结果。\n测试环境 项目 详情 GPU 4× 海光 Z100 DCU，每张 16GB，合计 64GB GPU 架构 gfx906 (Vega 20 / GCN 5.1) DTK 版本 DTK 26.04 (DCC2602-0317) PyTorch 2.10.0+das.opt1.dtk2604 测试工具 dcu_benchmark.py（基于 hy-smi 遥测采样） 采样间隔 1 秒 矩阵规模 4096 × 4096（Matmul 测试） 向量规模 64M 元素（Elementwise / 访存测试） 测试项目 我们使用 dcu_benchmark.py 在 4 张 Z100 上同时运行了以下 8 个微基准测试，每个测试持续约 20 秒，并在测试前后记录了空闲状态的基线数据：\nFP32 Matmul：单精度矩阵乘法 FP16 Matmul (HGEMM)：半精度矩阵乘法 BF16 Matmul：BF16 精度矩阵乘法 INT8 Matmul：整型 8-bit 矩阵乘法（torch._int_mm） INT32 Elementwise：32-bit 整型逐元素运算（xor + add） INT8 Elementwise：8-bit 整型逐元素运算 INT4 Unpack：4-bit 解包 + 逐元素运算 Memory Copy：显存带宽测试（float32 拷贝） Benchmark 代码 以下是在 Z100 上实际运行的微基准测试脚本 dcu_benchmark.py。使用方式：\npython dcu_benchmark.py --out-dir benchmark_output --seconds 20 --warmup 2 #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34;DCU microbenchmark and power sampler. This script is intended to run on a Hygon DCU / ROCm-compatible PyTorch environment. It records power/temperature/clock/utilization samples while running a small set of compute and memory benchmarks. \u0026#34;\u0026#34;\u0026#34; from __future__ import annotations import argparse import csv import json import math import os import re import shutil import subprocess import threading import time from dataclasses import asdict, dataclass from pathlib import Path from typing import Callable, Iterable import torch POWER_COLUMNS = [ \u0026#34;ts_epoch\u0026#34;, \u0026#34;phase\u0026#34;, \u0026#34;bdf\u0026#34;, \u0026#34;power_w\u0026#34;, \u0026#34;temp_edge_c\u0026#34;, \u0026#34;temp_junction_c\u0026#34;, \u0026#34;temp_mem_c\u0026#34;, \u0026#34;sclk_mhz\u0026#34;, \u0026#34;mclk_mhz\u0026#34;, \u0026#34;gpu_busy_pct\u0026#34;, \u0026#34;mem_busy_pct\u0026#34;, ] @dataclass class BenchResult: name: str dtype: str op: str device_count: int matrix_m: int | None matrix_n: int | None matrix_k: int | None seconds: float iterations: int throughput: float | None throughput_unit: str notes: str = \u0026#34;\u0026#34; class Phase: def __init__(self, initial: str = \u0026#34;idle\u0026#34;) -\u0026gt; None: self._value = initial self._lock = threading.Lock() def set(self, value: str) -\u0026gt; None: with self._lock: self._value = value def get(self) -\u0026gt; str: with self._lock: return self._value def run_cmd(args: list[str], timeout: float = 5.0) -\u0026gt; str: proc = subprocess.run( args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=timeout, check=False, ) return proc.stdout def pick_smi() -\u0026gt; list[str] | None: if shutil.which(\u0026#34;hy-smi\u0026#34;): return [\u0026#34;hy-smi\u0026#34;] if shutil.which(\u0026#34;rocm-smi\u0026#34;): return [\u0026#34;rocm-smi\u0026#34;] return None def extract_number(text: str) -\u0026gt; float | None: match = re.search(r\u0026#34;[-+]?\\d+(?:\\.\\d+)?\u0026#34;, text.replace(\u0026#34;,\u0026#34;, \u0026#34;\u0026#34;)) if match: return float(match.group(0)) return None def parse_smi_text(text: str) -\u0026gt; list[dict[str, str | float | int | None]]: \u0026#34;\u0026#34;\u0026#34;Best-effort parser for hy-smi/rocm-smi human and CSV-like output.\u0026#34;\u0026#34;\u0026#34; rows = [] bdf_re = re.compile(r\u0026#34;(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\\.[0-9a-fA-F]\u0026#34;) for line in text.splitlines(): if not line.strip(): continue bdf_match = bdf_re.search(line) if not bdf_match and (\u0026#34;GPU[\u0026#34; not in line and \u0026#34;card\u0026#34; not in line.lower()): continue bdf = bdf_match.group(0) if bdf_match else line.split()[0].strip(\u0026#34;:,\u0026#34;) lower = line.lower() def by_label(labels): for label in labels: idx = lower.find(label) if idx \u0026gt;= 0: m = re.search(r\u0026#34;[-+]?\\d+(?:\\.\\d+)?\u0026#34;, line[idx:idx+48]) if m: return float(m.group(0)) return None power = by_label([\u0026#34;power\u0026#34;, \u0026#34;pwr\u0026#34;, \u0026#34;average socket power\u0026#34;]) edge = by_label([\u0026#34;edge\u0026#34;, \u0026#34;temp\u0026#34;]) junction = by_label([\u0026#34;junction\u0026#34;, \u0026#34;hotspot\u0026#34;]) mem_temp = by_label([\u0026#34;mem temp\u0026#34;, \u0026#34;memory temp\u0026#34;, \u0026#34;hbm\u0026#34;]) sclk = by_label([\u0026#34;sclk\u0026#34;, \u0026#34;socclk\u0026#34;]) mclk = by_label([\u0026#34;mclk\u0026#34;, \u0026#34;memclk\u0026#34;]) gpu_busy = by_label([\u0026#34;gpu use\u0026#34;, \u0026#34;gpu busy\u0026#34;, \u0026#34;gpu%\u0026#34;]) mem_busy = by_label([\u0026#34;mem use\u0026#34;, \u0026#34;mem busy\u0026#34;, \u0026#34;vram%\u0026#34;]) if power is None: nums = [float(x) for x in re.findall(r\u0026#34;[-+]?\\d+(?:\\.\\d+)?\u0026#34;, line)] if nums: power = nums[0] rows.append({ \u0026#34;bdf\u0026#34;: bdf, \u0026#34;power_w\u0026#34;: power, \u0026#34;temp_edge_c\u0026#34;: edge, \u0026#34;temp_junction_c\u0026#34;: junction, \u0026#34;temp_mem_c\u0026#34;: mem_temp, \u0026#34;sclk_mhz\u0026#34;: int(sclk) if sclk is not None else None, \u0026#34;mclk_mhz\u0026#34;: int(mclk) if mclk is not None else None, \u0026#34;gpu_busy_pct\u0026#34;: int(gpu_busy) if gpu_busy is not None else None, \u0026#34;mem_busy_pct\u0026#34;: int(mem_busy) if mem_busy is not None else None, }) return rows def sample_smi(): smi = pick_smi() if smi is None: return [] commands = [ smi + [\u0026#34;--showpower\u0026#34;, \u0026#34;--showtemp\u0026#34;, \u0026#34;--showuse\u0026#34;, \u0026#34;--showclocks\u0026#34;], smi + [\u0026#34;--showpower\u0026#34;, \u0026#34;--showtemp\u0026#34;, \u0026#34;--showuse\u0026#34;], smi, ] for cmd in commands: try: rows = parse_smi_text(run_cmd(cmd)) if rows: return rows except Exception: continue return [] def power_sampler(path, phase, stop, interval): with path.open(\u0026#34;w\u0026#34;, newline=\u0026#34;\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: writer = csv.DictWriter(f, fieldnames=POWER_COLUMNS) writer.writeheader() while not stop.is_set(): ts = int(time.time()) rows = sample_smi() for row in rows: out = {col: \u0026#34;\u0026#34; for col in POWER_COLUMNS} out.update(row) out[\u0026#34;ts_epoch\u0026#34;] = ts out[\u0026#34;phase\u0026#34;] = phase.get() writer.writerow(out) f.flush() stop.wait(interval) def synchronize(): if torch.cuda.is_available(): torch.cuda.synchronize() def time_loop(fn, seconds, warmup=5): for _ in range(warmup): fn() synchronize() iters = 0 start = time.perf_counter() while True: fn() iters += 1 if time.perf_counter() - start \u0026gt;= seconds: break synchronize() elapsed = time.perf_counter() - start return elapsed, iters def all_devices(): if not torch.cuda.is_available(): return [torch.device(\u0026#34;cpu\u0026#34;)] return [torch.device(f\u0026#34;cuda:{i}\u0026#34;) for i in range(torch.cuda.device_count())] def bench_matmul(dtype, size, seconds): devices = all_devices() tensors = [] for dev in devices: a = torch.randn((size, size), device=dev, dtype=dtype) b = torch.randn((size, size), device=dev, dtype=dtype) tensors.append((a, b)) def step(): for a, b in tensors: torch.matmul(a, b) elapsed, iters = time_loop(step, seconds) flops = 2.0 * size * size * size * iters * len(devices) return BenchResult( name=f\u0026#34;{str(dtype).split(\u0026#39;.\u0026#39;)[-1]}_matmul\u0026#34;, dtype=str(dtype).split(\u0026#34;.\u0026#34;)[-1], op=\u0026#34;matmul\u0026#34;, device_count=len(devices), matrix_m=size, matrix_n=size, matrix_k=size, seconds=elapsed, iterations=iters, throughput=flops / elapsed / 1e12, throughput_unit=\u0026#34;TFLOP/s\u0026#34;, ) def bench_int8_matmul(size, seconds): devices = all_devices() if not hasattr(torch, \u0026#34;_int_mm\u0026#34;): return BenchResult(name=\u0026#34;int8_matmul\u0026#34;, dtype=\u0026#34;int8\u0026#34;, op=\u0026#34;torch._int_mm\u0026#34;, device_count=len(devices), matrix_m=size, matrix_n=size, matrix_k=size, seconds=0.0, iterations=0, throughput=None, throughput_unit=\u0026#34;TOPS\u0026#34;, notes=\u0026#34;torch._int_mm is unavailable in this PyTorch build\u0026#34;) tensors = [] try: for dev in devices: a = torch.randint(-128, 127, (size, size), device=dev, dtype=torch.int8) b = torch.randint(-128, 127, (size, size), device=dev, dtype=torch.int8) tensors.append((a, b)) def step(): for a, b in tensors: torch._int_mm(a, b) elapsed, iters = time_loop(step, seconds) ops = 2.0 * size * size * size * iters * len(devices) return BenchResult(name=\u0026#34;int8_matmul\u0026#34;, dtype=\u0026#34;int8\u0026#34;, op=\u0026#34;torch._int_mm\u0026#34;, device_count=len(devices), matrix_m=size, matrix_n=size, matrix_k=size, seconds=elapsed, iterations=iters, throughput=ops / elapsed / 1e12, throughput_unit=\u0026#34;TOPS\u0026#34;) except Exception as exc: return BenchResult(name=\u0026#34;int8_matmul\u0026#34;, dtype=\u0026#34;int8\u0026#34;, op=\u0026#34;torch._int_mm\u0026#34;, device_count=len(devices), matrix_m=size, matrix_n=size, matrix_k=size, seconds=0.0, iterations=0, throughput=None, throughput_unit=\u0026#34;TOPS\u0026#34;, notes=f\u0026#34;unsupported: {type(exc).__name__}: {exc}\u0026#34;) def bench_integer_elementwise(dtype, elems, seconds): devices = all_devices() tensors = [] for dev in devices: high = 127 if dtype is torch.int8 else 2**30 a = torch.randint(0, high, (elems,), device=dev, dtype=dtype) b = torch.randint(0, high, (elems,), device=dev, dtype=dtype) tensors.append((a, b)) def step(): for a, b in tensors: torch.bitwise_xor(a, b) torch.add(a, b) elapsed, iters = time_loop(step, seconds) ops = 2.0 * elems * iters * len(devices) return BenchResult( name=f\u0026#34;{str(dtype).split(\u0026#39;.\u0026#39;)[-1]}_elementwise\u0026#34;, dtype=str(dtype).split(\u0026#34;.\u0026#34;)[-1], op=\u0026#34;xor+add\u0026#34;, device_count=len(devices), seconds=elapsed, iterations=iters, throughput=ops / elapsed / 1e9, throughput_unit=\u0026#34;GOP/s\u0026#34;) def bench_int4_unpack(elems, seconds): devices = all_devices() packed_elems = max(1, elems // 2) tensors = [] for dev in devices: x = torch.randint(0, 255, (packed_elems,), device=dev, dtype=torch.uint8) tensors.append(x) def step(): for x in tensors: lo = x \u0026amp; 0x0F hi = (x \u0026gt;\u0026gt; 4) \u0026amp; 0x0F torch.add(lo, hi) elapsed, iters = time_loop(step, seconds) ops = 3.0 * packed_elems * iters * len(devices) return BenchResult(name=\u0026#34;int4_unpack_elementwise\u0026#34;, dtype=\u0026#34;int4_packed_uint8\u0026#34;, op=\u0026#34;unpack_nibbles+add\u0026#34;, device_count=len(devices), seconds=elapsed, iterations=iters, throughput=ops / elapsed / 1e9, throughput_unit=\u0026#34;GOP/s\u0026#34;, notes=\u0026#34;packed INT4 path; measures unpack/bitwise throughput, not tensor-core INT4 GEMM\u0026#34;) def bench_memory_bandwidth(elems, seconds): devices = all_devices() tensors = [] for dev in devices: a = torch.empty((elems,), device=dev, dtype=torch.float32) b = torch.empty((elems,), device=dev, dtype=torch.float32) tensors.append((a, b)) def step(): for a, b in tensors: b.copy_(a) elapsed, iters = time_loop(step, seconds) bytes_moved = elems * 4 * iters * len(devices) return BenchResult(name=\u0026#34;memory_copy\u0026#34;, dtype=\u0026#34;float32\u0026#34;, op=\u0026#34;copy\u0026#34;, device_count=len(devices), seconds=elapsed, iterations=iters, throughput=bytes_moved / elapsed / 1e9, throughput_unit=\u0026#34;GB/s\u0026#34;) def write_json(path, obj): path.write_text(json.dumps(obj, indent=2, ensure_ascii=False), encoding=\u0026#34;utf-8\u0026#34;) def main(): parser = argparse.ArgumentParser() parser.add_argument(\u0026#34;--out-dir\u0026#34;, default=\u0026#34;dcu_benchmark_out\u0026#34;) parser.add_argument(\u0026#34;--seconds\u0026#34;, type=float, default=20.0) parser.add_argument(\u0026#34;--idle-seconds\u0026#34;, type=float, default=8.0) parser.add_argument(\u0026#34;--cooldown-seconds\u0026#34;, type=float, default=8.0) parser.add_argument(\u0026#34;--sample-interval\u0026#34;, type=float, default=1.0) parser.add_argument(\u0026#34;--matmul-size\u0026#34;, type=int, default=4096) parser.add_argument(\u0026#34;--int8-size\u0026#34;, type=int, default=4096) parser.add_argument(\u0026#34;--elementwise-elems\u0026#34;, type=int, default=64 * 1024 * 1024) args = parser.parse_args() out_dir = Path(args.out_dir) out_dir.mkdir(parents=True, exist_ok=True) phase = Phase(\u0026#34;idle\u0026#34;) stop = threading.Event() power_csv = out_dir / \u0026#34;dcu_power_curve.csv\u0026#34; sampler = threading.Thread(target=power_sampler, args=(power_csv, phase, stop, args.sample_interval), daemon=True) sampler.start() results = [] try: time.sleep(args.idle_seconds) tests = [ (\u0026#34;fp32_matmul\u0026#34;, lambda: bench_matmul(torch.float32, args.matmul_size, args.seconds)), (\u0026#34;fp16_matmul\u0026#34;, lambda: bench_matmul(torch.float16, args.matmul_size, args.seconds)), (\u0026#34;bf16_matmul\u0026#34;, lambda: bench_matmul(torch.bfloat16, args.matmul_size, args.seconds)), (\u0026#34;int8_matmul\u0026#34;, lambda: bench_int8_matmul(args.int8_size, args.seconds)), (\u0026#34;int32_elementwise\u0026#34;, lambda: bench_integer_elementwise(torch.int32, args.elementwise_elems, args.seconds)), (\u0026#34;int8_elementwise\u0026#34;, lambda: bench_integer_elementwise(torch.int8, args.elementwise_elems, args.seconds)), (\u0026#34;int4_unpack\u0026#34;, lambda: bench_int4_unpack(args.elementwise_elems, args.seconds)), (\u0026#34;memory_copy\u0026#34;, lambda: bench_memory_bandwidth(args.elementwise_elems, args.seconds)), ] for name, fn in tests: phase.set(name) try: results.append(fn()) except Exception as exc: results.append(BenchResult(name=name, dtype=\u0026#34;unknown\u0026#34;, op=\u0026#34;unknown\u0026#34;, device_count=torch.cuda.device_count() if torch.cuda.is_available() else 0, seconds=0.0, iterations=0, throughput=None, throughput_unit=\u0026#34;\u0026#34;, notes=f\u0026#34;failed: {type(exc).__name__}: {exc}\u0026#34;)) phase.set(\u0026#34;cooldown\u0026#34;) time.sleep(args.cooldown_seconds) finally: stop.set() sampler.join(timeout=2.0) rows = [asdict(r) for r in results] result_csv = out_dir / \u0026#34;dcu_benchmark_results.csv\u0026#34; with result_csv.open(\u0026#34;w\u0026#34;, newline=\u0026#34;\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: writer = csv.DictWriter(f, fieldnames=list(rows[0].keys())) writer.writeheader() writer.writerows(rows) device_info = {\u0026#34;torch_version\u0026#34;: torch.__version__, \u0026#34;cuda_available\u0026#34;: torch.cuda.is_available(), \u0026#34;device_count\u0026#34;: torch.cuda.device_count() if torch.cuda.is_available() else 0, \u0026#34;devices\u0026#34;: []} if torch.cuda.is_available(): for idx in range(torch.cuda.device_count()): props = torch.cuda.get_device_properties(idx) device_info[\u0026#34;devices\u0026#34;].append({\u0026#34;index\u0026#34;: idx, \u0026#34;name\u0026#34;: props.name, \u0026#34;total_memory_gib\u0026#34;: props.total_memory / (1024**3), \u0026#34;major\u0026#34;: props.major, \u0026#34;minor\u0026#34;: props.minor, \u0026#34;gcnArchName\u0026#34;: getattr(props, \u0026#34;gcnArchName\u0026#34;, None)}) write_json(out_dir / \u0026#34;device_info.json\u0026#34;, device_info) print(f\u0026#34;Wrote {result_csv}\u0026#34;) print(f\u0026#34;Wrote {power_csv}\u0026#34;) return 0 if __name__ == \u0026#34;__main__\u0026#34;: raise SystemExit(main()) 功耗分析 以下是各测试场景下的单卡平均功耗和峰值功耗汇总：\n测试场景 平均功耗 (W/卡) 峰值功耗 (W/卡) 4 卡合计稳定功耗 空闲 (Idle) ~24 26 ~96W FP16 Matmul (HGEMM) 144.6 162 ~578W INT32 Elementwise 117.6 123 ~470W BF16 Matmul 113.7 129 ~455W INT8 Matmul 105.0 120 ~420W 可以看出：\nHGEMM (FP16) 是最耗电的负载，单卡峰值达到了 162W。这也与 HGEMM 同时具有高 GPU 占用率（94.7%）和高显存带宽占用率（32.8%）的特征一致。 INT32 Elementwise 的计算饱和度最高，GPU 占用率达到了 99.7%（几乎 100% 打满），但由于是纯整型计算不涉及显存带宽，功耗反而不如 HGEMM。 BF16 和 INT8 Matmul 的功耗相对较低，GPU 占用率约 90%，说明这些操作可能存在一定的流水线停顿或访存延迟。 温度与散热 在整个测试过程中，我们记录了 GPU Edge（边缘）温度、Junction（结温）和 Memory（显存）温度：\n测试场景 Edge 温度 (°C) Junction 温度 (°C) 显存温度 (°C) 空闲 (Idle) 46.4 48.0 50.0 FP16 Matmul (HGEMM) 51.1 (峰值 52) 62.0 (峰值 65) 57.7 (峰值 59) INT32 Elementwise 50.0 (峰值 51) 60.1 (峰值 62) 52.7 (峰值 54) BF16 Matmul 50.1 (峰值 51) 59.3 (峰值 62) 53.8 (峰值 55) INT8 Matmul 49.8 (峰值 51) 58.4 (峰值 61) 52.8 (峰值 54) 关键观察：\n在 20 秒的持续满载测试中，Junction 温度最高达到 65°C（HGEMM），远未达到典型的 GPU 温度墙（通常在 85-100°C），说明 Z100 的散热方案有充足的余量。 核心时钟（sclk）稳定在 ~1315 MHz，在整个测试过程中未见降频。这意味着 Z100 的散热和供电设计可以在满载状态下维持基础频率不掉。 显存时钟（mclk）恒定为 800 MHz，不随负载动态变化。 测试停止后约 4-5 秒内温度即回落到接近空闲水平，说明散热系统的热容较小、响应迅速。 GPU 与显存利用率 测试场景 GPU 占用率 (平均) GPU 占用率 (峰值) 显存带宽占用 (平均) INT32 Elementwise 99.7% 100% 0% FP16 Matmul (HGEMM) 94.7% 100% 32.8% BF16 Matmul 90.9% 100% 8.2% INT8 Matmul 90.3% 100% 1.8% 这些数据清晰地反映了 Z100 在不同计算模式下的行为差异：\nINT32 Elementwise 是纯计算密集型：GPU 几乎完全饱和（99.7%），但显存带宽占用为 0%，因为数据已经在寄存器中，没有新的大规模访存需求。 HGEMM 是典型的计算 + 访存混合型负载：GPU 占用率高（94.7%）的同时，显存带宽占用也最高（32.8%），这是因为 FP16 矩阵乘法需要在计算的同时频繁读取中间结果。 BF16 和 INT8 Matmul 的显存带宽占用很低（8.2% 和 1.8%），说明这些操作在 Z100 上可能受限于计算单元而非显存带宽。 功耗曲线特征 启动瞬态：从空闲切换到满载负载时，功耗在约 1-2 秒内从 ~24W 快速攀升至稳定值，没有明显的过冲（overshoot）。 稳态波动：在持续负载下，各卡的功耗波动范围很小（±3-5W），说明供电稳定。 负载结束后的快速回落：测试结束后，功耗在 1-2 秒内即回到空闲水平，与温度回落的 ~4-5 秒形成对比。 卡间差异：4 张卡之间存在一定的个体差异。例如在 BF16 Matmul 测试中，卡 04:00.0 平均约 117W，而卡 43:00.0 平均约 128W，差异约 10W。这属于正常工艺偏差范围。 总结 通过本次微基准测试，我们对海光 Z100 DCU 的性能特征有了以下认识：\n单卡 TDP 约 150-160W，峰值功耗实测 162W。4 卡满载系统总功耗约 580-650W。 核心时钟稳定：sclk 在 ~1315 MHz 维持稳定，满载不降频，散热余量充足。 计算能力峰值场景：INT32 整型计算可将 GPU 利用率压到接近 100%；FP16 HGEMM 是功耗最高的负载，同时消耗计算和显存带宽资源。 功耗效率：BF16 和 INT8 Matmul 的功耗相对高效，但 GPU 占用率仅约 90%，暗示可能存在进一步优化的空间（例如通过 Triton 手写 kernel 减少流水线停顿）。 显存带宽：mclk 固定 800 MHz，HGEMM 场景下显存带宽占用约 33%，说明在高强度计算场景下显存带宽可能是潜在的瓶颈。 这些数据为我们后续优化 vLLM 在 Z100 上的推理性能提供了重要的参考——例如在量化策略选择（INT8 vs BF16）、张量并行度设置、以及 batch size 调优时，都需要考虑不同计算模式下的功耗和利用率特征。\n","permalink":"https://blog.muwinds.com/archives/373/","summary":"\u003cp\u003e本文记录了我们使用自研微基准测试工具对 4× 海光 Z100 DCU 进行的一系列测试结果。\u003c/p\u003e\n\u003ch2 id=\"测试环境\"\u003e测试环境\u003c/h2\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e项目\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e详情\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eGPU\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e4× 海光 Z100 DCU，每张 16GB，合计 64GB\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eGPU 架构\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003egfx906 (Vega 20 / GCN 5.1)\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eDTK 版本\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eDTK 26.04 (DCC2602-0317)\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003ePyTorch\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e2.10.0+das.opt1.dtk2604\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e测试工具\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003edcu_benchmark.py（基于 hy-smi 遥测采样）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e采样间隔\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e1 秒\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e矩阵规模\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e4096 × 4096（Matmul 测试）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e向量规模\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e64M 元素（Elementwise / 访存测试）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"测试项目\"\u003e测试项目\u003c/h2\u003e\n\u003cp\u003e我们使用 dcu_benchmark.py 在 4 张 Z100 上同时运行了以下 8 个微基准测试，每个测试持续约 20 秒，并在测试前后记录了空闲状态的基线数据：\u003c/p\u003e","title":"海光 Z100 DCU微基准测试：功耗、温度与计算特性分析"},{"content":"环境信息 项目 详情 GPU 4× 海光 Z100 DCU，每张 16GB，合计 64GB GPU 架构 gfx906 (Vega 20 / GCN 5.1) DTK 版本 DTK 26.04 (DCC2602-0317) Python 3.10.12 PyTorch 2.10.0+das.opt1.dtk2604 vLLM 0.18.1+das.3266200.dtk2604 Triton 3.4.0（从源码编译，替换原 3.4.0+git1ef59765） transformers 5.5.0 flash_attn 2.8.3+das 上述镜像可以去海光社区：https://developer.sourcefind.cn/modelzoo/list/qwen3.5_vllm/detail?post_id=46176b42-0c0b-11f1-a49f-0242ac150003 获得\nDocker 镜像地址：harbor.sourcefind.cn:5443/dcu/admin/base/custom:vllm018-ubuntu22.04-dtk26.04-gemma4-0413\n核心问题 该服务器上的容器镜像是为 BW1000 (gfx928) 构建的，而实际 GPU 是 Z100 (gfx906)。存在以下 4 类问题：\n库路径冲突：/opt/hyhal/lib 中的 librocm_smi64.so.2 缺少 rsmi_init 符号 Triton 未适配 gfx906：MLIR AMDGPU 后端不识别 gfx906 vLLM GCN 架构检测失败：amdsmi 返回垃圾值 运行时缺失：_rocm_C 未安装；KV cache profiling 异常 修改一：LD_LIBRARY_PATH 修复（环境配置） 问题 LD_LIBRARY_PATH 中 /opt/hyhal/lib 排在前面，其 librocm_smi64.so.2 不导出 rsmi_init。\n错误信息 ImportError: /opt/dtk/lib/librccl.so.1: undefined symbol: rsmi_init 解决方案 必须将 DTK 的 rocm_smi 路径放在 LD_LIBRARY_PATH 最前面：\nexport LD_LIBRARY_PATH=/opt/dtk/.hyhal/rocm_smi/lib:/opt/dtk/lib:/opt/dtk/lib64:/opt/hyhal/lib:/opt/hyhal/lib64:${LD_LIBRARY_PATH} 路径优先级（从高到低） /opt/dtk/.hyhal/rocm_smi/lib ← 包含 rsmi_init /opt/dtk/lib /opt/dtk/lib64 /opt/hyhal/lib ← 缺少 rsmi_init /opt/hyhal/lib64 修改二：Triton 源码修改 前置步骤：备份旧 Triton，克隆源码\n# 备份 cp -r /usr/local/lib/python3.10/dist-packages/triton /usr/local/lib/python3.10/dist-packages/triton.bak cp -r /usr/local/lib/python3.10/dist-packages/triton-3.4.0+git1ef59765.dist-info /usr/local/lib/python3.10/dist-packages/triton-3.4.0+git1ef59765.dist-info.bak # 克隆 cd /tmp git clone https://github.com/triton-lang/triton.git --branch v3.4.0 --depth 1 triton-3.4.0-src cd triton-3.4.0-src 修改 2.1：TargetUtils.h — 添加 VEGA 到 ISAFamily 枚举 文件：third_party/amd/include/TritonAMDGPUToLLVM/TargetUtils.h\n修改前：\nenum class ISAFamily { Unknown, CDNA1, CDNA2, CDNA3, CDNA4, RDNA1, RDNA2, RDNA3, }; 修改后：\nenum class ISAFamily { Unknown, CDNA1, CDNA2, CDNA3, CDNA4, VEGA, RDNA1, RDNA2, RDNA3, }; 操作：\nsed -i \u0026#39;14a\\ VEGA,\u0026#39; third_party/amd/include/TritonAMDGPUToLLVM/TargetUtils.h 修改 2.2：TargetUtils.cpp — 添加 gfx906 架构映射和 VDot 支持 文件：third_party/amd/lib/TritonAMDGPUToLLVM/TargetUtils.cpp\n2.2a. deduceISAFamily() — 添加 gfx906 → VEGA 映射 修改前：\nswitch (kind) { case GK_GFX950: return CDNA4; case GK_GFX942: return CDNA3; case GK_GFX90A: return CDNA2; case GK_GFX908: return CDNA1; default: break; } 修改后：\nswitch (kind) { case GK_GFX950: return CDNA4; case GK_GFX906: return VEGA; case GK_GFX942: return CDNA3; case GK_GFX90A: return CDNA2; case GK_GFX908: return CDNA1; default: break; } 2.2b. supportsVDot() — 添加 VEGA 支持 修改前：\nbool supportsVDot() { switch (family) { case CDNA1: case CDNA2: case CDNA3: case CDNA4: case RDNA2: case RDNA3: return true; default: break; } return false; } 修改后：\nbool supportsVDot() { switch (family) { case CDNA1: case CDNA2: case VEGA: case CDNA3: case CDNA4: case RDNA2: case RDNA3: return true; default: break; } return false; } 修改 2.3：TargetInfo.cpp — Warp Size 修正 文件：third_party/amd/lib/TritonAMDGPUToLLVM/TargetInfo.cpp\n修改前：int TargetInfo::getWarpSize() const { return isCDNA() ? 64 : 32; }\n修改后：int TargetInfo::getWarpSize() const { return (isCDNA() || getISAFamily() == ISAFamily::VEGA) ? 64 : 32; }\ngfx906 和 CDNA 一样使用 64 线程/warp（wave64 模式）。\n修改 2.4：TargetInfo.cpp — permlanex16 指令兼容性修复（关键！） 在 warp reduction 操作中，非 CDNA 路径使用了 permlanex16 指令（CDNA2+ 引入），gfx906 (VEGA) 不支持该指令。\n错误信息：\nLLVM ERROR: Cannot select: intrinsic %llvm.amdgcn.permlanex16 修改：让 VEGA 走 DPP 广播路径而非 RDNA 的 permlanex16 路径\nif (isCDNA() || getISAFamily() == ISAFamily::VEGA) { // DPP 广播路径 } else { // RDNA permlanex16 路径 } 故意不修改的地方 isCDNA()：VEGA 不属于 CDNA，返回 false 正确，避免添加 MFMA 优化 isRDNA()：VEGA 也不属于 RDNA supportsDirectToLdsLoadBitWidth()：走 default 返回 false 是正确保守策略 Triton 编译安装 source /opt/dtk/env.sh export LD_LIBRARY_PATH=/opt/dtk/.hyhal/rocm_smi/lib:/opt/dtk/lib:/opt/dtk/lib64:/opt/hyhal/lib:/opt/hyhal/lib64:${LD_LIBRARY_PATH} cd /tmp/triton-3.4.0-src rm -rf build pip3 install . --no-build-isolation 编译时间较长（libtriton.so 约 600MB），约需 5-10 分钟。\n修改三：vLLM 源码修改 cp /usr/local/lib/python3.10/dist-packages/vllm/platforms/rocm.py /usr/local/lib/python3.10/dist-packages/vllm/platforms/rocm.py.bak 修改 3.1：rocm.py — 修复 GCN 架构查询 问题：DTK 的 amdsmi 返回垃圾值 gfx18446744073709551615\n修复 1：添加正则验证拒绝无效架构名\n修复 2：fallback 从 get_device_properties(\u0026quot;cuda\u0026quot;) 改为 get_device_properties(0)，避免死锁\n修改 3.2：rocm.py — 添加 gfx906 到 GFX9 架构识别 修改前：_ON_GFX9 = any(arch in _GCN_ARCH for arch in [\u0026quot;gfx90a\u0026quot;, \u0026quot;gfx942\u0026quot;, \u0026quot;gfx950\u0026quot;])\n修改后：_ON_GFX9 = any(arch in _GCN_ARCH for arch in [\u0026quot;gfx906\u0026quot;, \u0026quot;gfx90a\u0026quot;, \u0026quot;gfx942\u0026quot;, \u0026quot;gfx950\u0026quot;])\ngfx906 属于 GFX9 架构族。不加会导致 ViT attention backend 回退等问题。\n修改四：禁用 SKINNY_GEMM（环境变量） 问题：预编译 vLLM wheel 中 _rocm_C 自定义扩展未安装\nAttributeError: \u0026#39;_OpNamespace\u0026#39; \u0026#39;_rocm_C\u0026#39; object has no attribute \u0026#39;wvSplitK\u0026#39; 解决：\nexport VLLM_ROCM_USE_SKINNY_GEMM=0 修改五：手动指定 KV Cache 内存（启动参数） 问题：DTK ROCm 环境下 torch.cuda.memory_stats() 峰值统计异常，计算出负的可用 KV cache\nValueError: No available memory for the cache blocks. 解决：使用 --kv-cache-memory-bytes 手动指定 KV cache 为 1 GiB\n--kv-cache-memory-bytes 1073741824 最终启动命令 source /opt/dtk/env.sh export LD_LIBRARY_PATH=/opt/dtk/.hyhal/rocm_smi/lib:/opt/dtk/lib:/opt/dtk/lib64:/opt/hyhal/lib:/opt/hyhal/lib64:${LD_LIBRARY_PATH} export TRITON_HIP_LLD_PATH=/opt/dtk/llvm/bin/ld.lld export TRITON_CACHE_DIR=/tmp/triton_cache export VLLM_ROCM_USE_SKINNY_GEMM=0 python3 -m vllm.entrypoints.openai.api_server \\ --model /public/home/acchmzvrrk/models/Qwen3.5-27B \\ --tensor-parallel-size 4 \\ --dtype bfloat16 \\ --max-model-len 1024 \\ --gpu-memory-utilization 0.96 \\ --enforce-eager \\ --max-num-seqs 1 \\ --port 8000 \\ --host 0.0.0.0 \\ --skip-mm-profiling \\ --kv-cache-memory-bytes 1073741824 启动参数说明 参数 值 说明 --tensor-parallel-size 4 4 张 GPU 张量并行 --dtype bfloat16 BF16 精度 --max-model-len 1024 最大上下文长度 --gpu-memory-utilization 0.96 显存利用率 --enforce-eager - 禁用 CUDA Graph --skip-mm-profiling - 跳过 vision encoder --kv-cache-memory-bytes 1073741824 KV cache 1 GiB 内存使用分析 指标 数值 模型权重 (BF16) ~52 GB（总） 每卡模型占用 ~13.15 GiB 每卡总显存 16 GiB 每卡手动 KV Cache 1 GiB 每卡剩余 ~1.85 GiB 关键设计决策详解 为什么新建 VEGA 族而非映射到 CDNA1？ 不支持 MFMA 指令：gfx906 使用 FMA/DPP，映射到 CDNA1 会导致 LLVM 生成 MFMA 内部函数 Warp Size：gfx906 使用 64 线程/warp，非 CDNA 分支默认返回 32 AccelerateAMDMatmul：VEGA 不在 CDNA 中，不会添加 MFMA 优化 DPP 支持：gfx906 支持 DPP 广播，要走 CDNA 的 DPP 路径而非 RDNA 的 permlanex16 为什么 gfx906→gfx928 映射失败？ DTK 26.04 的 LLVM 是为 gfx928 构建的。生成的 HSACO 包含 gfx928 指令，在 gfx906 上无法运行。\nvLLM 架构检测的坑 amdsmi 返回 gfx18446744073709551615，而 torch.cuda.get_device_properties(\u0026quot;cuda\u0026quot;) 在 DTK 上会死锁，必须用 get_device_properties(0)。\nQwen3.5-27B 多模态模型处理 使用 --skip-mm-profiling 跳过 vision encoder。纯文本推理完全正常。\n_rocm_C 模块缺失 设置 VLLM_ROCM_USE_SKINNY_GEMM=0 回退到标准 PyTorch linear。\n首次推理注意事项 首次编译需 10-20 分钟，生成 1000+ 个缓存文件 后续推理直接使用 /tmp/triton_cache 缓存 完整文件修改清单 # 文件 修改内容 类型 2.1 TargetUtils.h 添加 VEGA 到 ISAFamily Triton 源码 2.2 TargetUtils.cpp GK_GFX906 + supportsVDot Triton 源码 2.3 TargetInfo.cpp getWarpSize VEGA 返回 64 Triton 源码 2.4 TargetInfo.cpp VEGA 走 DPP 路径 Triton 源码 3.1 rocm.py amdsmi 验证 + 死锁修复 vLLM 修补 3.2 rocm.py gfx906 加入 _ON_GFX9 vLLM 修补 1 环境变量 LD_LIBRARY_PATH 顺序 运行环境 4 环境变量 SKINNY_GEMM 禁用 运行环境 5 启动参数 skip-mm-profiling 运行参数 5 启动参数 kv-cache-memory-bytes 运行参数 测试验证 # 文本补全测试 curl http://localhost:8000/v1/completions \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;model\u0026#34;:\u0026#34;...Qwen3.5-27B\u0026#34;,\u0026#34;prompt\u0026#34;:\u0026#34;1+1=\u0026#34;,\u0026#34;max_tokens\u0026#34;:5}\u0026#39; # 对话测试 curl http://localhost:8000/v1/chat/completions \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;model\u0026#34;:\u0026#34;...Qwen3.5-27B\u0026#34;,\u0026#34;messages\u0026#34;:[{\u0026#34;role\u0026#34;:\u0026#34;user\u0026#34;,\u0026#34;content\u0026#34;:\u0026#34;你好\u0026#34;}],\u0026#34;max_tokens\u0026#34;:50}\u0026#39; 故障排查 问题 1：import torch 报 undefined symbol: rsmi_init 原因：LD_LIBRARY_PATH 顺序错误。解决：按修改一调整。\n问题 2：import triton 死锁 原因：Triton 为 BW1000 编译。解决：按修改二从源码重编。\n问题 3：GCN arch gfx18446744073709551615 原因：amdsmi 返回垃圾值。解决：按修改 3.1 加验证。\n问题 4：LLVM ERROR: permlanex16 原因：Triton 生成不支持指令。解决：按修改 2.4 走 DPP 路径。\n问题 5：HIP error: invalid device function 解决：完成修改 2.4 + 使用 \u0026ndash;skip-mm-profiling。\n问题 6：AttributeError: _rocm_C has no wvSplitK 解决：设置 VLLM_ROCM_USE_SKINNY_GEMM=0。\n问题 7：ValueError: No available memory for cache 解决：使用 \u0026ndash;kv-cache-memory-bytes 手动指定。\n问题 8：Free memory less than desired 解决：pkill -9 -f vllm 清理残留进程。\n备份文件清单 原始文件 备份位置 Triton 安装目录 triton.bak/ Triton dist-info triton-3.4.0\u0026hellip;dist-info.bak/ vLLM rocm.py rocm.py.bak ","permalink":"https://blog.muwinds.com/archives/370/","summary":"\u003ch2 id=\"环境信息\"\u003e环境信息\u003c/h2\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e项目\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e详情\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eGPU\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e4× 海光 Z100 DCU，每张 16GB，合计 64GB\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eGPU 架构\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003egfx906 (Vega 20 / GCN 5.1)\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eDTK 版本\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eDTK 26.04 (DCC2602-0317)\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003ePython\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e3.10.12\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003ePyTorch\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e2.10.0+das.opt1.dtk2604\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003evLLM\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e0.18.1+das.3266200.dtk2604\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eTriton\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e3.4.0（从源码编译，替换原 3.4.0+git1ef59765）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003etransformers\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e5.5.0\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eflash_attn\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e2.8.3+das\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e上述镜像可以去海光社区：\u003ca href=\"https://developer.sourcefind.cn/modelzoo/list/qwen3.5_vllm/detail?post_id=46176b42-0c0b-11f1-a49f-0242ac150003\"\u003ehttps://developer.sourcefind.cn/modelzoo/list/qwen3.5_vllm/detail?post_id=46176b42-0c0b-11f1-a49f-0242ac150003\u003c/a\u003e 获得\u003c/p\u003e","title":"海光 Z100 DCU 适配 vLLM 运行 Qwen3.5-27B 完整总结"},{"content":"最近社区都在说 Muon 用在 LLM 上的训练效果要比 AdamW 好很多，这里根据 Kimi 的论文（http://arxiv.org/abs/2502.16982）和仓库做了一些尝试。\n选用模型：minimind，Github 链接：https://github.com/jingyaogong/minimind\n硬件：AutoDL自己租个 nv 的卡就行\n常见的 AdamW 优化器就是在 Adam 的基础上在梯度更新时加上梯度衰减，这样的话可以避免更新的时候产生更大的参数。\n而 Muon 并没有走 Adam 的那条路，而是在 Momentum 动量的基础上进行改进，将二维的参数使用 Newton-Schulz iteration 进行更新，其余的部分仍然用 AdamW。\n再多了就是一堆社区做过的论文工作了，所以这里放上仓库改好的代码：\nimport math import torch import torch.optim def _newtonschulz5(x, steps): \u0026#34;\u0026#34;\u0026#34;5th-order Newton-Schulz orthogonalization for 2D matrices. From Moonlight / KellerJordan: returns US\u0026#39;V^T (not exact UV^T) where S\u0026#39;_{ii} ~ Uniform(0.5, 1.5). This approximation does not hurt performance and is cheaper to compute. \u0026#34;\u0026#34;\u0026#34; assert x.ndim == 2 a, b, c = (3.4445, -4.7750, 2.0315) X = x.bfloat16() transposed = X.size(0) \u0026gt; X.size(1) if transposed: X = X.T X = X / (X.norm() + 1e-7) for _ in range(steps): A = X @ X.T B = b * A + c * A @ A X = a * X + B @ X if transposed: X = X.T return X.float() _zeropower_fn = _newtonschulz5 if torch.cuda.is_available(): try: _compiled = torch.compile(_newtonschulz5, fullgraph=True) _compiled(torch.randn(4, 4, device=\u0026#34;cuda\u0026#34;), 1) _zeropower_fn = _compiled except Exception: pass def zeropower_via_newtonschulz5(G, steps): return _zeropower_fn(G, steps) def _adjust_lr_for_muon(lr, param_shape): \u0026#34;\u0026#34;\u0026#34;Scale learning rate by sqrt of the larger matrix dimension. From the Moonlight paper: adjusted_lr = lr * 0.2 * sqrt(max(rows, cols)). \u0026#34;\u0026#34;\u0026#34; return lr * 0.2 * math.sqrt(max(param_shape[:2])) class Muon(torch.optim.Optimizer): \u0026#34;\u0026#34;\u0026#34;Muon — MomentUm Orthogonalized by Newton-Schulz. 2D weight matrices receive an orthogonalized momentum update; all other parameters (biases, norms, embeddings, lm_head) fall back to AdamW. Reference: https://github.com/MoonshotAI/Moonlight \u0026#34;\u0026#34;\u0026#34; def __init__( self, params, lr=1e-3, weight_decay=0.1, momentum=0.95, nesterov=True, ns_steps=5, adamw_betas=(0.9, 0.95), adamw_eps=1e-8, ): defaults = dict( lr=lr, weight_decay=weight_decay, momentum=momentum, nesterov=nesterov, ns_steps=ns_steps, adamw_betas=adamw_betas, adamw_eps=adamw_eps, ) super().__init__(params, defaults) self._muon_param_ids = set() self._adamw_param_ids = set() def mark_muon_params(self, muon_params): for p in muon_params: self._muon_param_ids.add(id(p)) self.state[p][\u0026#34;use_muon\u0026#34;] = True def mark_adamw_params(self, adamw_params): for p in adamw_params: self._adamw_param_ids.add(id(p)) self.state[p][\u0026#34;use_muon\u0026#34;] = False @torch.no_grad() def step(self, closure=None): loss = None if closure is not None: with torch.enable_grad(): loss = closure() for group in self.param_groups: lr = group[\u0026#34;lr\u0026#34;] wd = group[\u0026#34;weight_decay\u0026#34;] momentum = group[\u0026#34;momentum\u0026#34;] nesterov = group[\u0026#34;nesterov\u0026#34;] ns_steps = group[\u0026#34;ns_steps\u0026#34;] betas = group[\u0026#34;adamw_betas\u0026#34;] eps = group[\u0026#34;adamw_eps\u0026#34;] muon_params = [] adamw_params = [] for p in group[\u0026#34;params\u0026#34;]: if p.grad is None: continue if self.state[p].get(\u0026#34;use_muon\u0026#34;, False): muon_params.append(p) else: adamw_params.append(p) # --- Muon update --- for p in muon_params: g = p.grad if g.ndim \u0026gt; 2: g = g.view(g.size(0), -1) state = self.state[p] if \u0026#34;momentum_buffer\u0026#34; not in state: state[\u0026#34;momentum_buffer\u0026#34;] = torch.zeros_like(g) buf = state[\u0026#34;momentum_buffer\u0026#34;] buf.mul_(momentum).add_(g) g = g.add(buf, alpha=momentum) if nesterov else buf u = zeropower_via_newtonschulz5(g, ns_steps) adjusted_lr = _adjust_lr_for_muon(lr, p.shape) p.mul_(1 - lr * wd) p.add_(u.to(dtype=p.dtype), alpha=-adjusted_lr) # --- AdamW update --- for p in adamw_params: g = p.grad state = self.state[p] if \u0026#34;step\u0026#34; not in state: state[\u0026#34;step\u0026#34;] = 0 state[\u0026#34;exp_avg\u0026#34;] = torch.zeros_like(p) state[\u0026#34;exp_avg_sq\u0026#34;] = torch.zeros_like(p) state[\u0026#34;step\u0026#34;] += 1 step = state[\u0026#34;step\u0026#34;] exp_avg = state[\u0026#34;exp_avg\u0026#34;] exp_avg_sq = state[\u0026#34;exp_avg_sq\u0026#34;] bias1 = 1 - betas[0] ** step bias2 = 1 - betas[1] ** step exp_avg.lerp_(g, 1 - betas[0]) exp_avg_sq.lerp_(g.square(), 1 - betas[1]) denom = exp_avg_sq.sqrt().div_(math.sqrt(bias2)).add_(eps) step_size = lr / bias1 p.addcdiv_(exp_avg, denom, value=-step_size) if wd != 0: p.mul_(1 - lr * wd) return loss def build_optimizer(model, args): wd = getattr(args, \u0026#34;weight_decay\u0026#34;, 0.1) if args.optimizer == \u0026#34;adamw\u0026#34;: return torch.optim.AdamW( model.parameters(), lr=args.learning_rate, weight_decay=wd ) if args.optimizer != \u0026#34;muon\u0026#34;: raise ValueError(f\u0026#34;Unknown optimizer: {args.optimizer}\u0026#34;) muon_params = [] adamw_params = [] for name, param in model.named_parameters(): if param.ndim \u0026gt;= 2 and \u0026#34;embed_tokens\u0026#34; not in name and \u0026#34;lm_head\u0026#34; not in name: muon_params.append(param) else: adamw_params.append(param) all_params = muon_params + adamw_params opt = Muon( all_params, lr=args.learning_rate, weight_decay=wd, momentum=getattr(args, \u0026#34;muon_momentum\u0026#34;, 0.95), nesterov=True, ns_steps=getattr(args, \u0026#34;muon_ns_steps\u0026#34;, 5), ) opt.mark_muon_params(muon_params) opt.mark_adamw_params(adamw_params) return opt 从代码中也能明显看出来，用 AdamW 的时候每次参数更新需要保存两个状态，而用 Muon 只需要保存一个就可以，能节省不少的显存占用。\n这里放一下 AdamW 和 Muon 在 500 个 step 迭代之后的 Loss：\n在 Pretrain 阶段时训练速度要比 AdamW 慢一点，但是到 SFT 阶段时可以根据情况做一个早停，这样的话训练速度会快一些。\n","permalink":"https://blog.muwinds.com/archives/365/","summary":"\u003cp\u003e最近社区都在说 Muon 用在 LLM 上的训练效果要比 AdamW 好很多，这里根据 Kimi 的论文（http://arxiv.org/abs/2502.16982）和仓库做了一些尝试。\u003c/p\u003e\n\u003cp\u003e选用模型：minimind，Github 链接：https://github.com/jingyaogong/minimind\u003c/p\u003e","title":"训练小参数LLM将优化器从AdamW换成Muon的尝试"},{"content":"map 是有序键值对容器，它的元素的键是唯一的．搜索、移除和插入操作拥有对数复杂度．map 通常实现为 红黑树（红黑树是一种自平衡的二叉搜索树．每个节点额外存储了一个 color 字段 (\u0026ldquo;RED\u0026rdquo; or \u0026ldquo;BLACK\u0026rdquo;)，用于确保树在插入和删除时保持平衡）．\n设想如下场景：现在需要存储一些键值对，例如存储学生姓名对应的分数：Tom 0，Bob 100，Alan 100．但是由于数组下标只能为非负整数，所以无法用姓名作为下标来存储，这个时候最简单的办法就是使用 STL 中的 map．\nmap 重载了 operator[]，可以用任意定义了 operator \u0026lt; 的类型作为下标（在 map 中叫做 key，也就是索引）：\nmap\u0026lt;Key, T\u0026gt; yourMap; 其中，Key 是键的类型，T 是值的类型，下面是使用 map 的实例：\nmap\u0026lt;string, int\u0026gt; mp; map 中不会存在键相同的元素，multimap 中允许多个元素拥有同一键．multimap 的使用方法与 map 的使用方法基本相同．\n插入与删除操作 可以直接通过下标访问来进行查询或插入操作．例如 mp[\u0026quot;Alan\u0026quot;]=100． 通过向 map 中插入一个类型为 pair\u0026lt;Key, T\u0026gt; 的值可以达到插入元素的目的，例如 mp.insert(pair\u0026lt;string,int\u0026gt;(\u0026quot;Alan\u0026quot;,100));； erase(key) 函数会删除键为 key 的 所有 元素．返回值为删除元素的数量． erase(pos): 删除迭代器为 pos 的元素，要求迭代器必须合法． erase(first,last): 删除迭代器在 [𝑓𝑖𝑟𝑠𝑡,𝑙𝑎𝑠𝑡) 范围内的所有元素． clear() 函数会清空整个容器． 在利用下标访问 map 中的某个元素时，如果 map 中不存在相应键的元素，会自动在 map 中插入一个新元素，并将其值设置为默认值（对于整数，值为零；对于有默认构造函数的类型，会调用默认构造函数进行初始化）．\n当下标访问操作过于频繁时，容器中会出现大量无意义元素，影响 map 的效率．因此一般情况下推荐使用 find() 函数来寻找特定键的元素．\n使用样例 map 用于存储复杂状态 在搜索中，我们有时需要存储一些较为复杂的状态（如坐标，无法离散化的数值，字符串等）以及与之有关的答案（如到达此状态的最小步数）．map 可以用来实现此功能．其中的键是状态，而值是与之相关的答案．下面的示例展示了如何使用 map 存储以 string 表示的状态．\n// 存储状态与对应的答案 map\u0026lt;string, int\u0026gt; record; // 新搜索到的状态与对应答案 string status; int ans; // 查找对应的状态是否出现过 map\u0026lt;string, int\u0026gt;::iterator it = record.find(status); if (it == record.end()) { // 尚未搜索过该状态，将其加入状态记录中 record[status] = ans; // 进行相应操作…… } else { // 已经搜索过该状态，进行相应操作…… } 遍历容器 可以利用迭代器来遍历关联式容器的所有元素．\nset\u0026lt;int\u0026gt; s; using si = set\u0026lt;int\u0026gt;::iterator; for (si it = s.begin(); it != s.end(); it++) cout \u0026lt;\u0026lt; *it \u0026lt;\u0026lt; endl; 需要注意的是，对 map 的迭代器解引用后，得到的是类型为 pair\u0026lt;Key, T\u0026gt; 的键值对．\n在 C++11 中，使用范围 for 循环会让代码简洁很多：\nset\u0026lt;int\u0026gt; s; for (auto x : s) cout \u0026lt;\u0026lt; x \u0026lt;\u0026lt; endl; 对于任意关联式容器，使用迭代器遍历容器的时间复杂度均为 𝑂(𝑛)．\n虽然 map 很强，但不是万能的：\n时间复杂度：map 底层是红黑树，每次操作（插入、查询）的时间复杂度是 O(log⁡N)O(\\log N)O(logN)。 如果是数组直接访问，复杂度是 O(1)O(1)。 如果题目时间限制非常非常紧（例如 0.5s 处理 10610^6106 数据），map 可能会超时。 替代方案 unordered_map： 底层是哈希表。 平均时间复杂度 O(1)O(1)，通常比 map 快。 缺点：在最坏情况下（哈希冲突）会退化到 O(N)O(N)；且不支持按顺序遍历（map 是自动排序的）。 建议：一般先用 map，如果超时了（TLE），再尝试改成 unordered_map。 ","permalink":"https://blog.muwinds.com/archives/360/","summary":"\u003cp\u003e\u003ccode\u003emap\u003c/code\u003e 是有序键值对容器，它的元素的键是唯一的．搜索、移除和插入操作拥有对数复杂度．\u003ccode\u003emap\u003c/code\u003e 通常实现为 红黑树（红黑树是一种自平衡的二叉搜索树．每个节点额外存储了一个 color 字段 (\u0026ldquo;RED\u0026rdquo; or \u0026ldquo;BLACK\u0026rdquo;)，用于确保树在插入和删除时保持平衡）．\u003c/p\u003e","title":"记忆回溯：C++ map"},{"content":"每天上班到单位打开电脑，除了 vscode 和 idea 以外就是打开claude code和codex（因为 mentor 也希望我能习惯用 AI 写代码），因此记录一些 vibe coding 需要注意的事儿。\n日常工作中的 vibe coding 其实就是 agentic engineering，让 LLM 决策和写代码的过程本身不可控，这时候就要参考线性规划问题的思路，也就是要给 LLM 上约束，接下来我将参考我的实习内容说说如何优化我们的 vibe coding\n1. 核心原则：先规划再实现 当我们在一个大的业务系统设计和实现一个新功能的时候，设计功能本身就要考虑流程的完备，真的要人来写完备的文档是很低效的，这时候我们可以让 Coding Agent 去阅读项目代码并思考这个功能要怎么实现，当然目前 LLM 本身不太是个能擅长架构的东西，所以这玩意儿生成的文档你务必得看一眼，思考这玩意儿说得不清晰与不完备之处，再让 LLM 去思考和修改，这样再让 LLM 去实施计划的时候可控性会好很多。\n2. 细节1： 版本控制 LLM 生成的东西本身就有抽卡的性质，尤其是项目代码这种 LLM 万一发疯给全删了你哭都没地方哭，所以一定要做好版本控制，翻车了还有挽救的余地，学个 git 顶天半天就够了。\n3. 细节2： AGENTS.md AGENTS.md 不是提示词，而是 Contract。不是表达模型应该如何思考，而是规定它如何工作、如何输出、如何被审计、如何被回滚。\n高质量 AGENTS.md 必须覆盖六个核心工程要素：\n六大工程要素\nCommands（可执行命令） Testing（测试能力） Project Structure（项目结构） Code Style（代码示例） Git Workflow（版本与提交规范） Boundaries（操作边界） 你要在项目里完整描述这个项目对应的环境、代码规范乃至注释规范这种细节，确保 LLM 生成出来的东西具有一定的可读性和一致性，这个文件可以根据项目的发展而不断更换~\n3. 换模型，效果最好的那种 这个对于学生党来说估计够呛，但是能上最好的模型就上最好的模型吧，模型的发展能让很多困难的事情都能事倍功半。\n下面是我的 Claude Code 配置方法：\n写好 CLAUDE.md，下面的是通用的 CLAUDE.md，借鉴 Karpathy 再中文翻译而来的，具体到项目维护你还是得针对项目写一个单独的 CLAUDE.md 使用中文回复，减少常见 LLM 编码错误的行为准则。根据需要与项目特定说明合并。 权衡：这些指南倾向于谨慎而非速度。对于琐碎的任务，请运用判断力。 1. 编码前先思考 不要妄加推测。不要隐藏困惑。要坦诚权衡利弊。 在实施之前： 明确陈述你的假设。如果不确定，就问清楚。 如果存在多种解释，请全部呈现——不要私下选择。 如果有更简单的方法，请说出来。在必要时予以回击。 如果有什么不清楚的地方，停下来。说出让你困惑的地方。问清楚。 2. 简洁至上 解决问题的最小代码量。无任何推测性内容。 没有超出所询问范围的特征。 一次性使用的代码不应进行抽象。 没有未被请求的“灵活性”或“可配置性”。 没有针对不可能情况的错误处理。 如果你写了200行代码，而实际只需要50行，那就重写吧。 问问自己：“一位资深工程师会说这过于复杂吗？”如果是，那就简化。 3. 手术变化 只触碰你必需之物。只收拾自己的烂摊子。 在编辑现有代码时： 不要“改进”相邻的代码、注释或格式。 不要重构没有问题的代码。 即使你有不同的想法，也要与现有风格保持一致。 如果你发现无关的死代码，请指出它——但不要删除它。 当你的更改创建了孤立项时： 删除因你的更改而变得无用的导入、变量或函数。 除非被要求，否则不要移除已存在的死代码。 测试要求：每一行更改都应直接追溯到用户的请求。 4. 目标驱动执行 定义成功标准。循环直至验证通过。 将任务转化为可验证的目标： “增加验证”→“为无效输入编写测试，然后确保它们通过测试” “修复这个bug”→“编写一个能重现该bug的测试用例，然后让测试通过” “重构X”→“确保测试前后均通过” 对于多步骤任务，陈述一个简短的计划： 1. [步骤] → 验证：[检查] 2. [步骤] → 验证：[检查] 3. [步骤] → 验证：[检查] 严格的成功标准让你能够独立循环。而宽松的标准（“让它工作”）则需要不断澄清。 这些指南在以下情况下有效：差异中不必要的更改减少，因过度复杂化导致的重写减少，以及在实施之前而非出错之后澄清问题。 装插件，推荐安装 frontend-design 和 chrome dev mcp 这两个插件，毕竟后端还是纯文字为主，但是前端的 E2E 测试和基本的设计并不是纯文字能解决的。 GPT 家族模型直接无脑用 Codex，其余的模型直接上 Claude Code 即可，其实 Codex App 基本就可以代替 OpenClaw 了…… ","permalink":"https://blog.muwinds.com/archives/359/","summary":"\u003cp\u003e每天上班到单位打开电脑，除了 vscode 和 idea 以外就是打开claude code和codex（因为 mentor 也希望我能习惯用 AI 写代码），因此记录一些 vibe coding 需要注意的事儿。\u003c/p\u003e\n\u003cp\u003e日常工作中的 vibe coding 其实就是 agentic engineering，让 LLM 决策和写代码的过程本身不可控，这时候就要参考线性规划问题的思路，也就是要给 LLM 上约束，接下来我将参考我的实习内容说说如何优化我们的 vibe coding\u003c/p\u003e","title":"实习的记忆记录5：Vibe Coding"},{"content":"由于有写了一半但是又得有同时修另外一个分支代码的需求，所以就得用stash了\n当你执行 git stash 时，Git 会做两件事：\n保存：将你当前**工作区（Working Directory）和暂存区（Staging Area）**中所有已跟踪文件的修改保存到一个栈（Stack）中。 还原：将你的工作目录还原到上一次提交（HEAD）时的干净状态。 此时，你的工作区是干净的，你可以随意切换分支。\n2. 基础命令详解 2.1 暂存修改 (Stash) 最简单的用法，保存当前所有修改（不包含未跟踪的新文件）：\ngit stash # 或者 git stash push 2.2 查看暂存列表 (List)\n你可以多次执行 stash，它们会像“栈”一样堆叠起来。查看所有保存的记录：\ngit stash list 输出示例：\nstash@{0}: WIP on master: 049d078 added the index file stash@{1}: WIP on master: c264051 Revert \u0026#34;added file_size\u0026#34; stash@{2}: WIP on master: 21d80a5 added number to log stash@{0} 是最新的记录。 WIP 意思是 Work In Progress（进行中的工作）。 2.3 恢复并删除 (Pop) —— 最常用\n将最新的暂存内容应用回工作区，并从栈中删除这条记录：\ngit stash pop 如果恢复过程中出现冲突，Git 不会删除这条 stash，你需要解决冲突后手动删除。\n2.4 恢复但这保留 (Apply)\n将最新的暂存内容应用回工作区，但保留栈中的记录（适合你想在多个分支应用同一套修改）：\ngit stash apply 如果要应用指定的某一条（例如第2条）：\ngit stash apply stash@{1} 2.5 删除暂存 (Drop \u0026amp; Clear)\n删除最新的一条：\ngit stash drop 删除指定的一条：\ngit stash drop stash@{1} 清空所有暂存记录（慎用！不可撤销）：\ngit stash clear 3. 进阶用法 (解决痛点) 3.1 痛点一：新创建的文件没有被 Stash？ 默认情况下，git stash 只保存被 Git 跟踪（Tracked）的文件。如果你新建了一个文件 NewFile.js 但还没 git add 过，执行 git stash 后，这个文件还会留在工作区，不会被收纳。\n解决方法：包含未跟踪文件\ngit stash -u # 或者全写 git stash --include-untracked 注：还有一个 -a (--all) 选项，连 .gitignore 忽略的文件也会一起 stash，通常不建议使用，因为可能会把编译生成的垃圾文件也存起来。\n3.2 痛点二：stash@{0} 分不清是啥？ 默认的 stash 备注是最后一次 commit 的信息，很难区分。\n解决方法：给 Stash 加注释\ngit stash push -m \u0026#34;修复了登录按钮的样式，但逻辑还没写完\u0026#34; # 旧版本 git 可能使用: git stash save \u0026#34;注释信息\u0026#34; 这样 git stash list 就会显示你的备注，清晰明了。\n3.3 痛点三：只想暂存一部分文件？ 假设你改了 A 和 B 两个文件，只想暂存 A，继续在 B 上工作。\n解决方法：交互式暂存\ngit stash push -p Git 会进入交互模式，逐个询问你每个文件的修改块（Hunk）是否要 stash（输入 y 或 n）。这和 git add -p 的逻辑一样。\n4. 常见工作流场景 场景 A：紧急修复 Bug 正在 dev 分支开发功能，改了5个文件。 老板大喊：“线上有个 Bug，快修！” 执行 git stash -u -m \u0026quot;开发到一半的功能A\u0026quot;。工作区变干净。 执行 git checkout master -\u0026gt; git checkout -b fix-bug。 修复 Bug，提交，合并。 切回开发分支：git checkout dev。 恢复现场：git stash pop。继续干活。 场景 B：代码写错分支了 你以为你在 dev 分支，写了一下午代码。 突然发现：“卧槽，我在 master 分支上！” 千万别提交。 执行 git stash。 切换到对的分支：git checkout dev。 把代码移过来：git stash pop。 完美转移。 5. 从 Stash 创建分支 如果你 stash 了一堆修改，过了很久才想起来去 pop，结果发现现在的代码和当时 stash 的代码差异巨大，执行 git stash pop 产生了极其复杂的冲突。\n这时候，与其在当前分支解决冲突，不如把这个 stash 拿出来单独变成一个新分支：\ngit stash branch \u0026lt;新分支名\u0026gt; stash@{n} 例子：\ngit stash branch recover-work stash@{1} 这个命令的神奇之处在于： 它会创建一个新分支，检出的基准点是当初你执行 stash 时所在的那个 Commit，而不是当前的 HEAD。这样能保证必定没有冲突地还原现场。\n6. 总结速查表 目的 命令 保存进度 git stash 保存进度(含新文件) git stash -u 保存并加备注 git stash -m \u0026quot;备注信息\u0026quot; 查看列表 git stash list 恢复并删除 git stash pop 恢复但保留 git stash apply 删除指定记录 git stash drop stash@{n} 清空所有 git stash clear ","permalink":"https://blog.muwinds.com/archives/358/","summary":"\u003cp\u003e由于有写了一半但是又得有同时修另外一个分支代码的需求，所以就得用stash了\u003c/p\u003e\n\u003cp\u003e当你执行 \u003ccode\u003egit stash\u003c/code\u003e 时，Git 会做两件事：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e保存\u003c/strong\u003e：将你当前**工作区（Working Directory）\u003cstrong\u003e和\u003c/strong\u003e暂存区（Staging Area）**中所有已跟踪文件的修改保存到一个栈（Stack）中。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e还原\u003c/strong\u003e：将你的工作目录还原到上一次提交（\u003ccode\u003eHEAD\u003c/code\u003e）时的干净状态。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e此时，你的工作区是干净的，你可以随意切换分支。\u003c/p\u003e","title":"实习的记忆记录4：git stash"},{"content":"转载自：https://frytea.com/archives/1616/\n默认情况下 Windows 安装的 Claude Code 会从这个位置读取配置：\nC:\\Users\\\u0026lt;YOUR_NAME\u0026gt;\\.claude 其他系统则类似的找到 ~/.claude 路径。\n官方流程在安装结束后就完成了，可以直接登录使用。如果需要修改配置将 Claude Code 接入第三方 API，就需要修改这里的 settings.json 配置文件，可以使用 CC-Switch 或是手动编写，但是修改后会发现不生效。\n经过调研，发现是最新版的 Claude Code 修改了一个参数，导致启动时一定要登录，这里可以手动修改一下 C:\\Users\\\u0026lt;YOUR_NAME\u0026gt;\\.claude.json 中的这个参数，即可正常使用：\nhasCompletedOnboarding: true 将配置中的 false 改为 true ，再重新运行 Claude Code 即可。\n","permalink":"https://blog.muwinds.com/archives/355/","summary":"\u003cp\u003e转载自：\u003ca href=\"https://frytea.com/archives/1616/\"\u003ehttps://frytea.com/archives/1616/\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e默认情况下 Windows 安装的 \u003ccode\u003eClaude Code\u003c/code\u003e 会从这个位置读取配置：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eC:\\Users\\\u0026lt;YOUR_NAME\u0026gt;\\.claude\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e其他系统则类似的找到 \u003ccode\u003e~/.claude\u003c/code\u003e 路径。\u003c/p\u003e\n\u003cp\u003e官方流程在安装结束后就完成了，可以直接登录使用。如果需要修改配置将 \u003ccode\u003eClaude Code\u003c/code\u003e 接入第三方 API，就需要修改这里的 \u003ccode\u003esettings.json\u003c/code\u003e 配置文件，可以使用 \u003ccode\u003eCC-Switch\u003c/code\u003e 或是手动编写，但是修改后会发现不生效。\u003c/p\u003e","title":"实习的记忆记录3：Windows 配置 Claude Code 解决 settings.json 不生效"},{"content":"以往用 Github 也是差不多这个流程，但是总是忘……\n正常的流程：先 Fork 然后 Clone 到本地，做修改后提交 Commit 再 Push 到自己的仓库，确保没冲突后再发起 Pull Request。\n# Fork 仓库到自己的 GitHub git clone https://github.com/你的用户名/仓库.git cd 仓库 # 添加上游仓库 git remote add upstream https://github.com/原仓库地址.git # 同步最新代码 git fetch upstream git rebase upstream/main git checkout -b feat/add-new-component # 创建描述性的分支名 git add 具体文件 # 避免 git add . git commit -m \u0026#34;feat: 添加登录表单验证\u0026#34; # 推送与创建 PR git push origin feat/add-new-component 注意：如果要开新的功能，就自己再开新的分支并同步上游代码然后再去改，这个分支相关的 PR 被合并后就可以删掉这个分支了\ngit checkout main git branch -d feat/previous-pr git push origin --delete feat/previous-pr 碰到冲突就会麻烦一些，可以自行 cherrypick 或者根据情况手动解决。\n","permalink":"https://blog.muwinds.com/archives/353/","summary":"\u003cp\u003e以往用 Github 也是差不多这个流程，但是总是忘……\u003c/p\u003e\n\u003cp\u003e正常的流程：先 Fork 然后 Clone 到本地，做修改后提交 Commit 再 Push 到自己的仓库，确保没冲突后再发起 Pull Request。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Fork 仓库到自己的 GitHub\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit clone https://github.com/你的用户名/仓库.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ecd\u003c/span\u003e 仓库\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 添加上游仓库\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit remote add upstream https://github.com/原仓库地址.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 同步最新代码\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit fetch upstream\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit rebase upstream/main\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit checkout -b feat/add-new-component  \u003cspan class=\"c1\"\u003e# 创建描述性的分支名\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit add 具体文件  \u003cspan class=\"c1\"\u003e# 避免 git add .\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit commit -m \u003cspan class=\"s2\"\u003e\u0026#34;feat: 添加登录表单验证\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 推送与创建 PR\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit push origin feat/add-new-component\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e注意：如果要开新的功能，就自己再开新的分支并同步上游代码然后再去改，这个分支相关的 PR 被合并后就可以删掉这个分支了\u003c/p\u003e","title":"实习的记忆记录 2：使用 Git 提交 PR"},{"content":"正式开始接触项目开发的第一天就是把项目从公司内网的机器上迁移到阿里云的公网机器上，组长让我写 Dockerfile 和 Docker Compose 开始折腾 CI/CD Pipeline 了。\nDockerfile好写，毕竟上学的时候写过，但是我用 Docker 一般也是以调试为主，很少有需要编排的需求，也就没写过 Docker Compose，所以我就要开始折腾 Docker Compose 相关的内容了。\n1. 为什么我们需要 Docker Compose？ 在没有 Compose 之前，如果你想运行一个包含 Web 服务（如 Python Flask）和数据库（如 Redis）的应用，你需要手动执行以下操作：\n创建一个 Docker 网络（以便容器通信）。 输入很长的命令启动 Redis 容器。 输入很长的命令启动 Web 容器，并手动指定环境变量、端口映射、挂载卷，还要链接到刚才的网络。 痛点：\n命令太长，容易记错。 难以在团队间共享配置（“在我的机器上能跑”的问题）。 容器间的启动顺序和依赖关系难以管理。 Compose 的解决方案： 使用一个 YAML 文件（docker-compose.yml）把所有的配置写下来，然后通过一条命令 docker compose up 启动所有服务。\n2. 核心概念 Docker Compose 有三个层级的概念：\nProject (项目): 默认情况下，存放 docker-compose.yml 文件的目录名就是项目名。一个项目包含多个服务。 Service (服务): 对应一个容器配置（例如：web 服务，db 服务）。 Container (容器): 服务启动后的具体实例。 3. docker-compose.yml 文件详解 这是 Compose 的核心。它使用 YAML 语法。以下是一个典型的结构解析：\nversion: \u0026#34;3.8\u0026#34; # (可选) 指定 Compose 文件版本，新版 Docker 可省略 services: # 定义服务列表 # --- 服务 1: Web 应用 --- webapp: build: . # 从当前目录的 Dockerfile 构建镜像 # image: nginx:latest # 或者直接使用现成的镜像，二选一 ports: - \u0026#34;8080:80\u0026#34; # 端口映射: 宿主机端口:容器端口 volumes: - ./code:/code # 数据卷挂载: 宿主机路径:容器路径 environment: # 环境变量 - DEBUG=True - DB_HOST=database # 注意：这里直接用服务名作为域名 depends_on: # 依赖关系 - database # 确保 database 先启动 networks: - my-net # 加入指定网络 # --- 服务 2: 数据库 --- database: image: postgres:13 environment: POSTGRES_PASSWORD: secret volumes: - db-data:/var/lib/postgresql/data # 使用具名卷持久化数据 networks: - my-net # 定义网络 (可选，默认会自动创建一个) networks: my-net: # 定义具名卷 (用于数据持久化) volumes: db-data: 关键指令解析： image vs build: image 直接拉取远程镜像；build 则根据 Dockerfile 现场构建。 ports: 暴露端口。格式为 \u0026quot;宿主机:容器\u0026quot;。 volumes: 绑定挂载 (Bind Mount): ./host/path:/container/path，常用于开发环境，代码实时同步。 具名卷 (Named Volume): vol_name:/data，常用于数据库数据持久化，由 Docker 管理存储位置。 environment: 设置容器内的环境变量（如数据库密码、API Key）。 depends_on: 控制启动顺序。注意：它只保证启动顺序，不保证被依赖的服务已经“准备好”（例如数据库完全加载完毕）。 服务发现 (Magic): 在同一个 Compose 网络中，服务名就是主机名 (Hostname)。比如 Web 服务想连接 Redis，代码里写 redis_host = \u0026quot;database\u0026quot; 即可，Docker 会自动解析 IP。 4. 常用命令 (CLI) 命令 作用 常用参数 docker compose up 启动所有服务 -d (后台运行) --build (强制重新构建镜像) docker compose down 停止并删除容器、网络 -v (同时删除数据卷，慎用！) docker compose ps 查看当前项目运行的容器 -a (查看所有) docker compose logs 查看服务日志 -f (实时跟踪) web (只看 web 服务的日志) docker compose stop 仅停止容器（不删除） docker compose start 启动已停止的容器 docker compose restart 重启服务 docker compose exec 进入容器执行命令 docker compose exec web bash docker compose config 检查 YAML 文件语法 5. 进阶技巧 A. 使用 .env 文件管理敏感信息\n不要把数据库密码直接写在 docker-compose.yml 里。 创建一个 .env 文件：\nDB_PASSWORD=mysecretpassword123 DB_USER=admin 在 docker-compose.yml 中引用：\nservices: db: image: postgres environment: POSTGRES_PASSWORD: ${DB_PASSWORD} Docker Compose 会自动读取同目录下的 .env 文件。\nB. 重启策略 (Restart Policy)\n在生产环境中，如果程序崩溃或服务器重启，你希望容器自动恢复。\nservices: web: # no: 默认值，不重启 # always: 总是重启 # on-failure: 只有非正常退出(错误码非0)才重启 # unless-stopped: 除非手动 stop，否则一直尝试重启 (推荐) restart: unless-stopped C. 扩展服务 (Scaling)\n如果你想测试负载均衡，可以瞬间启动 5 个 web 容器（前提是代码里没写死端口绑定）：\ndocker compose up -d --scale web=5 (注意：如果使用了 host port 映射如 80:80，扩展会报错端口冲突，通常配合负载均衡器如 Nginx 使用)\nD. 多文件覆盖 (Override)\n你可以有 docker-compose.yml (基础配置) 和 docker-compose.prod.yml (生产环境配置)。 运行命令时：\ndocker compose -f docker-compose.yml -f docker-compose.prod.yml up -d 后者会覆盖前者的配置（例如修改端口、环境变量）。\n6. 总结 Docker Compose 是单机容器编排的神器。\n对于开发：它能让你一键拉起包含数据库、缓存、消息队列的完整开发环境。 对于测试：CI/CD 流水线中常用它来运行集成测试。 对于小型生产环境：配合 restart: always 和挂载卷，足以支撑许多中小型应用。 ","permalink":"https://blog.muwinds.com/archives/352/","summary":"\u003cp\u003e正式开始接触项目开发的第一天就是把项目从公司内网的机器上迁移到阿里云的公网机器上，组长让我写 Dockerfile 和 Docker Compose 开始折腾 CI/CD Pipeline 了。\u003c/p\u003e\n\u003cp\u003eDockerfile好写，毕竟上学的时候写过，但是我用 Docker 一般也是以调试为主，很少有需要编排的需求，也就没写过 Docker Compose，所以我就要开始折腾 Docker Compose 相关的内容了。\u003c/p\u003e","title":"实习的记忆记录 1：Docker Compose"},{"content":"转自CSDN,https://blog.csdn.net/qq_39918087/article/details/143957089\n一票只知道教怎么装，现在nv都提供runfile了，复制一下指令喝杯水的工夫就装上去了还用你教啊？倒是卸载就没几个人出正经教程的。\n1、To remove CUDA Toolkit:\nsudo apt-get --purge remove \u0026#34;*cuda*\u0026#34; \u0026#34;*cublas*\u0026#34; \u0026#34;*cufft*\u0026#34; \u0026#34;*cufile*\u0026#34; \u0026#34;*curand*\u0026#34; \\ \u0026#34;*cusolver*\u0026#34; \u0026#34;*cusparse*\u0026#34; \u0026#34;*gds-tools*\u0026#34; \u0026#34;*npp*\u0026#34; \u0026#34;*nvjpeg*\u0026#34; \u0026#34;nsight*\u0026#34; \u0026#34;*nvvm*\u0026#34; 2.检查以下，到/usr/local/ 查看是否还有cuda相关文件夹。\ncd /usr/local/ # 如果有cuda等文件，如果有，就删掉。 sudo rm -rf /usr/local/cuda* ","permalink":"https://blog.muwinds.com/archives/351/","summary":"\u003cp\u003e转自CSDN,\u003ca href=\"https://blog.csdn.net/qq_39918087/article/details/143957089\"\u003ehttps://blog.csdn.net/qq_39918087/article/details/143957089\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e一票只知道教怎么装，现在nv都提供runfile了，复制一下指令喝杯水的工夫就装上去了还用你教啊？倒是卸载就没几个人出正经教程的。\u003c/p\u003e\n\u003cp\u003e1、To remove CUDA Toolkit:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt-get --purge remove \u003cspan class=\"s2\"\u003e\u0026#34;*cuda*\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*cublas*\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*cufft*\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*cufile*\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*curand*\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*cusolver*\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*cusparse*\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*gds-tools*\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*npp*\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*nvjpeg*\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;nsight*\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;*nvvm*\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e2.检查以下，到/usr/local/ 查看是否还有cuda相关文件夹。\u003c/p\u003e","title":"ubuntu 卸载CUDA Toolkit"},{"content":"什么是深度学习呢？深度学习是一种以深层计算堆栈为特征的机器学习方法。正是这种计算的深度使得深度学习模型能够分解在最具挑战性的真实世界数据集中发现的复杂和层次化的模式。\n凭借其强大和可扩展性，神经网络已成为深度学习的标志性模型。神经网络由神经元组成，每个神经元仅执行简单的计算。神经网络的力量来自于这些神经元能够形成的连接的复杂性。\n线性单元 那么让我们从神经网络的根本组件开始：单个神经元。作为一个图示，一个具有一个输入的神经元（或单元）看起来像：\n线性单元：$y = wx + b$\n输入是 x 。它与神经元的连接有一个权重，这个权重是 w 。每当一个值通过一个连接流动时，你将这个值乘以连接的权重。对于输入 x ，到达神经元的是 w * x 。神经网络通过修改其权重来“学习”。\nb 是一种特殊的权重，我们称之为偏差。偏差没有与任何输入数据相关联；相反，我们在图中放入一个 1 ，以便到达神经元的值只是 b （因为 1 * b = b ）。偏差使神经元能够独立于其输入来修改输出。\ny 是神经元最终输出的值。为了得到输出，神经元会将其通过连接接收到的所有值加总。这个神经元的激活值是 y = w * x + b ，或者用公式表示为 $y = wx + b$，一个经典的斜截式方程。\n尽管单个神经元通常只作为更大网络的一部分工作，但通常从单个神经元模型开始作为基准是有用的。单个神经元模型是线性模型。\n我们可以为每个附加特征向神经元添加更多的输入连接。为了找到输出，我们将每个输入与其连接权重相乘，然后将它们全部加在一起。\n具有两个输入的线性单元将拟合一个平面，而具有更多输入的单元将拟合一个超平面。\nKera中的线性单元\n在 Keras 中创建模型最简单的方法是通过 keras.Sequential ，它将神经网络创建为层的堆栈。我们可以使用密集层来创建上述模型。\n我们可以定义一个接受三个输入特征（ 'sugars' 、 'fiber' 和 'protein' ）并产生一个输出（ 'calories' ）的线性模型，如下所示：\nfrom tensorflow import keras from tensorflow.keras import layers # 创建只有一个单元的网络 model = keras.Sequential([ layers.Dense(units=1, input_shape=[3]) ]) 使用第一个参数 units ，我们定义我们想要的输出数量。在这种情况下，我们只是预测 'calories' ，所以我们将使用 units=1 。\n使用第二个参数 input_shape ，我们告诉 Keras 输入的维度。设置 input_shape=[3] 确保模型将接受三个特征作为输入（ 'sugars' 、 'fiber' 和 'protein' ）。\n这个模型现在可以拟合训练数据了！\n为什么 input_shape 是一个 Python 列表？\n我们在这门课程中将要使用的数据是表格数据，就像 Pandas DataFrame 中的数据一样。我们将为数据集中的每个特征提供一个输入。特征按列排列，所以我们总是会有 input_shape=[num_columns] 。Keras 在这里使用列表的原因是允许使用更复杂的数据集。例如，图像数据可能需要三个维度： [height, width, channels] 。\n深度神经网络 Layers\n神经网络通常将它们的神经元组织成层。当我们把具有相同输入集的线性单元聚集在一起时，我们就得到了一个密集层（Dense Layer）。全连接\u0026quot;（Fully Connected）指的是神经网络中相邻两层之间的每一个神经元都与另一层的所有神经元相互连接。这种连接方式是最经典和基础的神经网络层结构之一，也称为密集层（Dense Layer）\n你可以将神经网络中的每一层视为执行某种相对简单的转换。通过层层堆叠，神经网络可以以越来越复杂的方式转换其输入。在一个训练良好的神经网络中，每一层都是一个转换，让我们更接近解决方案。\n许多种类的层\nKeras 中的\u0026quot;层\u0026quot;是一个非常通用的概念。层本质上可以是任何类型的数据转换。许多层，如卷积层和循环层，通过使用神经元来转换数据，它们的主要区别在于形成的连接模式。然而，其他层则用于特征工程或简单的算术运算。有整个世界层的等待探索——去发现它们吧！\n激活函数\n然而，事实证明，两个密集层之间没有任何东西，与单个密集层本身相比并没有更好。单独的密集层永远无法带我们走出线条和平面的世界。我们需要的是非线性的东西。我们需要的是激活函数。\n激活函数简单来说就是我们对每一层的输出（它的激活值）应用的一些函数。最常见的是修正线性函数： $\\max (0,x)$\n整流函数的图像是一条线，其中负的部分被“整流”为零。将此函数应用于神经元的输出会在数据中产生一个弯曲，使我们远离简单的直线。\n当我们将整流器连接到一个线性单元时，我们得到一个整流线性单元或 ReLU。（因此，通常将整流函数称为\u0026quot;ReLU 函数\u0026quot;。）对一个线性单元应用 ReLU 激活意味着输出变为 max(0, w * x + b) ，我们可以在图中这样绘制：\n堆叠密集层\n现在我们有了非线性，让我们看看如何堆叠层来得到复杂的数据转换。\n堆叠密集层构成一个\u0026quot;全连接\u0026quot;网络。\n输出层之前的层有时被称为隐藏层，因为我们直接看不到它们的输出。\n现在，请注意最终（输出）层是一个线性单元（也就是说，没有激活函数）。这使得该网络适合回归任务，即我们试图预测某个任意的数值。其他任务（如分类）可能需要在输出层使用激活函数。\n构建序列模型\n我们一直在使用的 Sequential 模型将按顺序连接一系列层：第一层接收输入，最后一层产生输出。这创建了上图中的模型：\nfrom tensorflow import keras from tensorflow.keras import layers model = keras.Sequential([ # 隐藏的ReLU层 layers.Dense(units=4, activation=\u0026#39;relu\u0026#39;, input_shape=[2]), layers.Dense(units=3, activation=\u0026#39;relu\u0026#39;), # 线性输出 layers.Dense(units=1), ]) 随机梯度下降 和所有机器学习任务一样，我们从一个训练数据集开始。训练数据集中的每个示例都包含一些特征（输入）以及一个期望的目标（输出）。训练网络意味着调整其权重，使其能够将特征转换为目标。例如，在 80 Cereals 数据集中，我们希望构建一个网络，能够接收每种谷物的 'sugar' 、 'fiber' 和 'protein' 含量，并预测其 'calories' 。如果我们成功地训练一个网络来做到这一点，那么它的权重必须在某种程度上代表训练数据中这些特征与目标之间的关系。\n除了训练数据，我们还需要两个更多东西：\n一个\u0026quot;损失函数\u0026quot;，用于衡量网络的预测有多好。 一个能够告诉网络如何改变其权重的\u0026quot;优化器\u0026quot;。 损失函数\n我们已经看到了如何设计一个网络的架构，但我们还没有看到如何告诉网络要解决什么问题。这就是损失函数的工作。\n损失函数测量目标真实值与模型预测值之间的差异。\n不同的问题需要不同的损失函数。我们一直在研究回归问题，这类问题的任务是为某些数值进行预测——例如 80 种谷物中的卡路里含量，或红酒质量中的评分。其他回归任务可能包括预测房屋价格或汽车的燃油效率。\n回归问题中常见的损失函数是平均绝对误差（MAE）。对于每个预测值 y_pred ，MAE 通过绝对差值 abs(y_true - y_pred) 来衡量与真实目标值 y_true 之间的差异。\n数据集上的总 MAE 损失是所有这些绝对差值的平均值。\n平均绝对误差是拟合曲线与数据点之间的平均长度。\n除了 MAE 之外，回归问题中你可能会看到的其他损失函数包括均方误差（MSE）或 Huber 损失（两者在 Keras 中都有提供）。\n在训练过程中，模型将使用损失函数作为指导，以找到其权重的正确值（损失越低越好）。换句话说，损失函数告诉网络其目标。\n优化器（Only 随机梯度下降）\n我们已经描述了网络需要解决的问题，但现在我们需要说明如何解决它。这就是优化器的任务。优化器是一种调整权重以最小化损失的算法。\n深度学习中几乎所有的优化算法都属于随机梯度下降这一家族。它们是逐步训练网络的迭代算法。训练的一个步骤如下：\n采样一些训练数据，并将其输入网络以做出预测。 测量预测值与真实值之间的损失。 最后，在使损失减小的方向上调整权重。 然后就这样反复操作，直到损失变得尽可能小（或者直到它不再减少。）每次迭代的训练数据样本称为小批量（或简称\u0026quot;批量\u0026quot;），而完整的一轮训练数据称为一个 epoch。你训练的 epoch 数决定了网络会看到每个训练样本的次数。\n学习率（Learning Rate）\n注意，这条线在每个批量方向上只做了小幅度的移动（而不是完全移动）。这些移动的大小由学习率决定。较小的学习率意味着网络需要查看更多的子批量，其权重才能收敛到最佳值。\n学习率和子批量的大小是影响 SGD 训练过程的两项参数。它们之间的相互作用通常很微妙，而这两项参数的正确选择并不总是显而易见的。（我们将在练习中探讨这些影响。）\n幸运的是，对于大多数工作，无需进行广泛的超参数搜索即可获得令人满意的结果。Adam 是一种具有自适应学习率的 SGD 算法，这使得它适用于大多数问题而无需任何参数调整（在某种意义上，它是\u0026quot;自我调整\u0026quot;的）。Adam 是一个通用的优秀优化器。\n添加损失函数和优化器\n在定义模型后，您可以使用模型的 compile 方法添加损失函数和优化器：\nmodel.compile( optimizer=\u0026#34;adam\u0026#34;, loss=\u0026#34;mae\u0026#34;, ) 请注意，我们只需使用字符串即可指定损失函数和优化器。你也可以通过 Keras API 直接访问这些设置——例如，如果你想调整参数的话——但对我们来说，默认设置已经足够好了。\n名字里有什么意义？\n梯度 gradient 是一个向量，它告诉我们权重需要朝哪个方向变化。更精确地说，它告诉我们如何改变权重以使损失变化最快。我们称这个过程为梯度下降，因为它使用梯度来沿着损失曲线下降 descent 到最小值。随机 Stochastic 意味着“由机会决定”。我们的训练是随机的，因为小批量是从数据集中随机采样的。这就是它被称为 SGD 的原因！\n过拟合和欠拟合 之前 Machine Learning 也记录过，但是并没有对学习曲线了解太多。\n我们可以将训练数据中的信息分为两类：信号和噪声。信号是泛化部分，是能够帮助模型从新数据中进行预测的部分。噪声是仅适用于训练数据的部分；噪声是来自现实世界数据的随机波动，或者是所有偶然的、无信息的模式，这些模式实际上并不能帮助模型进行预测。噪声是看起来有用但实际上并非如此的部分。\n我们通过选择能够使训练集上的损失最小化的权重或参数来训练模型。然而，你可能知道，为了准确评估模型的性能，我们需要在新的数据集，即验证数据集上对其进行评估\n在训练模型时，我们一直按每个 epoch 绘制训练集上的损失。接下来，我们也将添加一个验证数据的绘图。这些图我们称为学习曲线。为了有效地训练深度学习模型，我们需要能够解读它们。\n验证损失给出了对未见过数据的预期误差的估计。\n现在，训练损失下降要么是因为模型学习了信号，要么是因为它学习了噪声。但验证损失只会下降，当模型学习了信号时。 （模型从训练集中学习的任何噪声都不会泛化到新数据中。）所以，当模型学习信号时，两条曲线都会下降，但当它学习噪声时，曲线之间会出现一个间隙。间隙的大小告诉你模型学习了多少噪声。\n理想情况下，我们希望创建的模型能学习所有信号而不学习任何噪声。这在实践中几乎不可能发生。相反，我们进行权衡。我们可以让模型以学习更多噪声为代价来学习更多信号。只要权衡对我们有利，验证损失就会继续下降。然而，在某个点上，权衡可能会对我们不利，成本超过了收益，验证损失开始上升。\n这种权衡表明，在训练模型时可能会出现两个问题：信号不足或噪声过多。欠拟合训练集是指由于模型没有学习到足够的信号，导致损失没有达到可能的最小值。过拟合训练集是指由于模型学习了过多的噪声，导致损失没有达到可能的最小值。训练深度学习模型的诀窍在于在这两者之间找到最佳平衡。\n我们将探讨几种从训练数据中获取更多信号同时减少噪声的方法。\n模型容量\n一个模型的容量指的是它能学习的模式的规模和复杂性。对于神经网络来说，这主要取决于它有多少个神经元以及它们是如何连接在一起的。如果您的网络看起来拟合数据不足，您应该尝试增加其容量。\n您可以通过使网络变宽（给现有层增加更多单元）或使其变深（增加更多层）来增加网络的容量。较宽的网络更容易学习更线性的关系，而较深的网络则更倾向于非线性关系。哪种更好取决于数据集。\nmodel = keras.Sequential([ layers.Dense(16, activation=\u0026#39;relu\u0026#39;), layers.Dense(1), ]) wider = keras.Sequential([ layers.Dense(32, activation=\u0026#39;relu\u0026#39;), layers.Dense(1), ]) deeper = keras.Sequential([ layers.Dense(16, activation=\u0026#39;relu\u0026#39;), layers.Dense(16, activation=\u0026#39;relu\u0026#39;), layers.Dense(1), ]) Early Stopping\n我们提到，当模型过于急切地学习噪声时，验证损失在训练过程中可能会开始增加。为了防止这种情况，我们只需在验证损失似乎不再减少时随时停止训练。以这种方式中断训练称为提前停止。\n一旦我们检测到验证损失开始再次上升，我们就可以将权重重置为最小值出现的位置。这确保了模型不会继续学习噪声并过拟合数据。\n使用早停机制也意味着我们更不容易在神经网络完成学习信号之前就停止训练，从而避免了过早停止训练的风险。因此，除了防止因训练时间过长导致过拟合，早停机制也能防止因训练时间不够导致欠拟合。只需将训练轮数设置为某个较大的数值（比你实际需要的多），早停机制会处理好剩下的部分。\n在 Keras 中，我们通过回调函数在训练中包含早停机制。回调函数只是你希望在神经网络训练过程中定期运行的函数。早停回调函数会在每个轮次后运行。（Keras 预定义了多种有用的回调函数，你也可以定义自己的。）\nfrom tensorflow.keras.callbacks import EarlyStopping early_stopping = EarlyStopping( min_delta=0.001, # 一轮最少要提升多少 patience=20, # 要跑多少轮epoch restore_best_weights=True, ) 这些参数表示：\u0026ldquo;如果在之前的 20 个 epoch 中，验证损失没有至少改善 0.001，那么停止训练并保留你找到的最佳模型。\u0026rdquo; 有时很难判断验证损失上升是由于过拟合还是仅仅由于随机批次变化。这些参数允许我们在何时停止训练时设置一些容许范围。\nDropout 和 批量归一化 深度学习的世界并不仅仅局限于密集层。你可以向模型中添加数十种不同类型的层。（可以尝试浏览一下 Keras 文档来获取一些示例！）有些层类似于密集层，定义神经元之间的连接，而另一些层可以进行预处理或执行其他类型的转换。\n我们将学习两种特殊的层，它们本身不包含任何神经元，但可以添加一些功能，有时以各种方式使模型受益。这两种层在现代架构中都很常用。\nDropout\n其中第一种是“dropout 层”，它可以帮助纠正过拟合。\n在之前的内容中我们讨论了过拟合是如何由网络在训练数据中学习虚假模式引起的。为了识别这些虚假模式，网络通常会依赖非常特定的权重组合，一种“共谋”的权重。由于如此特定，它们往往很脆弱：移除一个，共谋就会瓦解。\n这就是 dropout 的想法。为了打破这些共谋，我们在训练的每一步随机地丢弃一部分层的输入单元，这使得网络学习训练数据中那些虚假模式变得非常困难。相反，它必须寻找更广泛、更一般的模式，这些模式的权重模式往往更稳健。\n你也可以将 dropout 理解为创建一种网络集成。预测将不再由一个大型网络做出，而是由一组小型网络共同完成。委员会中的个体往往会犯不同类型的错误，但在同一时间又是正确的，这使得整个委员会比任何个体都更优秀。（如果你熟悉随机森林作为决策树的集成，其原理是相同的。）\n在 Keras 中，dropout rate 参数 rate 定义了要关闭的输入单元的百分比。将 Dropout 层放在要应用 dropout 的层之前：\nkeras.Sequential([ # ... layers.Dropout(rate=0.3), # 在下一层添加 30% 的Dropout layers.Dense(16), # ... ]) 批量归一化\n接下来我们将要研究的特殊层执行\u0026quot;批量归一化\u0026quot;（或\u0026quot;batchnorm\u0026quot;），这有助于纠正训练缓慢或不稳定的情况。\n对于神经网络来说，通常是一个好主意将所有数据放在一个共同的尺度上，也许可以使用 scikit-learn 的 StandardScaler 或 MinMaxScaler 之类的工具。原因在于 SGD 会根据数据产生的激活大小来调整网络权重。那些倾向于产生非常不同大小激活的特征可能会导致训练行为不稳定。\n现在，如果数据在进入网络之前进行归一化是好的，那么在网络内部进行归一化可能也会更好！实际上，我们有一种特殊的层可以做到这一点，那就是批量归一化层。批量归一化层会查看进入的每个批次，首先使用该批次自己的均值和标准差来归一化批次，然后还使用两个可训练的缩放参数将数据映射到新的尺度上。实际上，批量归一化会对它的输入进行一种协调的缩放。\n通常情况下，批量归一化被添加到优化过程中作为辅助手段（尽管有时它也能帮助提升预测性能）。带有批量归一化的模型往往需要更少的训练周期即可完成训练。此外，批量归一化还可以解决导致训练\u0026quot;停滞\u0026quot;的各种问题。建议在模型中加入批量归一化，尤其是在训练过程中遇到困难时。\n似乎批量归一化可以在网络的几乎任何位置使用。你可以把它放在一层之后或者放在一层和它的激活函数之间：\nlayers.Dense(16, activation=\u0026#39;relu\u0026#39;), layers.BatchNormalization(), layers.Dense(16), layers.BatchNormalization(), layers.Activation(\u0026#39;relu\u0026#39;), 如果你将它作为网络的第一层添加，它可以充当一种自适应预处理器，替代类似 Sci-Kit Learn 的 StandardScaler 。\n二元分类 将数据分类到两个类别中是一个常见的机器学习问题。你可能想要预测客户是否会购买，信用卡交易是否欺诈，深空信号是否显示出新行星的证据，或者医学测试是否显示出疾病的证据。这些都是二元分类问题。\n在你的原始数据中，类别可能由字符串如 \u0026quot;Yes\u0026quot; 和 \u0026quot;No\u0026quot; ，或者 \u0026quot;Dog\u0026quot; 和 \u0026quot;Cat\u0026quot; 表示。在使用这些数据之前，我们将分配一个类别标签：一个类别将是 0 ，另一个将是 1 。分配数字标签将数据转换为神经网络可以使用的格式。\n准确率和交叉熵\n准确率是用于衡量分类问题成功与否的众多指标之一。准确率是指正确预测数与总预测数的比率： accuracy = number_correct / total 。一个总是预测正确的模型将拥有 1.0 的准确率。在其他条件相同的情况下，只要数据集中的各类别出现频率大致相同，准确率就是一个合理的指标。\n准确率（以及大多数其他分类指标）的问题在于它不能被用作损失函数。SGD 需要一个变化平滑的损失函数，但准确率作为计数比，是跳跃式变化的。因此，我们必须选择一个替代品来充当损失函数。这个替代品就是交叉熵函数。\n现在，回想一下损失函数定义了网络在训练过程中的目标。在回归问题中，我们的目标是使预期结果与预测结果之间的距离最小化。我们选择了 MAE 来衡量这种距离。\n在分类中，我们想要的其实是在概率之间的距离，而交叉熵正是提供了这种距离。交叉熵是一种衡量一个概率分布与另一个概率分布之间距离的度量。\n交叉熵会惩罚错误的概率预测。\n我们的想法是希望我们的网络能够以概率 1.0 预测正确的类别。预测概率与 1.0 的距离越远，交叉熵损失就越大。\n我们使用交叉熵的技术原因有些微妙，但要记住的主要一点是：使用交叉熵作为分类损失；其他你可能关心的指标（如准确率）往往会随着它一起提高。\nSigmoid 函数生成概率\n交叉熵和准确率函数都需要概率作为输入，也就是说，需要 0 到 1 之间的数值。为了将密集层产生的实数值输出转换为概率，我们附加了一种新的激活函数——Sigmoid 激活函数。\nSigmoid 函数将实数映射到区间 $[0,1]$\n要得到最终的类别预测，我们定义一个阈值概率。通常这个阈值是 0.5，这样四舍五入就能得到正确的类别：低于 0.5 表示标签为 0 的类别，0.5 或以上表示标签为 1 的类别。Keras 在默认的准确率指标中使用 0.5 的阈值。\n","permalink":"https://blog.muwinds.com/archives/332/","summary":"\u003cp\u003e什么是深度学习呢？深度学习是一种以深层计算堆栈为特征的机器学习方法。正是这种计算的深度使得深度学习模型能够分解在最具挑战性的真实世界数据集中发现的复杂和层次化的模式。\u003c/p\u003e","title":"Machine Learning 记录 3"},{"content":"缺失值处理 1) 一个简单的选项：删除包含缺失值的列\n最简单的选择是删除包含缺失值的列。\n除非被删除列中的大多数值缺失，否则这种方法会使模型丢失大量（潜在有用！）的信息。举一个极端的例子，考虑一个包含10,000行的数据集，其中一列缺少一个条目。这种方法会完全删除该列！\n# 获取缺失值 cols_with_missing = [col for col in X_train.columns if X_train[col].isnull().any()] # 在训练集和验证集中删除同样的列 reduced_X_train = X_train.drop(cols_with_missing, axis=1) reduced_X_valid = X_valid.drop(cols_with_missing, axis=1) print(\u0026#34;MAE from Approach 1 (Drop columns with missing values):\u0026#34;) print(score_dataset(reduced_X_train, reduced_X_valid, y_train, y_valid)) 2) 更好的选择：插补\nImputation 会用一些数字填补缺失值。例如，我们可以沿每一列填充均值。\n在大多数情况下，插补的值不会完全正确，但它通常能比完全删除该列得到更准确的模型。\nfrom sklearn.impute import SimpleImputer # Imputation my_imputer = SimpleImputer() imputed_X_train = pd.DataFrame(my_imputer.fit_transform(X_train)) imputed_X_valid = pd.DataFrame(my_imputer.transform(X_valid)) # Imputation removed column names; put them back imputed_X_train.columns = X_train.columns imputed_X_valid.columns = X_valid.columns print(\u0026#34;MAE from Approach 2 (Imputation):\u0026#34;) print(score_dataset(imputed_X_train, imputed_X_valid, y_train, y_valid)) 3) 对插补的扩展\n插补是标准方法，通常效果良好。然而，插补的值可能与实际值系统性地高于或低于（这些值未在数据集中收集）。或者，缺失值的行可能在其他方面具有独特性。在这种情况下，通过考虑哪些值原本缺失，您的模型可以做出更好的预测。\n在这种方法中，我们像之前一样填充缺失值。此外，对于原始数据集中缺失条目的每一列，我们添加一个新列来显示填充条目的位置。在某些情况下，这将显著改善结果。在其他情况下，它根本不起作用。\n# 复制一份儿副本防止填充的时候直接对原始数据进行修改 X_train_plus = X_train.copy() X_valid_plus = X_valid.copy() # 创建新列表示哪些列会被填充 for col in cols_with_missing: X_train_plus[col + \u0026#39;_was_missing\u0026#39;] = X_train_plus[col].isnull() X_valid_plus[col + \u0026#39;_was_missing\u0026#39;] = X_valid_plus[col].isnull() # Imputation my_imputer = SimpleImputer() imputed_X_train_plus = pd.DataFrame(my_imputer.fit_transform(X_train_plus)) imputed_X_valid_plus = pd.DataFrame(my_imputer.transform(X_valid_plus)) # 填充的时候会移除列名，现在需要加回来 imputed_X_train_plus.columns = X_train_plus.columns imputed_X_valid_plus.columns = X_valid_plus.columns print(\u0026#34;MAE from Approach 3 (An Extension to Imputation):\u0026#34;) print(score_dataset(imputed_X_train_plus, imputed_X_valid_plus, y_train, y_valid)) 分类变量 分类变量只取有限数量的值。\n考虑一项调查，询问你多久吃一次早餐，并提供四个选项：“从不”、“很少”、“大多数日子”或“每天”。在这种情况下，数据是分类的，因为回答落在固定的类别中。\n如果人们回答一项关于他们拥有的汽车品牌的调查，回答将落入“本田”、“丰田”和“福特”等类别中。在这种情况下，数据也是分类的。\n如果你在不预先处理它们的情况下将这些变量输入大多数Python机器学习模型，你会得到错误。在这个教程中，我们将比较三种你可以用来准备你的分类数据的方法。\n看一个数据集：\nType Method Regionname Rooms Distance Postcode Bedroom2 Bathroom Landsize Lattitude Longtitude Propertycount 12167 u S Southern Metropolitan 1 5.0 3182.0 1.0 1.0 0.0 -37.85984 144.9867 6524 h SA Western Metropolitan 2 8.0 3016.0 2.0 2.0 193.0 -37.85800 144.9005 8413 h S Western Metropolitan 3 12.6 3020.0 3.0 1.0 555.0 -37.79880 144.8220 2919 u SP Northern Metropolitan 3 13.0 3046.0 3.0 1.0 265.0 -37.70830 144.9158 6043 h S Western Metropolitan 3 13.3 3020.0 3.0 1.0 673.0 -37.76230 144.8272 1）删除分类变量\n处理分类变量的最简单方法是直接从数据集中删除它们。如果列中不包含有用信息，这种方法才会有效。\ndrop_X_train = X_train.select_dtypes(exclude=[\u0026#39;object\u0026#39;]) drop_X_valid = X_valid.select_dtypes(exclude=[\u0026#39;object\u0026#39;]) print(\u0026#34;MAE from Approach 1 (Drop categorical variables):\u0026#34;) print(score_dataset(drop_X_train, drop_X_valid, y_train, y_valid)) 2) 序数编码\n序数编码将每个唯一值分配给不同的整数。\n这种方法假设类别存在排序关系：“Never”（0）\u0026lt;“Rarely”（1）\u0026lt;“Most days”（2）\u0026lt;“Every day”（3）。\n在这个例子中，这种假设是有道理的，因为类别之间存在明确的等级。并非所有分类变量在值上都有清晰的排序，但我们将那些具有清晰排序的变量称为有序变量。对于基于树的模型（如决策树和随机森林），你可以预期有序编码能够很好地与有序变量配合使用。\nfrom sklearn.preprocessing import OrdinalEncoder # 创建副本以避免直接修改原始数据 label_X_train = X_train.copy() label_X_valid = X_valid.copy() # 对每一列可分类的数据应用序数编码器 ordinal_encoder = OrdinalEncoder() label_X_train[object_cols] = ordinal_encoder.fit_transform(X_train[object_cols]) label_X_valid[object_cols] = ordinal_encoder.transform(X_valid[object_cols]) print(\u0026#34;MAE from Approach 2 (Ordinal Encoding):\u0026#34;) print(score_dataset(label_X_train, label_X_valid, y_train, y_valid)) 3) 独热编码\nOne-hot encoding 会创建新的列来指示原始数据中每个可能值的存在（或不存在）。为了理解这一点，我们将通过一个例子来讲解。\n在原始数据集中，“颜色”是一个具有三个类别的分类变量：“红色”、“黄色”和“绿色”。相应的独热编码为每个可能的值包含一列，并为原始数据集中的每一行包含一行。在原始值是“红色”的地方，我们在“红色”列中放入1；如果原始值是“黄色”，我们在“黄色”列中放入1，以此类推。\n与序数编码不同，独热编码不假设类别之间的顺序。因此，当分类数据没有明确的顺序时（例如，“红色”既不比“黄色”更优也不比它更劣），你可以预期这种方法会特别有效。我们称没有内在排序的分类变量为名义变量。\n如果分类变量取很多值（即，你通常不会用它来处理超过15个不同值的变量），独热编码通常表现不佳。\nfrom sklearn.preprocessing import OneHotEncoder # 对每一列可分类的数据应用One Hot Encoder OH_encoder = OneHotEncoder(handle_unknown=\u0026#39;ignore\u0026#39;, sparse=False) OH_cols_train = pd.DataFrame(OH_encoder.fit_transform(X_train[object_cols])) OH_cols_valid = pd.DataFrame(OH_encoder.transform(X_valid[object_cols])) # 序数编码后会删掉索引，放回去 OH_cols_train.index = X_train.index OH_cols_valid.index = X_valid.index # 扔掉原来的分类列，用独热编码后的列替代 num_X_train = X_train.drop(object_cols, axis=1) num_X_valid = X_valid.drop(object_cols, axis=1) # 将One Hot Encodeed后的列加入到扔掉分类列的数据中 OH_X_train = pd.concat([num_X_train, OH_cols_train], axis=1) OH_X_valid = pd.concat([num_X_valid, OH_cols_valid], axis=1) # 确保所有列都是字符串，防止类名类型冲突 OH_X_train.columns = OH_X_train.columns.astype(str) OH_X_valid.columns = OH_X_valid.columns.astype(str) print(\u0026#34;MAE from Approach 3 (One-Hot Encoding):\u0026#34;) print(score_dataset(OH_X_train, OH_X_valid, y_train, y_valid)) 我们获取训练数据中所有分类变量的列表。我们通过检查每一列的数据类型（或dtype）来实现这一点。object dtype表示该列包含文本（理论上它可以是其他东西，但这对我们的目的来说不重要）。对于上面的数据集，包含文本的列表示分类变量。\n# Get list of categorical variables s = (X_train.dtypes == \u0026#39;object\u0026#39;) object_cols = list(s[s].index) print(\u0026#34;Categorical variables:\u0026#34;) print(object_cols) 管道 Pipelines 管道是一种简单的方法，可以保持您的数据预处理和建模代码的条理清晰。具体来说，管道将预处理和建模步骤捆绑在一起，以便您可以将整个捆绑包作为一个步骤来使用。\n许多数据科学家在没有管道的情况下拼凑模型，但管道有一些重要的优势。这些包括：\n更清晰的代码：在预处理每个步骤中考虑数据可能会很混乱。使用管道，您就不需要手动跟踪每个步骤中的训练和验证数据。\n更少的错误：犯错误的机会更少，或者忘记预处理步骤。\n更容易投入生产：将模型从原型转变为可大规模部署的东西可能非常困难。我们不会在这里讨论许多相关的问题，但管道可以帮助。\n先看一下数据集：\nType Method Regionname Rooms Distance Postcode Bedroom2 Bathroom Car Landsize BuildingArea YearBuilt Lattitude Longtitude Propertycount 12167 u S Southern Metropolitan 1 5.0 3182.0 1.0 1.0 1.0 0.0 NaN 1940.0 -37.85984 144.9867 6524 h SA Western Metropolitan 2 8.0 3016.0 2.0 2.0 1.0 193.0 NaN NaN -37.85800 144.9005 8413 h S Western Metropolitan 3 12.6 3020.0 3.0 1.0 1.0 555.0 NaN NaN -37.79880 144.8220 2919 u SP Northern Metropolitan 3 13.0 3046.0 3.0 1.0 1.0 265.0 NaN 1995.0 -37.70830 144.9158 6043 h S Western Metropolitan 3 13.3 3020.0 3.0 1.0 2.0 673.0 673.0 1970.0 -37.76230 144.8272 第一步：定义预处理步骤\n类似于工作流程如何将预处理和建模步骤捆绑在一起，我们使用 ColumnTransformer 类将不同的预处理步骤捆绑在一起。下面的代码：对数值数据进行缺失值填充，并填充缺失值并对分类数据应用独热编码。\nfrom sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import OneHotEncoder # 对数值数据进行预处理 numerical_transformer = SimpleImputer(strategy=\u0026#39;constant\u0026#39;) # 对分类数据进行预处理 categorical_transformer = Pipeline(steps=[ (\u0026#39;imputer\u0026#39;, SimpleImputer(strategy=\u0026#39;most_frequent\u0026#39;)), (\u0026#39;onehot\u0026#39;, OneHotEncoder(handle_unknown=\u0026#39;ignore\u0026#39;)) ]) # 将上面的步骤绑定起来 preprocessor = ColumnTransformer( transformers=[ (\u0026#39;num\u0026#39;, numerical_transformer, numerical_cols), (\u0026#39;cat\u0026#39;, categorical_transformer, categorical_cols) ]) 第二步，定义模型\n接下来，我们使用熟悉的 RandomForestRegressor 类定义一个随机森林模型。\nfrom sklearn.ensemble import RandomForestRegressor model = RandomForestRegressor(n_estimators=100, random_state=0) 第三步：创建管道并评估\n最后，我们使用 Pipeline 类来定义一个包含预处理和建模步骤的流水线。有几个重要的注意事项：\n使用流水线，我们可以在一行代码中预处理训练数据并拟合模型。（相比之下，如果没有流水线，我们必须分别进行插补、独热编码和模型训练。如果我们要处理数值型和分类型变量，这会变得特别混乱！） 使用流水线，我们将未处理的特征 X_valid 输入到 predict() 命令中，流水线会自动在生成预测前对特征进行预处理。（然而，如果没有流水线，我们必须记得在做出预测前预处理验证数据。 from sklearn.metrics import mean_absolute_error # 绑定预处理和模型部分到管道 my_pipeline = Pipeline(steps=[(\u0026#39;preprocessor\u0026#39;, preprocessor), (\u0026#39;model\u0026#39;, model) ]) # 对训练集数据进行拟合 my_pipeline.fit(X_train, y_train) # 对验证集数据进行预测 preds = my_pipeline.predict(X_valid) # 评估 score = mean_absolute_error(y_valid, preds) print(\u0026#39;MAE:\u0026#39;, score) 交叉验证 机器学习是一个迭代的过程。\n你将面临关于使用哪些预测变量、使用什么类型的模型、向模型提供什么参数等选择。到目前为止，你通过使用验证集（或保留集）来衡量模型质量，以数据驱动的方式做出了这些选择。\n但这种方法的缺点是什么。要理解这一点，想象一下你有一个包含 5000 行数据的数据集。你通常会保留大约 20%的数据作为验证数据集，即 1000 行。但这在确定模型分数时会留下一些随机性。也就是说，一个模型可能在一组 1000 行数据上表现良好，即使它在另一组 1000 行数据上可能不准确。\n一般来说，验证集越大，模型质量评估中的随机性（即\u0026quot;噪声\u0026quot;）就越少，评估结果就越可靠。不幸的是，我们只能通过从训练数据中删除行来获得较大的验证集，而较小的训练数据集意味着更差的模型！\n什么是交叉验证？\n在交叉验证中，我们使用数据的不同子集来运行建模过程，以获得多个模型质量的度量。\n例如，我们可以先将数据分成 5 份，每份占完整数据集的 20%。在这种情况下，我们称将数据分成了 5 个“折”。\n然后，我们对每个折运行一个实验：\n在实验 1 中，我们使用第一个折作为验证（或保留）集，其余所有数据作为训练数据。这给我们提供了一个基于 20%保留集的模型质量度量。 在实验 2 中，我们保留第二折的数据（并使用除第二折以外的所有数据进行模型训练）。然后使用保留集来获得模型质量的第二次估计。 我们重复这个过程，每次使用一个折作为保留集。综合来看，100%的数据在某个时刻被用作保留集，最终我们得到一个基于数据集所有行的模型质量度量（即使我们不会同时使用所有行）。 什么时候应该使用交叉验证？\n交叉验证能提供更准确的模型质量度量，这在你要做出大量建模决策时尤其重要。然而，它可能需要更长时间来运行，因为它估计多个模型（每个折一个模型）。\n所以，考虑到这些权衡，什么时候应该使用每种方法呢？\n对于小数据集，如果额外的计算负担不是大问题，你应该运行交叉验证。 对于较大的数据集，单个验证集就足够了。你的代码运行速度会更快，而且你可能有足够的数据，以至于没有必要将其中一些数据用于保留。 没有简单的阈值来定义什么是大数据集或小数据集。但如果你的模型运行时间不到几分钟，那么切换到交叉验证可能值得。\n或者，你可以运行交叉验证，看看每个实验的分数是否相近。如果每个实验都得到相同的结果，那么一个单独的验证集可能就足够了。\n接着用上一部分的数据吧，继续使用管道\nfrom sklearn.ensemble import RandomForestRegressor from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer my_pipeline = Pipeline(steps=[(\u0026#39;preprocessor\u0026#39;, SimpleImputer()), (\u0026#39;model\u0026#39;, RandomForestRegressor(n_estimators=50, random_state=0)) ]) 我们使用 scikit-learn 中的 cross_val_score() 函数获取交叉验证分数。我们通过 cv 参数设置折数。\nfrom sklearn.model_selection import cross_val_score # Multiply by -1 since sklearn calculates *negative* MAE scores = -1 * cross_val_score(my_pipeline, X, y, cv=5, scoring=\u0026#39;neg_mean_absolute_error\u0026#39;) print(\u0026#34;MAE scores:\\n\u0026#34;, scores) scoring 参数用于选择报告的模型质量度量：在这种情况下，我们选择了负均绝对误差（MAE）。scikit-learn 的文档中列出了多个选项。\n我们指定负 MAE 这一点有点令人惊讶。Scikit-learn 有一个惯例，即所有指标都是这样定义的：数值越高越好。在这里使用负数使它们能够与这一惯例保持一致，尽管几乎在其他地方都很少见到负 MAE。\n我们通常需要一个单一的性能指标来比较不同的模型。因此，我们通过实验取平均值。\nprint(\u0026#34;Average MAE score (across experiments):\u0026#34;) print(scores.mean()) XGBoost 我们将随机森林方法称为一种\u0026quot;集成方法\u0026quot;。根据定义，集成方法结合了多个模型的预测（例如，在随机森林的情况下，是多个树）。\n接下来，我们将学习另一种集成方法，称为梯度提升。\n梯度提升\n梯度提升是一种通过循环迭代将模型添加到集成中的方法。它首先使用单个模型初始化集成，该模型的预测可能相当简单。（即使其预测非常不准确，后续添加到集成中的模型将解决这些错误。）然后，我们开始循环：\n首先，我们使用当前集成模型为数据集中的每个观测值生成预测。为了进行预测，我们将集成中所有模型的预测结果相加。 这些预测结果用于计算损失函数（例如常见的均方误差MSE）。 然后，我们使用损失函数来拟合一个新的模型，该模型将被添加到集成中。具体来说，我们确定模型参数，以便将这个新模型添加到集成中能够降低损失。（旁注：在“梯度提升”中的“梯度”指的是我们将使用梯度下降法在损失函数上确定这个新模型的参数。） 最后，我们将新模型添加到集成模型中 重复上述步骤 XGBoost 代表极端梯度提升，它是梯度提升的一种实现，并具有几个针对性能和速度的额外特性。（Scikit-learn 有另一种梯度提升版本，但 XGBoost 有一些技术优势。）\nfrom xgboost import XGBRegressor my_model = XGBRegressor() my_model.fit(X_train, y_train) 我们还进行预测并评估模型。\nfrom sklearn.metrics import mean_absolute_error predictions = my_model.predict(X_valid) print(\u0026#34;Mean Absolute Error: \u0026#34; + str(mean_absolute_error(predictions, y_valid))) 参数调优\nXGBoost 有几个参数可以显著影响准确性和训练速度。你应该首先理解的参数是：\nn_estimators 指定要经过上述建模周期的次数。它等于我们包含在集成中的模型数量。\n值太低会导致欠拟合，这会导致在训练数据和测试数据上都不准确的预测。 过高值会导致过拟合，这会导致在训练数据上准确预测，但在测试数据上不准确预测（这是我们关心的）。 典型值范围在 100-1000 之间，尽管这很大程度上取决于下面讨论的 learning_rate 参数。\n这是设置集成中模型数量的代码：\nmy_model = XGBRegressor(n_estimators=500) my_model.fit(X_train, y_train) early_stopping_rounds 提供了一种自动找到 n_estimators 理想值的方法。早停机制会在验证分数停止提升时使模型停止迭代，即使我们还没有达到 n_estimators 的硬停点。设置一个较高的 n_estimators 值，然后使用 early_stopping_rounds 找到最佳的停止迭代时间，这是一个明智的选择。\n由于随机性有时会导致一轮验证分数没有提升，你需要指定一个数字，表示在停止之前允许连续多少轮分数下降。设置 early_stopping_rounds=5 是一个合理的选择。在这种情况下，我们会在连续 5 轮验证分数下降后停止。\n使用 early_stopping_rounds 时，还需要为计算验证分数预留一些数据——这是通过设置 eval_set 参数来完成的。\n我们可以修改上面的示例以包含提前停止：\nmy_model = XGBRegressor(n_estimators=500) my_model.fit(X_train, y_train, early_stopping_rounds=5, eval_set=[(X_valid, y_valid)], verbose=False) 如果你后来想用所有数据拟合模型，将 n_estimators 设置为在早停时找到的最佳值。\n我们不必简单地将每个组件模型的预测值相加来获取预测值，而是在相加之前，可以将每个模型的预测值乘以一个小的数（称为学习率）。\n这意味着我们添加到集成中的每一棵树对我们的帮助都减少了。因此，我们可以设置更高的 n_estimators 值而不至于过拟合。如果我们使用早停，合适的树的数量将自动确定。\n一般来说，较小的学习率和较大的估计器数量会产生更精确的 XGBoost 模型，但它也会因为需要进行更多次迭代而使模型训练时间更长。默认情况下，XGBoost 设置 learning_rate=0.1 。\n修改上述示例以改变学习率得到以下代码：\nmy_model = XGBRegressor(n_estimators=1000, learning_rate=0.05) my_model.fit(X_train, y_train, early_stopping_rounds=5, eval_set=[(X_valid, y_valid)], verbose=False) 在运行时间是需要考虑的大数据集中，你可以使用并行处理来更快地构建模型。通常将参数 n_jobs 设置为你的机器上的核心数。在小数据集中，这不会有所帮助。\n生成的模型不会更好，因此针对拟合时间进行微优化通常只是分散注意力。但在大数据集中，如果你原本在 fit 命令上需要花费很长时间等待，这会很有用。\nmy_model = XGBRegressor(n_estimators=1000, learning_rate=0.05, n_jobs=4) my_model.fit(X_train, y_train, early_stopping_rounds=5, eval_set=[(X_valid, y_valid)], verbose=False) 数据泄露 数据泄漏（或泄漏）发生在你的训练数据包含有关目标的信息，但在模型用于预测时，类似的数据将不会可用。这会导致在训练集（甚至可能是验证数据）上表现出高性能，但模型在生产环境中表现不佳。\n换句话说，泄漏会导致模型看起来准确，直到你开始使用模型做决策，然后模型会变得非常不准确。\n泄漏主要有两种类型：目标泄漏和训练-测试污染。\n目标泄露\n目标泄漏是指你的预测模型包含了在预测时不可用的数据。重要的是要从数据变得可用的时间或时间顺序的角度来考虑目标泄漏，而不仅仅是某个特征是否有助于做出良好的预测。\n举一个例子会更有帮助。假设你想预测谁会患上肺炎。你原始数据的前几行看起来像这样：\ngot_pneumonia age 年龄 weight 体重 male 男性 took_antibiotic_medicine 服用抗生素 \u0026hellip; False 65 100 False False \u0026hellip; False 72 130 True False \u0026hellip; True 58 100 False True \u0026hellip; 人们患上肺炎后会服用抗生素药物以恢复健康。原始数据显示这些列之间存在强烈的关联，但在确定 got_pneumonia 的值后， took_antibiotic_medicine 的值经常被更改。这是目标泄露。\n模型会看到任何值为 False 的 took_antibiotic_medicine 的人都没有肺炎。由于验证数据与训练数据来自同一来源，该模式将在验证中重复出现，模型将获得很高的验证（或交叉验证）分数。\n但是，当模型随后在现实世界中部署时，它将非常不准确，因为当我们需要预测他们未来的健康状况时，即将患肺炎的患者还没有收到抗生素。\n为了防止这种类型的数据泄露，任何在目标值实现后更新（或创建）的变量都应该被排除。\n训练测试污染\n当你不小心区分训练数据与验证数据时，会发生另一种类型的泄露。\n回想一下，验证的目的是衡量模型在它之前未曾考虑过的数据上的表现。如果验证数据影响了预处理行为，你可能会以微妙的方式破坏这个过程。这有时被称为训练-测试污染。\n例如，假设你在调用 train_test_split() 之前运行预处理（比如拟合缺失值填充器）。结果会怎样？你的模型可能会获得良好的验证分数，让你对其充满信心，但在部署用于做决策时表现很差。\n毕竟，你将验证或测试数据中的信息融入了你的预测方式，所以它可能在这个特定数据上表现良好，即使它无法泛化到新数据。当进行更复杂的特征工程时，这个问题会变得更加微妙（也更危险）。\n如果你的验证基于简单的训练-测试拆分，请将验证数据排除在任何类型的拟合之外，包括预处理步骤的拟合。如果你使用 scikit-learn 的管道，这样做会更简单。在使用交叉验证时，将预处理步骤放在管道内部更是至关重要！\n我们将使用一个关于信用卡申请的数据集，并跳过基本的数据设置代码。最终结果是，每个信用卡申请的信息都存储在一个 DataFrame X 中。我们将用它来预测哪些申请被接受了，结果存储在一个 Series y 中。\nreports 报告 age 年龄 income 收入 share 份额 expenditure 支出 owner 所有者 selfemp 自雇 dependents 抚养人 months 月数 majorcards 主要卡片 active 0 0 37.66667 4.5200 0.033270 124.983300 True False 3 54 1 12 1 0 33.25000 2.4200 0.005217 9.854167 False False 3 34 1 13 2 0 33.66667 4.5000 0.004156 15.000000 True False 4 58 1 5 3 0 30.50000 2.5400 0.065214 137.869200 False False 0 25 1 7 4 0 32.16667 9.7867 0.067051 546.503300 True False 2 64 1 5 由于这是一个小数据集，我们将使用交叉验证来确保模型质量的准确度量。\nfrom sklearn.pipeline import make_pipeline from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score # 没有预处理，其实这里可以不需要管道的 my_pipeline = make_pipeline(RandomForestClassifier(n_estimators=100)) cv_scores = cross_val_score(my_pipeline, X, y, cv=5, scoring=\u0026#39;accuracy\u0026#39;) print(\u0026#34;Cross-validation accuracy: %f\u0026#34; % cv_scores.mean()) Cross-validation accuracy: 0.981052 随着经验的积累，你会发现能够达到 98%准确率的模型非常罕见。这种情况确实会发生，但罕见到足以让我们更仔细地检查数据是否存在目标泄露。\n这里是对数据的总结，您也可以在数据标签下找到：\ncard : 如果信用卡申请被接受，则为 1，否则为 0 reports : 主要负面报告的数量 age : 年龄 n 年加上十二分之一年 income : 年收入（除以 10,000） share : 月信用卡支出与年收入之比 expenditure : 平均每月信用卡支出 owner : 1 如果拥有住房，0 如果租房 selfempl : 1 如果是自雇，0 如果不是 dependents : 1 加上抚养人数 months : 在当前地址居住的月数 majorcards : 持有主要信用卡的数量 active : 活跃的信用卡账户数量 有几个变量看起来很可疑。例如， expenditure 是指这张卡上的支出还是申请前使用的卡上的支出？\n此时，基本的数据比较会非常有帮助：\n如上所示，所有未收到卡的人没有支出，而收到卡的人中只有 2%没有支出。我们的模型看起来具有高准确度并不令人意外。但这似乎也是一个目标泄漏的案例，其中支出可能指的是他们申请的卡的支出。\n由于 share 部分由 expenditure 决定，因此也应该被排除。变量 active 和 majorcards 稍微不太明确，但从描述来看，它们似乎存在问题。在大多数情况下，如果你无法追踪到创建数据的人以获取更多信息，那么安全起见更好。\n我们将按照以下方式运行一个无目标泄漏的模型：\n# 扔到有泄露嫌疑的特征 potential_leaks = [\u0026#39;expenditure\u0026#39;, \u0026#39;share\u0026#39;, \u0026#39;active\u0026#39;, \u0026#39;majorcards\u0026#39;] X2 = X.drop(potential_leaks, axis=1) # 评估 cv_scores = cross_val_score(my_pipeline, X2, y, cv=5, scoring=\u0026#39;accuracy\u0026#39;) print(\u0026#34;Cross-val accuracy: %f\u0026#34; % cv_scores.mean()) Cross-val accuracy: 0.830919 这种准确率相当低，可能会让人失望。然而，当用于新应用时，我们可以预期它大约有 80%的准确率，而漏数据模型可能会比这差得多（尽管它在交叉验证中的得分更高）。\n","permalink":"https://blog.muwinds.com/archives/323/","summary":"\u003ch2 id=\"缺失值处理\"\u003e缺失值处理\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e1) 一个简单的选项：删除包含缺失值的列\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e最简单的选择是删除包含缺失值的列。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/wp-content/uploads/2026/01/image-3.png\"\u003e\u003cimg loading=\"lazy\" src=\"/wp-content/uploads/2026/01/image-3-1024x211.png\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e除非被删除列中的大多数值缺失，否则这种方法会使模型丢失大量（潜在有用！）的信息。举一个极端的例子，考虑一个包含10,000行的数据集，其中一列缺少一个条目。这种方法会完全删除该列！\u003c/p\u003e","title":"Machine Learning 记录 2"},{"content":"记录写kaggle练习时记录的东西\n最基础的决策树模型 下面是假设：\n你的表弟靠房地产投机赚了数百万美元。他因为你对数据科学感兴趣，所以提出和你合伙做生意。他出资，你则提供预测不同房屋价值的模型。\n你问表弟过去是如何预测房产价值的，他说只是凭直觉。但进一步询问后发现，他其实是从过去看过的房子中总结出了价格规律，并利用这些规律来预测他正在考虑的新房的价格。\n机器学习的工作原理也类似。我们将从一种叫做决策树的模型开始。虽然还有更高级的模型可以给出更准确的预测，但决策树易于理解，并且是数据科学领域一些最佳模型的基础构建模块。\n它将房屋仅分为两类。任何待评估房屋的预测价格都是同类房屋的历史平均价格。\n我们利用数据决定如何将房屋分成两组，然后再确定每组的预测价格。从数据中捕捉模式的这一步骤称为模型拟合或训练。用于拟合模型的数据称为训练数据。\n你可以使用具有更多“分支”的决策树来捕捉更多因素。这些决策树被称为“深层”决策树。例如，一个同时考虑每栋房屋地块总面积的决策树可能如下所示：\n预测任何房屋的价格，只需遍历决策树，始终选择与该房屋特征相对应的路径即可。预测的房屋价格位于决策树的底部。我们进行预测的底部点称为叶子节点。\n一些Python和库的基本操作 Pandas 库中最重要的部分是 DataFrame。DataFrame 存储的数据类型类似于表格。这类似于 Excel 中的工作表或 SQL 数据库中的表。Pandas 为处理此类数据的大部分需求提供了强大的方法。\n例如，我们将查看澳大利亚墨尔本的房价数据。在实践练习中，您将把相同的流程应用到一个新的数据集，该数据集包含爱荷华州的房价数据。\n示例（墨尔本）数据位于文件路径 ../input/melbourne-housing-snapshot/melb_data.csv。\n我们使用以下命令加载和探索数据：\n# save filepath to variable for easier access\u0026lt;/em\u0026gt; melbourne_file_path = \u0026#39;../input/melbourne-housing-snapshot/melb_data.csv\u0026#39; # read the data and store data in DataFrame titled melbourne_data\u0026lt;/em\u0026gt; melbourne_data = pd.read_csv(melbourne_file_path) # print a summary of the data in Melbourne data\u0026lt;/em\u0026gt; melbourne_data.describe() Rooms Price Distance Postcode Bedroom2 Bathroom Car Landsize BuildingArea YearBuilt Lattitude Longtitude Propertycount count 13580.000000 1.358000e+04 13580.000000 13580.000000 13580.000000 13580.000000 13518.000000 13580.000000 7130.000000 8205.000000 13580.000000 13580.000000 mean 2.937997 1.075684e+06 10.137776 3105.301915 2.914728 1.534242 1.610075 558.416127 151.967650 1964.684217 -37.809203 144.995216 std 0.955748 6.393107e+05 5.868725 90.676964 0.965921 0.691712 0.962634 3990.669241 541.014538 37.273762 0.079260 0.103916 min 1.000000 8.500000e+04 0.000000 3000.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1196.000000 -38.182550 144.431810 25% 2.000000 6.500000e+05 6.100000 3044.000000 2.000000 1.000000 1.000000 177.000000 93.000000 1940.000000 -37.856822 144.929600 50% 3.000000 9.030000e+05 9.200000 3084.000000 3.000000 1.000000 2.000000 440.000000 126.000000 1970.000000 -37.802355 145.000100 75% 3.000000 1.330000e+06 13.000000 3148.000000 3.000000 2.000000 2.000000 651.000000 174.000000 1999.000000 -37.756400 145.058305 max 10.000000 9.000000e+06 48.100000 3977.000000 20.000000 8.000000 10.000000 433014.000000 44515.000000 2018.000000 -37.408530 145.526350 数据集包含太多变量，你难以理解，甚至无法清晰地打印出来。如何才能将这些海量数据精简成你能理解的形式？我们将首先凭直觉选择几个变量。要选择变量/列，我们需要查看数据集中的所有列。这可以通过 DataFrame 的 columns 属性完成（如下代码的最后一行）。\nmelbourne_data.columns Index([\u0026#39;Suburb\u0026#39;, \u0026#39;Address\u0026#39;, \u0026#39;Rooms\u0026#39;, \u0026#39;Type\u0026#39;, \u0026#39;Price\u0026#39;, \u0026#39;Method\u0026#39;, \u0026#39;SellerG\u0026#39;, \u0026#39;Date\u0026#39;, \u0026#39;Distance\u0026#39;, \u0026#39;Postcode\u0026#39;, \u0026#39;Bedroom2\u0026#39;, \u0026#39;Bathroom\u0026#39;, \u0026#39;Car\u0026#39;, \u0026#39;Landsize\u0026#39;, \u0026#39;BuildingArea\u0026#39;, \u0026#39;YearBuilt\u0026#39;, \u0026#39;CouncilArea\u0026#39;, \u0026#39;Lattitude\u0026#39;, \u0026#39;Longtitude\u0026#39;, \u0026#39;Regionname\u0026#39;, \u0026#39;Propertycount\u0026#39;], dtype=\u0026#39;object\u0026#39;) 你可以使用点号表示法提取变量。这一列存储在 Series 中，它与 DataFrame 非常相似，只是只有一列数据。我们将使用点号表示法来选择要预测的列，这列被称为预测目标 。按照惯例，预测目标被称为 y 。\ny = melbourne_data.Price 输入到模型中的列（之后用于进行预测）被称为“特征”。在本例中，这些特征就是用于确定房价的列。有时，除了目标值之外的所有列都会被用作特征。而有时，使用较少的特征效果会更好。\n现在，我们将构建一个只包含少量特征的模型。稍后您将看到如何迭代并比较使用不同特征构建的模型。\n我们通过在方括号内提供列名列表来选择多个特征。列表中的每一项都应该是一个字符串（带引号）。按照惯例，这些数据被称为 X。\nmelbourne_features = [\u0026#39;Rooms\u0026#39;, \u0026#39;Bathroom\u0026#39;, \u0026#39;Landsize\u0026#39;, \u0026#39;Lattitude\u0026#39;, \u0026#39;Longtitude\u0026#39;] X = melbourne_data[melbourne_features] 构建和使用模型的步骤如下：\n明确定义： 模型类型是什么？决策树？还是其他类型的模型？模型类型的其他一些参数也已指定。 拟合： 从提供的数据中捕捉模式。这是建模的核心。 对得到的模型进行预测 评估 ：确定模型预测的准确性。 from sklearn.tree import DecisionTreeRegressor # Define model. Specify a number for random_state to ensure same results each run melbourne_model = DecisionTreeRegressor(random_state=1) # Fit model melbourne_model.fit(X, y) 许多机器学习模型允许在模型训练过程中存在一定的随机性。为 random_state 指定一个数值可以确保每次运行都得到相同的结果。这被认为是一种良好的实践。你可以使用任意数值，模型质量不会显著依赖于你选择的具体值。\n模型验证（Only MAE） 几乎你构建的每一个模型都需要进行评估。在大多数（但并非所有）应用中，衡量模型质量的相关指标是预测准确率。换句话说，就是模型的预测结果是否接近实际情况。很多人在衡量预测准确率时会犯一个巨大的错误。他们用训练数据进行预测，然后将这些预测值与训练数据中的目标值进行比较。稍后您就会看到这种方法的弊端以及如何解决它，但我们先来思考一下应该如何操作。\n首先，你需要用一种易于理解的方式概括模型的质量。如果你比较1万套房屋的预测房价和实际房价，很可能会发现预测结果好坏参半。逐一查看这1万个预测值和实际值的列表毫无意义。我们需要将其概括为一个单一的指标。有很多指标可以用来概括模型质量，但我们先从一个名为平均绝对误差 （也称 MAE ）的指标开始。\n使用平均绝对误差 (MAE) 指标时，我们取每个误差的绝对值，将每个误差转换为正数。然后，我们计算这些绝对误差的平均值。这就是我们衡量模型质量的指标。简单来说，可以这样表述：平均而言，我们的预测误差约为 X。\n可以通过sklearn库中的自带的函数进行调用\nfrom sklearn.metrics import mean_absolute_error predicted_home_prices = melbourne_model.predict(X) mean_absolute_error(y, predicted_home_prices) 由于模型的实际价值在于对新数据进行预测，因此我们会评估模型在未用于构建模型的数据上的性能。最直接的方法是将部分数据从模型构建过程中排除，然后用这些数据来测试模型在之前未见过的数据上的准确性。这些数据被称为验证数据，划分验证数据的时候要注意等比例划分。\nscikit-learn 库有一个函数 train_test_split ，可以将数据按等比例分成两部分。我们将使用其中一部分数据作为训练数据来拟合模型，并将另一部分数据作为验证数据来计算 mean_absolute_error 。\nfrom sklearn.model_selection import train_test_split # split data into training and validation data, for both features and target # The split is based on a random number generator. Supplying a numeric value to # the random_state argument guarantees we get the same split every time we # run this script. train_X, val_X, train_y, val_y = train_test_split(X, y, random_state = 0) # Define model melbourne_model = DecisionTreeRegressor() # Fit model melbourne_model.fit(train_X, train_y) # get predicted prices on validation data val_predictions = melbourne_model.predict(val_X) print(mean_absolute_error(val_y, val_predictions)) 欠拟合和过拟合（Underfitting and Overfitting） 实际上，一棵树从顶层（所有房屋）到叶子节点之间有 10 个分支的情况并不少见。随着树的深度增加，数据集会被分割成越来越多的叶子节点，每个叶子节点包含的房屋数量也会减少。如果一棵树只有 1 个分支，它会将数据分成 2 组。如果每组再被分割一次，就会得到 4 组房屋。再将每组房屋分割一次，就会得到 8 组。如果我们通过在每一层都增加分支来不断增加组数，那么到第 10 层时，我们将得到 2^10也就是1024个节点\n当我们把房屋分散到许多叶子上时，每个叶子上的房屋数量也会减少。房屋数量很少的叶子做出的预测会非常接近这些房屋的实际价值，但它们对新数据的预测可能非常不可靠（因为每个预测都只基于少数房屋）。这是一种称为过拟合的现象，即模型几乎完美地拟合了训练数据，但在验证集和其他新数据上的表现却很差。\n另一方面，如果我们把决策树做得非常浅，它就无法将房屋划分成非常清晰的组别。极端情况下，如果一棵树只将房屋分成 2 或 4 组，每组房屋的种类仍然非常丰富。这样一来，即使在训练数据中，大多数房屋的预测结果也可能与实际情况相差甚远（同样的原因，在验证集上也会表现不佳）。当模型无法捕捉数据中重要的差异和模式，以至于即使在训练数据上表现也很差时，这种情况称为欠拟合。\n控制决策树深度的方法有很多，许多方法允许树中的某些路径比其他路径更深。但 max_leaf_nodes 参数提供了一种非常合理的方法来控制过拟合和欠拟合。我们允许模型生成的叶子节点越多，模型就越容易从上图中的欠拟合区域向过拟合区域移动。我们可以使用实用函数来帮助比较不同 max_leaf_nodes 值下的 MAE 得分：\nfrom sklearn.metrics import mean_absolute_error from sklearn.tree import DecisionTreeRegressor def get_mae(max_leaf_nodes, train_X, val_X, train_y, val_y): model = DecisionTreeRegressor(max_leaf_nodes=max_leaf_nodes, random_state=0) model.fit(train_X, train_y) preds_val = model.predict(val_X) mae = mean_absolute_error(val_y, preds_val) return(mae) 我们可以使用 for 循环来比较使用不同 max_leaf_nodes 值构建的模型的准确性。\nfor max_leaf_nodes \u0026lt;strong\u0026gt;in\u0026lt;/strong\u0026gt; [5, 50, 500, 5000]: my_mae = get_mae(max_leaf_nodes, train_X, val_X, train_y, val_y) print(\u0026#34;Max leaf nodes: %dMean Absolute Error:%d\u0026#34; %(max_leaf_nodes, my_mae)) 随机森林 随机森林使用许多决策树，并通过对每棵树的预测结果取平均值来进行预测。它通常比单个决策树具有更高的预测精度，并且在默认参数下也能很好地工作。如果持续建模，可以学习到性能更优的更多模型，但其中许多模型对参数的设置非常敏感。\n我们构建随机森林模型的方式与在 scikit-learn 中构建决策树的方式类似——这次使用的是 RandomForestRegressor 类而不是 DecisionTreeRegressor 类。\nfrom sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error forest_model = RandomForestRegressor(random_state=1) forest_model.fit(train_X, train_y) melb_preds = forest_model.predict(val_X) print(mean_absolute_error(val_y, melb_preds)) ","permalink":"https://blog.muwinds.com/archives/319/","summary":"\u003cp\u003e记录写kaggle练习时记录的东西\u003c/p\u003e\n\u003ch2 id=\"最基础的决策树模型\"\u003e最基础的决策树模型\u003c/h2\u003e\n\u003cp\u003e下面是假设：\u003c/p\u003e\n\u003cp\u003e你的表弟靠房地产投机赚了数百万美元。他因为你对数据科学感兴趣，所以提出和你合伙做生意。他出资，你则提供预测不同房屋价值的模型。\u003c/p\u003e","title":"Machine Learning记录1"},{"content":"第一次遇到技术面，由于我问题没答上来几道并且时间排不开，所以无疾而终……\n这个岗位的背景：会用Linux进行定位和调试，需要用Python+Django技术栈编写一些简单的运维工具。\n第一部分：根据简历进行自我介绍\n第二部分：技术面试：\nPython 中什么场景用Django合适？什么时候用FastAPI合适？ Django 适合的场景：\n全功能型 Web 应用\n需要内置完整功能（如 Admin 后台、ORM、用户认证、模板引擎）。\n适合内容管理系统（CMS）、电商平台、社交网站等业务逻辑复杂、需要快速原型开发的项目。\n强调开发效率与“开箱即用”\n团队希望减少重复造轮子，直接使用 Django 的成熟组件（如 Session 管理、路由、中间件）。\n项目需求明确，遵循 MVC 模式，适合中大型长期维护的系统。\n强数据驱动型项目\nORM 对复杂查询和数据库迁移支持完善，适合对数据一致性要求高的场景（如金融、企业信息化系统）。\nFastAPI 适合的场景：\n高性能 API 服务\n对并发和响应速度要求高（如微服务架构中的独立服务、实时数据处理接口）。\n利用异步支持（Async/Await）处理高并发 I/O 操作（如大量外部请求、WebSocket）。\n轻量级或微服务架构\n项目仅需提供 RESTful/GraphQL API，不需要内置前端模板或后台管理功能。\n适合前后端分离场景，前端独立部署（如 Vue/React 项目搭配 FastAPI 后端）。\n快速迭代的现代技术栈\n团队希望使用类型注解（Pydantic）、自动文档生成（OpenAPI）、依赖注入等现代特性。\n适合新兴领域如 IoT 数据接口、AI 模型服务、快速实验性项目。\n选型决策建议（补充说明）：\n团队熟悉度：若团队长期使用 Django 且业务匹配，切换成本低；若团队熟悉异步编程，FastAPI 更易上手。\n生态扩展：Django 插件市场丰富（如 Django REST Framework 可构建 API），FastAPI 轻量但依赖社区组合工具链。\n性能边界：99% 的项目两者均能满足性能需求，除非涉及极端高并发（如每秒数万请求），否则优先考虑开发效率。\nPython 中装饰器是什么 如何实现的？装饰器返回的是什么？ 装饰器是 Python 中一种强大的语法特性，它允许在不修改原始函数或类代码的情况下，为它们添加额外功能。以下是核心要点：\n装饰器是什么\n装饰器本质上是一个可调用对象，通常是一个函数或类，它可以：\n接收一个函数或类作为参数\n扩展或修改其功能\n返回一个新的函数或类\n通过 @decorator语法糖在定义时应用 如何实现装饰器\n装饰器的核心实现基于 闭包和函数作为一等公民 的特性： 基本实现模板：\ndef decorator(original_func): # 接收原始函数 def wrapper(*args, **kwargs): # 定义包装函数 # 添加额外功能（如日志、权限校验） result = original_func(*args, **kwargs) # 调用原始函数 # 添加后续处理 return result return wrapper # 返回包装函数 实际应用场景：\n日志记录：自动记录函数执行情况\n性能测试：计算函数执行时间\n权限校验：检查用户权限\n缓存机制：实现结果缓存\n单例模式：确保类只有一个实例\n面向对象的三大特性是什么？具体含义是什么？怎么用呢？ 封装\n核心思想：将数据（属性）和操作数据的方法（行为）捆绑在一起，并隐藏内部实现细节，仅对外暴露有限的接口。\n主要目的：\n保护数据的完整性，防止外部直接修改对象状态。\n降低模块间的耦合，提高代码安全性。\n举例：比如定义一个BankAccount类，将余额（balance）设为私有属性，提供公有的deposit()和withdraw()方法进行存取，外部无法直接修改余额。\n继承\n核心思想：允许一个类（子类）基于另一个类（父类）来构建，从而获得父类的属性和方法，并可以在此基础上进行扩展或修改。\n主要目的：\n实现代码复用，避免重复编写相同逻辑。\n建立类之间的层次关系，便于抽象和扩展。\n举例：比如有一个Vehicle（车辆）父类，包含move()方法；子类Car和Bicycle可以继承Vehicle，并重写或扩展move()方法。\n多态\n核心思想：同一操作（如方法调用）作用于不同类的对象时，可以产生不同的行为。多态通常依赖于继承或接口实现。\n主要目的：\n增强代码的灵活性，允许程序在运行时根据对象类型决定具体行为。\n支持“面向接口编程”，降低代码依赖。\n举例：比如Animal类有makeSound()方法，子类Dog和Cat分别重写该方法。当调用不同子类对象的makeSound()时，会分别输出“汪汪”和“喵喵”，而代码可以通过父类类型统一处理。\n4.进程、线程与协程的含义是什么？如何区分？场景是什么？\n进程\n定义：进程是操作系统进行资源分配和调度的基本单位。简单说，一个运行起来的程序就是一个进程。每个进程都拥有自己独立的地址空间、内存、数据栈以及各种系统资源（如文件句柄）。\n核心理解：进程是资源的“集装箱”，是程序的一次执行过程。进程间是相互隔离的，一个进程崩溃通常不会影响其他进程。\n线程\n定义：线程是操作系统能够进行运算调度的最小单位，是进程内的一个执行流。一个进程可以包含多个线程，这些线程共享进程的地址空间和大部分资源（如内存、文件）。\n核心理解：线程是进程内的“车间”，是CPU调度的基本单位。线程共享资源带来了高效的通信，但也带来了数据同步和线程安全的挑战。\n协程\n定义：协程是一种用户态的、更轻量级的“微线程”。它不由操作系统内核直接调度，而是由程序员在用户空间控制其创建、切换和销毁。协程的调度发生在用户态，没有线程切换的开销。\n核心理解：协程是线程内的“工人”，是协作式的。一个线程内可以运行成千上万个协程，由协程自己主动让出控制权，而不是被系统强制抢占。\n我们可以从以下几个维度进行清晰区分：\n维度 进程 线程 协程 调度单位 操作系统 操作系统 用户/程序员（运行时库） 切换开销 很高（切换页表、寄存器、内核栈等） 高（需要内核介入，切换线程上下文） 极低（只是保存/恢复少量寄存器，在用户态完成） 资源拥有 资源分配的基本单位，独立拥有内存、IO等 共享进程的资源，拥有独立的栈、计数器 共享线程的资源，拥有独立的上下文（栈、寄存器状态） 内存隔离 完全隔离，需IPC通信 共享内存，需同步机制（锁） 共享内存，但通常单线程内顺序执行，同步压力小 并发性 可多核并行 可多核并行 本质是协作式，单线程内并发，可利用多线程实现并行 健壮性 高，一个进程崩溃不影响其他进程 低，一个线程崩溃可能导致整个进程退出 取决于实现，未处理的异常可能导致整个线程/进程终止 比喻 一个独立的“工厂”，有独立的地皮和资源 工厂里的“车间”，共享工厂资源，独立工作 车间里的“工人”，协同完成一项任务，可随时停下换人 典型使用场景 进程的场景 需要高安全性和隔离性的任务：如浏览器（每个标签页一个进程）、Chrome；数据库服务器为每个连接创建独立进程（如PostgreSQL的早期模式）。 需要利用多核优势的独立程序：系统服务、独立的守护进程。 进程崩溃不能影响主程序的场景。 线程的场景 需要高性能并发且需要共享数据的任务：如Web服务器（如Tomcat, Nginx的worker进程内部用线程处理请求）、桌面GUI应用（一个线程处理UI，一个线程处理后台计算）。 多核CPU上进行密集型并行计算：如数据处理、科学计算，但要注意线程安全。 IO密集型应用，利用阻塞IO时的多线程来提高吞吐。 协程的场景 超高并发的IO密集型应用：这是协程的“主战场”。例如： 高性能网络服务：如微服务网关、即时通讯（IM）后端，单机可轻松支撑数万甚至十万级连接（如Go的net/http, Python的gevent/aiohttp）。 爬虫：高效管理成千上万个网络请求。 需要大量轻量级“并发”任务的游戏开发：每个游戏对象可以是一个协程，管理自己的状态机。 简化异步回调的“回调地狱”：用同步的代码写法实现异步的性能（async/await语法）。 第三部分：学校允许实习吗？时间怎么协调的？未来方向是什么？\n","permalink":"https://blog.muwinds.com/archives/316/","summary":"\u003cp\u003e第一次遇到技术面，由于我问题没答上来几道并且时间排不开，所以无疾而终……\u003c/p\u003e\n\u003cp\u003e这个岗位的背景：会用Linux进行定位和调试，需要用Python+Django技术栈编写一些简单的运维工具。\u003c/p\u003e","title":"中航信（航旅纵横）：运维开发面试记录"},{"content":"这是个很奇怪的需求，但是还是有\necho \u0026#34;deb http://http.kali.org/kali kali-rolling main contrib non-free\u0026#34; | sudo tee /etc/apt/sources.list.d/kali.list wget -q -O - https://archive.kali.org/archive-key.asc | sudo apt-key add - 当然了，也有移除kali源的\nsudo rm /etc/apt/sources.list.d/kali.list ","permalink":"https://blog.muwinds.com/archives/313/","summary":"\u003cp\u003e这是个很奇怪的需求，但是还是有\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;deb http://http.kali.org/kali kali-rolling main contrib non-free\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e sudo tee /etc/apt/sources.list.d/kali.list\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ewget -q -O - https://archive.kali.org/archive-key.asc \u003cspan class=\"p\"\u003e|\u003c/span\u003e sudo apt-key add -\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e当然了，也有移除kali源的\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo rm /etc/apt/sources.list.d/kali.list\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"Ubuntu安装kali源"},{"content":"最近在翻一些距离算法并考虑他们的GPU并行化实现，记录一下。\n这里从图片转成向量开始，利用opencv配合torchvision，转成tensor还是比较容易的。\nimport cv2 import torchvision.transforms.functional as F import torchvision.transforms as T image = cv2.imread(\u0026#39;test_pic1.png\u0026#39;) print(type(image),image.shape) img1 = F.to_tensor(image) print(type(img1),img1.shape) print(img1) 这里的to_tensor的大致实现如下：\ndef to_tensor(pic): \u0026#34;\u0026#34;\u0026#34;Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor. This function does not support torchscript. See :class:`~torchvision.transforms.ToTensor` for more details. Args: pic (PIL Image or numpy.ndarray): Image to be converted to tensor. Returns: Tensor: Converted image. \u0026#34;\u0026#34;\u0026#34; if not(F_pil._is_pil_image(pic) or _is_numpy(pic)): raise TypeError(\u0026#39;pic should be PIL Image or ndarray. Got {}\u0026#39;.format(type(pic))) if _is_numpy(pic) and not _is_numpy_image(pic): raise ValueError(\u0026#39;pic should be 2/3 dimensional. Got {} dimensions.\u0026#39;.format(pic.ndim)) default_float_dtype = torch.get_default_dtype() if isinstance(pic, np.ndarray): # handle numpy array # 如果是灰度的二维图像，加一个通道维度 if pic.ndim == 2: pic = pic[:, :, None] img = torch.from_numpy(pic.transpose((2, 0, 1))).contiguous() # backward compatibility # 除以255 if isinstance(img, torch.ByteTensor): return img.to(dtype=default_float_dtype).div(255) else: return img if accimage is not None and isinstance(pic, accimage.Image): nppic = np.zeros([pic.channels, pic.height, pic.width], dtype=default_float_dtype) pic.copyto(nppic) return torch.from_numpy(nppic) # 处理PIL的照片 if pic.mode == \u0026#39;I\u0026#39;: img = torch.from_numpy(np.array(pic, np.int32, copy=False)) elif pic.mode == \u0026#39;I;16\u0026#39;: img = torch.from_numpy(np.array(pic, np.int16, copy=False)) elif pic.mode == \u0026#39;F\u0026#39;: img = torch.from_numpy(np.array(pic, np.float32, copy=False)) elif pic.mode == \u0026#39;1\u0026#39;: img = 255 * torch.from_numpy(np.array(pic, np.uint8, copy=False)) else: img = torch.ByteTensor(torch.ByteStorage.from_buffer(pic.tobytes())) img = img.view(pic.size[1], pic.size[0], len(pic.getbands())) # 把HCW转成CHW格式 img = img.permute((2, 0, 1)).contiguous() if isinstance(img, torch.ByteTensor): return img.to(dtype=default_float_dtype).div(255) else: return img 从熟悉的欧氏距离开始，一个经典的欧氏距离就是两点之间计算，公式如下：\n$$ d(p,q) = \\sqrt {(p_1 - q_1 )^2 + (p_2 - q_2 )^2 } $$在几何距离和数学建模常见的聚类（例如K-Means）计算相对常用。\n对于两个点之间的差异，还有一个常用于网格路径优化的距离就是曼哈顿距离，本身的定义是两个坐标在坐标轴上的绝对值之和。\n$$ d = \\left| {x_1 - x_2 } \\right| + \\left| {y_1 - y_2 } \\right| $$\npytorch的快速实现：\nimport torch def EuclideanDistances(a,b): sq_a = a**2 sum_sq_a = torch.sum(sq_a,dim=1).unsqueeze(1) # m-\u0026gt;[m, 1] print(sum_sq_a) sq_b = b**2 sum_sq_b = torch.sum(sq_b,dim=1).unsqueeze(0) # n-\u0026gt;[1, n] bt = b.t() return torch.sqrt(sum_sq_a+sum_sq_b-2*a.mm(bt)) eu_dis = EuclideanDistances(img1.view(1,-1), img1.view(1,-1)) print(eu_dis) #两个tensor的欧氏距离,应该是0，但由于精度问题，结果会有误差，如果改为float64结果就稳定很多 此时就得引入范数的概念了，因为pytorch使用的是统一的距离计算函数，采用的就是范数的概念。\n对于一个n维的向量$x = (x_1 ,x_2 ,x_3 ,{\\rm{\\cdot\\cdot\\cdot}},x_n )$，p范数是向量各元素取p次方求和再开p次方根来衡量向量的长度。对应的式子：$||x||_p = ( \\sum_{i=1}^{n} |x_i|^p )^{1/p}$\n从这里也能看的出来，曼哈顿距离对应的就是L1的范数，欧氏距离对应的是L2范数，torch在CPU相关的代码如下，这里可以发现torch在传给具体硬件进行计算之前会将两个tensor进行广播和展平，在后续的运算中提供了数据的并行性。\ntemplate \u0026lt;typename F\u0026gt; // 这里的张量是已经处理好的， static void run_parallel_pdist(Tensor\u0026amp; result, const Tensor\u0026amp; self, const scalar_t p) { const scalar_t * const self_start = self.const_data_ptr\u0026lt;scalar_t\u0026gt;(); const scalar_t * const self_end = self_start + self.numel(); int64_t n = self.size(0); int64_t m = self.size(1); scalar_t * const res_start = result.data_ptr\u0026lt;scalar_t\u0026gt;(); int64_t combs = result.numel(); // n * (n - 1) / 2 // 并行计算，将输出索引k范围[0,comb-1]分成多个块，每个线程处理一段连续的k值 parallel_for(0, combs, internal::GRAIN_SIZE / (16 * m), [p, self_start, self_end, n, m, res_start](int64_t k, int64_t end) { const Vec pvec(p); double n2 = n - .5; // The -1 accounts for floating point truncation issues // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) // 将k映射到矩阵的i和j int64_t i = static_cast\u0026lt;int64_t\u0026gt;((n2 - std::sqrt(n2 * n2 - 2 * k - 1))); int64_t j = k - n * i + i * (i + 1) / 2 + i + 1; const scalar_t * self_i = self_start + i * m; const scalar_t * self_j = self_start + j * m; scalar_t * res = res_start + k; const scalar_t * const res_end = res_start + end; while (res != res_end) { // 这里的向量化操作调用了CPU的SIMD，先计算|a-b|^p，再开p次方 *res = F::finish(vec::map2_reduce_all\u0026lt;scalar_t\u0026gt;( [\u0026amp;pvec](Vec a, Vec b) { return F::map((a - b).abs(), pvec); }, F::red, self_i, self_j, m), p); res += 1; self_j += m; if (self_j == self_end) { self_i += m; self_j = self_i + m; } } }); } 在cuda的实现如下：\ntemplate \u0026lt;typename scalar_t, typename F\u0026gt; __global__ static void cdist_backward_kernel_cuda_impl(scalar_t * buffer, const scalar_t * grad, const scalar_t * x1, const scalar_t * x2, const scalar_t * dist, const scalar_t p, const int64_t r1, const int64_t r2, const int64_t m, const int64_t count, const int64_t r_size, const int64_t l1_size, const int64_t l2_size) { // 线程分配 const int y = (blockIdx.y * gridDim.z + blockIdx.z) * blockDim.y + threadIdx.y; const int init = blockIdx.x * blockDim.x + threadIdx.x; if (y \u0026gt;= count || init \u0026gt;= m) { return; } const int l = y / r_size; const int k = y % r_size; const int stride = blockDim.x * gridDim.x; const int l_size = r_size * m; int64_t i = k / r2; int64_t j = k % r2; const scalar_t grad_k = grad[y]; const scalar_t dist_k = dist[y]; const scalar_t * const start = x1 + l * l1_size + i * m; const scalar_t * const end = start + m; const scalar_t * self_i = start + init; const scalar_t * self_j = x2 + l * l2_size + j * m + init; scalar_t * buff_i = buffer + l * l_size + (r1 * j + i) * m + init; // 内存访问连续 for (; self_i \u0026lt; end; self_i += stride, self_j += stride, buff_i += stride) { const scalar_t res = F::backward(*self_i - *self_j, grad_k, dist_k, p); *buff_i = res; } } 在分布比较和一些GAN生成对抗网络（https://arxiv.org/abs/1701.07875）中，度量距离的方法有一个是Wasserstein距离的方法，这里的距离度量的就不是两个向量了，而是两个概率分布。我们将两个分布P，Q看成两堆土，如下图所示，希望把其中的一堆土移成另一堆土的位置和形状，有很多种可能的方案。推土代价被定义为移动土的量乘以土移动的距离，在所有的方案中，存在一种推土代价最小的方案，这个代价就称为两个分布的 Wasserstein 距离，也因此被称为推土机距离。\n图片来源：https://www.microsoft.com/en-us/research/blog/measuring-dataset-similarity-using-optimal-transport/\n数学的表达太过复杂，不过有大佬写好了这些方便理解。\nhttps://zhuanlan.zhihu.com/p/441197063\n文章通过做功的方式给出了直观的理解，将求解处理成了线性规划问题。\n先看一下每对图像之间的距离如何计算（调库）的：\nquery_image = cv2.imread(\u0026#39;query_image.png\u0026#39;, cv2.IMREAD_GRAYSCALE) img = cv2.imread(f\u0026#39;image_{i}.png\u0026#39;, cv2.IMREAD_GRAYSCALE) hist, _ = np.histogram(query_image, bins=10, range=(0, 255), density=True) # 计算距离，这里的cv2.DIST_L2正是之前提到的欧氏距离，在这里用来度量向量每个元素距离的 hist2, _ = np.histogram(img, bins=10, range=(0, 255), density=True) distance = cv2.EMD(hist, hist2, cv2.DIST_L2) 对应的代码文件是opencv源码仓库下的opencv/modules/imgproc/src/emd_new.cpp中，可以看到EMD函数本身是创建了个求解器进行计算的。\nfloat cv::EMD(InputArray _sign1, InputArray _sign2, int distType, InputArray _cost, float* lowerBound, OutputArray _flow) { CV_INSTRUMENT_REGION(); Mat sign1 = _sign1.getMat(); Mat sign2 = _sign2.getMat(); Mat cost = _cost.getMat(); CV_CheckEQ(sign1.cols, sign2.cols, \u0026#34;Signatures must have equal number of columns\u0026#34;); CV_CheckEQ(sign1.type(), CV_32FC1, \u0026#34;The sign1 must be 32FC1\u0026#34;); CV_CheckEQ(sign2.type(), CV_32FC1, \u0026#34;The sign2 must be 32FC1\u0026#34;); const int dims = sign1.cols - 1; const int size1 = sign1.rows; const int size2 = sign2.rows; Mat flow; if (_flow.needed()) { _flow.create(sign1.rows, sign2.rows, CV_32F); flow = _flow.getMat(); flow = Scalar::all(0); CV_CheckEQ(flow.type(), CV_32FC1, \u0026#34;Flow matrix must have type 32FC1\u0026#34;); CV_CheckTrue(flow.rows == size1 \u0026amp;\u0026amp; flow.cols == size2, \u0026#34;Flow matrix size does not match signatures\u0026#34;); } DistFunc dfunc = 0; if (distType == DIST_USER) { if (!cost.empty()) { CV_CheckEQ(cost.type(), CV_32FC1, \u0026#34;Cost matrix must have type 32FC1\u0026#34;); CV_CheckTrue(cost.rows == size1 \u0026amp;\u0026amp; cost.cols == size2, \u0026#34;Cost matrix size does not match signatures\u0026#34;); CV_CheckTrue(lowerBound == NULL, \u0026#34;Lower boundary can not be calculated if the cost matrix is used\u0026#34;); } else { CV_CheckTrue(dfunc == NULL, \u0026#34;Dist function must be set if cost matrix is empty\u0026#34;); } } else { CV_CheckNE(dims, 0, \u0026#34;Number of dimensions can be 0 only if a user-defined metric is used\u0026#34;); switch (distType) { case cv::DIST_L1: dfunc = distL1; break; case cv::DIST_L2: dfunc = distL2; break; case cv::DIST_C: dfunc = distC; break; default: CV_Error(cv::Error::StsBadFlag, \u0026#34;Bad or unsupported metric type\u0026#34;); } } EMDSolver state; const bool result = state.init(sign1, sign2, dims, dfunc, cost, lowerBound); if (!result \u0026amp;\u0026amp; lowerBound) { return *lowerBound; } // 调用迭代求解的函数 state.solve(); return (float)(state.calcFlow(_flow.needed() ? \u0026amp;flow : 0) / state.getWeight()); } // 开始迭代求解 void EMDSolver::solve() { if (ssize \u0026gt; 1 \u0026amp;\u0026amp; dsize \u0026gt; 1) { const float eps = CV_EMD_EPS * max_cost; for (int itr = 1; itr \u0026lt; MAX_ITERATIONS; itr++) { /* find basic variables */ if (findBasicVars() \u0026lt; 0) break; /* check for optimality */ const float min_delta = checkOptimal(); if (min_delta == CV_EMD_INF) CV_Error(cv::Error::StsNoConv, \u0026#34;\u0026#34;); /* if no negative deltamin, we found the optimal solution */ if (min_delta \u0026gt;= -eps) break; /* improve solution */ if (!checkNewSolution()) CV_Error(cv::Error::StsNoConv, \u0026#34;\u0026#34;); } } } 求解的基本集中于findBasicVars这块，对应的代码在下面，可以看到opencv是通过将u和v的节点做成一个链表进行管理，数据上并没有并行，从计算本身也没看出什么并行。\nint EMDSolver::findBasicVars() const { int i, j; int u_cfound, v_cfound; Node1D u0_head, u1_head, *cur_u, *prev_u; Node1D v0_head, v1_head, *cur_v, *prev_v; bool found; CV_Assert(u != 0 \u0026amp;\u0026amp; v != 0); /* initialize the rows list (u) and the columns list (v) */ u0_head.next = u; for (i = 0; i \u0026lt; ssize; i++) { u[i].next = u + i + 1; } u[ssize - 1].next = 0; u1_head.next = 0; v0_head.next = ssize \u0026gt; 1 ? v + 1 : 0; for (i = 1; i \u0026lt; dsize; i++) { v[i].next = v + i + 1; } v[dsize - 1].next = 0; v1_head.next = 0; /* there are ssize+dsize variables but only ssize+dsize-1 independent equations, so set v[0]=0 */ v[0].val = 0; v1_head.next = v; v1_head.next-\u0026gt;next = 0; /* loop until all variables are found */ u_cfound = v_cfound = 0; while (u_cfound \u0026lt; ssize || v_cfound \u0026lt; dsize) { found = false; if (v_cfound \u0026lt; dsize) { /* loop over all marked columns */ prev_v = \u0026amp;v1_head; cur_v = v1_head.next; found = found || (cur_v != 0); for (; cur_v != 0; cur_v = cur_v-\u0026gt;next) { float cur_v_val = cur_v-\u0026gt;val; j = (int)(cur_v - v); /* find the variables in column j */ prev_u = \u0026amp;u0_head; for (cur_u = u0_head.next; cur_u != 0;) { i = (int)(cur_u - u); if (getIsX(i, j)) { /* compute u[i] */ cur_u-\u0026gt;val = getCost(i, j) - cur_v_val; /* ...and add it to the marked list */ prev_u-\u0026gt;next = cur_u-\u0026gt;next; cur_u-\u0026gt;next = u1_head.next; u1_head.next = cur_u; cur_u = prev_u-\u0026gt;next; } else { prev_u = cur_u; cur_u = cur_u-\u0026gt;next; } } prev_v-\u0026gt;next = cur_v-\u0026gt;next; v_cfound++; } } if (u_cfound \u0026lt; ssize) { /* loop over all marked rows */ prev_u = \u0026amp;u1_head; cur_u = u1_head.next; found = found || (cur_u != 0); for (; cur_u != 0; cur_u = cur_u-\u0026gt;next) { float cur_u_val = cur_u-\u0026gt;val; i = (int)(cur_u - u); /* find the variables in rows i */ prev_v = \u0026amp;v0_head; for (cur_v = v0_head.next; cur_v != 0;) { j = (int)(cur_v - v); if (getIsX(i, j)) { /* compute v[j] */ cur_v-\u0026gt;val = getCost(i, j) - cur_u_val; /* ...and add it to the marked list */ prev_v-\u0026gt;next = cur_v-\u0026gt;next; cur_v-\u0026gt;next = v1_head.next; v1_head.next = cur_v; cur_v = prev_v-\u0026gt;next; } else { prev_v = cur_v; cur_v = cur_v-\u0026gt;next; } } prev_u-\u0026gt;next = cur_u-\u0026gt;next; u_cfound++; } } if (!found) return -1; } return 0; } ","permalink":"https://blog.muwinds.com/archives/282/","summary":"\u003cp\u003e最近在翻一些距离算法并考虑他们的GPU并行化实现，记录一下。\u003c/p\u003e\n\u003cp\u003e这里从图片转成向量开始，利用opencv配合torchvision，转成tensor还是比较容易的。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003ecv2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003etorchvision.transforms.functional\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nn\"\u003eF\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003etorchvision.transforms\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nn\"\u003eT\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eimage\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecv2\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eimread\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;test_pic1.png\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"n\"\u003eimage\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshape\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eimg1\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eF\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eto_tensor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eimg1\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"n\"\u003eimg1\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshape\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eimg1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ca href=\"/wp-content/uploads/2025/10/image-9.png\"\u003e\u003cimg loading=\"lazy\" src=\"/wp-content/uploads/2025/10/image-9.png\"\u003e\u003c/a\u003e\u003c/p\u003e","title":"各种距离算法小记"},{"content":"无论是打数模还是单纯的搞机器学习，都需要对最后训练结果进行分析，这时候常用的四个指标：准确率、召回率、精确率、F1分数。\n作为指标，一定会涉及几个方面\n用来描述什么 如何计算的 指标的意义是什么\n那为了计算这几个指标，我们需要了解什么是TP、TN、FP、FN\n用识别苹果举例\n假设我们有100张照片：\n其中有40张是真的苹果照片称之为正样本。\n另外60张不是苹果照片称之为负样本。\n模型预测可能有四种情况： 预测是苹果，实际是苹果，TruePositive (TP) 预测是苹果，实际不是苹果，FalsePositive (FP) 预测不是苹果，实际是苹果，FalseNegative (FN) 预测不是苹果，实际也不是苹果，TrueNegative (TN)\nTrue or False =有没有猜对\nPositive or Negative 表示猜的结果 是不是正类，是苹果为正类。 假设：\nTP (预测正确)：30个\nFN (漏检)：10个\nFP (误认为是)：15个\nTN (预测正确不是)：45个\n这四个指标可以形成一个混淆矩阵：\n1️⃣准确率 (Accuracy)\n所有预测结果中，预测正确的比例\n$Accuracy = \\frac{{TP + TN}}{{TP + TN + FP + FN}}$\n最直观的指标，但在数据不平衡时表现不佳\n2️⃣精确率 (Precision)\n模型所有预测为“苹果”的结果中，有多少是真正的苹果\n公式：${\\rm{Precission = }}\\frac{{{\\rm{TP}}}}{{{\\rm{TP + FP}}}}$\n是“预测的准不准”的指标，非常看重“减少误报”的场景\n3️⃣召回率 (Recall)\n所有真正的苹果中，被模型成功找出来了多少\n公式：${\\rm{Recall = }}\\frac{{{\\rm{TP}}}}{{{\\rm{TP + FN}}}}$\n关注的是“找的全不全”、“减少漏检”，宁可错检也不能漏检\n4️⃣F1分数 (F1-Score)：精确率和召回率的调和平均数\n公式：${\\rm{F1 = }}\\frac{{{\\rm{2}} \\times {\\rm{(Precission}} \\times {\\rm{Recall)}}}}{{{\\rm{Precission + Recall}}}}$\nF1分数综合的分数来评判模型的好坏。尤其是在数据不平衡时，比准确率更有参考价值。精确率和召回率通常是矛盾的\n","permalink":"https://blog.muwinds.com/archives/280/","summary":"\u003cp\u003e无论是打数模还是单纯的搞机器学习，都需要对最后训练结果进行分析，这时候常用的四个指标：准确率、召回率、精确率、F1分数。\u003c/p\u003e\n\u003cp\u003e作为指标，一定会涉及几个方面\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e用来描述什么\u003c/li\u003e\n\u003cli\u003e如何计算的\u003c/li\u003e\n\u003cli\u003e指标的意义是什么\u003cbr\u003e\n那为了计算这几个指标，我们需要了解什么是TP、TN、FP、FN\u003cbr\u003e\n用识别苹果举例\u003cbr\u003e\n假设我们有100张照片：\u003cbr\u003e\n其中有40张是真的苹果照片称之为正样本。\u003cbr\u003e\n另外60张不是苹果照片称之为负样本。\u003cbr\u003e\n模型预测可能有四种情况：\u003c/li\u003e\n\u003cli\u003e预测是苹果，实际是苹果，TruePositive (TP)\u003c/li\u003e\n\u003cli\u003e预测是苹果，实际不是苹果，FalsePositive (FP)\u003c/li\u003e\n\u003cli\u003e预测不是苹果，实际是苹果，FalseNegative (FN)\u003c/li\u003e\n\u003cli\u003e预测不是苹果，实际也不是苹果，TrueNegative (TN)\u003cbr\u003e\nTrue or False =有没有猜对\u003cbr\u003e\nPositive or Negative 表示猜的结果 是不是正类，是苹果为正类。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e假设：\u003c/p\u003e","title":"Accuracy、Recall、Precission、F1 Score分别是什么？"},{"content":"下面的内容转载自https://discuss.codecademy.com/t/setting-up-conda-in-git-bash/534473，本人测试效果相当好\nA) Open Git Bash A）打开 Git Bash Now that you have an idea of how conda should look, go ahead and open Git Bash. As you can see, there is nothing on the prompt to indicate whether you are inside an active conda environment or not. Unfortunately, it’s not quite that simple. If you type conda list in Git Bash, you will receive an error that looks something like this:\n现在你已经了解了 conda 应该是什么样子，继续打开 Git Bash。正如你所见，提示符上没有任何内容表明你是否处于活动的 conda 环境中。不幸的是，事情并没有那么简单。如果你在 Git Bash 中输入 conda list ，你会收到类似这样的错误：\nbash: conda: command not found This is because conda has only been set up to run from Anaconda Prompt by default.\n这是因为 conda 默认仅设置为从 Anaconda Prompt 运行。\nB) Add the conda shell script to your .bashrc B）将 conda shell 脚本添加到你的 .bashrc In order to make the conda command available in Git Bash, you need to add conda’s shell script to your .bashrc file. This is the same file that you store your bash aliases in (such as the sqlite3 alias you probably created when you followed these instructions).\n为了使 conda 命令在 Git Bash 中可用，你需要将 conda 的 shell 脚本添加到你的 .bashrc 文件中。该文件也用于存储你的 bash 别名（例如，你可能在按照这些说明操作时创建的 sqlite3 别名）。\nThe shell script we need is located inside of the folder your Anaconda distribution added to your computer. If you remember where that is, you can navigate there using the File Explorer. If you don’t remember, the easiest way to find it is to use the search bar and search for “Anaconda3” and scroll down to the folders:\n我们需要的 Shell 脚本位于 Anaconda 发行版添加到计算机的文件夹中。如果您记得它的位置，可以使用文件资源管理器导航到那里。如果您不记得了，最简单的方法是使用搜索栏搜索“Anaconda3”，然后向下滚动到文件夹：\nOpen this folder, then navigate to etc -\u0026gt; profile.d.\n打开此文件夹，然后导航到 etc -\u0026gt; profile.d 。\nInside of the profile.d folder, you should see a file called conda.sh. This is the file you want to add to your .bashrc file.\n在 profile.d 文件夹中，你应该看到一个名为 conda.sh 的文件。这是你想要添加到 .bashrc 文件的文件。\nNow, there are a few ways you can do this: a) you can open your .bashrc and type it in there; b) you can type the path to conda.sh in Git Bash and add it to your .bashrc from there; or c) you can open Git Bash in the profile.d folder and utilize the bash command PWD in order to lower the risk of spelling errors in the path. The last option is my preferred choice, so that is what I will cover here.\n现在，有几种方法可以做到这一点：a) 打开 .bashrc 文件并在其中输入；b) 在 Git Bash 中输入 conda.sh 的路径，然后将其添加到 .bashrc 文件；或者 c) 在 profile.d 文件夹中打开 Git Bash，并使用 bash 命令 PWD ，以降低路径拼写错误的风险。我更喜欢最后一种方式，因此我将在此介绍它。\nGo ahead and close Git Bash, and then right click inside the file explorer and choose “Git Bash Here”:\n继续关闭 Git Bash，然后在文件资源管理器中右键单击并选择“Git Bash Here”：\nThis will open a new instance of Git Bash to the correct directory.\n这将打开 Git Bash 的新实例到正确的目录。\nNow, so long as the path to your file has no spaces, you can add conda.sh to your .bashrc with this simple command:\n现在，只要文件路径没有空格，您就可以使用以下简单命令将 conda.sh 添加到您的 .bashrc 中：\necho \u0026#34;. ${PWD}/conda.sh\u0026#34; \u0026gt;\u0026gt; ~/.bashrc If the path does contain spaces (such as in your username), you will need to add single quotes in the command like so:\n如果路径确实包含空格（例如您的用户名），则需要在命令中添加单引号，如下所示：\necho \u0026#34;. \u0026#39;${PWD}\u0026#39;/conda.sh\u0026#34; \u0026gt;\u0026gt; ~/.bashrc After running this command, you will need to close Git Bash and reopen it for it to take effect.\n运行此命令后，您需要关闭 Git Bash 并重新打开它才能生效。\nC) Reopen Git Bash and activate conda! C）重新打开 Git Bash 并激活 conda！ When you reopen Git Bash, you still won’t see (base) in your prompt — that’s because you haven’t activated conda yet! To activate it, simply type the command conda activate and hit Enter. You should see (base) pop up above your prompt:\n当你重新打开 Git Bash 时，你仍然不会在提示符中看到 (base) ——那是因为你还没有激活 conda ！要激活它，只需输入命令 conda activate 并按 Enter 键。你应该会看到 (base) 弹出在提示符上方：\nAnd that’s it! You’ve successfully enabled conda in your Git Bash terminal. Now Git Bash can do virtually everything Anaconda Prompt can do, just make sure to use the conda activate command whenever you want to use conda in a new Git Bash window (you can also use conda deactivate if you want to exit your conda environment and use a different installation of Python).\n就这样！您已成功在 Git Bash 终端中启用 conda 。现在，Git Bash 几乎可以完成 Anaconda Prompt 的所有功能，只需确保在新的 Git Bash 窗口中使用 conda 时使用 conda activate 命令即可（如果您想退出 conda 环境并使用其他 Python 安装，也可以使用 conda deactivate 命令）。\n","permalink":"https://blog.muwinds.com/archives/277/","summary":"\u003cp\u003e下面的内容转载自\u003ca href=\"https://discuss.codecademy.com/t/setting-up-conda-in-git-bash/534473\"\u003ehttps://discuss.codecademy.com/t/setting-up-conda-in-git-bash/534473\u003c/a\u003e，本人测试效果相当好\u003c/p\u003e\n\u003ch4 id=\"a-open-git-bash-a打开-git-bash\"\u003eA) Open Git Bash A）打开 Git Bash\u003c/h4\u003e\n\u003cp\u003eNow that you have an idea of how \u003ccode\u003econda\u003c/code\u003e should look, go ahead and open Git Bash. As you can see, there is nothing on the prompt to indicate whether you are inside an active \u003ccode\u003econda\u003c/code\u003e environment or not. Unfortunately, it’s not quite that simple. If you type \u003ccode\u003econda list\u003c/code\u003e in Git Bash, you will receive an error that looks something like this:\u003cbr\u003e\n现在你已经了解了 \u003ccode\u003econda\u003c/code\u003e 应该是什么样子，继续打开 Git Bash。正如你所见，提示符上没有任何内容表明你是否处于活动的 \u003ccode\u003econda\u003c/code\u003e 环境中。不幸的是，事情并没有那么简单。如果你在 Git Bash 中输入 \u003ccode\u003econda list\u003c/code\u003e ，你会收到类似这样的错误：\u003c/p\u003e","title":"在 Git Bash 中设置 Conda"},{"content":"A.并行计算与并行结构 在所有四类计算机设计中，多级并行化现在是主要动力，功耗和成本是主要限制因素。在应用中基本上有两种并行性：\n1.数据级并行（DLP），产生的原因是有许多数据项可以同时操作。\n2.任务级并行性（TLP），产生的原因是创建了可以独立操作并在很大程度上并行运行的工作任务。\n计算机硬件反过来可以利用这两种应用程序并行性，四种主要方式：\n1.指令级并行利用编译器帮助，通过流水线等思想在适度水平上利用数据级并行，在中度水平上使用推测执行等思想。\n2.向量架构、图形处理器单元（GPU）和多媒体指令集通过将单个指令并行应用于数据集合来利用数据级并行性。\n3.线程级并行性在紧密耦合的硬件模型中利用数据级并行性或任务级并行性，允许并行线程之间进行交互。\n4.请求级并行性利用程序员或操作系统指定的任务之间的并行性。\nFlynn（1966 年）研究了 20 世纪 60 年代的并行计算时，他发现了一个简单的分类，我们今天仍在使用它的缩写。它们针对数据级并行和任务级并行。他观察了由多处理器最受限组件中的指令所要求的指令和数据流中的并行性，并将所有计算机分为以下四类：\n单指令流单数据流（SISD）——这一类别是单处理器。程序员将其视为标准顺序计算机，但它能充分利用指令级并行性（ILP）。第 3 章涵盖了使用超标量化和推测执行等 ILP 技术的 SISD 架构。\n单指令流多数据流（SIMD）——多个处理器使用不同的数据流执行相同的指令。SIMD 计算机通过将相同的操作并行应用于多个数据项来利用数据级并行性。每个处理器都有自己的数据存储器（SIMD 的 MD在此体现），但有一个单一的指令存储器和控制处理器，它负责检索和分配指令。第四章涵盖了数据级并行性（DLP）及其三种不同的架构：向量架构、标准指令集的多媒体扩展和 GPU。\n多指令流单数据流（MISD）——迄今为止尚未建造出这种类型的商用多处理器，但它完善了这种简单的分类。\n多指令流、多数据流（MIMD）-每个处理器都获取自己的指令并对其自己的数据进行操作，它针对的是任务级并行性。一般来说，MIMD 比 SIMD 更灵活，因此应用范围更广，但它的成本也比 SIMD 高。例如，MIMD 计算机也可以利用数据级并行性，尽管开销可能比 SIMD 计算机要高。这种开销意味着粒度必须足够大，才能有效地利用并行性。紧密耦合的 MIMD 架构，它利用线程级并行性，因为多个协作线程并行运行，松散耦合的 MIMD 架构——特别是集群和仓库规模计算机——它们利用请求级并行性，许多独立任务可以并行进行，几乎不需要通信或同步。\n这种分类法是一个粗略模型，因为许多并行处理器是 SISD、SIMD 和 MIMD 类别的混合体。\nB.指令集结构 ISA（指令集结构） 充当软件和硬件之间的边界。这次对 ISA 的快速回顾将使用 80x86、ARMv8 和 RISC-V 的示例来说明 ISA 的七个维度。最受欢迎的 RISC（精简指令集） 处理器来自 ARM，2015 年出货了 148 亿个芯片，或大约是 50 倍之多。\n今天所有的 ISA 都被归类为通用寄存器架构，其中操作数可以是寄存器或内存位置。80x86 有 16 个通用寄存器和 16 个可以存储浮点数据的寄存器，而 RISC-V 有 32 个通用寄存器和 32 个浮点寄存器（见图 1.4）。这类架构的两个流行版本是寄存器-内存指令集架构（ISAs）。\n1.ISA分类——如今几乎所有的ISA都被归类为通用寄存器架构，其中运算对象要么是寄存器，要么是内存位置。80x86有16个通用寄存器和16个可以保存浮点数据的寄存器，而RISC-V有32个通用寄存器、32个浮点寄存器（见图1.4）。该类的两个流行版本是寄存器存储器ISA，\n2.内存寻址——几乎所有的台式机和服务器计算机，包括80x86、ARMv8和RISC-V使用字节寻址来访问内存操作数。一些架构，如ARM v8，要求对象必须对齐。如果A mod s=0，则对字节地址A处大小为s字节的对象的访问将被对齐。（参见第A-8页图A.5。）80x86和RISC-V不需要对齐，但如果操作数对齐，访问通常会更快。\n3.寻址模式——除了指定寄存器和常量操作数外，寻址模式还指定内存对象的地址。RISC-V寻址模式有寄存器、立即（用于常数）和置换，其中向寄存器添加常数偏移以形成存储器地址。80x86支持这三种模式，以及三种位移变体：无寄存器（绝对）、两个寄存器（基于位移索引）和两个寄存器\n4.操作数的类型和大小——与大多数指令集架构（ISA）一样，80x86、ARMv8 和 RISC-V 支持 8 位（ASCII 字符）、16 位（Unicode 字符或半字）、32 位（整数或字）、64 位（双字或长整数）以及 IEEE 754 在 32 位（单精度）和 64 位（双精度）中的浮点数。80x86 还支持 80 位浮点数（扩展双精度）。\n5.操作——操作的一般类别包括数据传输、算术逻辑、控制和浮点。RISC-V 是一种简单且易于流水线化的指令集架构，它代表了 2017 年使用的 RISC 架构。\n6.控制流指令——几乎所有 ISA，都支持条件分支、无条件跳转、过程调用和返回。RISC-V x86 和ARM三者都使用 PC 相对寻址，其中分支地址由一个加到 PC 上的地址字段指定。存在一些细微差异。RISC-V 的条件分支（BE、BNE 等）测试寄存器的内容，而 80x86 和 ARMv8 的分支测试由算术/逻辑操作产生的条件码位。ARMv8 和 RISC-V 的过程调用将返回地址放在寄存器中，而 80x86 的调用（CALL F）将返回地址放在内存的栈上。\n7.在编码一个指令集架构——编码有两种基本选择：固定长度和可变长度。所有 ARMv8 和 RISC-V 指令都是 32 位长，这简化了指令解码。80x86 的编码是可变长度的，范围从 1 到 18 字节。可变长度指令可能比固定长度指令占用更少的空间，因此为 80x86 编写的程序通常比为 RISC-V 编译的程序更小。请注意，之前提到的选择将影响指令如何编码成二进制表示。例如，寄存器的数量和寻址方式的数量都会对指令的大小产生重大影响，因为寄存器字段和寻址方式字段可能会在单个指令中出现多次。（注意，ARMv8 和 RISC-V 后来提供了扩展，分别称为 Thurnb-2 和 RV64IC，它们分别提供了 16 位和 32 位长度的指令组合，以减少程序大小。这些 RISC 架构紧凑版本的代码大小小于 80x86。）\n","permalink":"https://blog.muwinds.com/archives/268/","summary":"\u003ch2 id=\"a并行计算与并行结构\"\u003eA.并行计算与并行结构\u003c/h2\u003e\n\u003cp\u003e在所有四类计算机设计中，多级并行化现在是主要动力，功耗和成本是主要限制因素。在应用中基本上有两种并行性：\u003c/p\u003e\n\u003cp\u003e1.数据级并行（DLP），产生的原因是有许多数据项可以同时操作。\u003c/p\u003e","title":"体系基础部分整理"},{"content":"这里把我的powershell的配置方法做一个整理，我的系统环境是Windows10 22H2\n1.关闭ps脚本安全策略\n因为我主用conda做环境配置（里面的带的库配置还是很全的，用着好使），powershell默认的配置是限制conda的ps脚本运行的，所以得关掉这个配置。\nSet-ExecutionPolicy -ExecutionPolicy RemoteSigned 2.安装psreadline\npowershell的快捷键配置在windows10下和vscode的poweshell终端不通，主要原因是缺少psreadline的问题，windows11配置了这一部分，所以在windows10系统下得安装psreadline这个powershell扩展模块。\nInstall-Module -Name PSReadLine -AllowClobber -Force 3.conda lazyload\nconda的ps脚本启动速度太墨迹了，很多时候还没到非得用conda的那个环境的地步，所以这里得把默认的powershell脚本更改一下。\n打开powershell输入命令，找到powershell配置文件\n$PROFILE 有conda init加载导致powershell很慢的时候，配置文件如下：\n#region conda initialize # !! Contents within this block are managed by \u0026#39;conda init\u0026#39; !! If (Test-Path \u0026#34;D:\\tool\\Anaconda\\Scripts\\conda.exe\u0026#34;) { (\u0026amp; \u0026#34;path\\to\\conda.exe\u0026#34; \u0026#34;shell.powershell\u0026#34; \u0026#34;hook\u0026#34;) | Out-String | ?{$_} | Invoke-Expression } #endregion 实现conda环境懒加载，只需把内容换成如下即可（此后执行conda activate 环境名时会自动初始化conda，无需预先加载）\n#region conda initialize # !! Contents within this block are managed by \u0026#39;conda init\u0026#39; !! function Load-Conda { If (Test-Path \u0026#34;path\\to\\conda.exe\u0026#34;) { (\u0026amp; \u0026#34;D:\\tool\\Anaconda\\Scripts\\conda.exe\u0026#34; \u0026#34;shell.powershell\u0026#34; \u0026#34;hook\u0026#34;) | Out-String | ?{$_} | Invoke-Expression } conda @args } Set-Alias conda Load-Conda #endregion ","permalink":"https://blog.muwinds.com/archives/267/","summary":"\u003cp\u003e这里把我的powershell的配置方法做一个整理，我的系统环境是Windows10 22H2\u003c/p\u003e\n\u003cp\u003e1.关闭ps脚本安全策略\u003c/p\u003e\n\u003cp\u003e因为我主用conda做环境配置（里面的带的库配置还是很全的，用着好使），powershell默认的配置是限制conda的ps脚本运行的，所以得关掉这个配置。\u003c/p\u003e","title":"我的PowerShell配置方法"},{"content":"转载自https://xzonn.top/posts/Remove-Intelligent-Image-Viewer.html\n某网盘更新到7.58版本之后附带了一个“智能看图”软件，关联了一堆常见图片格式。这个软件的名字前面加了个空格，所以在默认程序列表里很靠前。目前该网盘里没有删除这个功能的设置，所以需要手动编辑一下注册表，先按 Win + R 打开“运行”窗口，输入regedit并回车，打开注册表编辑器，\n然后删除以下项目：\nHKEY_CLASSES_ROOT\\BaiduNetdiskImageViewerAssociations HKEY_CURRENT_USER\\Software\\Baidu\\BaiduNetdiskImageViewer HKEY_CURRENT_USER\\Software\\RegisteredApplications\\BaiduNetdiskImageViewer 程序目录位于该网盘的安装目录内，默认为：%APPDATA%\\baidu\\BaiduNetdisk\\module\\ImageViewer\n不知道一个网盘软件为什么要捆绑一个看图软件，干好自己的事不行吗？另一个网盘甚至直接把客户端捆绑了浏览器，我用Chrome或者Edge不好吗，用你那破浏览器干啥？\n（2025-07-07更新）：最近这个网页访问量挺多，看来是有许多人被这个软件烦到了。这里提供一个批量删除的脚本，直接复制到记事本中，另存为remove_image_viewer.bat（注意，请确保保存时的编码是“ANSI”或“GBK”“GB2312”），然后双击运行即可：\n@echo off echo ========================================== echo ○○网盘“智能看图”功能卸载脚本 echo ========================================== echo. echo 此脚本将删除○○网盘捆绑的“智能看图”软件 echo 请确保以管理员身份运行此脚本 echo. pause echo 正在删除注册表项... echo. :: 删除注册表项1：HKEY_CLASSES_ROOT\\BaiduNetdiskImageViewerAssociations echo [1/3] 删除 HKEY_CLASSES_ROOT\\BaiduNetdiskImageViewerAssociations reg delete \u0026#34;HKEY_CLASSES_ROOT\\BaiduNetdiskImageViewerAssociations\u0026#34; /f \u0026gt;nul 2\u0026gt;\u0026amp;1 if %errorlevel% equ 0 ( echo ? 删除成功 ) else ( echo ? 删除失败或项目不存在 ) :: 删除注册表项2：HKEY_CURRENT_USER\\Software\\Baidu\\BaiduNetdiskImageViewer echo [2/3] 删除 HKEY_CURRENT_USER\\Software\\Baidu\\BaiduNetdiskImageViewer reg delete \u0026#34;HKEY_CURRENT_USER\\Software\\Baidu\\BaiduNetdiskImageViewer\u0026#34; /f \u0026gt;nul 2\u0026gt;\u0026amp;1 if %errorlevel% equ 0 ( echo ? 删除成功 ) else ( echo ? 删除失败或项目不存在 ) :: 删除注册表项3：HKEY_CURRENT_USER\\Software\\RegisteredApplications\\BaiduNetdiskImageViewer echo [3/3] 删除 HKEY_CURRENT_USER\\Software\\RegisteredApplications 中的 BaiduNetdiskImageViewer reg delete \u0026#34;HKEY_CURRENT_USER\\Software\\RegisteredApplications\u0026#34; /v \u0026#34;BaiduNetdiskImageViewer\u0026#34; /f \u0026gt;nul 2\u0026gt;\u0026amp;1 if %errorlevel% equ 0 ( echo ? 删除成功 ) else ( echo ? 删除失败或项目不存在 ) echo. echo 正在删除程序文件... :: 删除程序目录 set \u0026#34;imageviewer_path=%APPDATA%\\baidu\\BaiduNetdisk\\module\\ImageViewer\u0026#34; if exist \u0026#34;%imageviewer_path%\u0026#34; ( echo 找到程序目录: %imageviewer_path% echo 正在删除程序文件... rmdir /s /q \u0026#34;%imageviewer_path%\u0026#34; \u0026gt;nul 2\u0026gt;\u0026amp;1 if %errorlevel% equ 0 ( echo ? 程序文件删除成功 ) else ( echo ? 程序文件删除失败，可能文件正在使用中 echo 请手动删除: %imageviewer_path% ) ) else ( echo 程序目录不存在或已被删除 ) echo. echo ========================================== echo 卸载完成！ echo ========================================== echo. echo 建议重启计算机以确保所有更改生效 echo 如果问题仍然存在， echo 请检查以下路径是否还有残留文件： echo %APPDATA%\\baidu\\BaiduNetdisk\\module\\ImageViewer echo. echo 按任意键退出... pause \u0026gt;nul ","permalink":"https://blog.muwinds.com/archives/265/","summary":"\u003cp\u003e转载自\u003ca href=\"https://xzonn.top/posts/Remove-Intelligent-Image-Viewer.html\"\u003ehttps://xzonn.top/posts/Remove-Intelligent-Image-Viewer.html\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e某网盘更新到7.58版本之后附带了一个“智能看图”软件，关联了一堆常见图片格式。这个软件的名字前面加了个空格，所以在默认程序列表里很靠前。目前该网盘里没有删除这个功能的设置，所以需要手动编辑一下注册表，先按 \u003ccode\u003eWin\u003c/code\u003e + \u003ccode\u003eR\u003c/code\u003e 打开“运行”窗口，输入\u003ccode\u003eregedit\u003c/code\u003e并回车，打开注册表编辑器，\u003cbr\u003e\n然后删除以下项目：\u003c/p\u003e","title":"删除某网盘附带的“智能看图”"},{"content":"问题如题，我的CDN为Cloudflare，即便是去Cloudflare控制台清除缓存也无济于事\n百度和谷歌也没什么有效的解决方案，最后有朋友提醒我去把反向代理层也就是Nginx的缓存清除，问题解决。\n缓存目录路径：nginx/proxy_cache_dir\n","permalink":"https://blog.muwinds.com/archives/261/","summary":"\u003cp\u003e问题如题，我的CDN为Cloudflare，即便是去Cloudflare控制台清除缓存也无济于事\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/wp-content/uploads/2025/07/image-1.png\"\u003e\u003cimg loading=\"lazy\" src=\"/wp-content/uploads/2025/07/image-1-1024x382.png\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e百度和谷歌也没什么有效的解决方案，最后有朋友提醒我去把反向代理层也就是Nginx的缓存清除，问题解决。\u003c/p\u003e","title":"Next.js+CDN首页无法清除缓存问题"},{"content":"\n在某些环境安装AS的时候，比如网吧安装会有此类弹窗提示说；当前aehd.sys无法复制\n这个原因的是权限不足导致安装as错误\n解决方法：下载或者弹窗的浏览按钮复制aehd.sys到系统盘路径的：C:\\Windows\\System32\\drivers\n复制粘贴文件到该路径即可：C:\\Windows\\System32\\drivers\n","permalink":"https://blog.muwinds.com/archives/258/","summary":"\u003cp\u003e\u003ca href=\"/wp-content/uploads/2025/07/image.png\"\u003e\u003cimg loading=\"lazy\" src=\"/wp-content/uploads/2025/07/image.png\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e在某些环境安装AS的时候，比如网吧安装会有此类弹窗提示说；当前aehd.sys无法复制\u003c/p\u003e\n\u003cp\u003e这个原因的是权限不足导致安装as错误\u003c/p\u003e\n\u003cp\u003e解决方法：下载或者弹窗的浏览按钮复制aehd.sys到系统盘路径的：C:\\Windows\\System32\\drivers\u003c/p\u003e","title":"记录一波Android studio安装报错缺失文件"},{"content":"PID控制算法，可以将其看作是一个 “不断尝试缩小目标与现状之间差距”的智能调节器。它广泛应用于工业控制（如温度、压力、流量控制）、机器人、自动驾驶、无人机稳定系统等需要精确调节物理量的场景。\n其核心思想很简单：测量当前状态（Process Value, PV），与期望的目标状态（Setpoint, SP）进行比较得到误差（Error, e），然后根据误差的“现在大小”、“历史积累”和“未来变化趋势”三个方面来计算一个控制量（Control Output, u），驱动执行器去减小误差，使过程变量尽快、平稳、准确地达到设定值。\nPID 代表了比例 (P)、积分 (I)、微分 (D) 三种控制作用：\n1. 比例控制 (Proportional, P) - 关注“当下”的误差大小 原理： 控制输出 $u_p$ 与当前误差 $e(t)$ 成正比。 $u_p = K_p \\times e(t)$ 物理意义： “有多大的劲就用多大的力”： 误差越大，调节力度就越大。就像一个水温太高了，你根据温度高出目标值多少来决定把热水阀门关小多少。 作用： 提供基本的、快速的响应。 $K_{p}$ 越大，对当前误差的反应越迅速，系统响应越快。 局限性/副作用： 稳态误差： 仅凭比例作用，系统最终通常无法精确达到设定值，会存在一个固定的残余误差（尤其是在有持续干扰或系统固有的阻力/不平衡时）。想象一个带有摩擦的小车，比例控制只能让它停在接近目标点的地方，而不是精确点上。 可能导致振荡： $K_{p}$ 过大时，系统容易像荡秋千一样来回振荡，甚至不稳定。 主要参数：比例增益 $K_{p}$ 2. 积分控制 (Integral, I) - 关注“历史”积累的误差 原理： 控制输出 $u_i$ 与误差 $e(t)$ 随时间的积分（即历史积累的总误差）成正比。 $u_i = K_i \\times \\int_{0}^{t} e(\\tau) \\, d\\tau$（或者更常用 $u_i = \\left( \\frac{K_p}{T_i} \\right) \\times \\int_{0}^{t} e(\\tau) \\, d\\tau$，其中 $T_{i}$ 是积分时间） 物理意义： “差得太久，就得使劲补”： 它专注于消除系统长期存在的偏差（稳态误差）。例如，如果水温总是偏低一点点（比例作用无法消除的误差），积分作用会不断加大加热功率（或开大冷水阀门），直到误差完全消失。 作用： 消除稳态误差： 这是它最核心的作用。 局限性/副作用： 响应慢： 它需要时间积累误差才能有效，因此对快速变化的误差响应相对缓慢。 可能导致积分饱和和振荡： 如果 $K_{i}$ 过大（或 $T_{i}$ 过小），积分作用太强，系统会反应过度，导致超调过大（冲过头）、振荡加剧，甚至达到执行器的物理极限（如阀门全开或全关），并需要更长时间才能从这种状态恢复回来（这称为积分饱和）。 主要参数：积分增益 $K_{i}$ 或 积分时间 $T_{i}$ 3. 微分控制 (Derivative, D) - 关注“未来”变化的趋势 原理： 控制输出 $u_d$ 与误差 $e(t)$ 随时间的变化率（即导数或斜率）成正比。 $u_d = K_d \\times \\frac{de(t)}{dt}$ （或者更常用 $u_d = K_p \\times T_d \\times \\frac{de(t)}{dt}$，其中 $T_d$ 是微分时间） 物理意义： “刹车（或加油）要趁早”： 它预测未来误差变化的趋势。当误差正在快速变大时，它提供额外的抑制作用（刹车）；当误差正在快速减小时（意味着快要冲过头了），它提供减少作用的力度（松油门）。它就像一个阻尼器，抑制系统速度的变化，让运动更平稳。 作用： 抑制超调： 防止系统冲过设定点。 减小振荡： 让系统响应更平稳。 提高稳定性： 尤其有助于阻尼大惯性系统的振荡。 局限性/副作用： 对噪声敏感： 误差的微小波动（噪声）会被导数放大，导致控制输出抖动，甚至使系统不稳定。实际应用中通常需要对测量信号进行滤波。 对设定点突变反应强烈： 如果设定点突然改变，误差瞬间变化率极大，会导致微分项产生一个很大的冲击（spike）信号。 主要参数：微分增益 $K_d$ 或 微分时间 $T_d$ PID控制器的工作方式（总输出） PID 控制器的总输出 $u(t)$ 是这三个分量的和：\n$$ u(t) = u_p + u_i + u_d = K_p \\times e(t) + K_i \\times \\int e(t) \\, dt + K_d \\times \\frac{de(t)}{dt} $$或者（更常用的带时间参数的形式）：\n$$ u(t) = K_p \\times \\left[ e(t) + \\frac{1}{T_i} \\times \\int e(t) \\, dt + T_d \\times \\frac{de(t)}{dt} \\right] $$如何形象化理解三个作用？ 想象你在驾驶一辆车，目标是保持恒定速度（设点 SP）。\nP (比例)： 你通过油门/刹车控制车速。当前速度比目标低得越多，你踩油门越深；当前速度比目标高得越多，你踩刹车越重。只看当前的偏差。 I (积分)： 你发现持续一分钟车速都比目标值低 1 km/h，这意味着即使你按比例踩了油门，还不足以完全纠正偏差。于是，你决定再稍微多踩一点点油门，直到这个偏差彻底消失。关注偏差持续的时间。 D (微分)： 你看到速度表的指针正在飞速上升，即使当前速度还在目标值以下，但你预判到如果按现在的趋势踩油门，很快就会超速。于是你提前松了一点油门。或者看到指针急速下降，虽然当前速度还高于目标值，你预判它会掉下去太快，于是提前踩了一点点油门。关注偏差变化的快慢趋势。 PID 参数整定 (Tuning) PID 控制器的性能和效果极大程度依赖于三个参数（$K_p$, $K_i$/$T_i$, $K_d$/$T_d$）的选取。找到一组适合特定系统的最优参数的过程称为参数整定。这是使用 PID 最关键也最具挑战性的环节。\n常见的方法包括：\n手动试凑法： 工程师基于经验和系统响应观察进行调整（从 P 开始，然后加 I，最后加 D）。 齐格勒-尼克尔斯方法 (Ziegler-Nichols)： 一个经典的经验公式法，分为开环和闭环（临界比例度）两种方法。 软件辅助自动整定： 很多现代控制器或软件工具（如 MATLAB 的 PID Tuner）提供了自动整定功能，能基于系统模型或实际测试自动计算优化参数。 总结与关键点 P 是基础响应： 直接对抗当前误差。 I 消除残余误差： 对付累积的、持久的偏差。 D 预测和阻尼： 抑制变化率，让过程更平稳。 组合是王道： 三者协同工作才能达到快速、平稳、精确的控制效果。 参数是关键： 错误的参数会导致响应迟缓、振荡、超调甚至不稳定。 广泛适用性： PID 结构简单直观，鲁棒性好（对模型精度要求不高），因此在极其广泛的领域得到应用。 实际考虑： 实际应用中还需注意信号滤波、抗积分饱和、执行器限幅、微分项对噪声敏感等问题。 简单来说：PID 就像一个经验丰富的师傅在操作阀门（或其他执行器）来精准控制某个量（如温度）。他不仅根据“现在差多少”（P）来调阀门开度，还会想想“以前差了多少没补上”（I）来加大/减小点力度，同时观察着“变化有多快”（D）来预判是否需要提前收手（防冲过头）或加把劲（防掉速太快）。最终目标是把目标值稳稳当当地控制在想要的点上。 熟练掌握 PID 的核心概念和调参技巧是进行自动控制系统设计和应用的基本功。\n","permalink":"https://blog.muwinds.com/archives/245/","summary":"\u003cp\u003ePID控制算法，可以将其看作是一个 \u003cstrong\u003e“不断尝试缩小目标与现状之间差距”的智能调节器\u003c/strong\u003e。它广泛应用于工业控制（如温度、压力、流量控制）、机器人、自动驾驶、无人机稳定系统等需要精确调节物理量的场景。\u003c/p\u003e","title":"理解PID控制算法"},{"content":"一、基本概念 最优化：首先是一种理念，其次才是一种方法，它所追求的是一种“至善”之道，一种追求卓越的精神。\n例子：小明同学，烧一壶水要8分钟，灌开水要1分钟，取牛奶和报纸要5分钟，整理书包要6分钟，为了尽快做完这些事，怎样安排才能使时间最少？最少需要几分钟？\n最优化问题的数学模型的一般形式为： \\[\\begin{align*} \u0026amp;\\text{opt } z = f(x) \\\\ \u0026amp;\\text{s.t. } h_i(x) = 0, \\quad i = 1, \\cdots, l \\\\ \u0026amp;\\qquad g_j(x) \\leq 0, \\quad j = 1, \\cdots, m \\\\ \u0026amp;\\qquad t_k(x) \\geq 0, \\quad k = 1, \\cdots, n \\\\ \u0026amp;\\qquad x \\in D \\subseteq \\mathbb{R}^s \\end{align*}\\] opt是最优化的缩写，问题应该针对的是求最大值或者求最小值的问题，st是约束\n三个要素：决策变量（decision variable）、目标函数（objective function）、约束条件（constraints）\n满足st的解x称为可行解(feasiblesolution)，\n同时满足st和opt的解x称为最优解(Cptimal solution)，\n整个可行域上的最优解称为全局最优解(globaloptimalsolution)，\n可行域中某个领域上的最优解称为局部最优解(localoptimalsolution)，\n最优解所对应的目标函数值称为最优值(optimum)。\n优化模型的分类 （一）按有无约束条件可分为：\n1.无约束优化(unconstrained optimization)\n2.约束优化(constrained optimization)\n大部分实际问题都是约束优化的问题\n（二）按决策变量取值是否连续\n1.数学规划或连续优化\n可继续划分为\n线性规划(LP)Linear programming 和 非线性规划(NLP) Nonlinear programming\n在非线性规划中有一种规划叫做二次规划(QP)Quadratic programming，目标为二次函数，约束为线性函数。\n2.离散优化或组合优化\n包含：\n整数规划(IP)Integer programming，整数规划中又包含很重要的一类规划：0-1（整数)规划Zero-oneprogramming，这类规划问题的决策变量只取0或者1。\n（三)按目标的多少可分为：\n1.单目标规划\n2.多目标规划\n（四）按模型中参数和变量是否具有不确定性可分为：\n1.确定性规划\n2不确定性规划\n二、单目标规划 例题1:问题描述 汽车厂需制定生产计划，在以下约束条件下实现利润最大化\n例题1图表：汽车厂生产计划参数表 资源/类型 小型汽车 中型汽车 大型汽车 现有总量 钢材（吨） 1.5 3 5 600 时间（小时） 280 250 400 60,000 利润（万元） 2 3 4 — 决策变量：\n设生产小型、中型、大型汽车数量分别为 $$x_{1},x_{2},x_{3}$$总获利 $$ \\text{Max } z = 2x_{1} + 3x_{2} + 4x_{3} $$原料供应 $$ 1.5x_{1} + 3x_{2} + 5x_{3} \\leq 600 $$劳动时间 $$ 280x_{1} + 250x_{2} + 400x_{3} \\leq 60000 $$非负约束 $$ x_{1}, x_{2}, x_{3} \\geq 0 \\text{且为整数}$$生产小型64台，中型168台，大型0台，利润632\n例题2：问题描述 某自来水公司需从A、B、C三个水库向甲、乙、丙、丁四个用户供水，目标是通过合理分配供水量使公司获利最大。具体参数如下：\n水库供水量（千吨）： A：50 B：60 C：50 用户用水需求（千吨）： 基本用水量（必须满足）： 甲：30 | 乙：70 | 丙：10 | 丁：10 额外用水量（可选择性满足）： 甲：50 | 乙：30 | 丙：无 | 丁：无 成本与收入： 收入：900元/千吨（所有用户统一）。 其他支出：450元/千吨（固定成本）。 引水管理费（元/千吨，见下表）。 表1：引水管理费（元/千吨） 水库\\用户 甲 乙 丙 丁 A 160 130 220 170 B 140 130 190 150 C 190 200 230 — 表2：用户用水量约束（千吨） 用户 基本用水量 额外用水量上限 甲 30 50 乙 70 30 丙 10 0 丁 10 0 引水管理费表中缺的数值可以写一个超级大的数避免引去那边。\n决策变量 水库i向j区的日供水量为 x[i,j] (x[3,4]=0) 目标函数 $ Min\\,Z = \\begin{aligned}[t] \u0026160x_{11} + 130x_{12} + 220x_{13} + 170x_{14} \\\\ \u0026+ 140x_{21} + 130x_{22} + 190x_{23} + 150x_{24} \\\\ \u0026+ 190x_{31} + 200x_{32} + 230x_{33} \\end{aligned} $ 供应约束 $ \\begin{cases} x_{11} + x_{12} + x_{13} + x_{14} = 50 \\\\ x_{21} + x_{22} + x_{23} + x_{24} = 60 \\\\ x_{31} + x_{32} + x_{33} = 50 \\end{cases} $ 需求约束 $ \\begin{cases} 30 \\leq x_{11} + x_{21} + x_{31} \\leq 80 \u0026 \\text{引水管理费：24400（元）} \\\\ 70 \\leq x_{12} + x_{22} + x_{32} \\leq 140 \u0026 \\text{最大利润：47600（元）} \\\\ 10 \\leq x_{13} + x_{23} + x_{33} \\leq 30 \\\\ 10 \\leq x_{14} + x_{24} \\leq 50 \\end{cases} $ \u0026mdash; 例题4（下料问题）问题描述 某工厂需使用长度为5.5米的圆钢原材料，切割成以下三种规格的圆钢材料： - A型：长度3.1米，需求100根 - B型：长度2.1米，需求200根 - C型：长度1.2米，需求400根 目标：通过合理选择下料方式，使所需圆钢原材料总数最少（或余料总量最省）。 下料方式与余料分析 现有五种下料方式（一至五），每种方式切割后产生的A、B、C型数量及余料如下表所示： 图表：下料方案与余料统计表 | 材料/方式 | 方式一 | 方式二 | 方式三 | 方式四 | 方式五 | 总需求量 | | \u0026mdash; | \u0026mdash; | \u0026mdash; | \u0026mdash; | \u0026mdash; | \u0026mdash; | \u0026mdash; | | A型 | 1 | 1 | 0 | 0 | 0 | 100 | | B型 | 1 | 0 | 2 | 1 | 0 | 200 | | C型 | 0 | 2 | 1 | 2 | 4 | 400 | | 余料 | 0.3米 | 0米 | 0.1米 | 1.0米 | 0.7米 | — | 决策变量 设第i种截法的数量为 $ x_i $ 目标函数 min $ Z = \\sum_{i=1}^{5} x_i $ 约束条件有：需求约束 - $ x_1 + x_2 \\geq 100 $ - $ x_1 + 2x_3 + x_4 \\geq 200 $ - $ 2x_2 + x_3 + 2x_4 + 4x_5 \\geq 400 $ 非负约束 $ x_i \\geq 0, i = 1, 2, \\ldots, 5 $ 且为整数 下面是LINGO的代码： MODEL: SETS: HANG/1..3/:A; LIE/1..5/:B,X; XISHU(HANG,LIE):C; ENDSETS DATA: A=100 200 400; B=0.3 0 0.1 1.0 0.7; C=1 1 0 0 0 1 0 2 1 0 0 2 1 2 4; ENDDATA MIN=@SUM(LIE(J):X(J)); !MIN=@SUM(LIE(J):b(J)*X(J)); @FOR(HANG(I):@SUM(LIE(J):C(I,J)*X(J))\u0026gt;A(I)); @FOR(LIE(J):@GIN(X(J))); END 一维下料问题的一般表述 需要m种材料 $ A_1, A_2, \\ldots, A_m $，数量分别为 $ b_j $，对一件长的原材料可得出k种不同的切割方法，$ n_{ij} $ 表示第i种方法得到 $ A_j $ 部件的数量。用 $ x_i $ 表示第i种截法的原材料数量，则该问题的模型为： 目标函数\n$$ \\min Z = \\sum_{i=1}^{k} x_i $$约束条件\n$$ \\begin{cases} \\sum_{i=1}^{k} n_{ij} x_i \\geq b_j, \u0026 j = 1, 2, \\ldots, m \\\\ x_i \\geq 0, \u0026 i = 1, 2, \\ldots, k \\end{cases} $$ \u0026mdash; 例5 任务分配 分配甲，乙，丙，丁，戊去完成A，B，C，D，E五项任务，每人完成一项，每项任务只能由一个人去完成，五个人分别完成各项任务所需要的时间如下表，试作出任务分配使总时间最少。表格如下： | 任务\\人员 | A | B | C | D | E | | \u0026mdash; | \u0026mdash; | \u0026mdash; | \u0026mdash; | \u0026mdash; | \u0026mdash; | | 甲 | 8 | 6 | 10 | 9 | 12 | | 乙 | 9 | 12 | 7 | 11 | 9 | | 丙 | 7 | 4 | 3 | 5 | 8 | | 丁 | 9 | 5 | 8 | 11 | 8 | | 戊 | 4 | 6 | 7 | 5 | 11 | 指派问题或最优匹配问题 设有n项工作分配给n个人去做，每人做一项，由于各人的工作效率不同，因而完成同一工作所需要的时间 也就不同，设人员i完成工作j所需要的时间为c，问如何分配工作，使完成所有工作所用的总时间最少。（效率最高）？ 常用解法：0-1规划 决策变量 $$ x_{ij} = \\begin{cases} 0, \u0026 \\text{第$i$个人不做第$j$项工作} \\\\ 1, \u0026 \\text{第$i$个人做第$j$项工作} \\end{cases} $$ 目标函数 $$ \\min Z = \\sum_{i=1}^{n}\\sum_{j=1}^{n} c_{ij}x_{ij} $$ 约束条件 $$ \\begin{cases} \\sum\\limits_{i=1}^{n} x_{ij} = 1, \u0026 j=1,2,\\cdots,n \\\\[8pt] \\sum\\limits_{j=1}^{n} x_{ij} = 1, \u0026 i=1,2,\\cdots,n \\\\[8pt] x_{ij} \\in \\{0,1\\}, \u0026 i,j=1,2,\\cdots,n \\end{cases} $$ \u0026mdash; 例6：料场选址 某公司有6个建筑工地要开工，工地位置 (x**i,y**i)（单位：km）和水泥日用量 d**i（单位：t）由下表给出。公司目前有两个临时存放水泥的料场，分别位于 A(5,1) 和 B(2,7)，日存储量各20t。 1. 假设从料场到工地之间均有直线道路相连，试制定日运输计划，即从 A，B 分别向各工地送多少水泥，使总吨·千米数最小。 2. 为进一步减少吨·千米数，打算舍弃目前的两个料场，改建两个新料场，日存储量不变，问建在何处为好？ | 工地 | 位置 (x_i, y_i) | 日用量 d_i | | \u0026mdash; | \u0026mdash; | \u0026mdash; | | 1 | (1.25, 1.25) | 3 | | 2 | (8.75, 0.75) | 5 | | 3 | (0.5, 4.75) | 4 | | 4 | (5.75, 5) | 7 | | 5 | (3, 6.5) | 6 | | 6 | (7.25, 7.75) | 11 | 问题一部分：决策变量 料场的位置用 $px_{j},py_{j}$ 表示，日存储用 $g_{j}$ 表示，从料场 j 向工地 i 日运输量为 $c_{ij}$ 目标函数 $$ \\min Z = \\sum_{i=1}^{6}\\sum_{j=1}^{2} c_{ij}\\sqrt{(px_{j}-x_{i})^{2} + (py_{j}-y_{i})^{2}} $$ 约束条件 $$ \\text{s.t.}\\ \\begin{cases} \\sum\\limits_{i=1}^{6}c_{ij} \\leq g_{j}, \u0026 j=1,2 \\\\[10pt] \\sum\\limits_{j=1}^{2}c_{ij} = d_{i}, \u0026 i=1,2,\\cdots,6 \\end{cases} $$Model: Sets: Gd/1..6/: x, y, d; lch/a, b/: px, py, g; Links(gd, lch): c; Endsets Data: X = 1.25 8.75 0.5 5.75 3 7.25; Y = 1.25 0.75 4.75 5 6.5 7.75; D = 3 5 4 7 6 11; Px = 5 2; py = 1 7; g = 20 20; Enddata Min = @sum(links(i,j): c(i,j)*@sqrt((px(j)-x(i))^2+(py(j)-y(i))^2)); @for(gd(i): @sum(lch(j): c(i,j)) = d(i)); @for(lch(j): @sum(gd(i): c(i,j)) \u0026lt;= g(j)); end 第二问的实质是c[i,j]和px与py都成为了变量，使得之前一问的线性规划问题变成了非线性规划问题，模型本质上是一样的，只需要在LINGO软件里面把px和py的数据删掉就可以了\nModel: Sets: Gd/1..6/: x, y, d; lch/a, b/: px, py, g; Links(gd, lch): c; Endsets Data: X = 1.25 8.75 0.5 5.75 3 7.25; Y = 1.25 0.75 4.75 5 6.5 7.75; D = 3 5 4 7 6 11; g = 20 20; Enddata Min = @sum(links(i,j): c(i,j)*@sqrt((px(j)-x(i))^2+(py(j)-y(i))^2)); @for(gd(i): @sum(lch(j): c(i,j)) = d(i)); @for(lch(j): @sum(gd(i): c(i,j)) \u0026lt;= g(j)); end 问题是，非线性规划使用的非线性求解器实质上是只能求局部最优解的，利用的是一个近似迭代的方式，如果想要一个更好的解就需要调整参数，我也不咋会，自己试试咯……？\n","permalink":"https://blog.muwinds.com/archives/145/","summary":"\u003ch2 id=\"一基本概念\"\u003e一、基本概念\u003c/h2\u003e\n\u003cp\u003e最优化：首先是一种理念，其次才是一种方法，它所追求的是一种“至善”之道，一种追求卓越的精神。\u003cbr\u003e\n例子：小明同学，烧一壶水要8分钟，灌开水要1分钟，取牛奶和报纸要5分钟，整理书包要6分钟，为了尽快做完这些事，怎样安排才能使时间最少？最少需要几分钟？\u003c/p\u003e","title":"数学建模笔记-最优化问题"},{"content":"线性回归模型，它是统计学和机器学习中最基础、最核心的预测模型之一，主要用于解决回归问题，即预测一个连续的数值型输出。\n核心思想：建立“输入”与“输出”之间的线性关系 线性回归的核心目标非常直观：假设并建模目标变量 y（因变量、输出）与一个或多个预测变量 x（自变量、特征、输入）之间存在的线性关系。\n它试图找到一条（简单线性回归）或一个平面/超平面（多元线性回归）来“最佳”拟合数据点，从而当我们知道 x 的值时，就可以基于这条线/平面预测 y 的值。\n模型表示 简单线性回归： 只涉及一个预测变量 x 和一个目标变量 y。 模型方程： y = β₀ + β₁ * x + ε y: 目标变量 (Dependent/Target Variable) - 我们要预测的连续值（例如：房价、销售额、温度）。 x: 预测变量 (Independent/Predictor Variable/Feature) - 我们用来做预测的变量（例如：房屋面积、广告投入、时间）。 β₀: 截距 (Intercept) - 当 x = 0 时，直线与 y 轴相交的点（预测值）。它代表了模型中所有输入特征都为 0 时的基准预测值（注意：这个值本身可能没有实际物理意义）。 β₁: 斜率/系数 (Slope/Coefficient) - 表示 x 每增加（或减少）一个单位时，y 平均预期会相应增加（或减少）的量。 这是理解 x 对 y 影响方向和大小的关键。β₁ \u0026gt; 0 表示正相关（x 增大，y 增大）；β₁ \u0026lt; 0 表示负相关（x 增大，y 减小）。 ε: 误差/残差 (Error/Residual) - 模型无法完美解释的部分。它代表了实际观测值 y 与模型预测值 ŷ = β₀ + β₁ * x 之间的随机差异。这是由未包含在模型中的变量、测量误差或数据本身的随机性引起的。 多元线性回归： 涉及多个预测变量 x₁, x₂, ..., xₚ 来预测一个目标变量 y。 模型方程： y = β₀ + β₁*x₁ + β₂*x₂ + ... + βₚ*xₚ + ε y: 目标变量。 x₁, x₂, ..., xₚ: 多个预测变量。 β₀: 截距。 β₁, β₂, ..., βₚ: 每个预测变量对应的斜率/系数。βⱼ 的解释为：在保持模型中其他所有预测变量不变的前提下，xⱼ 每变化一个单位，y 平均预期会发生的变化量。 理解这种\u0026quot;保持其他变量不变\u0026quot;的条件对于避免混淆关联性和因果性至关重要。 ε: 误差项。 线性回归的“学习”过程就是寻找最优的系数 β₀, β₁, ..., βₚ，使得模型拟合数据的效果最好。这个“最好”的标准通常是：\n最小二乘法： 目标： 找到一组系数，使得模型对所有训练样本的预测值 ŷᵢ 和 实际观测值 yᵢ 之间的平方误差和最小化。 损失函数： Sum of Squared Residuals = Σᵢ(yᵢ - ŷᵢ)² = Σᵢ(εᵢ)²。 几何意义： 在二维图中，就是找一条直线，使得所有数据点到这条直线的垂直距离（残差）的平方和最小。 求解： 对于普通最小二乘法 (Ordinary Least Squares, OLS)，可以通过求导找到解析解（数学公式直接计算），也可以通过梯度下降等优化算法迭代求解（尤其是特征非常多或者涉及正则化时）。 拟合完模型后，我们需要评估它的好坏，并检查是否满足基本假设：\n拟合优度： R² (决定系数)： 最常用的指标之一。表示模型解释的目标变量 y 的总变异比例。范围在 0 到 1 之间（有时会是负数，表示模型比简单均值预测还差）。R² = 1 - (SS_res / SS_tot)，其中 SS_res 是残差平方和，SS_tot 是总平方和。越接近 1，表示模型拟合数据越好（解释变异比例越大）。 调整 R²： 对 R² 的修正，考虑了模型中特征个数的影响。在比较包含不同数量特征的模型时，调整 R² 比 R² 更公平。 均方根误差 (RMSE) / 均方误差 (MSE)： 直接衡量预测值 ŷ 与实际值 y 的平均偏差大小。MSE 是残差平方和的平均值，RMSE 是 MSE 的平方根。单位和 y 相同，数值越小越好。 Logistic 回归。\n虽然名字里有“回归”，但它本质上是一种非常经典的用于解决 分类 问题的统计学习方法，尤其是在二分类问题中应用极为广泛。它的核心思想是基于输入特征来预测样本属于某个类别的概率。\n$$ sigmoid(x) = \\frac{\\mathrm{1} }{\\mathrm{1} + e^{-x} } $$\n核心概念解析 目标是分类： 最终输出是一个类别标签（例如：是/否、垃圾邮件/非垃圾邮件、患病/健康、违约/不违约）。 最常见的应用场景是二分类。但通过扩展（例如 One-vs-Rest 或 Softmax），也可以用于多分类。 概率基础： Logistic 回归的核心不是直接预测类别标签，而是预测某个样本属于“正类”（通常标记为 1，例如“是”、“患病”）的概率。 模型的输出是一个介于 0 和 1 之间的概率值。0.5 通常作为决策边界： 如果预测概率 \u0026gt;= 0.5，则判定为正类 (1)。 如果预测概率 \u0026lt; 0.5，则判定为负类 (0)。（阈值可根据实际需求调整） Logistic 函数（Sigmoid 函数）： 核心转换器： Logistic 回归的核心在于它将线性回归模型 z 的输出，通过一个称为 Logistic 函数或 Sigmoid 函数的非线性函数，映射到 (0, 1) 区间内。 函数形式：\nσ(z) = 1 / (1 + e^(-z)) z 是线性模型的输出：z = β₀ + β₁x₁ + β₂x₂ + ... + βₚxₚ S 形曲线： 当 z 非常大（趋于正无穷）时，σ(z) 趋近于 1。 当 z 非常小（趋于负无穷）时，σ(z) 趋近于 0。 当 z = 0 时，σ(z) = 0.5。 作用： 这个 S 形曲线完美地将任何实数值 z 平滑地压缩到了 (0, 1) 区间，使其成为一个有效的概率估计（P(Y=1 | X)）。 模型结构： 线性部分： 首先，模型计算特征的线性加权和：z = β₀ + β₁x₁ + β₂x₂ + ... + βₚxₚ β₀: 截距项（也叫偏置项）。 β₁, β₂, ..., βₚ: 对应于每个特征 x₁, x₂, ..., xₚ 的系数（权重），反映了该特征对结果预测的影响力大小和方向（正系数增加概率，负系数降低概率）。 非线性转换： 将线性组合 z 输入 Sigmoid 函数，得到预测概率：P(Y=1 | X) = σ(z) = 1 / (1 + e^-z) 这个 P(Y=1 | X) 就是模型对给定输入 X 下样本属于正类 (1) 的概率估计。 如何“学习”（训练模型） 目标是找到最优的系数 β₀, β₁, ..., βₚ，使得模型预测的概率尽可能接近样本的真实标签。\n定义损失函数： Logistic 回归使用 二元交叉熵损失 或 对数损失。这个损失函数专门设计用于衡量预测概率分布与真实分布之间的差异，尤其适合衡量概率输出。 损失函数公式（简化形式）：\nLoss = - [ y * log(p) + (1 - y) * log(1 - p) ] y 是真实标签（0 或 1）。 p 是模型预测该样本属于正类的概率 (P(Y=1 | X))。 逻辑： 如果真实标签 y=1，损失为 -log(p)。预测概率 p 越接近 1，损失越小（趋近于 0）。 如果真实标签 y=0，损失为 -log(1 - p)。预测概率 p 越接近 0，损失越小（趋近于 0）。 预测越离谱 (e.g., y=1 但 p 接近 0)，损失越大。 优化： 模型的训练过程就是最小化所有训练样本上的总损失（或平均损失）。 这个最小化过程通常使用梯度下降或其变体（如随机梯度下降、Mini-batch 梯度下降）来求解。梯度下降通过计算损失函数关于每个系数 β 的梯度（偏导数），并沿梯度相反方向（即下降最快的方向）不断更新系数，逐步逼近最优解（使总损失最小）。 有时会加入正则化项（如 L1 或 L2 正则化）到损失函数中，以控制模型复杂度，防止过拟合。 ","permalink":"https://blog.muwinds.com/archives/210/","summary":"\u003cp\u003e\u003cstrong\u003e线性回归模型\u003c/strong\u003e，它是统计学和机器学习中最基础、最核心的预测模型之一，主要用于解决\u003cstrong\u003e回归问题\u003c/strong\u003e，即预测一个\u003cstrong\u003e连续的数值型输出\u003c/strong\u003e。\u003c/p\u003e\n\u003ch3 id=\"核心思想建立输入与输出之间的线性关系\"\u003e核心思想：建立“输入”与“输出”之间的线性关系\u003c/h3\u003e\n\u003cp\u003e线性回归的核心目标非常直观：\u003cstrong\u003e假设并建模目标变量 \u003ccode\u003ey\u003c/code\u003e（因变量、输出）与一个或多个预测变量 \u003ccode\u003ex\u003c/code\u003e（自变量、特征、输入）之间存在的线性关系。\u003c/strong\u003e\u003c/p\u003e","title":"线性回归\u0026Logistic Model"},{"content":" 打开powershell输入命令，找到powershell配置文件 $PROFILE 有conda init加载导致powershell很慢的时候，配置文件如下： #region conda initialize # !! Contents within this block are managed by \u0026#39;conda init\u0026#39; !! If (Test-Path \u0026#34;D:\\tool\\Anaconda\\Scripts\\conda.exe\u0026#34;) { (\u0026amp; \u0026#34;path\\to\\conda.exe\u0026#34; \u0026#34;shell.powershell\u0026#34; \u0026#34;hook\u0026#34;) | Out-String | ?{$_} | Invoke-Expression } #endregion 实现conda环境懒加载，只需把内容换成如下即可（此后执行conda activate 环境名时会自动初始化conda，无需预先加载）\n#region conda initialize # !! Contents within this block are managed by \u0026#39;conda init\u0026#39; !! function Load-Conda { If (Test-Path \u0026#34;path\\to\\conda.exe\u0026#34;) { (\u0026amp; \u0026#34;D:\\tool\\Anaconda\\Scripts\\conda.exe\u0026#34; \u0026#34;shell.powershell\u0026#34; \u0026#34;hook\u0026#34;) | Out-String | ?{$_} | Invoke-Expression } conda @args } Set-Alias conda Load-Conda #endregion 注意路径，保存后新开一个powershell窗口看效果。\n","permalink":"https://blog.muwinds.com/archives/208/","summary":"\u003col\u003e\n\u003cli\u003e打开powershell输入命令，找到powershell配置文件\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-powershell\" data-lang=\"powershell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003e$PROFILE\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003col start=\"2\"\u003e\n\u003cli\u003e有conda init加载导致powershell很慢的时候，配置文件如下：\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-powershell\" data-lang=\"powershell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e#region conda initialize\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# !! Contents within this block are managed by \u0026#39;conda init\u0026#39; !!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eIf\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eTest-Path\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;D:\\tool\\Anaconda\\Scripts\\conda.exe\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e(\u0026amp;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;path\\to\\conda.exe\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;shell.powershell\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;hook\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e \u003cspan class=\"nb\"\u003eOut-String\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e \u003cspan class=\"p\"\u003e?{\u003c/span\u003e\u003cspan class=\"nv\"\u003e$_\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e \u003cspan class=\"nb\"\u003eInvoke-Expression\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e#endregion\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e实现conda环境懒加载，只需把内容换成如下即可（此后执行\u003ccode\u003econda activate 环境名\u003c/code\u003e时会自动初始化conda，无需预先加载）\u003c/p\u003e","title":"PowerShell进去到conda的虚拟环境base特别慢（实现conda环境懒加载）"},{"content":"KMP算法（Knuth-Morris-Pratt）是一种高效的字符串匹配算法，它通过避免回溯主串指针来实现O(n+m)的时间复杂度。要深入理解KMP算法，需要掌握其核心思想——部分匹配表（Pi数组）和匹配过程中的智能回退机制。\n1. 核心思想：利用已匹配信息避免重复比较 传统暴力匹配算法在每次失败时需要回溯主串指针，导致大量重复比较。KMP算法通过预处理模式串，构建一个\u0026quot;部分匹配表\u0026quot;（Pi数组），利用这个表在匹配失败时只回退模式串指针，主串指针永不回溯。\n2. 部分匹配表（Pi数组） Pi数组存储了模式串每个位置的最长公共前缀后缀长度（Longest Proper Prefix which is also Suffix）。\n定义： pi[i] = 子串needle[0..i]中，既是真前缀（不包含自身）又是真后缀（不包含自身）的最长子串长度。\n示例（模式串\u0026quot;ababc\u0026quot;）： 示例（模式串\u0026quot;ababc\u0026quot;）： 子串 真前缀 真后缀 公共部分 pi[i] a 无 无 无 0 ab [a] [b] 无 0 aba [a, ab] [ba, a] \u0026ldquo;a\u0026rdquo; 1 abab [a,ab,aba] [bab,ab,b] \u0026ldquo;ab\u0026rdquo; 2 ababc [a,ab,aba,abab] [babc,abc,bc,c] 无 0 Pi数组结果：[0, 0, 1, 2, 0]\nPi数组构建过程： vector\u0026lt;int\u0026gt; pi(m, 0); // 初始化全0 for (int i = 1, j = 0; i \u0026lt; m; i++) { while (j \u0026gt; 0 \u0026amp;\u0026amp; needle[i] != needle[j]) j = pi[j-1]; // 回退到前一个匹配位置 if (needle[i] == needle[j]) j++; // 匹配时扩展公共长度 pi[i] = j; // 记录当前位置的LPS } 3. 匹配过程：智能回退机制 当匹配失败时，利用Pi值决定模式串指针回退的位置，主串指针不回溯。\nfor (int i = 0, j = 0; i \u0026lt; n; i++) { while (j \u0026gt; 0 \u0026amp;\u0026amp; haystack[i] != needle[j]) j = pi[j-1]; // 关键：回退模式串指针 if (haystack[i] == needle[j]) j++; if (j == m) // 完全匹配 return i - m + 1; } 匹配示例（主串：\u0026ldquo;abababc\u0026rdquo;，模式串：\u0026ldquo;ababc\u0026rdquo;）： 步骤1: [a]bababc ←→ [a]babc 匹配，j=1 步骤2: a[b]ababc ←→ a[b]abc 匹配，j=2 步骤3: ab[a]babc ←→ ab[a]bc 匹配，j=3 步骤4: aba[b]abc ←→ aba[b]c 匹配，j=4 步骤5: abab[a]bc ←→ abab[c] 不匹配 → 回退 j = pi[3] = 2 ▲(主串指针保持不动) ab[a]bc ←→ 回退后重新比较 步骤6: abab[a]bc ←→ ab[a]bc 匹配，j=3 步骤7: ababa[b]c ←→ aba[b]c 不匹配 → 回退 j = pi[2] = 1 步骤8: ababa[b]c ←→ a[b]c 不匹配 → 回退 j = pi[0] = 0 步骤9: ababa[b]c ←→ [a]bc 不匹配 → i继续前进 ... 4. KMP算法的本质理解 空间换时间：预处理Pi数组（O(m)时间） 后缀即前缀：利用模式串自身重复特性（如\u0026quot;abab\u0026quot;中\u0026quot;ab\u0026quot;即是前缀也是后缀） 回退即跳跃：失败时不重头开始，而是跳到\u0026quot;已匹配部分\u0026quot;的后缀起点 永不回溯主串：主串指针单向移动，时间复杂度稳定O(n+m) 5. 实际应用场景 文本编辑器中的查找功能 DNA序列匹配（长模式串匹配） 网络数据包内容分析 编译器词法分析 理解KMP的关键在于将模式串的自我匹配（构建Pi表）和主串的智能匹配（回退机制）看作同一思想的两种应用。掌握Pi数组的物理含义和回退时的\u0026quot;最长公共前后缀\u0026quot;原则，就能真正理解KMP的精髓。\n当然你可以：“叽里咕噜说啥呢”，直接：\nhaystack.find(needle); find 本身也是用的 kmp 算法解决的\n","permalink":"https://blog.muwinds.com/archives/207/","summary":"\u003cp\u003eKMP算法（Knuth-Morris-Pratt）是一种高效的字符串匹配算法，它通过避免回溯主串指针来实现O(n+m)的时间复杂度。要深入理解KMP算法，需要掌握其核心思想——\u003cstrong\u003e部分匹配表（Pi数组）\u003cstrong\u003e和\u003c/strong\u003e匹配过程中的智能回退机制\u003c/strong\u003e。\u003c/p\u003e","title":"KMP算法"},{"content":"一、定义与核心目的 本质：一种轻量级、快速执行的验证性测试，用于检测系统核心功能是否可运行。 目标： 快速发现阻断性缺陷（Blocking Bug），确保被测对象（如新构建的版本）具备进一步测试的价值。 避免在不可用的版本上浪费时间和资源执行深度测试。 隐喻来源：源自硬件测试，类比电路板通电后若冒烟则说明存在严重故障，需立即停止测试。 二、适用场景 持续集成（CI） 代码提交后触发自动化冒烟测试，验证构建是否通过基础检查（如编译成功、服务启动）。 版本发布前 上线前最后一轮验证，确保主流程（如用户登录、支付）未因最新修改而崩溃。 环境迁移后 新部署环境中快速确认系统是否可访问（如数据库连接、API网关响应）。 三、测试范围设计原则 最小化覆盖：仅测试最关键路径（Critical Path），而非全量功能。 示例： 电商平台：用户登录 → 商品搜索 → 加入购物车 → 结算 → 支付。 金融系统：账户查询 → 转账 → 交易记录查看。 高优先级用例：覆盖直接影响业务连续性的功能（如核心交易链路）。 排除非关键路径：不涉及边缘场景（如异常数据输入）、性能或安全测试。 四、执行逻辑与流程 前置条件 被测对象已完成部署（如新版本安装、环境配置）。 准备测试数据（如测试账号、基础商品信息）。 执行步骤 自动化脚本（推荐）：通过工具（如Postman、Selenium）执行预设用例，5-15分钟内完成。 手动检查（应急场景）：人工点击主流程，观察日志或界面反馈。 通过标准 核心功能无崩溃（如HTTP 500错误）、无阻塞性错误（如数据库连接失败）。 注意：不要求功能完全正确，仅需确认“可测试性”（Testability） 五、与企业级测试策略的关联 测试类型 目标 执行频率 深度 Smoke Test 验证核心功能可用性 每次构建/部署 浅层 Regression Test 确保修改未破坏现有功能 主要版本发布前 中层 Performance Test 评估系统负载能力 季度/重大变更后 深层 六、典型案例分析 场景：银行核心系统版本升级 冒烟测试设计： 用户登录（验证身份认证服务正常）。 账户余额查询（验证数据库连接与核心交易接口）。 单笔转账（验证交易引擎与风控规则引擎）。 失败处理： 若转账接口返回“系统忙”，标记构建失败并通知开发团队，暂停后续测试。 工具链示例： API测试：Postman + Newman（批量运行Collection）。 前端测试：Cypress（自动化模拟用户点击）。 流水线集成：Jenkins调用测试脚本，通过钉钉/邮件发送结果。 七、常见误区与避坑指南 误区：冒烟测试 = 简单测试 纠正：冒烟测试是策略性筛选，需基于业务风险设计，非随机选择用例。 误区：仅用于开发阶段 纠正：生产环境变更（如配置更新、热修复）后同样需要冒烟测试。 陷阱：忽视环境差异性 规避：确保测试数据与环境配置与生产环境一致（如使用影子表隔离测试数据）。 八、进阶实践 动态冒烟测试：根据代码变更范围自动调整测试用例（如代码影响支付模块时，仅执行支付相关冒烟测试）。 熔断机制：若连续3次构建冒烟测试失败，自动回滚至上一个稳定版本。 通过以上框架，可系统化掌握冒烟测试的核心逻辑，并针对企业级场景设计高效验证方案。\n","permalink":"https://blog.muwinds.com/archives/204/","summary":"\u003ch3 id=\"一定义与核心目的\"\u003e\u003cstrong\u003e一、定义与核心目的\u003c/strong\u003e\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e本质\u003c/strong\u003e：一种轻量级、快速执行的验证性测试，用于检测系统\u003cstrong\u003e核心功能\u003c/strong\u003e是否可运行。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e目标\u003c/strong\u003e：\n\u003cul\u003e\n\u003cli\u003e快速发现\u003cstrong\u003e阻断性缺陷\u003c/strong\u003e（Blocking Bug），确保被测对象（如新构建的版本）具备进一步测试的价值。\u003c/li\u003e\n\u003cli\u003e避免在不可用的版本上浪费时间和资源执行深度测试。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e隐喻来源\u003c/strong\u003e：源自硬件测试，类比电路板通电后若冒烟则说明存在严重故障，需立即停止测试。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"二适用场景\"\u003e\u003cstrong\u003e二、适用场景\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e持续集成（CI）\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e代码提交后触发自动化冒烟测试，验证构建是否通过基础检查（如编译成功、服务启动）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e版本发布前\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e上线前最后一轮验证，确保主流程（如用户登录、支付）未因最新修改而崩溃。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e环境迁移后\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e新部署环境中快速确认系统是否可访问（如数据库连接、API网关响应）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"三测试范围设计原则\"\u003e\u003cstrong\u003e三、测试范围设计原则\u003c/strong\u003e\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e最小化覆盖\u003c/strong\u003e：仅测试最关键路径（Critical Path），而非全量功能。\n\u003cul\u003e\n\u003cli\u003e\u003cem\u003e示例\u003c/em\u003e：\n\u003cul\u003e\n\u003cli\u003e电商平台：用户登录 → 商品搜索 → 加入购物车 → 结算 → 支付。\u003c/li\u003e\n\u003cli\u003e金融系统：账户查询 → 转账 → 交易记录查看。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e高优先级用例\u003c/strong\u003e：覆盖直接影响业务连续性的功能（如核心交易链路）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e排除非关键路径\u003c/strong\u003e：不涉及边缘场景（如异常数据输入）、性能或安全测试。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"四执行逻辑与流程\"\u003e\u003cstrong\u003e四、执行逻辑与流程\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e前置条件\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e被测对象已完成部署（如新版本安装、环境配置）。\u003c/li\u003e\n\u003cli\u003e准备测试数据（如测试账号、基础商品信息）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e执行步骤\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e自动化脚本\u003c/strong\u003e（推荐）：通过工具（如Postman、Selenium）执行预设用例，5-15分钟内完成。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e手动检查\u003c/strong\u003e（应急场景）：人工点击主流程，观察日志或界面反馈。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e通过标准\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e核心功能无崩溃（如HTTP 500错误）、无阻塞性错误（如数据库连接失败）。\u003c/li\u003e\n\u003cli\u003e\u003cem\u003e注意\u003c/em\u003e：不要求功能完全正确，仅需确认“可测试性”（Testability）\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"五与企业级测试策略的关联\"\u003e\u003cstrong\u003e五、与企业级测试策略的关联\u003c/strong\u003e\u003c/h3\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e测试类型\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e目标\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e执行频率\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e深度\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eSmoke Test\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e验证核心功能可用性\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e每次构建/部署\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e浅层\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eRegression Test\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e确保修改未破坏现有功能\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e主要版本发布前\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e中层\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003ePerformance Test\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e评估系统负载能力\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e季度/重大变更后\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e深层\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch3 id=\"六典型案例分析\"\u003e\u003cstrong\u003e六、典型案例分析\u003c/strong\u003e\u003c/h3\u003e\n\u003ch4 id=\"场景银行核心系统版本升级\"\u003e\u003cstrong\u003e场景：银行核心系统版本升级\u003c/strong\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e冒烟测试设计\u003c/strong\u003e：\n\u003col\u003e\n\u003cli\u003e用户登录（验证身份认证服务正常）。\u003c/li\u003e\n\u003cli\u003e账户余额查询（验证数据库连接与核心交易接口）。\u003c/li\u003e\n\u003cli\u003e单笔转账（验证交易引擎与风控规则引擎）。\u003c/li\u003e\n\u003c/ol\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e失败处理\u003c/strong\u003e：\n\u003cul\u003e\n\u003cli\u003e若转账接口返回“系统忙”，标记构建失败并通知开发团队，暂停后续测试。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"工具链示例\"\u003e\u003cstrong\u003e工具链示例\u003c/strong\u003e：\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eAPI测试\u003c/strong\u003e：Postman + Newman（批量运行Collection）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e前端测试\u003c/strong\u003e：Cypress（自动化模拟用户点击）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e流水线集成\u003c/strong\u003e：Jenkins调用测试脚本，通过钉钉/邮件发送结果。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"七常见误区与避坑指南\"\u003e\u003cstrong\u003e七、常见误区与避坑指南\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e误区：冒烟测试 = 简单测试\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e纠正\u003c/strong\u003e：冒烟测试是策略性筛选，需基于业务风险设计，非随机选择用例。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e误区：仅用于开发阶段\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e纠正\u003c/strong\u003e：生产环境变更（如配置更新、热修复）后同样需要冒烟测试。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e陷阱：忽视环境差异性\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e规避\u003c/strong\u003e：确保测试数据与环境配置与生产环境一致（如使用影子表隔离测试数据）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"八进阶实践\"\u003e\u003cstrong\u003e八、进阶实践\u003c/strong\u003e\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e动态冒烟测试\u003c/strong\u003e：根据代码变更范围自动调整测试用例（如代码影响支付模块时，仅执行支付相关冒烟测试）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e熔断机制\u003c/strong\u003e：若连续3次构建冒烟测试失败，自动回滚至上一个稳定版本。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e通过以上框架，可系统化掌握冒烟测试的核心逻辑，并针对企业级场景设计高效验证方案。\u003c/p\u003e","title":"Smoke Test冒烟测试理解"},{"content":"一、核心概念与设计意义 用户态（User Mode） 定义：用户态是应用程序运行的受限环境，无法直接访问硬件或执行特权指令。操作系统通过限制其权限，确保应用程序只能通过系统调用（如文件操作、网络通信）请求内核服务。 特点： 权限受限：仅能访问用户空间内存，无法操作硬件设备（如直接读写磁盘）。 安全性：即使应用程序崩溃或被攻击，也不会破坏系统核心功能。 非特权指令：只能执行普通运算指令（如加减乘除），无法执行停机、修改内存映射等敏感操作。 内核态（Kernel Mode） 定义：操作系统内核运行的特权模式，拥有对硬件、内存和进程调度的完全控制权。 特点： 最高权限：可执行所有CPU指令（包括特权指令），直接访问所有内存地址和硬件资源。 稳定性保障：负责处理中断、异常和系统调用，确保多任务调度和资源分配的可靠性。 不可抢占性：内核态进程运行时不可被中断（除非更高优先级中断发生）。 区分意义 安全隔离：防止用户程序误操作导致系统崩溃（如非法内存访问）。 资源管理：通过权限分层实现硬件资源的统一调度和高效利用。 性能优化：内核态直接操作硬件，避免用户态频繁切换的开销。 二、切换机制与实现细节 切换触发条件 主动切换（系统调用） 流程：用户程序通过系统调用（如read()）请求内核服务 → CPU执行syscall指令 → 保存用户态寄存器（RIP、RSP等） → 跳转至内核入口函数。 示例：read()函数的执行涉及： 参数传递：文件描述符（rdi）、缓冲区地址（rsi）、数据长度（rdx）。 系统调用号：通过rax寄存器指定（如Linux x86-64中__NR_read=0）。 被动切换（中断与异常） 中断：外部设备触发（如时钟中断、磁盘I/O完成），CPU暂停当前任务处理中断。 异常：程序错误（如缺页异常、除零错误），CPU强制切换到内核态处理。 恢复流程：内核处理完成后，通过iret指令恢复用户态上下文。 上下文切换细节 栈切换：用户态使用用户栈，内核态使用内核栈。切换时保存用户栈指针（ss3/rsp3）到内核栈，并加载内核栈指针（ss0/rsp0）。 寄存器保存：CPU自动保存RIP（指令指针）、RFLAGS（状态标志）等寄存器到内核栈。 权限级别变更：x86架构中，用户态为Ring 3，内核态为Ring 0。 三、实际应用与性能影响 典型场景 系统调用：文件读写（read()/write()）、进程创建（fork()）、网络通信（send()/recv()）。 硬件交互：设备驱动通过内核态直接控制硬件（如网卡数据包收发）。 内存管理：内核态处理虚拟内存映射和缺页异常（如mmap()）。 性能优化考量 切换开销：一次系统调用需约数百纳秒，频繁切换会显著降低性能（可通过批处理系统调用减少次数）。 零拷贝技术：内核态直接传递数据（如sendfile()），避免用户态与内核态间的多次数据拷贝。 ","permalink":"https://blog.muwinds.com/archives/203/","summary":"\u003ch3 id=\"一核心概念与设计意义\"\u003e\u003cstrong\u003e一、核心概念与设计意义\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e用户态（User Mode）\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e定义\u003c/strong\u003e：用户态是应用程序运行的受限环境，无法直接访问硬件或执行特权指令。操作系统通过限制其权限，确保应用程序只能通过系统调用（如文件操作、网络通信）请求内核服务。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e特点\u003c/strong\u003e：\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e权限受限\u003c/strong\u003e：仅能访问用户空间内存，无法操作硬件设备（如直接读写磁盘）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e安全性\u003c/strong\u003e：即使应用程序崩溃或被攻击，也不会破坏系统核心功能。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e非特权指令\u003c/strong\u003e：只能执行普通运算指令（如加减乘除），无法执行停机、修改内存映射等敏感操作。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e内核态（Kernel Mode）\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e定义\u003c/strong\u003e：操作系统内核运行的特权模式，拥有对硬件、内存和进程调度的完全控制权。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e特点\u003c/strong\u003e：\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e最高权限\u003c/strong\u003e：可执行所有CPU指令（包括特权指令），直接访问所有内存地址和硬件资源。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e稳定性保障\u003c/strong\u003e：负责处理中断、异常和系统调用，确保多任务调度和资源分配的可靠性。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e不可抢占性\u003c/strong\u003e：内核态进程运行时不可被中断（除非更高优先级中断发生）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e区分意义\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e安全隔离\u003c/strong\u003e：防止用户程序误操作导致系统崩溃（如非法内存访问）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e资源管理\u003c/strong\u003e：通过权限分层实现硬件资源的统一调度和高效利用。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e性能优化\u003c/strong\u003e：内核态直接操作硬件，避免用户态频繁切换的开销。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"二切换机制与实现细节\"\u003e\u003cstrong\u003e二、切换机制与实现细节\u003c/strong\u003e\u003c/h3\u003e\n\u003ch4 id=\"切换触发条件\"\u003e\u003cstrong\u003e切换触发条件\u003c/strong\u003e\u003c/h4\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e主动切换（系统调用）\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e流程\u003c/strong\u003e：用户程序通过系统调用（如\u003ccode\u003eread()\u003c/code\u003e）请求内核服务 → CPU执行\u003ccode\u003esyscall\u003c/code\u003e指令 → 保存用户态寄存器（RIP、RSP等） → 跳转至内核入口函数。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e示例\u003c/strong\u003e：\u003ccode\u003eread()\u003c/code\u003e函数的执行涉及：\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e参数传递\u003c/strong\u003e：文件描述符（\u003ccode\u003erdi\u003c/code\u003e）、缓冲区地址（\u003ccode\u003ersi\u003c/code\u003e）、数据长度（\u003ccode\u003erdx\u003c/code\u003e）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e系统调用号\u003c/strong\u003e：通过\u003ccode\u003erax\u003c/code\u003e寄存器指定（如Linux x86-64中\u003ccode\u003e__NR_read=0\u003c/code\u003e）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e被动切换（中断与异常）\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e中断\u003c/strong\u003e：外部设备触发（如时钟中断、磁盘I/O完成），CPU暂停当前任务处理中断。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e异常\u003c/strong\u003e：程序错误（如缺页异常、除零错误），CPU强制切换到内核态处理。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e恢复流程\u003c/strong\u003e：内核处理完成后，通过\u003ccode\u003eiret\u003c/code\u003e指令恢复用户态上下文。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch4 id=\"上下文切换细节\"\u003e\u003cstrong\u003e上下文切换细节\u003c/strong\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e栈切换\u003c/strong\u003e：用户态使用用户栈，内核态使用内核栈。切换时保存用户栈指针（\u003ccode\u003ess3\u003c/code\u003e/\u003ccode\u003ersp3\u003c/code\u003e）到内核栈，并加载内核栈指针（\u003ccode\u003ess0\u003c/code\u003e/\u003ccode\u003ersp0\u003c/code\u003e）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e寄存器保存\u003c/strong\u003e：CPU自动保存\u003ccode\u003eRIP\u003c/code\u003e（指令指针）、\u003ccode\u003eRFLAGS\u003c/code\u003e（状态标志）等寄存器到内核栈。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e权限级别变更\u003c/strong\u003e：x86架构中，用户态为Ring 3，内核态为Ring 0。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"三实际应用与性能影响\"\u003e\u003cstrong\u003e三、实际应用与性能影响\u003c/strong\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e典型场景\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e系统调用\u003c/strong\u003e：文件读写（\u003ccode\u003eread()\u003c/code\u003e/\u003ccode\u003ewrite()\u003c/code\u003e）、进程创建（\u003ccode\u003efork()\u003c/code\u003e）、网络通信（\u003ccode\u003esend()\u003c/code\u003e/\u003ccode\u003erecv()\u003c/code\u003e）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e硬件交互\u003c/strong\u003e：设备驱动通过内核态直接控制硬件（如网卡数据包收发）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e内存管理\u003c/strong\u003e：内核态处理虚拟内存映射和缺页异常（如\u003ccode\u003emmap()\u003c/code\u003e）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e性能优化考量\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e切换开销\u003c/strong\u003e：一次系统调用需约数百纳秒，频繁切换会显著降低性能（可通过批处理系统调用减少次数）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e零拷贝技术\u003c/strong\u003e：内核态直接传递数据（如\u003ccode\u003esendfile()\u003c/code\u003e），避免用户态与内核态间的多次数据拷贝。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e","title":"操作系统用户态和内核态"},{"content":"What are hypervisors? A hypervisor is a software that enables multiple virtual machines (VMs)—each with its own operating system (OS)—to run on one physical server. The hypervisor pools and allocates physical computing resources as needed by the VM, enabling efficiency, flexibility and scalability\nSometimes called a virtual machine monitor (VMM), the hypervisor manages VMs as they run alongside each other. It separates VMs from each other logically, assigning each its own slice of the underlying computing power, memory and storage. This prevents the VMs from interfering with each other. For example, if one OS suffers a crash or a security compromise, the others survive.\nBefore hypervisors hit the mainstream, most physical computers could only run one operating system at a time. This made them stable because the computing hardware only had to handle requests from that one OS. The downside of this approach was that it wasted resources because the operating system can only sometimes use all of the computer\u0026rsquo;s power.\nA hypervisor solves that problem. It is a small software layer that enables multiple instances of operating systems to run alongside each other, sharing the same physical computing resources. This process is called virtualization, and the operating system instances—referred to as virtual machines—are software emulations of physical computers that run on the host machine.\nThe term virtual machine is sometimes used interchangeably with virtual server, but they are not the same. Virtual servers are also made possible by hypervisors that act as a layer between the physical hardware and multiple unique virtual environments. But virtual servers replicate physical, bare-metal servers for applications like web servers, domain name servers, proxy servers and so forth. In contrast, virtual machines create virtual representations of physical computers.\nHypervisor benefits The following are some of the core benefits that a hypervisor can provide:\nEfficiency: Hypervisors help ensure maximum resource efficiency by allowing multiple VMs to use the same physical hardware, thus optimizing the usage of hardware resources. Scalability: Hypervisors can create, deploy and decommission VMs almost instantly. This capability allows organizations to quickly respond to changing workloads and meet business demands. Cost savings: Hypervisors allow multiple VMs to run on one physical machine. This consolidation saves costs by reducing hardware expenses. It also reduces the amount of power consumed, which cuts energy costs and helps reduce the overall carbon footprint associated with data centers. Portability: Hypervisors separate virtual machines from physical machines, making it easy to migrate VMs. This feature creates portability and allows IT teams to shift workloads and allocate resources between machines or platforms. Enhanced security: Hypervisors isolate VMs from each other and from the host machine, thus reducing the attack surface. Hypervisors can also take snapshots of a VM\u0026rsquo;s state, which allows users to restore the VM, create a backup or move it to a different host. Disaster recovery: In the event of a disaster, hypervisorshelp organizations avoid disruptions by enabling fast replication or cloning of a virtual machine. This disaster recovery support significantly enhances the resiliency of the environment and improves business continuity. How hypervisors work: Type 1 vs. type 2 As explained, hypervisors provide a layer between a VM and the underlying physical hardware, helping ensure that each has access to the physical resources it needs to execute.\nThe process starts with the host machine executing a hypervisor and installing it as an application on the machine, where it interacts with the host machine\u0026rsquo;s operating system. The hypervisor then loads the client operating systems of the virtual machines, creating isolated virtual environments on the host machines.\nThe hypervisor acts as the intermediary between the VMs and the physical hardware, allocating resources such as memory, bandwidth and storage for each VM. The physical hardware still handles the execution of resources. For example, the central processing unit (CPU) is still executing CPU instructions as requested by the VMs while the hypervisor manages the schedule.\nBoth physical machines and VMs typically communicate with the hypervisor through application programming interface (API) calls, protocols that enable software applications to communicate with each other to exchange data.\nThere are two main types of hypervisors: type 1 and type 2.\nType 1 hypervisor A type 1 hypervisor runs right on the underlying computer\u0026rsquo;s physical hardware, interacting directly with its central processing unit (CPU), memory and physical storage. For this reason, people also refer to type 1 hypervisors as bare-metal hypervisors or native hypervisors. A type 1 hypervisor takes the place of the host operating system.\nType 1 hypervisors are highly efficient because they directly access physical hardware. This capability also increases their security because there is nothing in between them and the CPU that an attacker might compromise. However, a type 1 hypervisor often requires a separate management machine to administer different VMs and control the host hardware.\nType 2 hypervisor A type 2 hypervisor—also referred to as an embedded or hosted hypervisor—doesn\u0026rsquo;t run directly on the underlying hardware. Instead, it runs as an application in an OS. Type 2 hypervisors rarely show up in server-based environments. Rather, they are suitable for individual PC users needing to run different operating systems. Examples include engineers, security professionals analyzing malware and business users who need access to applications only available on other software platforms.\nType 2 hypervisors often feature additional toolkits for users to install into the guest OS. These tools provide enhanced connections between the guest and the host OS, usually enabling the user to cut and paste between the two or access host OS files and folders from within the guest VM.\nA type 2 hypervisor enables quick and easy access to an alternative guest OS alongside the primary one running on the host system—this capability supports end-user productivity. A consumer might use it to access their favorite Linux®-based development tools while using a speech dictation system only found in Windows, for example.\nHowever, because a type 2 hypervisor must access computing, memory and network resources via the host OS, it introduces latency issues that can affect performance. It also introduces potential security risks if an attacker compromises the host OS because they could manipulate any guest OS running in the type 2 hypervisor.\nHypervisors and virtual desktop integration (VDI) Type 1 hypervisors can virtualize more than just server operating systems. They can also virtualize desktop operating systems for companies that want to centrally manage their end-user IT resources. Known as desktop as a service (DaaS), this technology delivers complete desktop virtualization environments, including operating systems, applications, files and user preferences from the cloud.\nVirtual desktop integration (VDI) lets users work on desktops running inside virtual machines on a central server, making it easier for IT staff to administer and maintain their OSs.\nIn this environment, a hypervisor will run multiple virtual desktops. Each desktop sits in its own VM, held in collections known as virtual desktop pools. Each VM serves a single user who accesses it over the network.\nThe user\u0026rsquo;s endpoint can be a relatively inexpensive thin client or a mobile device. This gives them the advantage of consistent access to the same desktop OS. They can get the same data and applications on any device without moving sensitive data outside a secure environment.\nUsers don\u0026rsquo;t connect to the hypervisor directly. Instead, they access a connection broker that coordinates with the hypervisor to source an appropriate virtual desktop from the pool.\nHypervisors in the marketplace Today, many hypervisors exist in the marketplace. Here are some top vendor-owned solutions.\nVMware ESXi VMware ESXi (Elastic Sky X Integrated) is a type 1 (or bare-metal) hypervisor targeting server virtualization in the data center. ESXi manages collections of VMware virtual machines.\nNote: Broadcom acquired VMware in 2023 and no longer offers its free version of VMware ESXi (formerly part of its free VMware vSphere virtual server virtualization offering). Since the acquisition, Broadcom has transitioned VMware away from perpetual licenses and support and subscription (SNS) renewals to a subscription-based pricing model.^{2} VMware vSphere has been renamed VMware vSphere Foundation (VVF), an enterprise virtualization platform, available as a paid subscription software offering.\nVMware Workstation Pro This hypervisor is compatible with desktop and laptop PCs running Windows and Linux operating systems.\nVMware Fusion Pro Also for desktop and laptop users, this hypervisor is the company\u0026rsquo;s MacOS-focused offering, which lets Mac users run a large range of guest operating systems. The VMware Fusion Pro isfree for personal use and paid for commercial use.\nNote: VMware discontinued Workstation Player and VMware Fusion Player since starting VMware Workstation Pro and Fusion Pro.\nOracle VM VirtualBox VirtualBox is a type 2 hypervisor running on Linux, Mac OS and Windows operating systems.\nNote: Oracle inherited this product when it bought Sun Microsystems in 2010.\nParallels Desktop Parallels Desktop is a hypervisor technology that allows users to run operating systems (like Linux or Windows) and other apps on a Mac.\nMicrosoft Hyper-V Hyper-V is Microsoft\u0026rsquo;s hypervisor designed for use on Windows systems. It shipped in 2008 as part of Windows Server, meaning that customers needed to install the entire Windows operating system to use it. Microsoft subsequently made a dedicated version called Hyper-V Server available, which ran on Windows ServerCore. This enabled administrators to run Hyper-V without installing the full version of Windows Server. Hyper-V is also available on Windows clients.\nMicrosoft designates Hyper-V as a type 1 hypervisor, even though it runs differently from many competitors. Hyper-V installs on Windows but runs directly on the physical hardware, inserting itself underneath the host OS. All guest operating systems run through the hypervisor, but the host system gets special hardware access, giving it a performance advantage.\nCitrix Hypervisor Citrix Hypervisor (formerly Xen Server of the Xen open-source project) is a commercial type 1 hypervisor that supports Linux and Windows operating systems.\nOpen-source hypervisors Open-source hypervisor technologies offer cost-effectiveness, customization options and strong community support. Popular open-source hypervisors include the following.\nXen hypervisor This open-source type 1 hypervisor runs on Intel and ARM architectures. It began as a project at the University of Cambridge, and its team subsequently commercialized it by founding XenSource, which Citrix bought in 2007.^{4}\nIn 2013, the open-source project became a collaborative project under the Linux Foundation. Many cloud service providers use Xen to power their product offerings.\nXen supports several types of virtualization, including hardware-assisted environments using Intel VT and AMD-V. It also supports paravirtualization, which tweaks the guest OS to work with a hypervisor, delivering performance gains.\nLinux KVM (Kernel-based Virtual Machine) KVM is a Linux-based type-1 hypervisor that can be added to most Linux operating systems, including Ubuntu, SUSE and Red Hat Enterprise Linux (RHEL).\nLinux also has hypervisor capabilities built directly into the Linux kernel. The kernel-based virtual machine (KVM) became part of the Linux kernel mainline in 2007 and complements QEMU, a hypervisor emulating the physical machine\u0026rsquo;s processor entirely in software. It supports most common Linux operating systems, including Solaris and Windows.\nKVM also supports virtualization extensions that Intel and AMD built into their processor architectures to better support hypervisors. These extensions, called Intel VT and AMD-V respectively, enable the processor to help the hypervisor manage multiple virtual machines. Where these extensions are available, the Linux kernel can use KVM. Otherwise, it falls back to QEMU.\nKVM is also downloadable on its own or as part of the free oVirt open-source virtualization solution.\nRed Hat OpenShift Virtualization Red Hat® OpenShift® Virtualization is based on KubeVirt, an open-source project that makes it possible to run VMs on a Kubernetes-managed container platform. KubeVirt delivers container-native virtualization by using a KVM within a Kubernetes container.\nOpenShift Virtualization builds extra facilities around the KVM hypervisor. These features include a virtualization manager that provides a centralized management system with a search-driven graphical user interface. The solution also includes secure virtualization technologies that harden the hypervisor against attacks aimed at the host or virtual machines. Red Hat\u0026rsquo;s hypervisor can run many operating systems, including Ubuntu.\nNote: Red Hat OpenShift Virtualization is the successor to Red Hat Enterprise Virtualization (RHV), which Red Hat announced it will end support for in 2026.^{5}\nChoosing the right hypervisor As explained, there are different categories of hypervisors and multiple brands of hypervisors within each category. If you’re looking to choose a hypervisor for your organization or for personal use, here are some factors that should guide your choice:\nPerformance Ecosystem Management tools Live migration Cost Performance Look for benchmark data that show how well the hypervisor performs in a production environment. Ideally, bare-metal hypervisors should support guest OS performance close to native speeds.\nEcosystem You will need good documentation and technical support to implement and manage hypervisors across multiple physical servers at scale. Also, look for a healthy community of third-party developers that can support the hypervisor with their own agents and plugins that offer capabilities, such as backup and restore capacity analysis and fail-over management.\nManagement tools Running VMs is one of many things you must manage when using a hypervisor. You must provision the VMs, maintain them, audit them and clean up disused ones to prevent \u0026ldquo;VM sprawl.\u0026rdquo; Ensure the vendor or third-party community supports the hypervisor architecture with comprehensive management tools.\nLive migration Live migration enables you to move VMs between hypervisors on different physical machines without stopping them, which can be useful for both fail-over and workload balancing.\nCost Consider the cost and fee structure involved in licensing hypervisor technology. Don\u0026rsquo;t just think about the cost of the hypervisor itself. The management software that makes it scalable to support an enterprise environment can often be expensive. Lastly, examine the vendor\u0026rsquo;s licensing structure, which may change depending on whether you deploy it in the cloud or locally.\n转载：https://www.ibm.com/think/topics/hypervisors\n","permalink":"https://blog.muwinds.com/archives/202/","summary":"\u003ch2 id=\"what-are-hypervisors\"\u003eWhat are hypervisors?\u003c/h2\u003e\n\u003cp\u003eA hypervisor is a software that enables multiple \u003ca href=\"https://www.ibm.com/think/topics/virtual-machines\"\u003evirtual machines (VMs)\u003c/a\u003e—each with its own operating system (OS)—to run on one physical server. The hypervisor pools and allocates physical computing resources as needed by the VM, enabling efficiency, flexibility and scalability\u003c/p\u003e\n\u003cp\u003eSometimes called a virtual machine monitor (VMM), the hypervisor manages VMs as they run alongside each other. It separates VMs from each other logically, assigning each its own slice of the underlying computing power, memory and storage. This prevents the VMs from interfering with each other. For example, if one OS suffers a crash or a security compromise, the others survive.\u003c/p\u003e","title":"What is hypervisor?"},{"content":"C/C++程序的编译过程分为四个关键阶段：预处理（Preprocessing）、编译（Compilation）、汇编（Assembly）和链接（Linking）。每个阶段承担不同任务，最终将源代码转换为可执行文件。\n一、预处理阶段（Preprocessing） 预处理是编译的第一步，主要处理源代码中以 # 开头的指令，生成经过处理的中间代码（.i 文件）。核心功能包括：\n宏展开\n预处理器将 #define 定义的宏替换为具体值或表达式。例如，#define PI 3.14 会在所有出现 PI 的地方替换为数值。 头文件包含\n#include 指令将头文件内容插入源文件。系统头文件（如 \u0026lt;stdio.h\u0026gt;）和用户头文件（如 \u0026quot;myheader.h\u0026quot;）的搜索路径不同。 条件编译\n通过 #ifdef、#ifndef 等指令，根据条件选择性地包含或排除代码块，常用于跨平台适配或功能开关。 清除注释与特殊符号处理\n删除注释，并处理 __LINE__、__FILE__ 等预定义宏，用于调试信息记录。 示例命令：\ngcc -E main.c -o main.i # 生成预处理文件 预处理器的核心功能 宏定义与替换 简单宏：通过#define定义常量或文本替换，例如#define PI 3.14159，预处理器将所有PI替换为数值。 带参宏：支持参数化替换，如#define MAX(a,b) ((a)\u0026gt;(b)?(a):(b))，需注意参数需用括号包裹以避免运算优先级错误。 取消宏：通过#undef可撤销已定义的宏。 文件包含 #include指令用于插入头文件内容到当前文件。 尖括号\u0026lt; \u0026gt;优先搜索系统路径（如标准库头文件）。 双引号\u0026quot; \u0026quot;优先搜索用户目录（如自定义头文件）。 头文件常通过#ifndef和#define防止重复包含（如#pragma once）。 条件编译 根据环境或配置选择编译代码块：\n#ifdef DEBUG // 调试模式下的代码\n#elif RELEASE // 发布模式代码 #endif 常用于跨平台适配（如区分Windows/Linux）或功能开关。 特殊指令与符号 #error：触发编译错误并显示消息，用于强制检查条件。 #pragma：编译器特定功能（如内存对齐优化）。 预定义宏：如__FILE__（当前文件名）、__LINE__（行号）、__DATE__（编译日期）等，用于调试和日志。 预处理与编译流程的关系 编译阶段划分\nC/C++编译分为四阶段： 预处理 → 编译优化 → 汇编 → 链接。\n预处理独立于后续阶段，仅处理文本替换和指令，不涉及语法分析。 预处理与预编译的区别 预处理：基于文本替换，是语言标准的一部分。 预编译：编译器优化技术（如预编译头文件PCH），非标准强制要求。 预处理的实际应用与注意事项 宏的潜在问题 副作用：宏替换可能导致多次表达式求值。例如：\n#define SQUARE(x) x*x\nint a = 2;\nSQUARE(a++); // 展开为a++*a++，结果不可预期\n应改用内联函数或完善括号。 头文件设计规范 自包含性：头文件应包含其依赖的其他头文件，避免调用方遗漏。 最小化依赖：仅包含必要内容，减少编译时间。 条件编译的典型场景 调试日志：通过DEBUG宏控制日志输出。 平台适配：使用_WIN32、__linux__等预定义宏编写跨平台代码。 替代宏的现代特性 常量定义：优先使用const或constexpr替代#define。 类型安全：用模板或内联函数替代带参宏，避免类型错误。 预处理指令示例 // 头文件保护 #ifndef MY_HEADER_H #define MY_HEADER_H #include \u0026lt;vector\u0026gt; // 函数声明 void process_data(); #endif // 条件编译调试信息 #if defined(DEBUG) \u0026amp;\u0026amp; !defined(NDEBUG) #define LOG(msg) std::cerr \u0026lt;\u0026lt; __FILE__ \u0026lt;\u0026lt; \u0026#34;:\u0026#34; \u0026lt;\u0026lt; __LINE__ \u0026lt;\u0026lt; \u0026#34; - \u0026#34; \u0026lt;\u0026lt; msg #else #define LOG(msg) #endif // 跨平台代码 #ifdef _WIN32 #define OS_NAME \u0026#34;Windows\u0026#34; #elif __APPLE__ #define OS_NAME \u0026#34;macOS\u0026#34; #endif 预处理是C/C++编译流程中灵活性最高的阶段，合理使用可提升代码可维护性和跨平台能力，但需警惕宏的滥用导致的维护困难。现代C++推荐通过类型安全特性（如constexpr、模板）替代传统宏，仅在必要场景（如条件编译、头文件保护）保留预处理指令。\n二、编译阶段（Compilation） 编译阶段将预处理后的代码转换为汇编代码（.s 文件），并进行语法分析和优化：\n词法与语法分析\n将代码分解为词法单元（如变量名、运算符），并构建抽象语法树（AST）。 语义分析与中间代码生成\n检查类型匹配等语义规则，并生成中间表示（如三地址码）。 代码优化\n通过优化选项（如 -O2）执行常量折叠、循环展开、函数内联等优化，提升执行效率。 生成汇编代码\n将中间代码转换为目标平台的汇编指令（如 x86 或 ARM 指令）。 示例命令：\ngcc -S main.i -o main.s # 生成汇编文件 三、汇编阶段（Assembly） 汇编器将汇编代码转换为机器码，生成目标文件（.o 或 .obj 文件）：\n逐行翻译\n每条汇编指令对应一条机器指令，生成二进制代码。 符号表生成\n记录函数和变量的地址引用（如 extern 声明的符号），但此时地址尚未最终确定。 段划分\n代码段（.text）、初始化数据段（.data）、未初始化数据段（.bss）等被分离存储。 示例命令：\ngcc -c main.s -o main.o # 生成目标文件 四、链接阶段（Linking） 链接器合并多个目标文件和库，解决符号引用，生成可执行文件：\n符号解析\n查找所有未定义符号（如函数和全局变量）的实际地址，确保跨文件的引用正确。 地址重定位\n根据程序的内存布局调整代码和数据的地址偏移量。 静态链接与动态链接 静态链接：将库代码直接嵌入可执行文件（.a 文件），生成独立但体积较大的程序。 动态链接：运行时加载共享库（.so 或 .dll），节省内存但依赖外部环境。 示例命令：\ngcc main.o -o app # 静态链接 gcc main.o -o app -lmylib # 动态链接 四阶段的关系与工具链 阶段独立性\n每个阶段可单独执行，例如通过 -E、-S、-c 选项分步生成中间文件。 工具链协作\n预处理由预处理器（如 cpp）完成，编译由编译器（如 gcc）完成，汇编由汇编器（如 as）完成，链接由链接器（如 ld）完成。 跨平台兼容性\n汇编阶段生成的机器码与目标平台指令集相关，链接阶段需适配不同操作系统的库格式。 实现跨平台的通用策略 代码可移植性设计 抽象层：通过硬件抽象层（HAL）或跨平台框架（如Java、Electron）隐藏平台差异； 条件编译：使用预处理器指令（如#ifdef _WIN32）为不同平台生成代码分支。 构建工具链适配 交叉编译：使用工具链（如GCC的-march选项）为目标平台生成二进制文件； 容器化：通过Docker封装程序与依赖环境，实现跨平台部署。 混合链接策略\n结合静态与动态链接的优势： 核心模块静态链接以减少依赖； 非核心功能动态链接以便更新（如插件机制）。 ","permalink":"https://blog.muwinds.com/archives/199/","summary":"\u003cp\u003eC/C++程序的编译过程分为四个关键阶段：\u003cstrong\u003e预处理（Preprocessing）、编译（Compilation）、汇编（Assembly）和链接（Linking）\u003c/strong\u003e。每个阶段承担不同任务，最终将源代码转换为可执行文件。\u003c/p\u003e","title":"C/C++编译的四个阶段"},{"content":"A.原子性 **即一个操作或者多个操作，要么全部执行并且执行的过程不会被任何因素打断，要么就都不执行。**原子性确保对共享资源的操作不会被其他线程干扰，从而避免数据不一致性（如竞态条件）\n本质：原子性依赖于硬件和编译器的支持，例如CPU的原子指令（如x86的LOCK前缀）或编译器生成的原子操作代码，确保操作在底层不可分割。\n一个很经典的例子就是银行账户转账问题：\n比如从账户A向账户B转1000元，那么必然包括2个操作：从账户A减去1000元，往账户B加上1000元。试想一下，如果这2个操作不具备原子性，会造成什么样的后果。假如从账户A减去1000元之后，操作突然中止。然后又从B取出了500元，取出500元之后，再执行往账户B加上1000元 的操作。这样就会导致账户A虽然减去了1000元，但是账户B没有收到这个转过来的1000元。\n所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。\n原子性在多线程中的作用： 1.解决竞态条件\n典型场景如共享计数器的自增操作i++，实际包含读取→修改→写入三步。若多个线程同时执行，可能导致最终结果小于预期。\n// 非原子操作：线程不安全 int counter = 0; counter++; // 可能被其他线程打断 // 原子操作：线程安全 atomic_int atomic_counter = 0; atomic_fetch_add(\u0026amp;atomic_counter, 1); // C11原子函数 在头文件\u0026lt;stdatomic.h\u0026gt;中定义 C atomic_fetch_add（volatile A * obj，M arg）; Atomically replaces the value pointed by obj with the result of addition of arg to the old value of obj, and returns the value obj held previously. The operation is read-modify-write operation. The version orders memory accesses according to memory_order_seq_cst. （自C11以来） C atomic_fetch_add_explicit（volatile A * obj，M arg，memory_order order）; This version orders memory accesses according to order. （自C11以来） 2.替代锁机制提升性能\n原子操作通过硬件直接支持，无需锁的获取与释放，减少线程阻塞和上下文切换开销，适用于高并发场景。\n// 使用互斥锁（低效） pthread_mutex_lock(\u0026amp;lock); counter++; pthread_mutex_unlock(\u0026amp;lock); // 使用原子操作（高效） atomic_fetch_add(\u0026amp;atomic_counter, 1); 原子性的实现原理 1.硬件支持\n原子指令：如x86的LOCK前缀指令，锁定总线或缓存行，阻止其他核或线程访问同一内存地址。\n内存屏障（Memory Barrier）：防止编译器和CPU对指令重排序，确保操作顺序符合预期。\n2.编译器与标准库支持\nC11原子类型：如atomic_int、atomic_bool，通过\u0026lt;stdatomic.h\u0026gt;提供原子操作函数（如atomic_load、atomic_store）。\n原子函数实现：如atomic_fetch_add内部调用原子指令，保证操作的不可分割性。\n原子操作的应用场景： 1.简单计数器\n多线程环境下统计任务完成数，使用原子自增操作替代锁，提升性能。\natomic_int task_count = 0; void worker_thread() { for (int i = 0; i \u0026lt; 1000; i++) { atomic_fetch_add(\u0026amp;task_count, 1); } } 2.标志位控制\n如线程间通信的状态标志，需原子读写以避免脏读。\natomic_bool is_ready = false; // 线程A设置标志 atomic_store(\u0026amp;is_ready, true); // 线程B读取标志 if (atomic_load(\u0026amp;is_ready)) {/* ... */} 3.无锁数据结构\n如无锁队列的实现依赖原子比较交换（CAS）操作。\natomic_int* ptr = /* 共享指针 */; int old_val = atomic_load(ptr); do { int new_val = compute_new_val(old_val); } while (!atomic_compare_exchange_strong(ptr, \u0026amp;old_val, new_val)); // CAS操作 注意事项与局限性： 1.类型限制\n原子操作仅支持基本数据类型（如int、bool），复杂结构需自行封装或使用锁。\n2.操作复杂性\n复合操作（如多变量更新）仍需锁机制，原子操作无法直接保证整体原子性。\n3.内存顺序（Memory Order）\nC11允许指定内存顺序（如memory_order_relaxed、memory_order_seq_cst），需根据场景平衡性能与正确性。\natomic_int a = 0; atomic_store_explicit(\u0026amp;a, 42, memory_order_release); \u0026lt;em\u0026gt;// 指定内存顺序 4.平台差异性\n不同CPU架构对原子指令的支持不同，需考虑可移植性。\n与锁机制的对比： 特性 原子操作 锁机制 性能 高（无阻塞） 低（上下文切换开销） 适用场景 简单变量操作（如计数、标志位） 复杂逻辑或数据结构 实现复杂度 低（直接调用原子函数） 高（需管理锁的获取与释放） 扩展性 有限（仅支持基本类型） 灵活（可保护任意代码块） 要想在多线程环境下保证原子性，则可以通过锁、synchronized来确保。volatile是无法保证复合操作的原子性。\nB.可见性 可见性问题（Visibility Problem） 是并发编程中的核心挑战之一，指当一个线程修改了共享变量的值后，其他线程无法立即感知到这一变化，导致程序行为不符合预期。这种现象的本质是数据的不一致性，通常由硬件架构（如CPU缓存）和编译器/运行时优化引起。\n可见性问题的根源： 1. CPU缓存架构 现代CPU通过多级缓存（L1/L2/L3）加速数据访问，但每个核心的缓存是独立的。例如：\n线程A在CPU核心1运行，修改了变量x，写入核心1的缓存。 线程B在CPU核心2运行，读取的x可能仍是核心2缓存中的旧值。\n此时，线程B无法“看到”线程A的修改，导致数据不一致。 2. 编译器优化 编译器为提高性能，可能将变量缓存在寄存器中，而非每次从内存读取。例如：\n// 线程A循环检查标志位 while (flag == 0) {/* 等待 */} 若flag未被声明为volatile，编译器可能优化为：\n// 错误优化：将flag缓存在寄存器，导致死循环 register int cached_flag = flag; while (cached_flag == 0) {} 将flag缓存在寄存器导致数据不可见，此时，即使内存中的flag被其他线程或外部事件（如中断）修改，cached_flag仍会保持初始值。循环条件while (cached_flag == 0)会基于寄存器中的旧值判断，导致无法感知实际变化，从而陷入死循环。\n3. 指令重排序 CPU或编译器可能对指令重新排序以提高效率，但这种优化可能破坏程序逻辑。例如：\n// 初始代码 x = 1;ready = 1; // 重排序后的实际执行顺序（假设x和ready无依赖） ready = 1;x = 1; 若其他线程看到ready=1后立即读取x，可能读到未初始化的x=0。\n可见性问题的表现场景： 1. 多线程共享变量 int counter = 0; // 共享变量 // 线程A void thread_A() { counter = 42; // 修改counter } // 线程B void thread_B() { printf(\u0026#34;%d\u0026#34;, counter); // 可能输出0（旧值） } 问题：线程B可能因缓存或优化，读取到counter的旧值。\n2. 硬件/中断场景\nint sensor_value; // 传感器数据，由中断更新 // 中断服务程序（ISR） void ISR() { sensor_value = read_sensor(); // 更新值 } // 主程序循环读取 while (1) { if (sensor_value \u0026gt; 100) { // 可能读取旧值 trigger_alarm(); } } 问题：编译器可能将sensor_value缓存在寄存器，导致主程序无法感知中断中的更新。\n三、解决可见性问题的方法 1. 使用volatile关键字 强制每次访问变量时都从内存读取或写入，禁用寄存器缓存。\nvolatile int sensor_value; // 确保主程序读取最新值 2. 内存屏障（Memory Barrier） 通过特殊指令（如mfence）或原子操作，确保指令顺序和缓存一致性。\n// 写操作后插入内存屏障 x = 1; __sync_synchronize(); // GCC内置内存屏障 ready = 1; 3. 锁（Lock）和原子变量 锁（Mutex）：通过锁的获取和释放隐式插入内存屏障。 pthread_mutex_lock(\u0026amp;lock); counter++; // 临界区操作 pthread_mutex_unlock(\u0026amp;lock); 原子变量（C11 _Atomic）：\n#include \u0026lt;stdatomic.h\u0026gt; _Atomic int counter = 0; atomic_store(\u0026amp;counter, 42); // 写操作保证可见性 关键注意事项: 1.volatile ≠ 线程安全\nvolatile仅解决可见性，不保证原子性。例如：\nvolatile int counter = 0; counter++; // 非原子操作！可能被多个线程同时打断 此时仍需使用锁或原子操作。\n2.不同语言/平台的差异\nJava的volatile还禁止指令重排序（通过内存屏障），而C/C++的volatile不提供此保证。 C11/C++11的原子变量（_Atomic、std::atomic）是更现代的解决方案。 C.有序性 即程序执行的顺序按照代码的先后顺序执行。\n举个简单的例子，看下面这段代码：\nint i = 0; boolean flag = false; i = 1; //语句1 flag = true; //语句2 上面代码定义了一个int型变量，定义了一个boolean类型变量，然后分别对两个变量进行赋值操作。从代码顺序上看，语句1是在语句2前面的，那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗？不一定，为什么呢？这里可能会发生指令重排序（Instruction Reorder）。\n下面解释一下什么是指令重排序，一般来说，处理器为了提高程序运行效率，可能会对输入代码进行优化，它不保证程序中各个语句的执行先后顺序同代码中的顺序一致，但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。\n比如上面的代码中，语句1和语句2谁先执行对最终的程序结果并没有影响，那么就有可能在执行过程中，语句2先执行而语句1后执行。但是要注意，虽然处理器会对指令进行重排序，但是它会保证程序最终结果会和代码顺序执行结果相同，那么它靠什么保证的呢？再看下面一个例子：\nint a = 10; //语句1 int r = 2; //语句2 a = a + 3; //语句3 r = a*a; //语句4 这段代码有4个语句，那么可能的一个执行顺序是：\n语句2 -\u0026gt; 语句1 -\u0026gt; 语句3 -\u0026gt; 语句4\n那么可不可能是这个执行顺序：\n语句2 -\u0026gt; 语句1 -\u0026gt; 语句4 -\u0026gt; 语句3。\n不可能，因为处理器在进行重排序时是会考虑指令之间的数据依赖性，如果一个指令Instruction 2必须用到Instruction 1的结果，那么处理器会保证Instruction 1会在Instruction 2之前执行。虽然重排序不会影响单个线程内程序执行的结果，但是多线程呢？下面看一个例子：\n//线程1: context = loadContext(); //语句1 inited = true; //语句2 //线程2: while(!inited ){ sleep() } doSomethingwithconfig(context); 上面代码中，由于语句1和语句2没有数据依赖性，因此可能会被重排序。假如发生了重排序，在线程1执行过程中先执行语句2，而此是线程2会以为初始化工作已经完成，那么就会跳出while循环，去执行doSomethingwithconfig(context)方法，而此时context并没有被初始化，就会导致程序出错。\n从上面可以看出，指令重排序不会影响单个线程的执行，但是会影响到线程并发执行的正确性。也就是说，要想并发程序正确地执行，必须要保证原子性、可见性以及有序性。只要有一个没有被保证，就有可能会导致程序运行不正确。\n在Java内存模型中，允许编译器和处理器对指令进行重排序，但是重排序过程不会影响到单线程程序的执行，却会影响到多线程并发执行的正确性。\n在Java里面，可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性，很显然，synchronized和Lock保证每个时刻是有一个线程执行同步代码，相当于是让线程顺序执行同步代码，自然就保证了有序性。另外，Java内存模型具备一些先天的“有序性”，即不需要通过任何手段就能够得到保证的有序性，这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来，那么它们就不能保证它们的有序性，虚拟机可以随意地对它们进行重排序。\n","permalink":"https://blog.muwinds.com/archives/190/","summary":"\u003ch2 id=\"a原子性\"\u003eA.原子性\u003c/h2\u003e\n\u003cp\u003e**即一个操作或者多个操作，要么全部执行并且执行的过程不会被任何因素打断，要么就都不执行。**原子性确保对共享资源的操作不会被其他线程干扰，从而避免数据不一致性（如竞态条件）\u003c/p\u003e","title":"并发编程三大特性——原子性、可见性、有序性"},{"content":"双指针顾名思义，就是同时使用两个指针，在序列、链表结构上指向的是位置，在树、图结构中指向的是节点，通过或同向移动，或相向移动来维护、统计信息。\n713. 乘积小于 K 的子数组\n给你一个整数数组 nums 和一个整数 k ，请你返回子数组内所有元素的乘积严格小于k 的连续子数组的数目。\n示例 1：\n输入：nums = [10,5,2,6], k = 100 输出：8 解释：8 个乘积小于 100 的子数组分别为：[10]、[5]、[2]、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。 需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。 示例 2：\n输入：nums = [1,2,3], k = 0 输出：0 提示:\n1 \u0026lt;= nums.length \u0026lt;= 3 * 10^{4} 1 \u0026lt;= nums[i] \u0026lt;= 1000 0 \u0026lt;= k \u0026lt;= 10^{6} class Solution { public: int numSubarrayProductLessThanK(vector\u0026lt;int\u0026gt; \u0026amp;nums, int k) { long long tmp = 1ll; int ans = 0; // 开一个l和r的双指针 for (int l = 0, r = 0; r \u0026lt; nums.size(); r++) { // 每次r指针往右移动的时候，tmp都乘以nums[r] tmp *= nums[r]; while (l \u0026lt;= r \u0026amp;\u0026amp; tmp \u0026gt;= k) // 如果tmp大于或者等于k了，就要把l指针往右移动，缩小tmp，所以移动l的时候要除以nums[l] tmp /= nums[l++]; // 每次将结果加到答案里面 ans += r - l + 1; } return ans; } }; 设两个指针分别为l和r，另外设置一个变量tmp记录[l,r]内所有数的乘积。最开始l,r都是都在最左面，先向右移动，直到第一次发现 tmp\u0026gt;=k，这时就固定r，右移l，直到tmp\u0026lt;k。那么对于每个r，l是它能延展到的左边界，由于正整数乘积的单调性，此时以r为右端点的满足题目条件的区间个数为 r-l+1个。\n对于当前窗口 [l, r]，以 r 结尾的所有子数组的起始点可以是 l, l+1, ..., r，共 r - l + 1 个。例如窗口 [5, 2, 6] 时，以 6 结尾的子数组有 [5,2,6]、[2,6]、[6]，共 3 个，即 r-l+1 = 3。\n接下来看一个子序列匹配应用的题：\n524. Longest Word in Dictionary through Deleting\nGiven a string s and a string array dictionary, return the longest string in the dictionary that can be formed by deleting some of the given string characters. If there is more than one possible result, return the longest word with the smallest lexicographical order. If there is no possible result, return the empty string.\nExample 1:\nInput: s = \u0026#34;abpcplea\u0026#34;, dictionary = [\u0026#34;ale\u0026#34;,\u0026#34;apple\u0026#34;,\u0026#34;monkey\u0026#34;,\u0026#34;plea\u0026#34;] Output: \u0026#34;apple\u0026#34; Example 2:\nInput: s = \u0026#34;abpcplea\u0026#34;, dictionary = [\u0026#34;a\u0026#34;,\u0026#34;b\u0026#34;,\u0026#34;c\u0026#34;] Output: \u0026#34;a\u0026#34; Constraints:\ns and dictionary[i] consist of lowercase English letters.\n1 \u0026lt;= s.length \u0026lt;= 1000\n1 \u0026lt;= dictionary.length \u0026lt;= 1000\n1 \u0026lt;= dictionary[i].length \u0026lt;= 1000\nclass Solution { public: string findLongestWord(string s, vector\u0026lt;string\u0026gt; \u0026amp;dictionary) { // 先按字母序排序 sort(dictionary.begin(), dictionary.end()); int mx = 0; // 设定一下指针和最大长度 string ans = \u0026#34;\u0026#34;; for (int i = dictionary.size() - 1; i \u0026gt;= 0; i--) { int x = 0; for (int j = 0; j \u0026lt; s.size(); j++) { if (s[j] == dictionary[i][x]) x++; } if (x == dictionary[i].size()) { if (x \u0026gt;= mx) mx = x, ans = dictionary[i]; } } return ans; } }; 此类问题需要将字符串 s与dictionary[i]进行匹配，判断dictionary[i]是否为s的子序列。解决这种问题只需先将两个指针一个x放在s开始位置，一个j放在dictionary[i]开始位置，如果x==j说明dictionary[i]的第j位已经在s中找到了第一个对应，可以进而检测后面的部分了，那么x和j同时加一。如果上述等式不成立，则dictionary[i]的第j位仍然没有被匹配上，所以只给x加一，在x的后面部分再继续寻找。最后，如果x已经移到了超尾位置，说明整个字符串都可以被匹配上，也就是dictionary[i]是s的一个子序列，否则不是。\n利用序列有序性：很多时候在序列上使用双指针之所以能够正确地达到目的，是因为序列的某些性质，最常见的就是利用序列的有序性。\n167. 两数之和 II - 输入有序数组\n给你一个下标从 1 开始的整数数组 numbers ，该数组已按非递减顺序排列，请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index_{1}] 和 numbers[index_{2}] ，则 1 \u0026lt;= index_{1} \u0026lt; index_{2} \u0026lt;= numbers.length 。\n以长度为 2 的整数数组 [index_{1}, index_{2}] 的形式返回这两个整数的下标 index_{1}和index_{2}。\n你可以假设每个输入 只对应唯一的答案 ，而且你 不可以 重复使用相同的元素。\n你所设计的解决方案必须只使用常量级的额外空间。\n示例 1：\n输入：numbers = [2,7,11,15], target = 9 输出：[1,2] 解释：2 与 7 之和等于目标数 9 。因此 index_{1} = 1, index_{2} = 2 。返回 [1, 2] 。 示例 2：\n输入：numbers = [2,3,4], target = 6 输出：[1,3] 解释：2 与 4 之和等于目标数 6 。因此 index_{1} = 1, index_{2} = 3 。返回 [1, 3] 。 示例 3：\n输入：numbers = [-1,0], target = -1 输出：[1,2] 解释：-1 与 0 之和等于目标数 -1 。因此 index_{1} = 1, index_{2} = 2 。返回 [1, 2] 。 提示：\n2 \u0026lt;= numbers.length \u0026lt;= 3 * 10^{4} -1000 \u0026lt;= numbers[i] \u0026lt;= 1000 numbers 按 非递减顺序 排列 -1000 \u0026lt;= target \u0026lt;= 1000 仅存在一个有效答案 这种问题也是双指针的经典应用了，虽然二分也很方便，但时间复杂度上多一个 logN，而且代码不够简洁。\n接下来介绍双指针做法：既然要找到两个数，且这两个数不能在同一位置，那其位置一定是一左一右。由于两数之和固定，那么两数之中的小数越大，大数越小。考虑到这些性质，那我们不妨从两边接近它们。\n首先假定答案就是 1 和 n，如果发现 num[1]+num[n]\u0026gt;target，说明我们需要将其中的一个元素变小，而 \\mathit{num}[1] 已经不能再变小了，所以我们把指向 n 的指针减一，让大数变小。\n同理如果发现 num[1]+num[n]\u0026lt;target，说明我们要将其中的一个元素变大，但num[n] 已经不能再变大了，所以将指向 1 的指针加一，让小数变大。\n推广到一般情形，如果此时我们两个指针分别指在 l,r 上，且 l\u0026lt;r, 如果 num[l]+num[r]\u0026gt;target，就将 r 减一，如果 num[l]+num[r]\u0026lt;target，就将 l 加一。这样 l 不断右移，r 不断左移，最后两者各逼近到一个答案。\nclass Solution { public: vector\u0026lt;int\u0026gt; twoSum(vector\u0026lt;int\u0026gt; \u0026amp;numbers, int target) { int r = numbers.size() - 1, l = 0; vector\u0026lt;int\u0026gt; ans; ans.clear(); while (l \u0026lt; r) { if (numbers[l] + numbers[r] \u0026gt; target) r--; else if (numbers[l] + numbers[r] == target) { ans.push_back(l + 1), ans.push_back(r + 1); return ans; } else l++; } return ans; } }; ","permalink":"https://blog.muwinds.com/archives/152/","summary":"\u003cp\u003e双指针顾名思义，就是同时使用两个指针，在序列、链表结构上指向的是位置，在树、图结构中指向的是节点，通过或同向移动，或相向移动来维护、统计信息。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://leetcode.cn/problems/subarray-product-less-than-k/\"\u003e713. 乘积小于 K 的子数组\u003c/a\u003e\u003c/p\u003e","title":"双指针"},{"content":"有两个人 A 和 B。 A 通过网络向 B 发送一条消息。B 收到了之后，怎么确认：\n1.这条消息没有被中间人篡改过\n2.这条消息确实来自于 A，而不是某个 C 伪造出来的\n答案就是使用 MAC。A 在发送消息时，计算出该消息的 MAC 值，并将其一起发送给 B。B 收到之后，便可以根据 MAC 确认 1 和 2.\n直接出公式：MAC = 散列 + 对称密钥\n也就是说，消息验证码 MAC 是散列技术和对称密钥技术的联合使用。\n首先说散列，散列的作用是防篡改。\n任何一段消息，不管有多长，都可以通过一个散列算法计算出一个固定长度的散列值。常用的散列算法是 md5 和 sha 系列。\n散列算法的特点是：\n1.结果是固定长度\n2.计算速度非常快\n3.只要两条消息的内容不一样（哪怕只有一个 bit 不一样），其散列的结果一样的概率极低极低（不是完全没有）\n4.极其难以从散列值反向推导出消息的内容\n散列怎么做到防篡改的呢？\n我们在网上下载文件的时候，很多网站会在下载链接旁边展示该文件的散列值和所采用的散列算法。我们下载完成后，用一样的算法对文件内容计算一次散列值，然后和网站上的散列值比对一下，如果一样，则可以断定文件在下载过程中没有被中间人篡改过。因为如果有一个攻击者 C，在中间拦截了文件，并做了手脚，然后再发送给我们的话，文件的散列值就会变 （根据散列算法的特点3）。那我们比对的时候就会发现不一样。\n接下来说密钥。密钥的作用是身份验证。\n在下载文件的场景下，散列就足够了。可是在文章开头的例子中，A 向 B 发消息，仅仅用散列还不够，为什么呢？\n因为 B 无法提前知道消息的散列值。\n你可能会问，如果 A 在发送每条消息的时候，都把散列值一起发送给 B，B 收到后再逐条比对，可不可以呢？不可以，因为如果有攻击者 C 的话，消息和散列值会被一起拦截，一起替换。C 改掉消息内容之后，顺便把散列值也更新一下，再发送给 B，B 是不能发现任何问题的。\n于是密钥就出场了。\nB 无法提前知道消息的散列值，但是 A 和 B 可以提前商量好一个密钥 K，分别秘密保存一份。A 发每条消息的时候，也一并发送一个散列值，但这个散列值不是单纯消息的散列值，而是把消息和密钥进行拼接，然后再对拼接的结果进行散列（这里做了简化，实际复杂一些，但原理一样）。\n现在再假设有攻击者 C，他拦截到消息和散列值，然而却无法对消息进行篡改，因为他不知道密钥是什么，无法计算出新的散列值。他也无法推测出密钥是什么。因为散列算法的特点4。所以，有了带密钥的散列之后，B 就可以同时确认：\n1,这条消息没有被中间人篡改过\n2.这条消息确实来自于 A，而不是某个 C 伪造出来的\n这个带密钥的散列值，就是消息验证码 MAC。\n","permalink":"https://blog.muwinds.com/archives/151/","summary":"\u003cp\u003e有两个人 A 和 B。 A 通过网络向 B 发送一条消息。B 收到了之后，怎么确认：\u003c/p\u003e\n\u003cp\u003e1.这条消息没有被中间人篡改过\u003c/p\u003e\n\u003cp\u003e2.这条消息确实来自于 A，而不是某个 C 伪造出来的\u003c/p\u003e\n\u003cp\u003e答案就是使用 MAC。A 在发送消息时，计算出该消息的 MAC 值，并将其一起发送给 B。B 收到之后，便可以根据 MAC 确认 1 和 2.\u003c/p\u003e","title":"什么是消息验证码 MAC？"},{"content":"Oauth2.0 定义了几种授权流程，其中最重要、也最基础的是 Authorization Code 授权。先看流程图：\n1，用户访问 A 网站\n2，A 打开 B 的授权页面\n3，用户看到授权界面，输入账号密码进行授权\n4，B 验证用户是合法的之后，生成一个 authorization code，然后通过 302 跳转的方式，把这个 code 发送给 A\n5，A 用这个 code 调用 B 的 api，获得 access token\n6，A 用 access token 访问资源\n问题来了，authorization code 是什么？\n- code 一个一次性的临时凭证，可以用它来兑换 access token\n- code 是由鉴权服务器生成的一个随机且唯一的字符串\n- 鉴权服务器会记录下 code 的生成时间、将要兑换给谁（例子里是 A）、授权的用户是谁\n- 在第 5 步，鉴权服务器会验证 A 的身份，并且检查 code 的有效期。通过的话就会生成 access token\n- code 是一次性的，在第 5 步成功换取 access token 之后就会作废\n- code 的有效期非常短，可能不会超过 1 分钟。A 在第 4 步获取到 code 之后，要立刻走第 5 步\n为什么不在第 4 步直接把 access token 发送给 A，而要通过看似多余的 code 再转换一下，搞这么复杂呢？这个问题的答案非常重要，是整个授权流程的关键所在。\n如果在第 4 步直接把 access token 发送给 A，会有什么问题呢？\n问题就是，第 4 步是一个 302 跳转，如果在第 4 步传递 access token，会导致 access token 暴露在 url 中，这是非常不安全的方式，因为 url 很容易捕捉到。\n所以我们不能在第 4 步直接传递 access token。那换成一个 code，再用 code 去换，就更安全了吗？你可以能会问，code 同样会泄漏，那攻击者拿到 code，也可以用 code 去换 access token，有什么区别呢？\n区别在于，攻击者拿到 code 是不能拿去换 access token 的，两个原因：\n1，上面说了，换取 access token 的时候，鉴权服务器会验证 A 的身份。具体怎么验证，在后面说明。\n2，code 的有效期极短，留给攻击者的时间极短\n另外，即便攻击者成功突破了上面两个限制，拿到了 access token，鉴权服务器也能很快发现。这是因为，攻击者必须抢在 A 之前去兑换 access token。那么，等到 A 走第 5 步的时候，鉴权服务器就会发现，这个 code 已经被用过了。此时这就是一个攻击已经发生的信号：有两个不同的服务器，尝试用同一个 code 来兑换 access token，正常情况下是不应该发生的。此时鉴权服务器可以立刻采取措施。例如，可以把刚才兑换出去的 access token 作废。所以，搞出来一个 code，确实会安全很多。\n那么，怎么验证 A 的身份呢？\n所有的 A，应该说都是 B 的合作方。那么在开始合作之前，一定需要做一些前期的准备。具体来说，B 通常会提供一个申请网站，A 通过这个网站填写一些信息，提交合作申请。B 的工作人员会审核，通过之后，系统会给 A 自动分配一个 client_id 和一个 client_secret。client_id 就是一个唯一 id，client_secret 是一个密钥。A 需要把 client_secret 妥善保管在服务器上，不能泄露。在用 code 兑换 access token 的时候，需要用这个 client_secret 对请求生成 MAC（消息验证码）。鉴权服务器就是依靠这个 MAC 来验证 A 的身份的。\n这里需要用到密码学的知识，本文就不展开解释了，有兴趣请看 什么是消息验证码MAC 。总之，攻击者因为没有这个密钥，无法对请求做出 MAC，因此就算窃取到 code，也不能用 code 来兑换 access token。\n根据上面的梳理，可以知道，要实现一套 Authorization Code 授权流程，需要做两个 endpoint：\n1，Auth Endpoint： GET /oauth2/auth　这就是第 2 步的授权页面地址\n参数：\nclient_id 发起授权的合作方的 client_id。必填\nredirect_uri 回调地址，也就是第 4 步 302 跳转的地址，用于接收 code。这个参数是可选的。因为在前期发起合作申请的时候，A 是需要提前将这个地址注册给 B 的。如果没有传，则使用注册的默认值\nscope 授权范围。这个也是可选的。B 可以把资源进行划分，分成多个 scope。A 发起授权的时候，可以指明要哪些 scope 的访问权限\nstate 一个由 A 生成的，随机且唯一的字符串。可选，但强烈建议要有。这个是用来防 CSRF 攻击的\n这个 endpoint 需要做以下几件事情：\n- 检查 client_id 是否合法\n- 检查 redirect_url 是否和事先注册的相符\n- 检查 scope。如果申请的时候有注册要使用的 scope，这里要检查传入的 scope 是否相符\n- 展示登录框对用户进行验证，展示提示信息，比如“即将授权给 xxx”等\n- 验证用户，通过后生成 code，并 302 跳转到 redirect_url，附上 code 和 state。其中 state 就是参数 state，原封不动传回去即可\n2，Token Endpoint： POST /oauth2/token　这就是第 5 步用 code 兑换 access token 的接口\n参数：\ncode 必填\nclient_id 必填\nredirect_uri 可选\nhmac 必填\n这个 endpoint 需要做以下几件事情：\n- 检查 code 是否已过期\n- 检查 code 是否已经使用过，如果是，很可能已经泄漏，需要采取相关措施\n- 检查 client_id 是否合法，client_id 和 code 对应的 client_id 是否一致\n- 检查 redirect_uri 参数和上面调用 Auth Endpoint 时传的 redirect_uri 是否完全一致。为什么这么做，请看 如果攻击者操控了 redirect_uri，会怎样？\n- 检查 hmac\n- 检查都通过，废弃掉 code，生成 access token 并返回\n以上就是实现最基本、最核心的 authorization code 授权流程需要做的事情。但实际中，这样很有可能是不够的。主要是因为 access token 是有有效期的。过期之后，会有两个很常见的需求：\na，刷新 access token。具体请看 这篇文章\nb，静默授权。如果 A 方没有 refresh token，或者 refresh token 过期了，但又不想让用户重新进行授权操作，希望可以在无需用户参与的情况下获得新的 access token\n为了满足需求 a，需要给 Token Endpoint 增加两个参数：grant_type 和 refresh_token，如下：\nToken Endpoint： POST /oauth2/token\n参数：\ngrant_type 必填。如果是用 code 兑换 access token，传 authorization_code；如果是用 refresh token 刷新，传 refresh_token\ncode 当 grant_type = authorization_code 时必填\nclient_id 必填\nredirect_uri 可选\nrefresh_token 当 grant_type = refresh_token 时必填\nhmac 必填\n为了满足需求 b，需要给 Auth Endpoint 增加一个参数： prompt，如下：\nAuth Endpoint： GET /oauth2/auth\n参数：\nclient_id 必填\nredirect_uri 可选\nscope 可选\nprompt 可选。可传 none 或不传。如果不传，则按正常展示授权页面，如果传 prompt = none，则不展示页面，改为检查该用户最近是否执行过相同的授权，如果授权过，这次则直接自动执行授权而无需用户参与，也就是静默授权。但是如果不符合条件，报错\nstate 可选\n实现了这两个 api，基本上就实现了基本的 Authorization Code 授权流程。\n","permalink":"https://blog.muwinds.com/archives/150/","summary":"\u003cp\u003eOauth2.0 定义了几种授权流程，其中最重要、也最基础的是 Authorization Code 授权。先看流程图：\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://img2020.cnblogs.com/blog/419544/202106/419544-20210609172848603-384012851.png\"\u003e\u003c/p\u003e\n\u003cp\u003e　　1，用户访问 A 网站\u003c/p\u003e\n\u003cp\u003e　　2，A 打开 B 的授权页面\u003c/p\u003e\n\u003cp\u003e　　3，用户看到授权界面，输入账号密码进行授权\u003c/p\u003e\n\u003cp\u003e　　4，B 验证用户是合法的之后，生成一个 authorization code，然后通过 302 跳转的方式，把这个 code 发送给 A\u003c/p\u003e","title":"Authorization Code 授权原理和实现方法"},{"content":"一、背景 platformio现有的板子库里面没有ESP32-S3-N16R8（8MB PSRAM + 16MB FLASH）的开发板模型，直接强行套用，要么就是解锁不了8MB PSRAM，要么就下载后运行不起来。但是Arduino可以正常解锁。\n二、简单解决办法 先选用esp32-s3-devkitc-1作为开发板模型，在它的基础上做额外的修改：\n具体修改配置已经整理到platformio.ini，如下：\n[env:esp32s3] platform = espressif32 board = esp32-s3-devkitc-1 framework = arduino ; 指定为16MB的FLASH分区表 board_build.arduino.partitions = default_16MB.csv ; 指定FLASH和PSRAM的运行模式 board_build.arduino.memory_type = qio_opi ; 预定义宏，启用PSRAM build_flags = -DBOARD_HAS_PSRAM ; 指定FLASH容量为16MB board_upload.flash_size = 16MB 编译、下载和运行即可。\n","permalink":"https://blog.muwinds.com/archives/148/","summary":"\u003ch2 id=\"一背景\"\u003e一、背景\u003c/h2\u003e\n\u003cp\u003eplatformio现有的板子库里面没有ESP32-S3-N16R8（8MB PSRAM + 16MB FLASH）的开发板模型，直接强行套用，要么就是解锁不了8MB PSRAM，要么就下载后运行不起来。但是Arduino可以正常解锁。\u003c/p\u003e","title":"VS Code+platformio配置ESP32-S3-N16R8（8MB PSRAM + 16MB FLASH）工程"},{"content":"在现代计算机中，所有数据都以二进制形式存储，即 0 和 1 两种状态。计算机对二进制数据进行的运算（如加、减、乘、除）被称为位运算，即对二进制数的每一位进行操作的运算。\n为了更好地理解位运算，举个简单的例子：假设我们有如下代码进行两个整数的加法运算：\nint a = 35; int b = 47; int c = a + b; 计算机会将这两个整数转换为二进制形式，然后进行加法运算：\n35: 0010 0011 47: 0010 1111 ---------------- 82: 0101 0010 因此，与直接使用 +、-、*、/ 运算符相比，合理运用位运算可以显著提高代码在机器上的执行效率。\n2. 位运算概览 符号 描述 运算规则 \u0026amp; 与 两个位都为1时，结果才为1 或 ^ 异或 两个位相同为0，相异为1 ~ 取反 0变1，1变0 \u0026laquo; 左移 各二进位全部左移若干位，高位丢弃，低位补0 \u0026raquo; 右移 各二进位全部右移若干位，高位补0或符号位补齐 3. 按位与运算符（\u0026amp;） 定义：对参与运算的两个数据的二进制位进行\u0026quot;与\u0026quot;运算。\n运算规则：\n0 \u0026amp; 0 = 0 0 \u0026amp; 1 = 0 1 \u0026amp; 0 = 0 1 \u0026amp; 1 = 1 总结：只有两位同时为1时，结果才为1，否则结果为0。\n例如：3 \u0026amp; 5 即 0000 0011 \u0026amp; 0000 0101 = 0000 0001，因此 3 \u0026amp; 5 的值为1。\n注意：负数按补码形式参与按位与运算。\n用途：\n清零：如果想将一个单元清零，只要与一个各位都为零的数值相与，结果为零。 取一个数的指定位：例如，取数 X = 1010 1110 的低4位，只需另找一个数 Y = 0000 1111，然后 X \u0026amp; Y = 0000 1110 即可得到 X 的指定位。 判断奇偶：通过判断最未位是0还是1来决定奇偶，可以用 if ((a \u0026amp; 1) == 0) 代替 if (a % 2 == 0) 来判断 a 是否为偶数。 4. 按位或运算符（|） 定义：对参与运算的两个对象的二进制位进行\u0026quot;或\u0026quot;运算。\n运算规则：\n0 | 0 = 0 0 | 1 = 1 1 | 0 = 1 1 | 1 = 1 总结：只要有一个为1，其值为1。\n例如：3 | 5 即 0000 0011 | 0000 0101 = 0000 0111，因此 3 | 5 的值为7。\n注意：负数按补码形式参与按位或运算。\n用途：\n设置某些位为1：例如，将数 X = 1010 1110 的低4位设置为1，只需另找一个数 Y = 0000 1111，然后 X | Y = 1010 1111 即可得到。 5. 异或运算符（^） 定义：对参与运算的两个数据的二进制位进行\u0026quot;异或\u0026quot;运算。\n运算规则：\n0 ^ 0 = 0 0 ^ 1 = 1 1 ^ 0 = 1 1 ^ 1 = 0 总结：相应位相同为0，相异为1。\n性质：\n交换律 结合律： (a ^ b) ^ c == a ^ (b ^ c) 对于任何数 x，都有 x ^ x = 0，x ^ 0 = x 自反性：a ^ b ^ b = a ^ 0 = a 用途：\n翻转指定位：例如，将数 X = 1010 1110 的低4位翻转，只需另找一个数 Y = 0000 1111，然后 X ^ Y = 1010 0001 即可得到。 与0相异或值不变：例如 1010 1110 ^ 0000 0000 = 1010 1110 交换两个数： void Swap(int \u0026amp;a, int \u0026amp;b) { if (a != b) { a ^= b; b ^= a; a ^= b; } } 6. 取反运算符（~） 定义：对参与运算的一个数据的二进制位进行\u0026quot;取反\u0026quot;运算。\n运算规则：\n~1 = 1111 1110 ~0 = 1111 1111 即：\n~1 = -2 ~0 = -1 总结：将 0 变 1，1 变 0。\n用途：\n使一个数的最低位为零：例如，使 a 的最低位为0，可以表示为：a \u0026amp; ~1。~1 的值为 1111 1111 1111 1110，再按\u0026quot;与\u0026quot;运算，最低位一定为0。 7. 左移运算符（\u0026laquo;） 定义：将一个运算对象的各二进制位全部左移若干位，高位丢弃，低位补0。\n例如，设 a = 1010 1110，a = a \u0026lt;\u0026lt; 2 将 a 的二进制位左移2位、右补0，即得 a = 1011 1000。\n若左移时舍弃的高位不包含1，则每左移一位，相当于该数乘以2。\n8. 右移运算符（\u0026raquo;） 定义：将一个数的各二进制位全部右移若干位，高位补0或补符号位，右边丢弃。\n例如，a = a \u0026gt;\u0026gt; 2 将 a 的二进制位右移2位，左补0 或补符号位，具体取决于数的正负。\n操作数每右移一位，相当于该数除以2。\n9. 复合赋值运算符 位运算符与赋值运算符结合，组成新的复合赋值运算符，它们是：\n\u0026amp;= 例：a \u0026amp;= b 相当于 a = a \u0026amp; b |= 例：a |= b 相当于 a = a | b \u0026gt;\u0026gt;= 例：a \u0026gt;\u0026gt;= b 相当于 a = a \u0026gt;\u0026gt; b \u0026lt;\u0026lt;= 例：a \u0026lt;\u0026lt;= b 相当于 a = a \u0026lt;\u0026lt; b ^= 例：a ^= b 相当于 a = a ^ b 运算规则与前述的复合赋值运算符的运算规则相似。\n不同长度的数据进行位运算：\n如果两个不同长度的数据进行位运算，系统会将二者按右端对齐，然后进行位运算。\n以\u0026quot;与运算\u0026quot;为例说明如下：\n在C语言中，long 型占4个字节，int 型占2个字节。如果一个 long 型数据与一个 int 型数据进行\u0026quot;与运算\u0026quot;，右端对齐后，左边不足的位按以下三种情况补足：\n如果整型数据为正数，左边补16个0。 如果整型数据为负数，左边补16个1。 如果整型数据为无符号数，左边也补16个0。 例如：\nlong a = 123; int b = 1; long result = a \u0026amp; b; long a = 123; unsigned int b = 1; long result = a \u0026amp; b; ","permalink":"https://blog.muwinds.com/archives/143/","summary":"\u003cp\u003e在现代计算机中，所有数据都以二进制形式存储，即 0 和 1 两种状态。计算机对二进制数据进行的运算（如加、减、乘、除）被称为位运算，即对二进制数的每一位进行操作的运算。\u003c/p\u003e\n\u003cp\u003e为了更好地理解位运算，举个简单的例子：假设我们有如下代码进行两个整数的加法运算：\u003c/p\u003e","title":"位运算记录"},{"content":"前途一片阴暗，好凉快，人生一波三折，好便宜，生活千疮百孔，好透气。工作天天加班，好充实。忙的焦头烂额，好暖和。工资少得可怜，好携带。人生一贫如洗，好干净。计划又泡汤了，泡汤很舒服啊，因为太多人对我蹬鼻子上脸，导致我的面部比较扁平，我是被害的，话到嘴边又咽了下去，每天以此获得饱腹感\n","permalink":"https://blog.muwinds.com/archives/133/","summary":"\u003cp\u003e前途一片阴暗，好凉快，人生一波三折，好便宜，生活千疮百孔，好透气。工作天天加班，好充实。忙的焦头烂额，好暖和。工资少得可怜，好携带。人生一贫如洗，好干净。计划又泡汤了，泡汤很舒服啊，因为太多人对我蹬鼻子上脸，导致我的面部比较扁平，我是被害的，话到嘴边又咽了下去，每天以此获得饱腹感\u003c/p\u003e","title":"前途一片阴暗，好凉快"},{"content":"一部分转载自巴哈姆特：https://home.gamer.com.tw/creationDetail.php?sn=4114818\nstringstream是一個C++底下的類別，專門拿來讀取字串並且處理，很多時候拿來做字串的切割，或者是int跟string類別之間的轉換。\n头文件（其实竞赛中多数是直接万能头文件的 xD）\nC++\n#include\u0026lt;sstream\u0026gt; ●把int型態的數字轉成string\n此時的stringstream就像個橋樑，可以負責當中間轉換的部分。\nstringstream提供了\u0026raquo;與\u0026laquo;運算子來讀取或寫入:\n代表寫入stringstream中，\u0026laquo;代表從stringstream拿出。\n#include \u0026lt;sstream\u0026gt; using namespace std; int main() { stringstream s1; int number =1234; string output;//要把number轉成字串型態的容器 cout\u0026lt;\u0026lt;\u0026#34;number=\u0026#34;\u0026lt;\u0026lt;number\u0026lt;\u0026lt;endl;//顯示number=1234; s1\u0026lt;\u0026lt;number; //將以int宣告的number放入我們的stringstream中 s1\u0026gt;\u0026gt;output; cout\u0026lt;\u0026lt;\u0026#34;output=\u0026#34;\u0026lt;\u0026lt;output\u0026lt;\u0026lt;endl;//顯示output=1234; } 值得注意的是我們兩行顯示都是1234但是他的資料型態卻已經不一樣了，第一個是int的型態，第二行則是string的型態。\n●相反的stringstream也可以將string轉成int\n#include \u0026lt;sstream\u0026gt; using namespace std; int main() { stringstream string_to_int; string s1=\u0026#34;12345\u0026#34;; int n1; string_to_int\u0026lt;\u0026lt;s1; //也可以使用string_to_int.str(s1); //或者 s1=string_to_int.str(); string_to_int\u0026gt;\u0026gt;n1; cout\u0026lt;\u0026lt;\u0026#34;s1=\u0026#34;\u0026lt;\u0026lt;s1\u0026lt;\u0026lt;endl;//s1=12345 cout\u0026lt;\u0026lt;\u0026#34;n1=\u0026#34;\u0026lt;\u0026lt;n1\u0026lt;\u0026lt;endl;//n1=12345 } 看一道题：\n输入的第一行有一个数字 NN 代表接下來有 NN 行数字，每一行有若干个的整数(最多 2020 个，每行最多 200200 个字符)，请你输出每一行数字的和。\n输入格式 第一行一个整数 NN。\n接下来输入 N 行，每行若干个整数。\n输出格式 输出 N 行，每行一个整数表示。\n#include\u0026lt;bits/stdc++.h\u0026gt; #define ll long long #define ull unsigned long long using namespace std; int main() { ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); int N; cin\u0026gt;\u0026gt;N; stringstream ss; string s; getline(cin,s); for(int i=0;i\u0026lt;N;i++) { int a,sum=0; getline(cin,s); ss.clear(); ss.str(s); while(1) { ss \u0026gt;\u0026gt; a; if(ss.fail())break; sum += a; } cout\u0026lt;\u0026lt;sum\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; } return 0; } ","permalink":"https://blog.muwinds.com/archives/128/","summary":"\u003cp\u003e一部分转载自巴哈姆特：\u003ca href=\"https://home.gamer.com.tw/creationDetail.php?sn=4114818\"\u003ehttps://home.gamer.com.tw/creationDetail.php?sn=4114818\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003estringstream是一個C++底下的類別，專門拿來讀取字串並且處理，很多時候拿來做字串的切割，或者是int跟string類別之間的轉換。\u003c/p\u003e","title":"stringstream用法整理"},{"content":"问题如题，本博客也有过这个问题\n按f12发现这部分交由static.cloudflareinsight.com/beacon.min.js进行加载，现代的浏览器都有严格cors的策略了，咋可能加载的出来……\n解决方案，参考cloudflare论坛的这篇文章：\nhttps://community.cloudflare.com/t/beacon-min-js-as-malware/117552\n然后去dashboard里面去把对应域名的insight关掉就行\n点击Manage site的蓝色超链接：\n","permalink":"https://blog.muwinds.com/archives/127/","summary":"\u003cp\u003e问题如题，本博客也有过这个问题\u003c/p\u003e\n\u003cp\u003e按f12发现这部分交由static.cloudflareinsight.com/beacon.min.js进行加载，现代的浏览器都有严格cors的策略了，咋可能加载的出来……\u003c/p\u003e","title":"接入Cloudflare的网站发现部分css和js加载不出来的解决方法"},{"content":"给打印机插到打印的电脑上，开始安装驱动，卡在这里：\n在此之前我插到自己的电脑装过驱动试过，卡个2分钟也就装上了， 我也就没管出去吃饭了，结果吃完一小时再回来还是这个鬼窗口，我就觉得事情不对了。\n按照谷歌搜索，按图索骥，查了一下设备管理器，没找到USB打印支持，但是看到个USB大容量存储设备，百度搜了一下看到了惠普的这篇文章：\nhttps://h30471.www3.hp.com/t5/da-yin-ji-shi-yong-xiang-guan-wen-ti/HP-M1216da-yin-ji-bei-zi-dong-shi-bie-wei-da-rong-liang-cun-chu-qi-wu-fa-da-yin/td-p/1151404\n这个帖子写到：\nhttp://h30318.www3.hp.com/pub/softlib/software12/COL53553/Im-129228-1/LJM1130_M1210_SI_Utility.exe\n下载后运行， 选择打印机的型号之后，点击禁用，然后拔掉数据线,重新运行驱动,提示连接打印机的时候,在插入数据线.安装后再来打印测试\n测了一下，秒好\n","permalink":"https://blog.muwinds.com/archives/126/","summary":"\u003cp\u003e给打印机插到打印的电脑上，开始安装驱动，卡在这里：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2025/03/202503221742616376130990.png\"\u003e\u003c/p\u003e\n\u003cp\u003e在此之前我插到自己的电脑装过驱动试过，卡个2分钟也就装上了， 我也就没管出去吃饭了，结果吃完一小时再回来还是这个鬼窗口，我就觉得事情不对了。\u003c/p\u003e","title":"惠普m1216打印机无法安装驱动无法打印"},{"content":"Windows11改变了睡眠的几个状态，取消了hibernate机制，但没有针对hybrid sleep机制作出改变，所以导致睡眠久了卡在hibernate（以及一些各种古怪问题）上导致内核崩溃自动关机\n所以关键就是要开启hibernate模式\n先管理员模式打开cmd，把hibernate模式给打开\npowercfg /h on 再去控制面板把休眠开关给打开：\n最后要恢复休眠机制：\ncmd依次输入：\nREG ADD HKLM\\SYSTEM\\CurrentControlSet\\Control\\Power\\PowerSettings\\238C9FA8-0AAD-41ED-83F4-97BE242C8F20\\9d7815a6-7ee4-497e-8888-515a05f02364 /v Attributes /t REG_DWORD /d 2 /f control.exe powercfg.cpl,,3 在弹出的窗口里修改进入休眠的时间：\n","permalink":"https://blog.muwinds.com/archives/125/","summary":"\u003cp\u003eWindows11改变了睡眠的几个状态，取消了hibernate机制，但没有针对hybrid sleep机制作出改变，所以导致睡眠久了卡在hibernate（以及一些各种古怪问题）上导致内核崩溃自动关机\u003c/p\u003e","title":"Windows 11睡眠自动关机解决"},{"content":"vscode运行python脚本的时候会出现很多__pycache__文件夹，内含.pyc文件，上传git时这些文件没必要传上去，所以需要在gitignore中加入规则。\n解决\n在.gitignore中添加\n**/pycache\n这样可以把文件夹中的__pycache__文件夹忽略，并且子文件夹中的__pycache__也一并忽略。\n参考链接\ngit忽略规则\n","permalink":"https://blog.muwinds.com/archives/124/","summary":"\u003cp\u003evscode运行python脚本的时候会出现很多__pycache__文件夹，内含.pyc文件，上传git时这些文件没必要传上去，所以需要在gitignore中加入规则。\u003c/p\u003e","title":"git忽略文件相关问题"},{"content":"\n模板题：洛谷p3379\n题目描述 如题，给定一棵有根多叉树，请求出指定两个点直接最近的公共祖先。\n输入格式 第一行包含三个正整数 N,M,SN,M,S，分别表示树的结点个数、询问的个数和树根结点的序号。\n接下来 N−1N−1 行每行包含两个正整数 x,yx,y，表示 xx 结点和 yy 结点之间有一条直接连接的边（数据保证可以构成树）。\n接下来 MM 行每行包含两个正整数 a,ba,b，表示询问 aa 结点和 bb 结点的最近公共祖先。\n输出格式 输出包含 MM 行，每行包含一个正整数，依次为每一个询问的结果。\n输入输出样例 输入 #1\n5 5 4 3 1 2 4 5 1 1 4 2 4 3 2 3 5 1 2 4 5 输出 #1\n4 4 1 4 4 说明/提示 对于 30%30% 的数据，N≤10N≤10，M≤10M≤10。\n对于 70%70% 的数据，N≤10000N≤10000，M≤10000M≤10000。\n对于 100%100% 的数据，1≤N,M≤5000001≤N,M≤500000，1≤x,y,a,b≤N1≤x,y,a,b≤N，不保证 a≠ba=b。\n样例说明：\n该树结构如下：\n第一次询问：2,42,4 的最近公共祖先，故为 44。\n第二次询问：3,23,2 的最近公共祖先，故为 44。\n第三次询问：3,53,5 的最近公共祖先，故为 11。\n第四次询问：1,21,2 的最近公共祖先，故为 44。\n第五次询问：4,54,5 的最近公共祖先，故为 44。\n故输出依次为 4,4,1,4,44,4,1,4,4。\n2021/10/4 数据更新 @fstqwq：应要求加了两组数据卡掉了暴力跳。\n一个经典的思想就是倍增法去解决，那什么是倍增算法呢？\n思路很快就有了，但是要落实到代码上是另外一回事儿\n首先要把树输入进去，在C++中一个经典的思路就是利用vector把两个邻边打进去就能轻松解决\n然后开一个lg数组，初始化好2^i的数，根据题目就能轻松判断出只要父节点的数组开到22就好。\n那么如何初始化2^i呢？\nfor(int i=1;i\u0026lt;=N;i++){ lg[i]=lg[i-1];//先令lg[i]为上一个计算好的lg[i-1] if(i==1\u0026lt;\u0026lt;lg[i-1])lg[i]++;//i==1\u0026lt;\u0026lt;lg[i-1]这里的意思是如果i等于2的lg[n-1]次方的话，lg[i]加一 } 这样就把记录2^i的数组lg[i]初始化好了。\n然后就是把整棵树遍历，将每个节点的深度和父节点记录下来，开一个表示深度的数组de和表示父节点的数组fa\n//dfs遍历 void dfs(int u,int v){ de[u]=de[v]+1; fa[u][0]=v;//2的0次方是1，也就是向上走一步就是u的父亲v //(1\u0026lt;\u0026lt;i)\u0026lt;=de[u]意思是当2^i小于节点u的深度的时候 for(int i=1;(1\u0026lt;\u0026lt;i)\u0026lt;=de[u];i++){ //简单的状态转移方程:节点 u 的第 2^i 层祖先，等于节点 u 的第 2^(i-1) 层祖先的第 2^(i-1) 层祖先。 fa[u][i]=fa[fa[u][i-1]][i-1]; } //注意:i对应第2^i层 层数=1,2,4,8,...... 设k=2^i //u的上k层祖先即为u的上k/2个祖先的第k/2个祖先且k%2=0（因为k=2^i）（雾） //例如u的上4层祖先为上2层祖先的上2层祖先 for(int i=0;i\u0026lt;g[u].size();i++){ if(g[u][i]!=v)dfs(g[u][i],u);//没有遍历完就接着走下一层遍历 } } 接下来是开始进行lca函数，把两个节点的lca给整出来\nC++\n//最近公共祖先 int lca(int x,int y){ if(de[x]\u0026lt;de[y])swap(x,y);//保证x深于y while(de[x]!=de[y])x=fa[x][lg[de[x]-de[y]]-1];//如果深度不同的时候，让x节点和y节点在同一深度上 //lg[dep[x]-dep[y]]表示log(dep[x]-dep[y])+1,再减去1 //让x往上跳2^log([dep[x]-dep[y])层 /* 倍增 是不是任何一个正整数都能被一些2的n次方加起来? 答案是:可以的，因为每个数字都可以用二进制表示 十进制：16=16 15=8+4+2+1 10=8+2 5=4+1 ... 二进制：10000=10000 1111=1000+100+10+1 1010=1000+10 101=100+1 ... 所以倍增LCA的意思就是: 每次以2的k次方跳跃!! （以上大致改编于arfa大佬的 [博客](https://www.luogu.org/blog/acking/solution-p3379)） 这里就要用到lg数组判断x要往上跳多少 Q1：为什么要一步一步以2的k次方跳跃？ 因为fa[x][k]表示的是x的2^N层上的祖先,所以不能一步跳到上k（k为任意数）层的 让x往上跳(2^k1+2^k2+2^k3+......)层 才能让x和y达到同一高度 Q2：为什么x可以一步一步跳上y那一层？ 设 k=log([dep[x]-dep[y]) k是向下取整，例如dep[x]-dep[y]=5=(101)2=(100)2+(1)2（二进制表示） 那么就要往上跳2次，分别跳 (100)2 、 (1)2 层 （所以，log(x)也可以表示x二进制除了最高位的1之外都去掉的形式） 往上跳了一次以后，dep[x]-dep[y]=(101)2-(100)2=(1)2，再往上跳(1)2层就可以了 x一步一步逼近y那一层，dep[x]-dep[y]一步一步减小，就能达到了 */ if(x==y)return x; //如果`x和y是同一点的话,说明这个y本来就是x的祖先,所以直接返回`x for(int k=lg[de[x]]; k\u0026gt;=0; k--) if(fa[x][k]!=fa[y][k]) x=fa[x][k],y=fa[y][k]; //注意:层数=......,8,4,2,1 设p=2^k //如果`x的上p层的祖先的爸爸还是和`y的上p层的祖先的爸爸不一致 //那么就让ta们往上爬p层 return fa[x][0];//返回`x的爸爸(lca) } 最后完整的代码：\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; int N,M,S; const int MAX_N=5e6+9; vector\u0026lt;int\u0026gt;g[MAX_N]; int lg[MAX_N]; //lg[i]的定义:log(i)+1 int fa[MAX_N][22],de[MAX_N];//倍增法，fa[i][k]表示节点i走2^k层的祖先是哪个点 //最近公共祖先 int lca(int x,int y){ if(de[x]\u0026lt;de[y])swap(x,y);//保证x深于y while(de[x]!=de[y])x=fa[x][lg[de[x]-de[y]]-1];//如果深度不同的时候，让x节点和y节点在同一深度上 //lg[dep[x]-dep[y]]表示log(dep[x]-dep[y])+1,再减去1 //让x往上跳2^log([dep[x]-dep[y])层 /* 倍增 是不是任何一个正整数都能被一些2的n次方加起来? 答案是:可以的，因为每个数字都可以用二进制表示 十进制：16=16 15=8+4+2+1 10=8+2 5=4+1 ... 二进制：10000=10000 1111=1000+100+10+1 1010=1000+10 101=100+1 ... 所以倍增LCA的意思就是: 每次以2的k次方跳跃!! （以上大致改编于arfa大佬的 [博客](https://www.luogu.org/blog/acking/solution-p3379)） 这里就要用到lg数组判断x要往上跳多少 Q1：为什么要一步一步以2的k次方跳跃？ 因为fa[x][k]表示的是x的2^N层上的祖先,所以不能一步跳到上k（k为任意数）层的 让x往上跳(2^k1+2^k2+2^k3+......)层 才能让x和y达到同一高度 Q2：为什么x可以一步一步跳上y那一层？ 设 k=log([dep[x]-dep[y]) k是向下取整，例如dep[x]-dep[y]=5=(101)2=(100)2+(1)2（二进制表示） 那么就要往上跳2次，分别跳 (100)2 、 (1)2 层 （所以，log(x)也可以表示x二进制除了最高位的1之外都去掉的形式） 往上跳了一次以后，dep[x]-dep[y]=(101)2-(100)2=(1)2，再往上跳(1)2层就可以了 x一步一步逼近y那一层，dep[x]-dep[y]一步一步减小，就能达到了 */ if(x==y)return x; //如果`x和y是同一点的话,说明这个y本来就是x的祖先,所以直接返回`x for(int k=lg[de[x]]; k\u0026gt;=0; k--) if(fa[x][k]!=fa[y][k]) x=fa[x][k],y=fa[y][k]; //注意:层数=......,8,4,2,1 设p=2^k //如果`x的上p层的祖先的爸爸还是和`y的上p层的祖先的爸爸不一致 //那么就让ta们往上爬p层 return fa[x][0];//返回`x的爸爸(lca) } //dfs遍历 void dfs(int u,int v){ de[u]=de[v]+1; fa[u][0]=v;//2的0次方是1，也就是向上走一步就是u的父亲v //(1\u0026lt;\u0026lt;i)\u0026lt;=de[u]意思是当2^i小于节点u的深度的时候 for(int i=1;(1\u0026lt;\u0026lt;i)\u0026lt;=de[u];i++){ //简单的状态转移方程:节点 u 的第 2^i 层祖先，等于节点 u 的第 2^(i-1) 层祖先的第 2^(i-1) 层祖先。 fa[u][i]=fa[fa[u][i-1]][i-1]; } //注意:i对应第2^i层 层数=1,2,4,8,...... 设k=2^i //u的上k层祖先即为u的上k/2个祖先的第k/2个祖先且k%2=0（因为k=2^i）（雾） //例如u的上4层祖先为上2层祖先的上2层祖先 for(int i=0;i\u0026lt;g[u].size();i++){ if(g[u][i]!=v)dfs(g[u][i],u);//没有遍历完就接着走下一层遍历 } } int main(){ ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin\u0026gt;\u0026gt;N\u0026gt;\u0026gt;M\u0026gt;\u0026gt;S; //初始化lg数组 /*当 i=1 的时候，i=1 的二进制是 1，1 \u0026lt;\u0026lt; lg[i-1] 是 1 \u0026lt;\u0026lt; 0 = 1，所以 lg[i] = lg[0] + 1 → 0+1=1？ 然后当 i=2，lg[i] 一开始等于 lg[1]=1，接着检查 2 == 1\u0026lt;\u0026lt;1 → 2 == 2，所以 lg[2] 变为 2。这是个合理的 log2 实现，记录的是每个数的最大次方指数。 */ for(int i=1;i\u0026lt;=N;i++){ lg[i]=lg[i-1];//先令lg[i]为上一个计算好的lg[i-1] if(i==1\u0026lt;\u0026lt;lg[i-1])lg[i]++; } for(int i=0;i\u0026lt;N-1;i++){ int x,y; cin\u0026gt;\u0026gt;x\u0026gt;\u0026gt;y; g[x].push_back(y); g[y].push_back(x); } //遍历整棵树，拉出来每个节点的深度 dfs(S,0);//传入根节点和深度 for(int i=0; i\u0026lt;M; i++) { int x,y; cin\u0026gt;\u0026gt;x\u0026gt;\u0026gt;y; cout\u0026lt;\u0026lt;lca(x,y)\u0026lt;\u0026lt;endl; } return 0; } ","permalink":"https://blog.muwinds.com/archives/123/","summary":"\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2025/02/202502091739100275557208.png\"\u003e\u003c/p\u003e\n\u003cp\u003e模板题：洛谷p3379\u003c/p\u003e\n\u003ch2 id=\"题目描述\"\u003e题目描述\u003c/h2\u003e\n\u003cp\u003e如题，给定一棵有根多叉树，请求出指定两个点直接最近的公共祖先。\u003c/p\u003e\n\u003ch2 id=\"输入格式\"\u003e输入格式\u003c/h2\u003e\n\u003cp\u003e第一行包含三个正整数 N,M,S\u003cem\u003eN\u003c/em\u003e,\u003cem\u003eM\u003c/em\u003e,\u003cem\u003eS\u003c/em\u003e，分别表示树的结点个数、询问的个数和树根结点的序号。\u003c/p\u003e","title":"记录模板题：图论-树：最近公共祖先（LCA）"},{"content":"首先放一下原先的异步函数代码：\n@staticmethod async def check_subdomain(domain: str): \u0026#34;\u0026#34;\u0026#34;异步子域名枚举\u0026#34;\u0026#34;\u0026#34; resolver = aiodns.DNSResolver() found = [] try: with open(\u0026#34;app/1knames.txt\u0026#34;) as f: subdomains = [line.strip() for line in f] except FileNotFoundError: print(\u0026#34;Subdomain dictionary not found\u0026#34;) return False semaphore = asyncio.Semaphore(500) # 限制并发量 async def _query(sub: str): async with semaphore: full_domain = f\u0026#34;{sub}.{domain}\u0026#34; print(full_domain) try: a_records = await resolver.query(full_domain, \u0026#39;A\u0026#39;) if a_records: found.append(full_domain) return except aiodns.error.DNSError: pass try: cname_records = await resolver.query(full_domain, \u0026#39;CNAME\u0026#39;) if cname_records: found.append(full_domain) except aiodns.error.DNSError: pass batch_size = 2000 for i in range(0, len(subdomains), batch_size): batch = subdomains[i:i+batch_size] tasks = [_query(sub) for sub in batch] await asyncio.gather(*tasks) assets_table = AssetsTable(host=domain,subdomain=json.dumps(found)) assets_info = AssetsInfo(assets_table) assets_info.update_host_info() return True 乍一看没啥问题，但是如果在windows下跑起来就会发现啥输出都没有，如果一步步调试就会神奇的发现卡在第一行初始化\naiodns是异步dns模块，运行aiodns相关的函数不仅需要写成异步的形式，在windows下还需要更改asyncio的异步运行模式\n在函数前面加一行就可以解决：\nasyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) ","permalink":"https://blog.muwinds.com/archives/122/","summary":"\u003cp\u003e首先放一下原先的异步函数代码：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@staticmethod\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003echeck_subdomain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edomain\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u0026#34;异步子域名枚举\u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eresolver\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eaiodns\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDNSResolver\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003efound\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nb\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;app/1knames.txt\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003esubdomains\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eline\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estrip\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003eline\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"ne\"\u003eFileNotFoundError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Subdomain dictionary not found\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esemaphore\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003easyncio\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSemaphore\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e500\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e  \u003cspan class=\"c1\"\u003e# 限制并发量\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_query\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esub\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"n\"\u003esemaphore\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003efull_domain\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003esub\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e.\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003edomain\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efull_domain\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ea_records\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003eresolver\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efull_domain\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;A\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ea_records\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003efound\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efull_domain\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"n\"\u003eaiodns\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eerror\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDNSError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003epass\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ecname_records\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003eresolver\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efull_domain\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;CNAME\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ecname_records\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003efound\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efull_domain\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"n\"\u003eaiodns\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eerror\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDNSError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003epass\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ebatch_size\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e2000\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"nb\"\u003erange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003elen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esubdomains\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003ebatch_size\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ebatch\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esubdomains\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"n\"\u003ebatch_size\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003etasks\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e_query\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esub\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003esub\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003ebatch\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003easyncio\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egather\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003etasks\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eassets_table\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eAssetsTable\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehost\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003edomain\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003esubdomain\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edumps\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efound\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eassets_info\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eAssetsInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eassets_table\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eassets_info\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eupdate_host_info\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e乍一看没啥问题，但是如果在windows下跑起来就会发现啥输出都没有，如果一步步调试就会神奇的发现卡在第一行初始化\u003c/p\u003e","title":"Windows平台Python aiodns模块异步代码无法执行"},{"content":"先发题目内容：\n问题描述 小明是一名勇敢的冒险家，他在一次探险途中发现了一组神秘的宝石，这些宝石的价值都不同。但是，他发现这些宝石会随着时间的推移逐渐失去价值，因此他必须在规定的次数内对它们进行处理。\n小明想要最大化这些宝石的总价值。他有两种处理方式：\n选出两个最小的宝石，并将它们从宝石组中删除。 选出最大的宝石，并将其从宝石组中删除。 现在，给你小明手上的宝石组，请你告诉他在规定的次数内，最大化宝石的总价值是多少。\n输入格式 第一行包含一个整数 tt，表示数据组数。\n对于每组数据，第一行包含两个整数 nn 和 kk，表示宝石的数量和规定的处理次数。\n第二行包含 nn 个整数 a1,a2,\u0026hellip;,ana1,a2,\u0026hellip;,a**n，表示每个宝石的价值。\n输出格式 对于每组数据，输出一个整数，表示在规定的次数内，最大化宝石的总价值。\n样例输入 6 5 1 2 5 1 10 6 5 2 2 5 1 10 6 3 1 1 2 3 6 1 15 22 12 10 13 11 6 2 15 22 12 10 13 11 5 1 999999996 999999999 999999997 999999998 999999995 样例输出 21 11 3 62 46 3999999986 样例说明 在第一个测试用例中，两个最小值是 11 和 22，去掉它们，数组为 [5，10，6][5，10，6]，和为 2121。而最大值为 1010，去掉它，则数组为 [2,5,1,6][2,5,1,6]，和为 1414。最优的操作为执行一次操作一，得到最好的答案为 2121。\n在第二个测试用例中，最优的处理结果先删除两个最小值，然后再删除一个最大值。\n评测数据规模 对于 100100% 的评测数据，1≤t≤10,3≤n≤2⋅105,1≤k≤99999,2k\u0026lt;n1≤t≤10,3≤n≤2⋅105,1≤k≤99999,2k\u0026lt;n。\n运行限制 语言 最大运行时间 最大运行内存 C++ 2s 256M 蓝桥提供正解的思路是前缀和配合莫队算法，前缀和还好理解，这道题用莫队算法如何处理呢？\n先说什么是莫队算法：\n按照题目的要求，要么进行删掉一个最大的操作，要么就是一次性删掉两个价值最小的操作，总功能操作k次。\n我们可以直接把k次操作全部放给删掉价值最大的宝石，然后每次k-1，删掉价值最大的宝石的次数也是k-1，而删除两个最小的宝石则是k+1，每一次遍历得到的结果与之前得到的最优解对比，最终找出价值最大的宝石来。\n下面是代码：\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; using ll = long long; const int N = 2e5+9; int a[N]; ll prefix[N]; int main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin \u0026gt;\u0026gt; t; while(t--){ int n,k; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; k; for(int i=1;i\u0026lt;=n;++i) cin \u0026gt;\u0026gt; a[i]; //先排序再构造前缀和数组 sort(a+1,a+n+1); for(int i=1;i\u0026lt;=n;++i) prefix[i]=prefix[i-1]+a[i]; //左删除m次，那么右侧删除k-m次 int m; //mx用于存遍历m时的每组值，ans存最大值 ll mx=0,ans=0; for(m=0;m\u0026lt;=k;++m){ mx=prefix[n-k+m]-prefix[2*m]; ans=max(ans,mx); } cout \u0026lt;\u0026lt; ans \u0026lt;\u0026lt; endl; } return 0; } ","permalink":"https://blog.muwinds.com/archives/121/","summary":"\u003cp\u003e先发题目内容：\u003c/p\u003e\n\u003ch3 id=\"问题描述\"\u003e问题描述\u003c/h3\u003e\n\u003cp\u003e小明是一名勇敢的冒险家，他在一次探险途中发现了一组神秘的宝石，这些宝石的价值都不同。但是，他发现这些宝石会随着时间的推移逐渐失去价值，因此他必须在规定的次数内对它们进行处理。\u003c/p\u003e","title":"题解：蓝桥例题3260最大区间和"},{"content":"C++ 标准库中实现了前缀和函数 std::partial_sum，定义于头文件 中。\n一个经典的例题：\n暴力当然直接TLE\n//这段代码是能拿50分的，大规模会有超时问题 //没有很好的利用前缀和 #include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; int main(){ int n; cin\u0026gt;\u0026gt;n; vector\u0026lt;int\u0026gt;a; for(int i=0;i\u0026lt;n;i++){ int t;cin\u0026gt;\u0026gt;t; a.push_back(t); } int m; cin\u0026gt;\u0026gt;m; for(int i=0;i\u0026lt;m;i++){ int l,r,t=0; cin\u0026gt;\u0026gt;l\u0026gt;\u0026gt;r; for(int j=l-1;j\u0026lt;=r-1;j++)t+=a[j]; cout\u0026lt;\u0026lt;t\u0026lt;\u0026lt;endl; } return 0; } 将上面代码的加起来的过程给优化一下，在输入阶段就将数组改写成前缀和的形式，输出的时候只要s[r]-s[l-1]就可以了\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; int main(){ int n; cin\u0026gt;\u0026gt;n; vector\u0026lt;int\u0026gt;a;a.push_back(0); for(int i=1;i\u0026lt;=n;i++){ int t;cin\u0026gt;\u0026gt;t; a.push_back(t+a[i-1]); } int m;cin\u0026gt;\u0026gt;m; for(int i=0;i\u0026lt;m;i++){ int l,r; cin\u0026gt;\u0026gt;l\u0026gt;\u0026gt;r; cout\u0026lt;\u0026lt;a[r]-a[l-1]\u0026lt;\u0026lt;endl; } return 0; } 从一维扩展到二维，思考方式就多了\n一个是基于容斥原理，什么是容斥原理？这涉及到数学上集合的概念了。先看下面的图：\n现在给了A,B,C三个集合的元素数量，要求你求出至少在一个元素的数量，他不能是直接把三个集合的元素加起来，必须得把重复的元素给扣掉，计算这个数量的过程就是容斥原理。\n现在回到二维前缀和的部分，给定大小为 m *n 的二维数组 A，要求出其前缀和 S。那么，S 同样是大小为 m* n 的二维数组，且\n类比一维的情形，S{i,j} 应该可以基于 S{i-1,j} 或 S{i,j-1} 计算，从而避免重复计算前面若干项的和。但是，如果直接将 S{i-1,j} 和 S{i,j-1} 相加，再加上 A{i,j}，会导致重复计算 S{i-1,j-1} 这一重叠部分的前缀和，所以还需要再将这部分减掉。这就符合了容斥原理。由此得到如下递推关系：\n实现时，直接遍历 (i,j) 求和即可。\n同样的道理，在已经预处理出二位前缀和后，要查询左上角为 (i[1],j[1])、右下角为 (i[2],j[2]) 的子矩阵的和，可以计算\n这可以在 O(1) 时间内完成。\n在二维的情形，以上算法的时间复杂度可以简单认为是 O(mn)，即与给定数组的大小成线性关系。但是，当维度 k 增大时，由于容斥原理涉及的项数以指数级的速度增长，时间复杂度会成为 O(2^kN)，这里 k 是数组维度，而 N 是给定数组大小。因此，该算法不再适用。\n接下来是差分：\n差分的定义：在多张图画中，主体结构不变，展现其他较小变化的多张图片，可称其互为差分。\n在算法中，他是一种和前缀和相对的策略\n这里的a[i]即数组中每个元素\n它可以维护多次对序列的一个区间加上一个数，并在最后询问某一位的数或是多次询问某一位的数。注意修改操作一定要在查询操作之前。\n例题：\n思路\n这题明显就是差分\n根据差分数组的定义： d[i]=a[i]-a[i-1]，可以发现，在d[x]上加上z,会让后面的学生全部加上z。↓\n但这是把后面全部都加了，还要减掉一节，所以↓\n把两个综合起来就是↓\n所以每一次变化只要把d[x]+z,d[y+1]-z就好了。\n因为 d[i]=a[i]-a[i-1]\n所以 a[i-1]+d[i]=a[i]\n最后再根据 a[i-1]+d[i]=a[i]输出每一个同学的分数\n题解：\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; int main(){ int n,p; cin\u0026gt;\u0026gt;n\u0026gt;\u0026gt;p; vector\u0026lt;int\u0026gt;a(n+1,0),d(n+1,0);//动态分配内存的数组 for(int i=1;i\u0026lt;=n;i++){ cin\u0026gt;\u0026gt;a[i]; d[i]=a[i]-a[i-1];//差分 } int x,y,z; for(int i=0;i\u0026lt;p;i++){ cin\u0026gt;\u0026gt;x\u0026gt;\u0026gt;y\u0026gt;\u0026gt;z; d[x]+=z,d[y+1]-=z; } int min=300; //找出分数最少的学生 for(int i=1;i\u0026lt;=n;i++){ a[i]=a[i-1]+d[i]; if(min\u0026gt;a[i])min=a[i]; } //经过上面的操作，将时间复杂度给干到O(n)了 cout\u0026lt;\u0026lt;min\u0026lt;\u0026lt;endl; return 0; } ","permalink":"https://blog.muwinds.com/archives/120/","summary":"\u003cp\u003eC++ 标准库中实现了前缀和函数 std::partial_sum，定义于头文件 \u003cnumeric\u003e 中。\u003c/p\u003e\n\u003cp\u003e一个经典的例题：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2025/02/202502051738737737464851.png\"\u003e\u003c/p\u003e\n\u003cp\u003e暴力当然直接TLE\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e//这段代码是能拿50分的，大规模会有超时问题\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e//没有很好的利用前缀和\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e#\u003c/span\u003e\u003cspan class=\"nx\"\u003einclude\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nx\"\u003ebits\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"nx\"\u003estdc\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eh\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eusing\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003enamespace\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003estd\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nx\"\u003ecin\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"nx\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nx\"\u003evector\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"nx\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nx\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003et\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"nx\"\u003ecin\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"nx\"\u003et\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"nx\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003epush_back\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003et\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nx\"\u003ecin\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003el\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"nx\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"nx\"\u003et\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"nx\"\u003ecin\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"nx\"\u003el\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"nx\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nx\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"nx\"\u003el\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"nx\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"nx\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"nx\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"nx\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"nx\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"nx\"\u003ecout\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e\u003cspan class=\"nx\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e\u003cspan class=\"nx\"\u003eendl\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e将上面代码的加起来的过程给优化一下，在输入阶段就将数组改写成前缀和的形式，输出的时候只要s[r]-s[l-1]就可以了\u003c/p\u003e","title":"前缀和 \u0026 差分"},{"content":"来源：在vscode MinGW GCC C++编译环境下使用httplib.h发送http请求 (zkea.net)\n在vscode MinGW GCC C++编译环境下使用httplib.h，在编译时报错：\nundefined reference to `__imp_closesocket'\ncpp-httplib是C++的一个http类库，使用它可以很方便的实现http请求，但在vs code环境下编译会报错，原因是因为没有引用ws2_32.lib，在visual studio环境下，会自动引用这个库：\n#if defined(_MSC_VER) #if _MSC_VER \u0026lt; 1900 #error Sorry, Visual Studio versions prior to 2015 are not supported #endif #pragma comment(lib, \u0026#34;ws2_32.lib\u0026#34;) #ifdef _WIN64 using ssize_t = __int64; #else using ssize_t = long; #endif pragma comment(lib, \u0026quot;ws2_32.lib\u0026quot;)这个语法只在visual studio中有效。那MinGW C++编译器要如何链接这个库呢？\n我们打开.vscode目录下的task.json，在build参数后面加上\u0026quot;-lws2_32\u0026quot;就可以了。\n","permalink":"https://blog.muwinds.com/archives/119/","summary":"\u003cp\u003e来源：\u003ca href=\"https://www.zkea.net/blog/detail/vscode-mingw-gcc-httplib.html\"\u003e在vscode MinGW GCC C++编译环境下使用httplib.h发送http请求 (zkea.net)\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e在vscode MinGW GCC C++编译环境下使用\u003ca href=\"https://github.com/yhirose/cpp-httplib\"\u003ehttplib.h\u003c/a\u003e，在编译时报错：\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eundefined reference to `__imp_closesocket'\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003ecpp-httplib是C++的一个http类库，使用它可以很方便的实现http请求，但在vs code环境下编译会报错，原因是因为没有引用\u003ccode\u003ews2_32.lib\u003c/code\u003e，在visual studio环境下，会自动引用这个库：\u003c/p\u003e","title":"在vscode MinGW GCC C++编译环境下使用httplib.h编译报错"},{"content":"This unlicensed Photoshop app has been disabled.\n最近adobe好像更新了什么，大面积出现未经授权许可禁止使用。（这两年的官方盗版被一网打尽了）\n解决方法\n原理：禁止Adobe启动时候的联网审查→禁止Adobe联网\n步骤：\n1、点击桌面右下开始菜单，打开设置（sitting）\n2、搜索并打开防火墙\n3、打开高级设置\n4、找到出站规则\n5、新建出站规则\n进去后，先点一下“下一步”然后选择adobe的运行程序，然后一直下一步\n然后会被要求给新出站规则命名，随便取一个，方便记忆就stop adobe connection\n8、至此，就终止了PS这个程序的联网检查。就不再出现禁止使用了\n另外，还可以继续禁止adobe自动更新服务\n1、桌面右键 “我的电脑” 打开管理\n2、选择服务→找到AdobeUpdateService→右键选择属性，禁止活动状态改为 禁止\n作者：凌霜雪猫 https://www.bilibili.com/read/cv22875455/ 出处：bilibili\n","permalink":"https://blog.muwinds.com/archives/118/","summary":"\u003cp\u003eThis unlicensed Photoshop app has been disabled.\u003c/p\u003e\n\u003cp\u003e最近adobe好像更新了什么，大面积出现未经授权许可禁止使用。（这两年的官方盗版被一网打尽了）\u003c/p\u003e\n\u003cp\u003e解决方法\u003c/p\u003e\n\u003cp\u003e原理：禁止Adobe启动时候的联网审查→禁止Adobe联网\u003c/p\u003e","title":"Adobe系列软件出现This unlicensed Photoshop app has been disabled.禁止使用"},{"content":"接手一个项目，甲方希望在项目中实现指定用户的限流，这需要我们在go项目中实现一个限流器。\n限流器的实现有很多经典的思想，不过令牌桶的思路简单，运行效率高，它是以恒定的速度往木桶里加入令牌，木桶满了则不再加入令牌。\n服务收到请求时尝试从木桶中取出一个令牌，如果能够得到令牌则继续执行后续的业务逻辑。如果没有得到令牌，直接返回访问频率超限的错误码或页面等，不继续执行后续的业务逻辑。\n特点：由于木桶内只要有令牌，请求就可以被处理，所以令牌桶算法可以支持突发流量。\n同时由于往木桶添加令牌的速度是恒定的，且木桶的容量有上限，所以单位时间内处理的请求书也能够得到控制，起到限流的目的。\n假设加入令牌的速度为 1token/10ms，桶的容量为500，在请求比较的少的时候（小于每10毫秒1个请求）时，木桶可以先\u0026quot;攒\u0026quot;一些令牌（最多500个）。\n当有突发流量时，一下把木桶内的令牌取空，也就是有500个在并发执行的业务逻辑，之后要等每10ms补充一个新的令牌才能接收一个新的请求。\n不过官方已经写好了令牌桶的限流器，我们直接拿过来写好中间件放到路由就好~\nmiddleware/rateLimit.go\npackage middleware import ( \u0026#34;IpDatabase/model\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;golang.org/x/time/rate\u0026#34; ) var limiterMap = make(map[string]*rate.Limiter) var mu sync.Mutex // getLimiter 获取或创建一个特定 key 的限流器 func getLimiter(key string) *rate.Limiter { mu.Lock() defer mu.Unlock() // 如果限流器已经存在，返回现有的限流器 if limiter, exists := limiterMap[key]; exists { return limiter } // 否则，创建一个新的限流器 key_limit_info, err := model.ListUser(\u0026amp;model.UserInfo{ Api_key: key, }) if err != nil { return nil } limiter := rate.NewLimiter(rate.Limit(key_limit_info[0].ReqRate), key_limit_info[0].ReqRate) // 每秒1个请求，突发3个请求 limiterMap[key] = limiter return limiter } // 限流中间件，基于用户的 key（如API Key或User ID） func RateLimiterByKey(c *gin.Context) { // 假设用户通过 `Authorization` header 提供 key key := c.GetHeader(\u0026#34;Authorization\u0026#34;) // 如果没有提供 key，返回错误 if key == \u0026#34;\u0026#34; { BadResponse(\u0026#34;API Key is required\u0026#34;, c) return } // 获取或创建限流器 limiter := getLimiter(key) // 检查是否允许请求 if !limiter.Allow() { BadResponse(\u0026#34;Too many requests\u0026#34;, c) return } // 如果通过限流器，继续处理请求 c.Next() } 写进路由\nfunc setApiRouter(engine *gin.Engine) { apiRouter := engine.Group(\u0026#34;/api\u0026#34;) { apiRouter.GET(\u0026#34;/search_ip_info\u0026#34;, middleware.IsExpiredKey(), middleware.RateLimiterByKey, controller.SearchIp) } } 打完收工！\n","permalink":"https://blog.muwinds.com/archives/117/","summary":"\u003cp\u003e接手一个项目，甲方希望在项目中实现指定用户的限流，这需要我们在go项目中实现一个限流器。\u003c/p\u003e\n\u003cp\u003e限流器的实现有很多经典的思想，不过令牌桶的思路简单，运行效率高，它是以恒定的速度往木桶里加入令牌，木桶满了则不再加入令牌。\u003c/p\u003e","title":"go gin框架实现限流"},{"content":"https://v1-14.docs.kubernetes.io/zh/docs/tasks/configure-pod-container/assign-cpu-resource/#cpu-%E5%8D%95%E4%BD%8D\nrequests：代表容器启动请求的资源限制，分配的资源必须要达到此要求\nlimits：代表最多可以请求多少资源\n单位m：CPU的计量单位叫毫核(m)。一个节点的CPU核心数量乘以1000，得到的就是节点总的CPU总数量。如，一个节点有两个核，那么该节点的CPU总量为2000m。\n下面拿双核举例：\nresources: requests: cpu: 50m #等同于0.05 memory: 512Mi limits: cpu: 100m #等同于0.1 memory: 1Gi 含义：该容器启动时请求50/2000的核心（2.5％）并且允许最多使用100/1000核心（5％）。\n0.05个核除总核数量2就是2.5%了，0.1个核除总核数就2是5%了\nresources: requests: cpu: 100m #等同于0.1 memory: 512Mi limits: cpu: 200m #等同于0.2 memory: 1Gi cpu单位m的含义：该容器启动时请求100/2000的核心（5％）并且允许最多使用200/1000核心（10％）\n0.1个核除总核数量2就是5%了，0.2个核除总核数2就是10%了\n可以看到下图是限制200m也就是0.2个核，最高可以跑到0.2\n————————————————\n版权声明：本文为博主原创文章，遵循 CC 4.0 BY-SA 版权协议，转载请附上原文出处链接和本声明。 原文链接：https://blog.csdn.net/yucaifu1989/article/details/106690965\n","permalink":"https://blog.muwinds.com/archives/115/","summary":"\u003cp\u003e\u003ca href=\"https://v1-14.docs.kubernetes.io/zh/docs/tasks/configure-pod-container/assign-cpu-resource/#cpu-%E5%8D%95%E4%BD%8D\"\u003ehttps://v1-14.docs.kubernetes.io/zh/docs/tasks/configure-pod-container/assign-cpu-resource/#cpu-%E5%8D%95%E4%BD%8D\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003erequests：代表容器启动请求的资源限制，分配的资源必须要达到此要求\u003c/p\u003e\n\u003cp\u003elimits：代表最多可以请求多少资源\u003c/p\u003e\n\u003cp\u003e单位m：CPU的计量单位叫毫核(m)。一个节点的CPU核心数量乘以1000，得到的就是节点总的CPU总数量。如，一个节点有两个核，那么该节点的CPU总量为2000m。\u003c/p\u003e","title":"如何理解k8s中limit限制cpu单位官方对单位的解释："},{"content":"1.签到题 答案的第一位是**1 second number is eight\n*叁：*9\n最后一个是八\n第一题：1 8 9 8（北京大学的创校时间xD），这读完都知道在哪吧……\n2.小北问答 1某厂的CPU采用了大小核架构，大核有超线程，小核没有超线程。已知物理核心数为12，逻辑核心数为16，大核数量为____，小核数量为____。\n都说了鸡兔同笼嘛，Ez\nx+y=12\n2x+y=16\nx=4 y=8\n2 C语言中，假设有函数 void f(const void **p);，我们有 void **q;，请问不使用强制类型转换，直接调用 f(q) 是否符合 C 的规范？\n虽然这学期学了C语言，我的感觉是不符合，const是不能改的指针？\n3 ARM架构的sve2指令集具有可变向量长度，且无需重新编译代码，就可以在不同向量长度的硬件上运行。sve2指令集的最小硬件向量长度是____，最大硬件向量长度是____。\nfp4 是一种新的数字格式，近期发布的许多硬件都增加了对 fp4 的支持。SE2M1（一位符号，两位 exponent，一位 mantissa）条件下，fp4 能精确表示的最大数字是____，能精确表示的最小的正数是____。\n虽然很常听到，但是却没实际了解过，看到这篇文章：\nhttps://www.cnblogs.com/lemonzhang/p/17843336.html\n跟着规则简单算一下：\n1.1*2^expo=1.1*2^1\n1.1二进制计算为1.5\n1.5*2=3\n1.5*2^-2=0.375\n好吧，算的不对，找gpt咯\n![O3KYG}ZCK2EZ0BH]QNM59Q1.png](/wp-content/uploads/2025/01/202501261737821728211170.png)\n储存 （填写字母，字母之间不要空格）\nZNS（Zoned Namespaces）SSD是一种新型储存设备，关于传统SSD与ZNS（Zoned Namespaces）SSD的行为差异，以下哪些说法是正确的？（多选）\nA. 当写入一个已有数据的位置时，传统SSD会直接原地覆盖，而ZNS SSD必须先执行Zone Reset操作\nB. 传统SSD的FTL会维护逻辑地址到物理地址的映射表，而ZNS SSD可以显著简化或消除这个映射过程\nC. 当可用空间不足时，传统SSD会自动触发垃圾回收，而ZNS SSD需要主机端主动管理并执行显式擦除\nD. 传统SSD一般支持任意位置的随机读取，而ZNS SSD只支持顺序读取\nE. 传统SSD通常需要较大比例的预留空间(Over-Provisioning)，而ZNS SSD可以将这部分空间暴露给用户使用\nZNS确实是第一次接触的概念\n跟着画饼文档快速猜一下？\n![R@3_ON)PQ4J91F{W[`KN~H.png\n能推出来A是对的，ZNS提到由于数据不会被不断地重写，因此提高了耐久度\nB也是对的，管理传入数据缓存和逻辑到物理映射需要大量数据\nC也是对的，垃圾收集说了\nD没说，但是感觉应该错，不然SSD干啥……\nE也提到了，是对的\nA B C E\n十发交到最后这个题都没改对 我佛了\n6. OpenMPI （填写patter为x.y.z，版本号的前缀v省略） OpenMPI 是一个开源的消息传递接口 (MPI) 实现，在高性能计算领域被广泛使用。截至2025年1月18日，OpenMPI 发布的最新稳定版本为 _____，在此版本的 OpenMPI中内置使用的 PRRTE 的版本为 _____。大家可以了解一下PRRTE的作用，OpenMPI 4 到 5 的架构变化，还挺有趣的。\n上官网查我还不会？\n![YSWU9@1TJF2}2WH0MY67Z]Q.png](/wp-content/uploads/2025/01/202501261737821812177851.png)\n5.0.6 3.0.7\nRDMA 是一种高性能网络通信技术，它允许计算机直接访问远程内存，从而大大降低了通信延迟和 CPU 开销。目前，主流的 RDMA 实现包括 InfiniBand、RoCE、RoCEv2 和 iWARP。下图中从左到右的四列展示了四种 RDMA 实现的架构图，请你说出按照从左到右的顺序，说出下图中的四列分别对应了什么 RDMA 的实现架构_____。\nA: RoCE B: RoCEv2 C: iWARP D: InfiniBand\n前几天讲座说过这个（如果不是前几天的 北大linux俱乐部B站号肯定也有，看到这个图我都恢复记忆了xD）\n![)@57]LM}O~405WBXOVJ({6G.png](/wp-content/uploads/2025/01/202501261737821939485408.png)\nHPCKit （填数字序号，数字之间不要空格）\nHPCKit 是针对鲲鹏平台深度优化的HPC基础软件，请选择以下组件的具体作用。\nA. BiSheng B. HMPI C. KML D. KBLAS E. EXAGEAR\n选项：\n高性能数学计算加速库\n基础线性代数过程库\n高性能通信库\nX86到ARM的二进制指令动态翻译软件\n编译器套件\n编译器套件选A，之前折腾openEuler的时候看到过\nhttps://docs.openeuler.org/zh/docs/20.03_LTS_SP3/docs/thirdparty_migration/bisheng.html\n高性能通信库，我看HMPI的名字就比较像通信用的，B\nKML我还真不知道，谷歌一下才知道是高性能数学加速库\nKBLAS，感觉是数学的，已经选了一个那就是线性代数咯？\nEXAGEAR不说了，就剩一个了，猜都猜出来了。\n53124\nCXL （填数字，保留两位有效数字）\n在传统的AI/ML计算中，模型训练和推理通常涉及大量的数据传输，尤其是在需要在CPU和GPU之间频繁交换数据时。例如，一个深度学习模型的训练任务可能包含以下步骤：\n数据加载和预处理在CPU上完成。\n预处理后的数据从CPU传输到GPU进行训练计算。\n训练完成后，模型更新结果传回CPU进行后续处理。\n假设有以下条件：\n• 每次批处理需要传输的数据量为1GB。\n• GPU每秒钟可以完成10次这样的批处理。\n• 传统架构下，CPU到GPU的PCIe传输延迟为50μs，传输带宽为10GB/s。\n• CXL架构下，传输延迟降至10μs，且数据访问可直接完成，无需显式传输。\n假设总训练任务包含10000次批处理。比较传统架构和CXL架构下完成任务所需的总时间，计算加速比（传统架构时间 / CXL架构时间），保留两位有效数字。\n（该题基于理想化模型，与真实情况并非完全符合）\n传输要0.1s，延迟50us，0.1s的gpu批处理\n0.00000005s，算了直接忽略吧，反正只让保留两位小数\nCXL传输速度快在加个0.1s就行，算出来差不多2.00？\n量子计算 （填数字，小数形式）\n量子计算是一种基于量子力学原理的计算方式，它利用量子比特的叠加态和纠缠态来进行计算，被认为是下一代计算技术。加速量子计算的模拟、数据处理等负载也是目前高性能计算领域的热点之一。\n初始状态为∣0⟩∣0⟩的量子比特，经过一次Hadamard门(H门)操作后，测量得到∣0⟩∣0⟩的概率是______？经过两次Hadamard门(H门)操作后，测量得到∣0⟩∣0⟩的概率是______？\n这个是真跪了，从来没了解过，直接GPT了，生病了真写不动。\n![67QF%SFXK7E}DLXXF_IL5F.png\n问题C：不简单的编译 【提示：只要是算卷积算出来的都可以，不能去hack结果校验过程。算法什么的都可以换，但预期解不需要这么做。本题分值为100分，可以尝试简单方法】 众所周知，HPC领域中常用的、性能比较好的语言有 C, C++, Fortran。这些语言常用的编译器有五款，其中 AMD, Intel, NVIDIA 御三家各维护了一款编译器，而开源社区维护了 GNU 和 LLVM 两款编译器。\n现在有这么一个涉及了三种语言的计算卷积的小项目（见附件）\nhandout/ ├── CMakeLists.txt ├── filter.F90 └── main.cpp 使用合适的编程语言、编译器、编译选项编译这个项目，让它跑快一点。\n你可以修改CMakeLists.txt、filter.F90并提交，提交时你可以从五家编译器中选择用你喜爱的一家。\n直接默认的文件啥都不动用GCC跑要33s（我用GCC用习惯了），所以先换llvm看看（是的，llvm我没用过），clang要34s……\n所以得要对cmake的文件进行优化\ncmake_minimum_required(VERSION 3.20) project(FilterProject LANGUAGES C CXX) add_executable(program main.cpp filter.F90) set_source_files_properties(filter.F90 PROPERTIES LANGUAGE C) target_compile_options(program PRIVATE -O3 # 启用最高级别优化 -march=native # 针对本地硬件生成优化代码 -ftree-vectorize # 启用自动向量化 -funroll-loops ) 上面的优化完跑一下，当时用的是x86-amd，用时5s，很难拿分\n借助文心一言（是的，跟gpt斗法一个晚上没斗出结果，跟intel oneapi折腾半天结果是调半天调不明白），然后给我的思路是在gcc就用上avx指令集做simd，（我超这没想出来当时我是不是猪……）\n![(I[QDKKJR81EYAX4~CI10E.png\n把头文件重新发给gpt，写了一份儿新的filter文件：\n后来搜索引擎再给我一个思路就是内存对齐的效果更好，委托gpt重新写了一版\n粘贴，make，0.7s\n更新：等C题的评测机好了之后，精度过不去\n所以filter.f90不动，我就尝试改cmakelist.txt，90分稳定过了……\nD题： 说明 给定两个序列，长度分别是len1和len2，输出他们最长公共子序列的长度。\n输入输出 输入文件input.dat包含两个序列，格式如下：\n首先是两个size_t类型的整数len1和len2，分别表示两个序列的长度。\n接下来是两个长度分别为len1和len2的element_t类型的数组，分别表示两个序列。本题中element_t是一个int类型。\n输出文件output.dat包含一个size_t类型的整数，表示最长公共子序列的长度。\n样例 input.dat\n5 6 1 2 3 4 5 1 2 3 4 5 6 output.dat\n5 自己改了几个函数结果交上去直接0……提示找不到output.txt\n交baseline的试试？喜提7分……\n所以又得优化\n先开个并行化，然后想起来前几天看的并行计算导论提到按列走效率会高点，所以改了一下：\n#include \u0026lt;vector\u0026gt; #include \u0026lt;utility\u0026gt; #include \u0026lt;cstdlib\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;omp.h\u0026gt; typedef int element_t; size_t lcs(element_t* arr_1, element_t* arr_2, size_t len_1, size_t len_2) { std::vector\u0026lt;size_t\u0026gt; previous(len_2 + 1, 0); // previous row std::vector\u0026lt;size_t\u0026gt; current(len_2 + 1, 0); // current row // 对内存访问顺序进行优化，保证数据按列访问 for (size_t i = 1; i \u0026lt;= len_1; ++i) { #pragma omp parallel for num_threads(16) for (size_t j = 1; j \u0026lt;= len_2; ++j) { if (arr_1[i - 1] == arr_2[j - 1]) { current[j] = previous[j - 1] + 1; } else { current[j] = std::max(current[j - 1], previous[j]); } } previous.swap(current); // 交换 current 和 previous } return previous[len_2]; // 结果在最后的 previous 行 } 交过去19分……\n后来gpt就开始掉链子，然后在Pod上都跑错结果，要么就是效率不理想，发了好几次就差一次机会了，我想到这是华为鲲鹏，arm架构，那就利用arm架构的arm neon，gpt助我！\n![R7Z(RS]XEKUXHO`39RZ@B@4.png](/wp-content/uploads/2025/01/202501261737822210577098.png)\nPod上试了几个数据，我去，这次gpt给力，交了，61分\n#include \u0026lt;utility\u0026gt; #include \u0026lt;cstdlib\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;omp.h\u0026gt; #include \u0026lt;arm_neon.h\u0026gt; typedef int element_t; size_t lcs(element_t* arr_1, element_t* arr_2, size_t len_1, size_t len_2) { size_t* previous = (size_t*)calloc(len_2 + 1, sizeof(size_t)); size_t* current = (size_t*)calloc(len_2 + 1, sizeof(size_t)); for (size_t i = 1; i \u0026lt;= len_1; ++i) { // Parallelize the inner loop for each row, ensure correct data handling #pragma omp parallel for for (size_t j = 1; j \u0026lt;= len_2; j += 4) { // Process 4 elements at a time with NEON // Load 4 elements from arr_2[j-1..j+3] int32x4_t arr_2_values = vld1q_s32(\u0026amp;arr_2[j - 1]); // Process 4 elements of arr_1[i-1] size_t prev_val = previous[j - 1]; // Initialize with previous[j-1] int32x4_t current_vals = vld1q_s32(\u0026amp;arr_1[i - 1]); // Compare 4 elements at once uint32x4_t match_mask = vceqq_s32(arr_2_values, current_vals); // Find maximum in current and previous rows, update current for (size_t k = 0; k \u0026lt; 4 \u0026amp;\u0026amp; j + k \u0026lt;= len_2; ++k) { if (arr_1[i - 1] == arr_2[j + k - 1]) { current[j + k] = previous[j + k - 1] + 1; } else { current[j + k] = std::max(current[j + k - 1], previous[j + k]); } } } // Ensure all threads are synchronized before swapping #pragma omp barrier // Swap current and previous arrays after all threads finish updating std::swap(current, previous); } size_t result = previous[len_2]; free(previous); free(current); return result; } i题： 评测环境是cuda\ncuda我很少玩，有个大概的了解\n借助gpt……\n感觉纯cudaMemCpy会出问题，以及这把减法操作给整没了，我自己加上去（再加了个__device__前缀），再让他用cudaMallocManaged改了改，交上去，16分。\n接下来开始折腾各种内存优化和对齐，最后尝试预处理，v100的pods上提升不错，交上去一看，从16%涨到26%，血赚\n#include \u0026lt;math.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;stdint.h\u0026gt; #include \u0026lt;vector_types.h\u0026gt; #include \u0026lt;cuda_runtime.h\u0026gt; typedef double d_t; struct d3_t { d_t x, y, z; }; __device__ d3_t operator-(const d3_t\u0026amp; a, const d3_t\u0026amp; b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; } __device__ d_t norm(const d3_t\u0026amp; v) { return sqrt(v.x*v.x + v.y*v.y + v.z*v.z); } __constant__ d3_t c_src; __global__ void computeDist1(const d3_t* __restrict__ mir, d_t* dist1, int64_t mirn) { int64_t j = blockIdx.x * blockDim.x + threadIdx.x; if (j \u0026gt;= mirn) return; dist1[j] = norm(mir[j] - c_src); } __global__ void computeData(const d3_t* mir, const d_t* dist1, const d3_t* sen, int64_t mirn, int64_t senn, d_t* data, int tile_size) { extern __shared__ char shared_mem[]; d3_t* shared_mir = (d3_t*)shared_mem; d_t* shared_dist1 = (d_t*)(shared_mem + tile_size * sizeof(d3_t)); int64_t sen_idx = blockIdx.x * blockDim.x + threadIdx.x; if (sen_idx \u0026gt;= senn) return; const d3_t sensor = sen[sen_idx]; const d_t freq_factor = 6.283185307179586 * 2000; d_t a = 0.0, b = 0.0; // 分块处理所有mir元素 for (int64_t tile_start = 0; tile_start \u0026lt; mirn; tile_start += tile_size) { int64_t tile_end = min(tile_start + tile_size, mirn); int64_t num_in_tile = tile_end - tile_start; // 协作加载当前tile到共享内存 for (int64_t j = threadIdx.x; j \u0026lt; num_in_tile; j += blockDim.x) { int64_t global_idx = tile_start + j; if (global_idx \u0026lt; mirn) { shared_mir[j] = mir[global_idx]; shared_dist1[j] = dist1[global_idx]; } } __syncthreads(); // 处理当前tile中的元素 for (int64_t j = 0; j \u0026lt; num_in_tile; ++j) { d3_t mirror = shared_mir[j]; d_t dist1_val = shared_dist1[j]; d_t dist2 = norm(mirror - sensor); d_t phase = freq_factor * (dist1_val + dist2); d_t cos_val, sin_val; sincos(phase, \u0026amp;sin_val, \u0026amp;cos_val); a += cos_val; b += sin_val; } __syncthreads(); // 确保下一tile加载前完成计算 } data[sen_idx] = hypot(a, b); } int main() { FILE* fi = fopen(\u0026#34;in.data\u0026#34;, \u0026#34;rb\u0026#34;); d3_t src; int64_t mirn, senn; d3_t *mir, *sen; fread(\u0026amp;src, sizeof(d3_t), 1, fi); fread(\u0026amp;mirn, sizeof(int64_t), 1, fi); mir = (d3_t*)malloc(mirn * sizeof(d3_t)); fread(mir, sizeof(d3_t), mirn, fi); fread(\u0026amp;senn, sizeof(int64_t), 1, fi); sen = (d3_t*)malloc(senn * sizeof(d3_t)); fread(sen, sizeof(d3_t), senn, fi); fclose(fi); // 设备内存分配 d3_t *d_mir, *d_sen; d_t *d_data, *d_dist1; cudaMalloc(\u0026amp;d_mir, mirn * sizeof(d3_t)); cudaMalloc(\u0026amp;d_sen, senn * sizeof(d3_t)); cudaMalloc(\u0026amp;d_data, senn * sizeof(d_t)); cudaMalloc(\u0026amp;d_dist1, mirn * sizeof(d_t)); // 预计算dist1的数组 // 将src拷贝到常量内存 cudaMemcpyToSymbol(c_src, \u0026amp;src, sizeof(d3_t)); // 创建多个CUDA流 const int num_streams = 4; cudaStream_t streams[num_streams]; for (int i = 0; i \u0026lt; num_streams; ++i) { cudaStreamCreate(\u0026amp;streams[i]); } // 同步传输mir数据并预计算dist1 cudaMemcpy(d_mir, mir, mirn * sizeof(d3_t), cudaMemcpyHostToDevice); dim3 block_pre(512); dim3 grid_pre((mirn + block_pre.x - 1) / block_pre.x); computeDist1\u0026lt;\u0026lt;\u0026lt;grid_pre, block_pre\u0026gt;\u0026gt;\u0026gt;(d_mir, d_dist1, mirn); cudaDeviceSynchronize(); // 确保预计算完成 // 检查共享内存需求 int max_shared_mem_per_block; cudaDeviceGetAttribute(\u0026amp;max_shared_mem_per_block, cudaDevAttrMaxSharedMemoryPerBlock, 0); size_t per_element_size = sizeof(d3_t) + sizeof(d_t); // 每个mir元素需要的共享内存 size_t tile_size = max_shared_mem_per_block / per_element_size; if (tile_size == 0) tile_size = 1; // 确保至少处理一个元素 // 分块处理时使用二维拷贝优化 size_t chunk = (senn + 3) / 4; for (int i = 0; i \u0026lt; num_streams; ++i) { cudaStreamCreate(\u0026amp;streams[i]); size_t offset = i * chunk; size_t valid = min(chunk, senn - offset); if (valid == 0) continue; // 异步拷贝传感器数据 cudaMemcpyAsync(d_sen + offset, sen + offset, valid * sizeof(d3_t), cudaMemcpyHostToDevice, streams[i]); dim3 block(512); dim3 grid((valid + block.x - 1) / block.x); // 计算所需共享内存大小 size_t shared_mem_size = tile_size * (sizeof(d3_t) + sizeof(d_t)); computeData\u0026lt;\u0026lt;\u0026lt;grid, block, shared_mem_size, streams[i]\u0026gt;\u0026gt;\u0026gt;( d_mir, d_dist1, d_sen + offset, mirn, valid, d_data + offset, tile_size ); } // 同步所有流并销毁 for (int i = 0; i \u0026lt; num_streams; ++i) { cudaStreamSynchronize(streams[i]); cudaStreamDestroy(streams[i]); } // 拷贝结果回主机 d_t* host_data = (d_t*)malloc(senn * sizeof(d_t)); cudaMemcpy(host_data, d_data, senn * sizeof(d_t), cudaMemcpyDeviceToHost); // 输出结果 FILE* fo = fopen(\u0026#34;out.data\u0026#34;, \u0026#34;wb\u0026#34;); fwrite(host_data, sizeof(d_t), senn, fo); fclose(fo); // 释放资源 free(mir); free(sen); free(host_data); cudaFree(d_mir); cudaFree(d_sen); cudaFree(d_data); return 0; } K题： 靓号生成器 Beauty begins with the first hexadecimal digit\n题目描述 【提醒：附件 Tab 中有 baseline】\n小鸣最近迷上了区块链，他发现每个人的密钥地址是一个以\u0026quot;0x\u0026quot;开头的42位十六进制字符串，例如:\n0x7d1a13c226F9951F44392a5446dF518bAE240cFe 作为一个极客，小鸣想要一些特殊的靓号地址。比如他想要一个以\u0026quot;888\u0026quot;开头的密钥地址，那就是类似:\n0x888xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 这样的地址。\n生成一个密钥n地址的过程是:\n随机生成一个32字节的私钥 用椭圆曲线算法(secp256k1)根据私钥生成公钥 取公钥的Keccak-256(SHA3)哈希值的后20字节作为地址 你的任务是帮助小鸣寻找他想要的靓号。\n输入格式 从vanity.in，表示想要的地址前缀(不包含\u0026quot;0x\u0026quot;)，每行一个，共10个。保证前缀由十六进制字符(0-9，a-f)组成，长度为6。\n输出格式 输出到vanity.out，对于vanity.in的每一行输入，输出两行:\n第一行是找到的完整地址(包含\u0026quot;0x\u0026quot;) 第二行是对应的私钥的十六进制表示(64位十六进制字符) 样例输入文件 171155 1114a2 12110d 1115c1 141125 111569 111340 11120f 11139a 11112b 样例输出文件 0x17115578a0a064245393165842283c52aecd9c86 c0f41a77bd65228406a0ea94793e4291c839ce0caeb7c6222ca079f70fc2aae6 0x1114a29bbb9df0054ac71c885bbdcd28ae60144d c8e61d9f2e8f7135bef5bc1229911b2f76b5a58ec44ce4ba27802d50b4dd023d 0x12110d4c7375ada8c3136c5cc8aaf0b3f4e81476 c11d97179ec80ab0eee1b554663d4e9ca366c001c15a47d726bde3e3efc403fe 0x1115c119687c793ffb48713f892bec4d4e41ea06 3345a82eda0445269c1f1c58fb9b9f5b9205687227aa4b7b173e485c84fddd75 0x14112520013f9b825baf6a18d926c2a9200a6935 b28f0a7e17a34b05078f54cd92259cb4250c2cce585f5324f56b21e928f73f1f 0x1115694f0b8d1830b7a980919f3e395b20add55d 187cf8b506f2ecde0f4941505f87c0e23dc258d9a8c1b741c17f072fcf3d4dd9 0x1113403525cb1d3779ce3d07324d756a4fcca011 cf36074ed78da76dc346fda35f87a8c88dfd460be3478ee759df805eb3e3daf5 0x11120fcc1d03f7c100a28c2de3f3c4bc54fbe9f2 4437b65386ffb9be43fa344eeab38da7b6513c0d5a06a874860d2f9537d7c3e8 0x11139aeb7403ff90616df43eb619185305b443f5 f2ac2544e46884d6c69433f2de5b174a281c315f107694d9c486ef72ed83995f 0x11112b4798a26153d80cb05e99982049510a7db4 7709e4d5ac06b532f985d4e6ce38eb163ee6c4721f4f32fbcec740467b9495f9 运行环境 容器名为vanity。\n附件中有handout，其中包含了baseline、正确性检查工具与secp256k1库。builddeps.sh可以帮你编译依赖库，生成可执行文件vanity、正确性检查工具chk并运行。\n评分标准 提交一个vanity.cpp，程序将在 Intel® Xeon® Platinum 8358 Processor 的8个核心上运行。\n评测命令行：\ng++ -O3 -march=native vanity.cpp -o vanity -lsecp256k1 -lcrypto -fopenmp -lpthread ./vanity 运气大师，交baseline居然过了？\n![DG5LU]@LFH8L6Q89Z%6MA8.png\n但是也用pthread重写了一版就是，交上去报wa，蚌埠\nJ题： HPL-MxP In theory, theory and practice are the same. In practice, they are not.\n背景 性能测试一直是 HPC 领域最重要的课题之一。1979年，Jack Dongarra 等人发布了 LINPACK benchmarks，至今仍然是性能测试的事实标准之一。2024年11月的Top500榜单中，El Capitan 以 1,742.00PFlops/s 的成绩位居榜首。这个成绩是通过运行 HPL 测试得到的。\n随着时代的发展，fp64 不再是衡量 HPC 系统性能的唯一标准。fp16、int8、int4、int1 等低精度计算也逐渐成为 HPC 领域的热点。同时，为了符合 AI 等负载的需求，硬件制造商也在有意降低硬件的 fp64 性能发展速度，将更多面积用在更低精度的计算上。因此，HPL 也需要不断更新，以适应新的硬件和应用场景。\n目标 HPL-MxP 也叫 HPL-AI，旨在使用充分利用 fp16 等精度较低的硬件进行 fp64 计算，提高 HPL 的性能。\n任务 本题介绍的是用 32 位浮点数实现的 HPL-MxP，这也是 HPL-MxP 的官方实现。你可以使用任意方法进行加速。\n评测 评测容器镜像：hpckit。程序运行在配备 Kunpeng 920 处理器的服务器上，可以使用 2 个核心，10G内存。保证 module 命令可用，没有加载任何模块，请在run.sh中加载自己所需的环境。\n为了方便你获取没有加载任何变量的环境，你可以用full容器，我们提供的yaml也是如此。hpckit容器会默认启用bisheng编译器，本题评测时我们进行了特殊处理。\n我们会在hpl-mxp 目录中 bash run.sh 来编译并运行你的程序。注意，hpl-ai.c程序不可修改，会以hpc-ai.o的形式提供在程序运行目录，其中包括了 main 函数及其他性能无关函数。你可以参考现有的Makefile使用hpl-ai.o。\nbash run.sh 最终得分与对应有效算力成正比。满分为 105 GFLOPs，baseline 为 10 GFLOPs。假设有效算力为xx，你的得分为 x−5x−5 。\n提交 请将你的代码打包成 submission.tar 并提交，应该具有如下结构。如果收到“run.sh无法找到”的报错，请注意第一级文件夹的名字应该是hpl-mxp\n|-- hpl-mxp # 必须是hpl-mxp |-- hpl-ai.c |-- run.sh |-- Makefile |-- ... 参考 HPL-MxP 华为HPCKit介绍 Fugaku超算的HPL-MxP实现：解决本题并不需要用到这个仓库，但可以参考它的高性能ARM SVE内核实现 直接跑baseline会过了第一个环节的浮点转换就会卡死，所以跟着主函数文件来到sgetrf那个玩意儿\n试过neon的simd啥的，包没用的，很快就能关注到题目中的arm sve，chatgpt继续登场：\n![{1D{VZCA0QNME]BOHJLR9A.png\nC++\n#include \u0026lt;stdio.h\u0026gt; #include\u0026lt;omp.h\u0026gt; #include \u0026lt;arm_sve.h\u0026gt; #include \u0026lt;stddef.h\u0026gt; #include \u0026#34;hpl-ai.h\u0026#34; #define A(i, j) *HPLAI_INDEX2D(A, (i), (j), lda) void sgetrf_nopiv(int m, int n, float *A, int lda) { int j; int nb = 32; int jb = nb; // Use unblock code. if (nb \u0026gt; m || nb \u0026gt; n) { sgetrf2_nopiv(m, n, A, lda); return; } int min_mn = m \u0026lt; n ? m : n; for (j = 0; j \u0026lt; min_mn; j += nb) { if (min_mn - j \u0026lt; nb) { jb = min_mn - j; } // Factor panel sgetrf2_nopiv(m - j, jb, \u0026amp;A[j * lda + j], lda); if (j + jb \u0026lt; n) { // strsm optimization with SVE svbool_t pg = svptrue_b32(); // Create a mask for all elements svfloat32_t vec_A_jj = svld1_f32(pg, \u0026amp;A[j * lda + j]); svfloat32_t vec_A_jj_next = svld1_f32(pg, \u0026amp;A[(j + jb) * lda + j]); // Create a vector with all elements set to 1.0f svfloat32_t one = svdup_f32(1.0f); // Perform matrix multiply add (svmla) svfloat32_t result = svmla_f32_x(pg, vec_A_jj, vec_A_jj_next, one); svst1_f32(pg, \u0026amp;A[(j + jb) * lda + j], result); // GEMM with SVE if (j + jb \u0026lt; m) { svfloat32_t vec_A_next_row = svld1_f32(pg, \u0026amp;A[(j + jb) * lda + j]); svfloat32_t vec_A_next_col = svld1_f32(pg, \u0026amp;A[j * lda + j + jb]); svfloat32_t updated_result = svmul_f32_x(pg, vec_A_next_row, vec_A_next_col); svst1_f32(pg, \u0026amp;A[(j + jb) * lda + j + jb], updated_result); } } } } 经过9*9=81难的错误调试，总算过了编译顺利跑了起来，然后来到最后的gmres是直接报nan……\nG题（julia题）： 通知 如果你需要装包，可以这样做：\nimport Pkg Pkg.activate(temp=true) Pkg.add(\u0026#34;包\u0026#34;) 背景 【提醒：附件 Tab 中有数据生成器与主程序 】\n小 T 最近在学习并行计算，他发现在大规模数据处理中，寻找最大的 K 个元素是一个非常常见的需求。比如在搜索引擎中，需要找出最相关的前 K 个结果；在推荐系统中，需要推荐最匹配的 K 个商品。\n小 T 想要实现一个高效的 Top-K 算法，他希望你能帮助他完成这个任务。而 Julia 最近在科学计算领域，因其方便的并行计算和高性能的特性，受到了越来越多的关注。所以小 T 希望你使用 Julia 语言来实现这个算法，并尽可能地优化其性能。\n说明 给定一个包含 N 个数（整数或浮点数）的数组，找出其中最大的 K 个数。\n你需要实现一个签名为 topk(data, k) 的函数，其中 data 是一个包含 N 个数（整数或浮点数）的数组，k 是一个正整数。\n函数的返回值是一个包含 K 个整数的数组，这 K 个数是输入数组中最大的 K 个数的下标（从 1 开始），且应当按照下标对应数值从大到小排序。如果输入数组中存在多个相同的数，则下标小的优先。\n你不需要考虑文件输入或输出，只需要实现 topk 函数。评测程序将会调用你的实现进行评测。\n评测环境 容器镜像：julia。已设置环境变量 JULIA_PKG_SERVER，无需担心装包的网络问题。\n运行选手程序前，会先运行 julia -e 'using Pkg; Pkg.instantiate()' 来安装依赖。\n评测方法 提交一个 impl.jl 文件，包括 topk函数，函数签名是：\n@inbounds function topk(data::AbstractVector{T}, k) where T # your code here end 评测时使用 Julia 1.11.2 版本。\n程序会在配备 Intel® Xeon® Platinum 8358 Processor 的服务器上以 julia -t 8 的方式运行，也即可以使用 8 个线程。\n评分标准 所有结果必须正确，否则不得分。\n测试点如下所示，其中 N 是数组长度，K 是需要找的最大数的个数，N 和 K 都为 2 的幂。每道题中整型（Int64）与浮点型（Float64）测试点分开计算，占比均为50%。\nN = 8192，K = 8 ：10 分，结果正确即可得分，运行时间需小于 1 分钟。 N = 32768, K = 128：20分，结果正确即可得分，运行时间需小于 1 分钟。 N = 220220, K = 256：40分，按性能赋分。 N = 230230, K = 1024：130分，按性能赋分。 对于按性能赋分的测试点，对比你的实现与标准库中单线程的实现，计算加速比为 x，得分计算方法如下。其中 f(x)f(x) 为 sigmoid 函数变体。\n得分 整形加速比 浮点加速比 0% x \u0026lt; 2 x \u0026lt; 2 f(x)f(x) x x 100% x \u0026gt;= 10 x \u0026gt;= 13 我们给出评测用的baseline用时和std运行时间如下：\nN = 8192, baseline: 0.238063315, 0.246213896, std: 0.000169, 0.000112 N = 32768, 0.000344826, 0.000510477, std: 0.000118, 0.000142 N = 220220, 0.006957641, 0.014872915, std: 0.000611, 0.000951 N = 230230, 9.672722409, 18.007807025, std: 0.470093, 0.769184 提示 Julia 标准库中的 partialsort 和 partialsortperm 函数可以实现 Top-K 的计算。 关于 Julia 性能优化，可以参考官网的 Performance Tips。 可能会有帮助的一些第三方 Julia 库： BenchmarkTools JET PProf 陈久宁老师的Julia讲座对新手很友好。Julia 教程可以参考 官方文档。中文材料可以参考李东风老师的 Julia 语言入门。我们的 Julia沙龙介绍了一些 Julia 可能出现性能问题的原因。 TopK 算法的一种实现思路是使用基数排序，可以参考这篇文章。 impl.jl中也有一些提示，可以参考。 开始先简单用std糊弄了个：\nusing Base.Threads # 原子类型定义，用于线程安全的操作 mutable struct Atomic{T}; @atomic x::T; end @inbounds function topk(data::AbstractVector{T}, k) where T # 将数值和下标配对 indexed_data = [(i, data[i]) for i in 1:length(data)] # 数据划分为8份 num_threads = nthreads() chunk_size = div(length(data), num_threads) chunks = [indexed_data[(i-1)*chunk_size+1:min(i*chunk_size, length(data))] for i in 1:num_threads] # 定义一个并行函数，处理每个线程的排序和选择最大K个 function parallel_sort(chunk) return sort(chunk, by = x -\u0026gt; (-x[2], x[1]))[1:k] end # 创建一个数组来存储每个线程的局部结果 results = Vector{Tuple{Int, T}}(undef, k * num_threads) # 使用多个线程并行化排序操作 @threads for i in 1:num_threads local_sorted = parallel_sort(chunks[i]) # 将结果写入共享数组 for j in 1:k results[(i-1)*k + j] = local_sorted[j] end end # 合并所有线程的结果并进行排序 final_sorted = sort(results, by = x -\u0026gt; (-x[2], x[1])) # 返回前K个下标 return [final_sorted[i][1] for i in 1:k] end 30分骗到手\n但是后面的难如登天，借助文心一言改写成堆排序自己再改simd和inbounds也就32分……\nimport Pkg Pkg.activate(temp=true) Pkg.add(\u0026#34;DataStructures\u0026#34;) using Base.Threads using DataStructures # 并行化选择TopK的下标的核心计算函数 function parallel_topk_indices(chunk::Vector{Tuple{Int, T}}, k::Int) where T # 使用最大堆来选择TopK的下标 max_heap = BinaryMaxHeap{Tuple{T, Int}}() for (idx, value) in chunk # 插入堆中，堆按值的大小自动排序（这里是负值因为BinaryMaxHeap是最大堆） push!(max_heap, (-value, idx)) # 负值以模拟最大堆行为 # 如果堆的大小超过k，移除最小的元素 if length(max_heap) \u0026gt; k pop!(max_heap) end end # 从堆中提取元素，并获取下标 result = Vector{Int}(undef, length(max_heap)) @simd for i in 1:length(max_heap) (_, idx) = pop!(max_heap) result[i] = idx end return result end # 主函数：计算前K大的数的下标 @inbounds function topk(data::Vector{T}, k::Int) where T # 数据划分为线程数份 num_threads = nthreads() chunk_size = div(length(data), num_threads) # 预分配存储结果的数组 results = Vector{Int}(undef, k * num_threads) all_indices = Vector{Int}(undef, k * num_threads) # 确保大小足够大 # 使用多个线程并行化排序操作 @threads for i in 1:num_threads # 计算每个线程的分片范围 start_idx = (i - 1) * chunk_size + 1 end_idx = min(i * chunk_size, length(data)) # 提取当前分片的数据 local_chunk = [(idx, data[idx]) for idx in start_idx:end_idx] # 对当前分片进行TopK下标选择 local_topk_indices = parallel_topk_indices(local_chunk, k) # 将结果写入共享数组（按线程对应位置） offset = (i - 1) * k for j in 1:k results[offset + j] = local_topk_indices[j] end end # 合并所有线程的结果 # 只合并有效的K个元素，避免超出数组边界 all_indices[1:k*num_threads] .= results[1:k*num_threads] # 使用这些下标从原始数据中获取对应的值 values = data[all_indices] # 对值进行排序，并选择前K个元素对应的下标 sort_perm = sortperm(values, rev=true) final_topk_indices = all_indices[sort_perm[1:k]] return final_topk_indices end 最后靠deepseek躺赢的一局\nimport Pkg Pkg.activate(temp=true) Pkg.add(\u0026#34;DataStructures\u0026#34;) using Base.Threads using DataStructures # 并行化选择TopK下标的核心计算函数 function parallel_topk_indices(data::Vector{T}, start_idx::Int, end_idx::Int, k::Int) where T # 处理无效的k值或空区间 k = max(0, min(k, end_idx - start_idx + 1)) k == 0 \u0026amp;\u0026amp; return Int[] # 使用最小堆维护当前最大的k个元素 min_heap = BinaryMinHeap{Tuple{T, Int}}() @inbounds for idx in start_idx:end_idx value = data[idx] if length(min_heap) \u0026lt; k push!(min_heap, (value, idx)) else if value \u0026gt; first(min_heap)[1] pop!(min_heap) push!(min_heap, (value, idx)) end end end # 将堆中元素按降序输出 n = length(min_heap) result = Vector{Int}(undef, n) for i in n:-1:1 result[i] = pop!(min_heap)[2] end return result end # 主函数：计算前K大的数的下标 @inbounds function topk(data::Vector{T}, k::Int) where T len = length(data) k = min(k, len) len == 0 \u0026amp;\u0026amp; return Int[] num_threads = nthreads() chunk_size = div(len, num_threads) # 存储各线程结果的数组 thread_results = Vector{Vector{Int}}(undef, num_threads) @threads for i in 1:num_threads start_idx = (i-1)*chunk_size + 1 end_idx = i == num_threads ? len : i * chunk_size # 直接处理数据分片，避免创建临时数组 thread_results[i] = parallel_topk_indices(data, start_idx, end_idx, k) end # 合并所有结果 all_indices = vcat(thread_results...) # 当总候选数不超过k时直接返回 length(all_indices) \u0026lt;= k \u0026amp;\u0026amp; return all_indices # 高效部分排序获取前k大元素的下标 values = data[all_indices] sort_order = partialsortperm(values, 1:k, rev=true) return all_indices[sort_order] end E题： 更新 目前版本的评测机可用如下的代码输出到文件，文件末尾不应有额外换行。请实现从命令行参数读取输入文件位置的功能，不要硬编码forest.in。\nfor (int i = 0; i \u0026lt; n; i++) { for (int j = 0; j \u0026lt; n; j++) { fo \u0026lt;\u0026lt; grid[i * n + j] \u0026lt;\u0026lt; \u0026#34; \u0026#34;; } fo \u0026lt;\u0026lt; std::endl; } 本题评测机的运行脚本，供参考：\n. /etc/profile \u0026gt;/dev/null #加载环境krun init \u0026gt;/dev/nullif [ \u0026#34;$JOB_COMPLETION_INDEX\u0026#34; -eq 0 ]; then krun host -n -H \u0026gt; hostfile mpirun --hostfile hostfile -np 64 -ppn 16 /data/forest /problem/input.dat /data/output.datelse sleep infinityfi 因为选手的pod本来就是sleep infinity开启的，所以你可以登录任意的pod，运行：\n. /etc/profile \u0026gt;/dev/null #加载环境krun init \u0026gt;/dev/null krun host -n -H \u0026gt; hostfile mpirun --hostfile hostfile -np 4 -ppn 1 /data/forest /problem/input.dat /data/output.dat 背景描述 在西西努努魔法大陆的一片神秘的森林中，生长着一种神奇的树木——呆呆树。呆呆树不仅外表呆萌，还有一个奇特的性质：它们虽然会燃烧，但永远不会完全消失！当火灾发生时，呆呆树会从树木状态转化为燃烧状态，最终变成灰烬。然而，灰烬中蕴藏着生机，在魔法的作用下，呆呆树就能从灰烬中重生！\n近日，这片森林并不太平，一场火灾打破了森林的宁静。在火灾蔓延的同时，黑魔法师乌乌和善良的魔法师芙芙正在这片森林中展开一场激烈的对抗。乌乌可以使用魔法召唤惊雷，点燃森林中的某棵呆呆树，试图用火焰吞噬一切。而芙芙则不甘示弱，她可以用神奇的魔法妙手回春，让一定区域内的呆呆树灰烬重新焕发生机，从而拯救这片森林。两人的魔法在森林中交织，火势时而蔓延，时而消退，呆呆树的状态也在不断变化。\n作为森林的守护者，你需要模拟火灾的蔓延过程，从而获知呆呆树的状态变化。由于森林的面积很大，为了加快模拟的速度，你想到了借助 MPI 的力量，将模拟任务分配到多个进程中并行处理。时间紧迫，快来帮助人们拯救这片森林吧！\n任务描述 你需要模拟一个 n×nn×n 的呆呆树森林网格，森林网格的坐标范围为 [0,n−1]×[0,n−1][0,n−1]×[0,n−1] 。每个格点可能是以下四种状态之一：\n0：空地 1：树木 2：燃烧 3：灰烬 呆呆树的状态会随着时间的变化而更新。模拟以时间步为粒度进行，每个时间步分为两个阶段：\n魔法事件阶段： 天降惊雷：黑魔法师乌乌在坐标 (x,y)(x,y) 处落下惊雷。如果坐标 (x,y)(x,y) 的格点状态为树木（状态1）的话，则将其状态转化为燃烧（状态2）。 妙手回春：魔法师芙芙让一片矩形区域 [sx,ex]×[sy,ey][s**x,e**x]×[sy,ey] 内的灰烬恢复生机。对于矩形区域 [sx,ex]×[sy,ey][s**x,e**x]×[sy,ey] 内（包含边界）的每一个格点，如果其状态为灰烬（状态3）的话，则将其状态转化为树木（状态1）。 在该阶段，可能会发生两种魔法事件： 如果该时间步没有魔法事件，则在魔法事件阶段中，森林的状态不会发生任何改变。 状态转移阶段： 如果一个格点的状态是树木（状态1），且其周围（即上下左右的四个格点）中至少有一个格点的状态是燃烧（状态2），则该格点的状态会转化为燃烧（状态2）。 如果一个格点的状态是燃烧（状态2），则该格点的状态会转化为灰烬（状态3）。 在该阶段，按照以下规则进行状态转移： 在同一时间步中，魔法事件阶段先发生，状态转移阶段后发生。也就是说，在同一时间步中，状态转移阶段的状态输入即为魔法事件阶段的状态输出。\n你的任务是模拟火灾的蔓延过程，并输出最终每个格点的状态。\n评测环境 容器镜像：intel。编译器已经配置为 mpiicpx，并且已经加载了 mpi 模块。\n输入输出约定 输入格式 本题需要从文件读取输入。输入文件为文本文件，，文件名从命令行参数获取。\n输入文件的格式如下：\n第一行包含三个整数 n ，m 和 t，分别表示网格的边长、魔法事件的数量、模拟的总时间步数。\n接下来 n 行，每行包括 n 个整数，表示初始时刻每个格点的状态（0：空地、1：树木、2：燃烧、3：灰烬）。\n接下来 m 行，每行描述一个魔法事件，格式为 “\u0026lt;时间步\u0026gt; \u0026lt;事件类型\u0026gt; \u0026lt;参数\u0026gt;”，事件类型包括：\n1：天降惊雷，参数为惊雷降落处的坐标 x 和 y 。题目输入保证 0≤x,y≤n−10≤x,y≤n−1 。 2：妙手回春，参数为矩形区域的范围 s**x，sy，e**x，ey 。题目输入保证 0≤sx≤ex≤n−10≤s**x≤e**x≤n−1 且 0≤sy≤ey≤n−10≤sy≤ey≤n−1 。 在输入文件中，魔法事件的顺序按照其发生的时间步顺序升序排列。并且，每个时间步最多发生一个魔法事件。\n你可以使用handout中的gencase.py生成测试数据。\n输出格式 你需要把结果输出到输出文件中，文件名从命令行参数获取。\n输出的格式为：输出 nn 行，每行包含 nn 个用空格分开的整数，表示最终时刻每个格点的状态。\n样例输入输出 样例一 输入文件 ： 6 2 4 1 1 1 2 1 1 1 0 1 1 1 1 0 1 2 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 0 0 1 0 0 3 2 0 1 2 3 4 1 4 5 输出文件： 3 3 3 2 3 3 2 0 2 3 3 3 0 3 2 3 0 2 0 3 0 3 0 2 2 3 2 3 2 3 1 0 0 2 0 0 对样例的解释： 本样例的地图尺寸为 6×66×6 ，发生了 22 个魔法事件（分别在时间步 33 和时间步 44 发生），要求输出模拟 44 个时间步之后的模拟结果。\n森林的初始状态如下：\n1 1 1 2 1 1 1 0 1 1 1 1 0 1 2 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 0 0 1 0 0 时间步 11 没有发生魔法事件，故只进行状态转移。时间步 11 结束后的森林状态如下：\n1 1 2 3 2 1 1 0 2 2 1 1 0 2 3 2 0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 0 0 1 0 0 时间步 22 没有发生魔法事件，故只进行状态转移。时间步 22 结束后的森林状态如下：\n1 2 3 3 3 2 1 0 3 3 2 1 0 3 3 3 0 1 0 2 0 2 0 1 1 1 1 1 1 1 1 0 0 1 0 0 时间步 33 发生了类型为 22 的魔法事件（即“妙手回春”），范围为 [0,2]×[1,3][0,2]×[1,3] ，该范围内状态3的格点均转化为状态1。时间步 33 的魔法事件阶段结束后，森林状态如下：\n1 2 1 1 3 2 1 0 1 1 2 1 0 1 1 1 0 1 0 2 0 2 0 1 1 1 1 1 1 1 1 0 0 1 0 0 紧接着还需要发生时间步 33 的状态转移阶段。时间步 33 结束后的森林状态如下：\n2 3 2 1 3 3 1 0 1 2 3 2 0 2 1 2 0 1 0 3 0 3 0 1 1 2 1 2 1 1 1 0 0 1 0 0 时间步 44 发生了类型为 11 的魔法事件（即“天降惊雷”），坐标为 (4,5)(4,5) ，该点的状态恰好为树木（状态1），所以会转变为燃烧（状态2）。时间步 44 的魔法事件阶段结束后，森林状态如下：\n2 3 2 1 3 3 1 0 1 2 3 2 0 2 1 2 0 1 0 3 0 3 0 1 1 2 1 2 1 2 1 0 0 1 0 0 紧接着还需要发生时间步 44 的状态转移阶段。时间步 44 结束后的森林状态如下：\n3 3 3 2 3 3 2 0 2 3 3 3 0 3 2 3 0 2 0 3 0 3 0 2 2 3 2 3 2 3 1 0 0 2 0 0 即为题目要求输出的状态。\n样例二 输入文件 forest.in ：16 5 7 2 2 1 1 0 2 1 1 1 1 1 1 0 0 1 1 1 0 1 1 1 0 1 0 1 1 1 0 1 1 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 0 1 0 1 1 1 2 1 0 1 1 1 1 1 1 1 1 0 0 0 1 1 1 0 1 1 1 1 1 0 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 0 1 1 0 1 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 2 1 1 1 1 1 1 0 0 1 0 1 1 1 1 1 0 0 1 1 1 1 1 0 1 1 0 1 1 1 1 2 4 0 13 2 2 1 7 9 4 2 6 2 12 13 5 2 1 8 13 11 7 1 7 0 输出文件 forest.out ：3 3 3 3 0 3 3 3 3 3 3 3 0 0 3 3 3 0 3 3 3 0 3 0 3 3 3 0 3 3 3 3 0 3 3 3 3 0 3 3 3 0 3 3 3 3 3 3 3 3 0 3 3 3 3 0 3 0 3 3 3 3 3 0 2 3 2 3 2 3 3 3 0 0 0 3 3 3 0 1 1 2 1 2 0 0 3 3 3 3 3 3 3 0 2 1 2 1 1 1 2 3 3 3 3 3 3 3 3 3 3 2 3 2 1 0 3 3 3 3 3 3 3 0 3 3 2 0 2 1 1 1 2 3 3 0 3 3 3 3 0 2 1 0 2 0 0 1 2 2 0 3 3 3 3 3 3 2 1 1 3 3 3 0 3 2 2 3 3 0 3 3 2 1 1 1 3 3 3 3 3 0 2 2 3 2 0 2 1 1 1 1 3 3 0 3 3 3 3 2 0 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 2 1 1 1 1 1 1 1 3 0 3 3 3 3 3 3 3 0 0 1 0 1 1 1 3 3 0 0 3 3 3 3 2 0 1 1 0 1 1 1 评分标准 测试环境 本题的评测环境为：4 节点，每节点 16 个核。运行在配备 Intel® Xeon® Platinum 8358 Processor 的服务器上。每个节点配备 32GiB 内存。\n测试点和评分标准 本题有一个测试点，规模和评分标准如下。在限制时间内得到正确结果可以得到基本分，在满分时间内得到正确结果可以得到满分，分数随时间线性变化。\n序号 n t 时间限制 满分时间 基本分值 满分分值 1 16384 2000 110s 68s 50 150 纯纯整理好题面给deepseek了，真牛啊，gpt o1折腾半天跟他扯mpi全过程无限删对话开对话，deepseek这个直接出正确答案……\n#include \u0026lt;mpi.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;unordered_map\u0026gt; struct Event { int step; int type; int params[4]; }; int main(int argc, char** argv) { MPI_Init(\u0026amp;argc, \u0026amp;argv); int rank, size; MPI_Comm_rank(MPI_COMM_WORLD, \u0026amp;rank); MPI_Comm_size(MPI_COMM_WORLD, \u0026amp;size); int n, m, t; std::unordered_map\u0026lt;int, Event\u0026gt; events; int* global_grid = nullptr; int* event_data = nullptr; if (rank == 0) { FILE* fin = fopen(argv[1], \u0026#34;r\u0026#34;); fscanf(fin, \u0026#34;%d %d %d\u0026#34;, \u0026amp;n, \u0026amp;m, \u0026amp;t); global_grid = new int[n * n]; for (int i = 0; i \u0026lt; n; ++i) { for (int j = 0; j \u0026lt; n; ++j) { fscanf(fin, \u0026#34;%d\u0026#34;, \u0026amp;global_grid[i * n + j]); } } events.reserve(m); for (int k = 0; k \u0026lt; m; ++k) { Event e; fscanf(fin, \u0026#34;%d %d\u0026#34;, \u0026amp;e.step, \u0026amp;e.type); if (e.type == 1) { fscanf(fin, \u0026#34;%d %d\u0026#34;, \u0026amp;e.params[0], \u0026amp;e.params[1]); e.params[2] = e.params[3] = 0; } else { fscanf(fin, \u0026#34;%d %d %d %d\u0026#34;, \u0026amp;e.params[0], \u0026amp;e.params[1], \u0026amp;e.params[2], \u0026amp;e.params[3]); } events[e.step] = e; } fclose(fin); event_data = new int[m * 6]; int idx = 0; for (const auto\u0026amp; pair : events) { const Event\u0026amp; e = pair.second; event_data[idx++] = e.step; event_data[idx++] = e.type; event_data[idx++] = e.params[0]; event_data[idx++] = e.params[1]; event_data[idx++] = e.params[2]; event_data[idx++] = e.params[3]; } } MPI_Bcast(\u0026amp;n, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(\u0026amp;m, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(\u0026amp;t, 1, MPI_INT, 0, MPI_COMM_WORLD); if (rank != 0) { event_data = new int[m * 6]; } MPI_Bcast(event_data, m * 6, MPI_INT, 0, MPI_COMM_WORLD); if (rank != 0) { for (int i = 0; i \u0026lt; m; ++i) { Event e; e.step = event_data[i * 6]; e.type = event_data[i * 6 + 1]; e.params[0] = event_data[i * 6 + 2]; e.params[1] = event_data[i * 6 + 3]; e.params[2] = event_data[i * 6 + 4]; e.params[3] = event_data[i * 6 + 5]; events[e.step] = e; } delete[] event_data; } else { delete[] event_data; } int remainder = n % size; int local_rows = n / size + (rank \u0026lt; remainder ? 1 : 0); int start_row = (rank \u0026lt; remainder) ? (rank * (n / size + 1)) : (remainder * (n / size + 1) + (rank - remainder) * (n / size)); int end_row = start_row + local_rows - 1; int* local_data = new int[local_rows * n]; if (rank == 0) { int* ptr = global_grid; for (int dest = 0; dest \u0026lt; size; ++dest) { int dest_remainder = n % size; int dest_local_rows = n / size + (dest \u0026lt; dest_remainder ? 1 : 0); int dest_start_row = (dest \u0026lt; dest_remainder) ? (dest * (n / size + 1)) : (dest_remainder * (n / size + 1) + (dest - dest_remainder) * (n / size)); if (dest == 0) { memcpy(local_data, ptr, dest_local_rows * n * sizeof(int)); } else { MPI_Send(ptr, dest_local_rows * n, MPI_INT, dest, 0, MPI_COMM_WORLD); } ptr += dest_local_rows * n; } delete[] global_grid; } else { MPI_Recv(local_data, local_rows * n, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); } int** current = new int*[local_rows + 2]; int** next = new int*[local_rows + 2]; for (int i = 0; i \u0026lt; local_rows + 2; ++i) { current[i] = new int[n]; next[i] = new int[n]; if (i \u0026gt;= 1 \u0026amp;\u0026amp; i \u0026lt;= local_rows) { memcpy(current[i], local_data + (i - 1) * n, n * sizeof(int)); } else { memset(current[i], 0, n * sizeof(int)); } } delete[] local_data; for (int step = 1; step \u0026lt;= t; ++step) { auto it = events.find(step); if (it != events.end()) { const Event\u0026amp; e = it-\u0026gt;second; if (e.type == 1) { int x = e.params[0]; int y = e.params[1]; if (x \u0026gt;= start_row \u0026amp;\u0026amp; x \u0026lt;= end_row) { int local_x = x - start_row + 1; if (current[local_x][y] == 1) { current[local_x][y] = 2; } } } else { int sx = e.params[0]; int sy = e.params[1]; int ex = e.params[2]; int ey = e.params[3]; int overlap_sx = std::max(sx, start_row); int overlap_ex = std::min(ex, end_row); int overlap_sy = std::max(sy, 0); int overlap_ey = std::min(ey, n - 1); if (overlap_sx \u0026lt;= overlap_ex \u0026amp;\u0026amp; overlap_sy \u0026lt;= overlap_ey) { for (int x = overlap_sx; x \u0026lt;= overlap_ex; ++x) { int local_x = x - start_row + 1; for (int y = overlap_sy; y \u0026lt;= overlap_ey; ++y) { if (current[local_x][y] == 3) { current[local_x][y] = 1; } } } } } } if (rank \u0026gt; 0) { MPI_Sendrecv(current[1], n, MPI_INT, rank - 1, 0, current[0], n, MPI_INT, rank - 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); } if (rank \u0026lt; size - 1) { MPI_Sendrecv(current[local_rows], n, MPI_INT, rank + 1, 0, current[local_rows + 1], n, MPI_INT, rank + 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); } for (int i = 1; i \u0026lt;= local_rows; ++i) { for (int j = 0; j \u0026lt; n; ++j) { int old_val = current[i][j]; if (old_val == 2) { next[i][j] = 3; } else if (old_val == 1) { int up = (i - 1 \u0026gt;= 0) ? current[i - 1][j] : 0; int down = (i + 1 \u0026lt; local_rows + 2) ? current[i + 1][j] : 0; int left = (j \u0026gt; 0) ? current[i][j - 1] : 0; int right = (j \u0026lt; n - 1) ? current[i][j + 1] : 0; next[i][j] = (up == 2 || down == 2 || left == 2 || right == 2) ? 2 : 1; } else { next[i][j] = old_val; } } } memcpy(next[0], current[0], n * sizeof(int)); memcpy(next[local_rows + 1], current[local_rows + 1], n * sizeof(int)); std::swap(current, next); } int* sendbuf = new int[local_rows * n]; for (int i = 0; i \u0026lt; local_rows; ++i) { memcpy(sendbuf + i * n, current[i + 1], n * sizeof(int)); } if (rank == 0) { int** final_grid = new int*[n]; for (int i = 0; i \u0026lt; n; ++i) { final_grid[i] = new int[n]; } for (int i = 0; i \u0026lt; local_rows; ++i) { memcpy(final_grid[start_row + i], sendbuf + i * n, n * sizeof(int)); } delete[] sendbuf; for (int src = 1; src \u0026lt; size; ++src) { int src_remainder = n % size; int src_local_rows = n / size + (src \u0026lt; src_remainder ? 1 : 0); int* recvbuf = new int[src_local_rows * n]; MPI_Recv(recvbuf, src_local_rows * n, MPI_INT, src, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); int src_start_row = (src \u0026lt; src_remainder) ? (src * (n / size + 1)) : (src_remainder * (n / size + 1) + (src - src_remainder) * (n / size)); for (int i = 0; i \u0026lt; src_local_rows; ++i) { memcpy(final_grid[src_start_row + i], recvbuf + i * n, n * sizeof(int)); } delete[] recvbuf; } FILE* fout = fopen(argv[2], \u0026#34;w\u0026#34;); for (int i = 0; i \u0026lt; n; ++i) { for (int j = 0; j \u0026lt; n; ++j) { if (j != 0) fprintf(fout, \u0026#34; \u0026#34;); fprintf(fout, \u0026#34;%d\u0026#34;, final_grid[i][j]); } fprintf(fout, \u0026#34;\\n\u0026#34;); } fclose(fout); for (int i = 0; i \u0026lt; n; ++i) { delete[] final_grid[i]; } delete[] final_grid; } else { MPI_Send(sendbuf, local_rows * n, MPI_INT, 0, 0, MPI_COMM_WORLD); delete[] sendbuf; } for (int i = 0; i \u0026lt; local_rows + 2; ++i) { delete[] current[i]; delete[] next[i]; } delete[] current; delete[] next; MPI_Finalize(); return 0; } 不过这个题倒是让我折腾半天mpi相关的部署和运行……\nL题： Accelerating EDA 注：更新了 Baseline。Input 位于只读文件系统，所以需要以只读方式打开；请修改您的代码，以避免出现 Assertion Error。\n背景 EDA，也就是电子设计自动化，是一种用于设计电子系统的方法。EDA工具可以帮助工程师设计和分析电子系统，从而提高设计效率。EDA工具通常包括模拟仿真、数字仿真、布局设计、布线设计等功能，帮助工程师快速设计出高质量的电子系统。正是 EDA，使大规模集成电路的设计成为可能，推动了电子系统的发展。\nEDA的主要任务包括：\n逻辑设计：使用硬件描述语言（如Verilog或VHDL）描述电路的功能。 仿真与验证：通过仿真工具验证设计的正确性，确保电路在逻辑和时序上符合预期。 综合：将高级的逻辑描述转换为门级网表（gate-level netlist），即由基本逻辑门（如AND、OR、NOT等）组成的电路。 布局布线（Place and Route）：将逻辑门映射到物理芯片上，并确定它们的位置和连接方式。 时序分析：确保电路在物理实现后能够满足时序要求，避免信号延迟等问题。 在EDA的布局布线阶段，布线优化是一个关键问题。布线优化的目标是在满足电路性能要求的前提下，最小化导线的长度和面积，从而减少功耗、提高芯片的性能和可靠性。\n随着技术的进展，EDA工具也遇到了很多瓶颈。如布局设计、布线设计等，都是NP完全问题，难以在合理的时间内找到最优解。因此，如何加速EDA工具成为了一个重要的问题。\n说明 RSMT 在布线优化中，直角斯坦纳最小树（Rectilinear Steiner Minimal Tree, RSMT）问题是一个经典问题。RSMT的目标是在平面上用直角线（即水平和垂直线）连接一组给定的点（称为引脚或节点），并且可以通过引入额外的点（称为斯坦纳点）来减少总布线长度。RSMT问题的解是一个树结构，称为直角斯坦纳树，它连接了所有的引脚，并且总长度最小。\nRSMT问题在VLSI（超大规模集成电路）设计中非常重要，因为它可以用于估计电路的布线长度、布线拥塞和互连延迟。RSMT问题的难点在于它是一个NP完全问题，意味着随着引脚数量的增加，问题的复杂度会急剧上升，难以在多项式时间内找到最优解。\nFLUTE 方法 FLUTE（Fast Lookup Table Based Rectilinear Steiner Minimal Tree Algorithm）是一种基于查找表的快速RSMT算法，专门用于VLSI设计中的低度网络（即引脚数量较少的网络）。FLUTE的核心思想是通过预计算的查找表来快速构建RSMT，从而在保证高精度的同时，显著提高计算速度。\nFLUTE的主要特点：\n查找表（Lookup Table）：FLUTE通过预计算所有可能的低度网络的RSMT解，并将这些解存储在查找表中。对于给定的网络，FLUTE只需查找表中的相应条目，即可快速得到最优解。\n低度网络的优化：FLUTE特别适用于低度网络（引脚数量较少的网络），因为低度网络的解空间较小，查找表可以覆盖所有可能的解。对于高度网络（引脚数量较多的网络），FLUTE使用网络分割技术将网络分解为多个低度子网络，然后分别求解。\n精度与速度的权衡：FLUTE允许用户通过调整参数来控制精度和计算速度之间的权衡。用户可以选择更高的精度，但计算时间会相应增加；或者选择更快的计算速度，但精度可能会有所降低。\nFLUTE的工作流程： 查找表生成：FLUTE首先为低度网络生成查找表。查找表中存储了每个网络的潜在最优布线长度向量（POWV）和对应的潜在最优斯坦纳树（POST）。这些数据是通过边界压缩技术和递归算法生成的。 网络分割：对于高度网络，FLUTE使用网络分割技术将网络分解为多个低度子网络。分割时，FLUTE会评估不同的分割方式，并选择最优的分割方案。 查找表查询：对于每个低度子网络，FLUTE通过查找表快速找到最优的RSMT解。 结果合并：将各个子网络的解合并，得到整个网络的RSMT。 FLUTE的优势： 速度快：FLUTE的计算速度非常快，尤其是对于低度网络，几乎可以与RMST（上文是RSMT，这里是RMST，并不相同）算法相媲美。 精度高：FLUTE在低度网络上能够找到最优解，对于高度网络也能提供非常接近最优的解。 灵活性：FLUTE允许用户通过调整参数来控制精度和计算速度之间的权衡，适用于不同的应用场景。 目标 本项目的目标是加速FLUTE算法。我们提供了baseline.cpp可供使用，这是一个单线程程序。\n运行方式 确保可执行文件的同目录下有POMV9.dat和PORT9.dat，这是FLUTE算法的查找表。运行时需要传入两个参数，分别是输入文件和输出文件。例如：\n./flute data.in.bin data.out.bin 数据格式 你可以使用datagen生成数据，也可以使用我们提供的数据进行测试。输入输出都是二进制格式，数据格式如下：\n输入文件：data.in.bin 网络数量（num_nets）：一个整数，表示网络的总数。 引脚起始索引（pin_st）：一个整数数组，长度为num_nets + 1，表示每个网络的引脚在data_x和data_y数组中的起始位置。 引脚坐标（data_x 和 data_y）：两个数组，分别存储所有引脚的x坐标和y坐标。 输出文件：data.out.bin 结果x坐标（result_x）：一个数组，存储所有分支的x坐标。 结果y坐标（result_y）：一个数组，存储所有分支的y坐标。 邻居节点索引（result_n）：一个数组，存储每个分支的邻居节点索引。 附件中提供了一组参考输入输出文件，d1.in.bin与d1.out.bin。你也可以通过datagen.cpp直接生成测试文件。\n运行环境 本题较为特殊，需要提交一个env文件指定运行环境，可选的环境如下：\nintel：Intel 8358 CPU，16核心，24GB内存 arm：Kunpeng 920 CPU，16核心，24GB内存 cuda：NVIDIA L40 GPU，48G显存，附 4 核心 CPU，24GB内存 npu：华为 Ascend 910B NPU，64G显存，附 20 核心 CPU，128GB内存。选手可用测试镜像crmirror.lcpu.dev/hpcgame/asend:ubuntu20.04。 评测标准 本题总分 200，采用分点评测的方式。\nnum_nets = 10000的测试点，只验证正确性，20分。\nnum_nets = 1000000的输入180分，设运行时长为T，评分规则如下：\nT大于 800s：不得分 20 \u0026lt; T \u0026lt;= 800：得分 = 450/(x-15) 14 \u0026lt; T \u0026lt;= 20：得分 = 90 + 15*(20-T) T \u0026lt;= 14：满分 提交内容 提交一个impl.cpp文件、以及选择的运行环境、自定义的编译选项（flags）\n运行环境可以参考上文，自定义的编译选项可以为空，也可以指定编译选项，例如-O2。\n最终我们运行的编译命令是：\nintel：icpx impl.cpp -o impl $(flags) arm：bisheng-clang++ impl.cpp -o impl $(flags) cuda：nvcc impl.cu -o impl -arch=sm_89 $(flags) npu：联系管理员，手工评测 开始想的主打intel（实质是因为华为npu不太敢写，然后cuda的L40半天pending）\n先交baseline再在编译的flag里面加各种优化先骗个20分咯xD\n在考虑优化，简单在main里面对循环加openmp的并行化，交上去，28分\n后续思考能不能拉进去avx指令集做进一步的优化，然并卵，写不明白交上去就wa掉了。\n借助ai？这么长的文本也没几个ai能处理的来\n{\u0026ldquo;env\u0026rdquo;:\u0026ldquo;intel\u0026rdquo;,\u0026ldquo;flags\u0026rdquo;:\u0026quot;-O3 -march=native -ftree-vectorize -funroll-loops -mavx2 -ffast-math -qopenmp\u0026quot;}\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;limits.h\u0026gt; #include \u0026lt;algorithm\u0026gt; #include \u0026lt;cassert\u0026gt; #include \u0026lt;omp.h\u0026gt; using std::max; using std::min; typedef unsigned DTYPE; typedef struct { DTYPE x, y; // starting point of the branch int n; // index of neighbor } Branch; typedef struct { int deg; // degree DTYPE length; // total wirelength Branch *branch; // array of tree branches } Tree; // Major functions void readLUT(); DTYPE flute_wl(int d, DTYPE x[], DTYPE y[], int acc); //Macro: DTYPE flutes_wl(int d, DTYPE xs[], DTYPE ys[], int s[], int acc); Tree flute(int d, DTYPE x[], DTYPE y[], int acc); //Macro: Tree flutes(int d, DTYPE xs[], DTYPE ys[], int s[], int acc); DTYPE wirelength(Tree t); void printtree(Tree t); #define POWVFILE \u0026#34;POWV9.dat\u0026#34; // LUT for POWV (Wirelength Vector) #define PORTFILE \u0026#34;PORT9.dat\u0026#34; // LUT for PORT (Routing Tree) #define D 9 // LUT is used for d \u0026lt;= D, D \u0026lt;= 9 #define FLUTEROUTING 1 // 1 to construct routing, 0 to estimate WL only #define REMOVE_DUPLICATE_PIN 0 // Remove dup. pin for flute_wl() \u0026amp; flute() #define ACCURACY 8 // Default accuracy is 8 //#define MAXD 2008840 // max. degree of a net that can be handled #define MAXD 1000 // max. degree of a net that can be handled // Other useful functions DTYPE flutes_wl_LD(int d, DTYPE xs[], DTYPE ys[], int s[]); DTYPE flutes_wl_MD(int d, DTYPE xs[], DTYPE ys[], int s[], int acc); DTYPE flutes_wl_RDP(int d, DTYPE xs[], DTYPE ys[], int s[], int acc); Tree flutes_LD(int d, DTYPE xs[], DTYPE ys[], int s[]); Tree flutes_MD(int d, DTYPE xs[], DTYPE ys[], int s[], int acc); Tree flutes_RDP(int d, DTYPE xs[], DTYPE ys[], int s[], int acc); #if REMOVE_DUPLICATE_PIN==1 #else #define flutes_wl(d, xs, ys, s, acc) flutes_wl_ALLD(d, xs, ys, s, acc) #define flutes(d, xs, ys, s, acc) flutes_ALLD(d, xs, ys, s, acc) #endif #define flutes_wl_ALLD(d, xs, ys, s, acc) flutes_wl_LMD(d, xs, ys, s, acc) #define flutes_ALLD(d, xs, ys, s, acc) flutes_LMD(d, xs, ys, s, acc) #define flutes_wl_LMD(d, xs, ys, s, acc) \\ (d\u0026lt;=D ? flutes_wl_LD(d, xs, ys, s) : flutes_wl_MD(d, xs, ys, s, acc)) #define flutes_LMD(d, xs, ys, s, acc) \\ (d\u0026lt;=D ? flutes_LD(d, xs, ys, s) : flutes_MD(d, xs, ys, s, acc)) #define ADIFF(x,y) ((x)\u0026gt;(y)?(x-y):(y-x)) // Absolute difference #if D\u0026lt;=7 #elif D==8 #elif D==9 #define MGROUP 362880/4 // Max. # of groups, 9! = 362880 #define MPOWV 79 // Max. # of POWVs per group #endif int numgrp[10]={0,0,0,0,6,30,180,1260,10080,90720}; typedef struct point { DTYPE x, y; int o; //ZZ: temporarily store discretized x value indices } POINT; typedef POINT* POINTptr; struct csoln { unsigned char parent; unsigned char seg[12]; // Add: 0..i, Sub: j..11; seg[i+1]=seg[j-1]=0 unsigned char row[D-2], col[D-2]; unsigned char neighbor[2*D-2]; }; struct csoln *LUT[D+1][MGROUP]; // storing 4 .. D int numsoln[D+1][MGROUP]; void readLUT(); Tree flute(int d, DTYPE x[], DTYPE y[], int acc); Tree flutes_LD(int d, DTYPE xs[], DTYPE ys[], int s[]); Tree flutes_MD(int d, DTYPE xs[], DTYPE ys[], int s[], int acc); Tree flutes_RDP(int d, DTYPE xs[], DTYPE ys[], int s[], int acc); Tree dmergetree(Tree t1, Tree t2); Tree hmergetree(Tree t1, Tree t2, int s[]); Tree vmergetree(Tree t1, Tree t2); void printtree(Tree t); void readLUT() { static bool LUTread = false; if(LUTread) return; LUTread = true; FILE *fpwv, *fprt; struct csoln *p; int d, i, j, k, kk, ns, nn, ne; unsigned char line[99], *linep, c; unsigned char charnum[256]; unsigned char divd[256], modd[256], div16[256], mod16[256], dsq; for (i=0; i\u0026lt;=255; i++) { if (\u0026#39;0\u0026#39;\u0026lt;=i \u0026amp;\u0026amp; i\u0026lt;=\u0026#39;9\u0026#39;) charnum[i] = i - \u0026#39;0\u0026#39;; else if (i\u0026gt;=\u0026#39;A\u0026#39;) charnum[i] = i - \u0026#39;A\u0026#39; + 10; else // if (i==\u0026#39;$\u0026#39; || i==\u0026#39;\\n\u0026#39; || ... ) charnum[i] = 0; div16[i] = i/16; mod16[i] = i%16; } fpwv=fopen(POWVFILE, \u0026#34;r\u0026#34;); if (fpwv == NULL) { printf(\u0026#34;Error in opening %s\\n\u0026#34;, POWVFILE); printf(\u0026#34;Please make sure %s and %s\\n\u0026#34; \u0026#34;are in the current working directory.\\n\u0026#34;, POWVFILE, PORTFILE); exit(1); } #if FLUTEROUTING==1 fprt=fopen(PORTFILE, \u0026#34;r\u0026#34;); if (fprt == NULL) { printf(\u0026#34;Error in opening %s\\n\u0026#34;, PORTFILE); printf(\u0026#34;Please make sure %s and %s\\n(found in the Flute directory of UMpack)\\n\u0026#34; \u0026#34;are in the current working directory.\\n\u0026#34;, POWVFILE, PORTFILE); exit(1); } #endif for (d=4; d\u0026lt;=D; d++) { for (i=0; i\u0026lt;=255; i++) { divd[i] = i/d; modd[i] = i%d; } dsq=d*d; fscanf(fpwv, \u0026#34;d=%d\\n\u0026#34;, \u0026amp;d); #if FLUTEROUTING==1 fscanf(fprt, \u0026#34;d=%d\\n\u0026#34;, \u0026amp;d); #endif for (k=0; k\u0026lt;numgrp[d]; k++) { ns = (int) charnum[fgetc(fpwv)]; if (ns==0) { // same as some previous group fscanf(fpwv, \u0026#34;%d\\n\u0026#34;, \u0026amp;kk); numsoln[d][k] = numsoln[d][kk]; LUT[d][k] = LUT[d][kk]; } else { fgetc(fpwv); // \u0026#39;\\n\u0026#39; numsoln[d][k] = ns; p = (struct csoln*) malloc(ns*sizeof(struct csoln)); LUT[d][k] = p; for (i=1; i\u0026lt;=ns; i++) { linep = (unsigned char*)fgets((char*)line, 99, fpwv); p-\u0026gt;parent = charnum[*(linep++)]; j = 0; while ((p-\u0026gt;seg[j++] = charnum[*(linep++)]) != 0) ; j = 10; while ((p-\u0026gt;seg[j--] = charnum[*(linep++)]) != 0) ; #if FLUTEROUTING==1 nn = 2*d-2; ne = 2*d-3; fread(line, 1, d-2, fprt); linep=line; for (j=d; j\u0026lt;nn; j++) { c = *(linep++); if (c \u0026gt;= dsq) { c -= dsq; p-\u0026gt;neighbor[divd[c]] = j; ne--; } p-\u0026gt;row[j-d] = divd[c]; p-\u0026gt;col[j-d] = modd[c]; p-\u0026gt;neighbor[j] = j; // initialized } fread(line, 1, ne+1, fprt); linep=line; // last char = \\n for (j=0; j\u0026lt;ne; j++) { c = *(linep++); p-\u0026gt;neighbor[div16[c]] = mod16[c]; } #endif p++; } } } } } static int compare_x(const void *a, const void *b) { const POINT *pa = *(const POINTptr*)a; const POINT *pb = *(const POINTptr*)b; if (pa-\u0026gt;x \u0026lt; pb-\u0026gt;x) return -1; if (pa-\u0026gt;x \u0026gt; pb-\u0026gt;x) return 1; if (pa-\u0026gt;y \u0026lt; pb-\u0026gt;y) return -1; if (pa-\u0026gt;y \u0026gt; pb-\u0026gt;y) return 1; return (pa \u0026lt; pb) ? -1 : 1; // 确保稳定性 } // 比较函数，用于按y排序ptp数组 static int compare_y(const void *a, const void *b) { const POINT *pa = *(const POINTptr*)a; const POINT *pb = *(const POINTptr*)b; if (pa-\u0026gt;y \u0026lt; pb-\u0026gt;y) return -1; if (pa-\u0026gt;y \u0026gt; pb-\u0026gt;y) return 1; if (pa-\u0026gt;x \u0026lt; pb-\u0026gt;x) return -1; if (pa-\u0026gt;x \u0026gt; pb-\u0026gt;x) return 1; return (pa \u0026lt; pb) ? -1 : 1; } Tree flute(int d, DTYPE x[], DTYPE y[], int acc) { unsigned allocateSize = MAXD; if (d \u0026gt; MAXD) { allocateSize = d + 1; } // 使用对齐的内存分配以提高SIMD效率 DTYPE *xs, *ys; int *s; POINT *pt; POINTptr *ptp; posix_memalign((void **)\u0026amp;xs, 64, sizeof(DTYPE) * allocateSize); posix_memalign((void **)\u0026amp;ys, 64, sizeof(DTYPE) * allocateSize); posix_memalign((void **)\u0026amp;s, 64, sizeof(int) * allocateSize); pt = (POINT *)malloc(sizeof(POINT) * allocateSize); ptp = (POINTptr *)malloc(sizeof(POINTptr) * allocateSize); for (int i = 0; i \u0026lt; d; i++) { pt[i].x = x[i]; pt[i].y = y[i]; ptp[i] = \u0026amp;pt[i]; } // 使用快速排序按x排序 qsort(ptp, d, sizeof(POINTptr), compare_x); // 使用SIMD填充xs数组 #pragma omp simd aligned(xs : 64) for (int i = 0; i \u0026lt; d; i++) { xs[i] = ptp[i]-\u0026gt;x; ptp[i]-\u0026gt;o = i; } // 使用快速排序按y排序 qsort(ptp, d, sizeof(POINTptr), compare_y); // 使用SIMD填充ys和s数组 #pragma omp simd aligned(ys, s : 64) for (int i = 0; i \u0026lt; d; i++) { ys[i] = ptp[i]-\u0026gt;y; s[i] = ptp[i]-\u0026gt;o; } Tree t = flutes(d, xs, ys, s, acc); // 释放内存 free(xs); free(ys); free(s); free(pt); free(ptp); return t; } // xs[] and ys[] are coords in x and y in sorted order // s[] is a list of nodes in increasing y direction // if nodes are indexed in the order of increasing x coord // i.e., s[i] = s_i in defined in paper // The points are (xs[s[i]], ys[i]) for i=0..d-1 // or (xs[i], ys[si[i]]) for i=0..d-1 // ZZ: RDP denotes Remove Duplicate Pin, simple, like std::unique Tree flutes_RDP(int d, DTYPE xs[], DTYPE ys[], int s[], int acc) { int i, j, ss; for (i=0; i\u0026lt;d-1; i++) { if (xs[s[i]]==xs[s[i+1]] \u0026amp;\u0026amp; ys[i]==ys[i+1]) { if (s[i] \u0026lt; s[i+1]) ss = s[i+1]; else { ss = s[i]; s[i] = s[i+1]; } for (j=i+2; j\u0026lt;d; j++) { ys[j-1] = ys[j]; s[j-1] = s[j]; } for (j=ss+1; j\u0026lt;d; j++) xs[j-1] = xs[j]; for (j=0; j\u0026lt;=d-2; j++) if (s[j] \u0026gt; ss) s[j]--; i--; d--; } } return flutes_ALLD(d, xs, ys, s, acc); } // For low-degree, i.e., 2 \u0026lt;= d \u0026lt;= D Tree flutes_LD(int d, DTYPE xs[], DTYPE ys[], int s[]) { int k, pi, i, j; struct csoln *rlist, *bestrlist; DTYPE dd[2*D-2]; // 0..D-2 for v, D-1..2*D-3 for h DTYPE minl, sum; DTYPE l[MPOWV+1]; int hflip; Tree t; t.deg = d; t.branch = (Branch *) malloc((2*d-2)*sizeof(Branch)); if (d == 2) { // printf(\u0026#34;s[0]=%d, s[1]=%d, xs[0,1]=[%u,%u], ys[0,1]=[%u,%u]\\n\u0026#34;, // s[0], s[1], xs[0], xs[1], ys[0], ys[1]); minl = xs[1]-xs[0]+ys[1]-ys[0]; t.branch[0].x = xs[s[0]]; t.branch[0].y = ys[0]; t.branch[0].n = 1; t.branch[1].x = xs[s[1]]; t.branch[1].y = ys[1]; t.branch[1].n = 1; } else if (d == 3) { minl = xs[2]-xs[0]+ys[2]-ys[0]; t.branch[0].x = xs[s[0]]; t.branch[0].y = ys[0]; t.branch[0].n = 3; t.branch[1].x = xs[s[1]]; t.branch[1].y = ys[1]; t.branch[1].n = 3; t.branch[2].x = xs[s[2]]; t.branch[2].y = ys[2]; t.branch[2].n = 3; t.branch[3].x = xs[1]; t.branch[3].y = ys[1]; t.branch[3].n = 3; } else { k = 0; if (s[0] \u0026lt; s[2]) k++; if (s[1] \u0026lt; s[2]) k++; for (i=3; i\u0026lt;=d-1; i++) { // p0=0 always, skip i=1 for symmetry pi = s[i]; for (j=d-1; j\u0026gt;i; j--) if (s[j] \u0026lt; s[i]) pi--; k = pi + (i+1)*k; } if (k \u0026lt; numgrp[d]) { // no horizontal flip hflip = 0; for (i=1; i\u0026lt;=d-3; i++) { dd[i]=ys[i+1]-ys[i]; dd[d-1+i]=xs[i+1]-xs[i]; } } else { hflip = 1; k=2*numgrp[d]-1-k; for (i=1; i\u0026lt;=d-3; i++) { dd[i]=ys[i+1]-ys[i]; dd[d-1+i]=xs[d-1-i]-xs[d-2-i]; } } minl = l[0] = xs[d-1]-xs[0]+ys[d-1]-ys[0]; rlist = LUT[d][k]; for (i=0; rlist-\u0026gt;seg[i]\u0026gt;0; i++) minl += dd[rlist-\u0026gt;seg[i]]; bestrlist = rlist; l[1] = minl; j = 2; while (j \u0026lt;= numsoln[d][k]) { rlist++; sum = l[rlist-\u0026gt;parent]; for (i=0; rlist-\u0026gt;seg[i]\u0026gt;0; i++) sum += dd[rlist-\u0026gt;seg[i]]; for (i=10; rlist-\u0026gt;seg[i]\u0026gt;0; i--) sum -= dd[rlist-\u0026gt;seg[i]]; if (sum \u0026lt; minl) { minl = sum; bestrlist = rlist; } l[j++] = sum; } t.branch[0].x = xs[s[0]]; t.branch[0].y = ys[0]; t.branch[1].x = xs[s[1]]; t.branch[1].y = ys[1]; for (i=2; i\u0026lt;d-2; i++) { t.branch[i].x = xs[s[i]]; t.branch[i].y = ys[i]; t.branch[i].n = bestrlist-\u0026gt;neighbor[i]; } t.branch[d-2].x = xs[s[d-2]]; t.branch[d-2].y = ys[d-2]; t.branch[d-1].x = xs[s[d-1]]; t.branch[d-1].y = ys[d-1]; if (hflip) { if (s[1] \u0026lt; s[0]) { t.branch[0].n = bestrlist-\u0026gt;neighbor[1]; t.branch[1].n = bestrlist-\u0026gt;neighbor[0]; } else { t.branch[0].n = bestrlist-\u0026gt;neighbor[0]; t.branch[1].n = bestrlist-\u0026gt;neighbor[1]; } if (s[d-1] \u0026lt; s[d-2]) { t.branch[d-2].n = bestrlist-\u0026gt;neighbor[d-1]; t.branch[d-1].n = bestrlist-\u0026gt;neighbor[d-2]; } else { t.branch[d-2].n = bestrlist-\u0026gt;neighbor[d-2]; t.branch[d-1].n = bestrlist-\u0026gt;neighbor[d-1]; } for (i=d; i\u0026lt;2*d-2; i++) { t.branch[i].x = xs[d-1-bestrlist-\u0026gt;col[i-d]]; t.branch[i].y = ys[bestrlist-\u0026gt;row[i-d]]; t.branch[i].n = bestrlist-\u0026gt;neighbor[i]; } } else { // !hflip if (s[0] \u0026lt; s[1]) { t.branch[0].n = bestrlist-\u0026gt;neighbor[1]; t.branch[1].n = bestrlist-\u0026gt;neighbor[0]; } else { t.branch[0].n = bestrlist-\u0026gt;neighbor[0]; t.branch[1].n = bestrlist-\u0026gt;neighbor[1]; } if (s[d-2] \u0026lt; s[d-1]) { t.branch[d-2].n = bestrlist-\u0026gt;neighbor[d-1]; t.branch[d-1].n = bestrlist-\u0026gt;neighbor[d-2]; } else { t.branch[d-2].n = bestrlist-\u0026gt;neighbor[d-2]; t.branch[d-1].n = bestrlist-\u0026gt;neighbor[d-1]; } for (i=d; i\u0026lt;2*d-2; i++) { t.branch[i].x = xs[bestrlist-\u0026gt;col[i-d]]; t.branch[i].y = ys[bestrlist-\u0026gt;row[i-d]]; t.branch[i].n = bestrlist-\u0026gt;neighbor[i]; } } } t.length = minl; return t; } // For medium-degree, i.e., D+1 \u0026lt;= d \u0026lt;= D2 Tree flutes_MD(int d, DTYPE xs[], DTYPE ys[], int s[], int acc) { unsigned allocateSize = MAXD; if (d \u0026gt; MAXD) allocateSize = d+1; DTYPE *x1 = (DTYPE*) malloc(sizeof(DTYPE)*allocateSize); DTYPE *x2 = (DTYPE*) malloc(sizeof(DTYPE)*allocateSize); DTYPE *y1 = (DTYPE*) malloc(sizeof(DTYPE)*allocateSize); DTYPE *y2 = (DTYPE*) malloc(sizeof(DTYPE)*allocateSize); int *si = (int*) malloc(sizeof(int)*allocateSize); int *s1 = (int*) malloc(sizeof(int)*allocateSize); int *s2 = (int*) malloc(sizeof(int)*allocateSize); float *score = (float*) malloc(sizeof(float)*2*allocateSize); float *penalty = (float*) malloc(sizeof(float)*allocateSize); DTYPE *distx = (DTYPE*) malloc(sizeof(DTYPE)*allocateSize); DTYPE *disty = (DTYPE*) malloc(sizeof(DTYPE)*allocateSize); // DTYPE x1[MAXD], x2[MAXD], y1[MAXD], y2[MAXD]; // int si[MAXD], s1[MAXD], s2[MAXD]; // float score[2*MAXD], penalty[MAXD]; // DTYPE distx[MAXD], disty[MAXD]; float pnlty, dx, dy; DTYPE ll, minl, coord1, coord2; int i, r, p, maxbp, bestbp, bp, nbp, ub, lb, n1, n2, nn1, nn2, ms, newacc; Tree t, t1, t2, bestt1, bestt2; int mins, maxs, minsi, maxsi; DTYPE xydiff; #pragma omp simd for(i = 0; i \u0026lt; (int)allocateSize; ++i) { distx[i] = disty[i] = 0; } if (s[0] \u0026lt; s[d-1]) { ms = max(s[0], s[1]); for (i=2; i\u0026lt;=ms; i++) ms = max(ms, s[i]); if (ms \u0026lt;= d-3) { for (i=0; i\u0026lt;=ms; i++) { x1[i] = xs[i]; y1[i] = ys[i]; s1[i] = s[i]; } x1[ms+1] = xs[ms]; y1[ms+1] = ys[ms]; s1[ms+1] = ms+1; s2[0] = 0; for (i=1; i\u0026lt;=d-1-ms; i++) s2[i] = s[i+ms]-ms; t1 = flutes_LMD(ms+2, x1, y1, s1, acc); t2 = flutes_LMD(d-ms, xs+ms, ys+ms, s2, acc); t = dmergetree(t1, t2); free(t1.branch); free(t2.branch); free(x1); free(x2); free(y1); free(y2); free(si); free(s1); free(s2); free(score); free(penalty); free(distx); free(disty); return t; } } else { // (s[0] \u0026gt; s[d-1]) ms = min(s[0], s[1]); for (i=2; i\u0026lt;=d-1-ms; i++) ms = min(ms, s[i]); if (ms \u0026gt;= 2) { x1[0] = xs[ms]; y1[0] = ys[0]; s1[0] = s[0]-ms+1; for (i=1; i\u0026lt;=d-1-ms; i++) { x1[i] = xs[i+ms-1]; y1[i] = ys[i]; s1[i] = s[i]-ms+1; } x1[d-ms] = xs[d-1]; y1[d-ms] = ys[d-1-ms]; s1[d-ms] = 0; s2[0] = ms; for (i=1; i\u0026lt;=ms; i++) s2[i] = s[i+d-1-ms]; t1 = flutes_LMD(d+1-ms, x1, y1, s1, acc); t2 = flutes_LMD(ms+1, xs, ys+d-1-ms, s2, acc); t = dmergetree(t1, t2); free(t1.branch); free(t2.branch); free(x1); free(x2); free(y1); free(y2); free(si); free(s1); free(s2); free(score); free(penalty); free(distx); free(disty); return t; } } // Find inverse si[] of s[] for (r=0; r\u0026lt;d; r++) si[s[r]] = r; // Determine breaking directions and positions dp[] lb=max((d-acc)/5, 2); ub=d-1-lb; // Compute scores #define AA 0.6 // 2.0*BB #define BB 0.3 //#define CCC max(0.425-0.005*d-0.015*acc, 0.1) //#define CCC max(0.43-0.005*d-0.01*acc, 0.1) #define CCC max(0.41-0.005*d, 0.1) #define DD 4.8 float DDD = DD/(d-1); // Compute penalty[] dx = CCC*(xs[d-2]-xs[1])/(d-3); dy = CCC*(ys[d-2]-ys[1])/(d-3); for (r = d/2, pnlty = 0; r\u0026gt;=2; r--, pnlty += dx) penalty[r] = pnlty, penalty[d-1-r] = pnlty; penalty[1] = pnlty, penalty[d-2] = pnlty; penalty[0] = pnlty, penalty[d-1] = pnlty; for (r = d/2-1, pnlty = dy; r\u0026gt;=2; r--, pnlty += dy) penalty[s[r]] += pnlty, penalty[s[d-1-r]] += pnlty; penalty[s[1]] += pnlty, penalty[s[d-2]] += pnlty; penalty[s[0]] += pnlty, penalty[s[d-1]] += pnlty; //#define CC 0.16 //#define v(r) ((r==0||r==1||r==d-2||r==d-1) ? d-3 : abs(d-1-r-r)) // for (r=0; r\u0026lt;d; r++) // penalty[r] = v(r)*dx + v(si[r])*dy; // Compute distx[], disty[] xydiff = (xs[d-1] - xs[0]) - (ys[d-1] - ys[0]); if (s[0] \u0026lt; s[1]) mins = s[0], maxs = s[1]; else mins = s[1], maxs = s[0]; if (si[0] \u0026lt; si[1]) minsi = si[0], maxsi = si[1]; else minsi = si[1], maxsi = si[0]; for (r=2; r\u0026lt;=ub; r++) { if (s[r] \u0026lt; mins) mins = s[r]; else if (s[r] \u0026gt; maxs) maxs = s[r]; distx[r] = xs[maxs] - xs[mins]; if (si[r] \u0026lt; minsi) minsi = si[r]; else if (si[r] \u0026gt; maxsi) maxsi = si[r]; disty[r] = ys[maxsi] - ys[minsi] + xydiff; } if (s[d-2] \u0026lt; s[d-1]) mins = s[d-2], maxs = s[d-1]; else mins = s[d-1], maxs = s[d-2]; if (si[d-2] \u0026lt; si[d-1]) minsi = si[d-2], maxsi = si[d-1]; else minsi = si[d-1], maxsi = si[d-2]; #pragma omp simd for (r=d-3; r\u0026gt;=lb; r--) { if (s[r] \u0026lt; mins) mins = s[r]; else if (s[r] \u0026gt; maxs) maxs = s[r]; distx[r] += xs[maxs] - xs[mins]; if (si[r] \u0026lt; minsi) minsi = si[r]; else if (si[r] \u0026gt; maxsi) maxsi = si[r]; disty[r] += ys[maxsi] - ys[minsi]; } nbp=0; #pragma omp simd for (r=lb; r\u0026lt;=ub; r++) { if (si[r]\u0026lt;=1) score[nbp] = (xs[r+1] - xs[r-1]) - penalty[r] - AA*(ys[2]-ys[1]) - DDD*disty[r]; else if (si[r]\u0026gt;=d-2) score[nbp] = (xs[r+1] - xs[r-1]) - penalty[r] - AA*(ys[d-2]-ys[d-3]) - DDD*disty[r]; else score[nbp] = (xs[r+1] - xs[r-1]) - penalty[r] - BB*(ys[si[r]+1]-ys[si[r]-1]) - DDD*disty[r]; nbp++; if (s[r]\u0026lt;=1) score[nbp] = (ys[r+1] - ys[r-1]) - penalty[s[r]] - AA*(xs[2]-xs[1]) - DDD*distx[r]; else if (s[r]\u0026gt;=d-2) score[nbp] = (ys[r+1] - ys[r-1]) - penalty[s[r]] - AA*(xs[d-2]-xs[d-3]) - DDD*distx[r]; else score[nbp] = (ys[r+1] - ys[r-1]) - penalty[s[r]] - BB*(xs[s[r]+1]-xs[s[r]-1]) - DDD*distx[r]; nbp++; } if (acc \u0026lt;= 3) newacc = 1; else { newacc = acc/2; if (acc \u0026gt;= nbp) acc = nbp-1; } minl = (DTYPE) INT_MAX; bestt1.branch = bestt2.branch = NULL; for (i=0; i\u0026lt;acc; i++) { maxbp = 0; for (bp=1; bp\u0026lt;nbp; bp++) if (score[maxbp] \u0026lt; score[bp]) maxbp = bp; score[maxbp] = -9e9f; #define BreakPt(bp) ((bp)/2+lb) #define BreakInX(bp) ((bp)%2==0) p = BreakPt(maxbp); // Breaking in p if (BreakInX(maxbp)) { // break in x n1 = n2 = 0; for (r=0; r\u0026lt;d; r++) { if (s[r] \u0026lt; p) { s1[n1] = s[r]; y1[n1] = ys[r]; n1++; } else if (s[r] \u0026gt; p) { s2[n2] = s[r]-p; y2[n2] = ys[r]; n2++; } else { // if (s[r] == p) i.e., r = si[p] s1[n1] = p; s2[n2] = 0; y1[n1] = y2[n2] = ys[r]; nn1 = n1; nn2 = n2; n1++; n2++; } } t1 = flutes_LMD(p+1, xs, y1, s1, newacc); t2 = flutes_LMD(d-p, xs+p, y2, s2, newacc); ll = t1.length + t2.length; coord1 = t1.branch[t1.branch[nn1].n].y; coord2 = t2.branch[t2.branch[nn2].n].y; if (t2.branch[nn2].y \u0026gt; max(coord1, coord2)) ll -= t2.branch[nn2].y - max(coord1, coord2); else if (t2.branch[nn2].y \u0026lt; min(coord1, coord2)) ll -= min(coord1, coord2) - t2.branch[nn2].y; } else { // if (!BreakInX(maxbp)) n1 = n2 = 0; for (r=0; r\u0026lt;d; r++) { if (si[r] \u0026lt; p) { s1[si[r]] = n1; x1[n1] = xs[r]; n1++; } else if (si[r] \u0026gt; p) { s2[si[r]-p] = n2; x2[n2] = xs[r]; n2++; } else { // if (si[r] == p) i.e., r = s[p] s1[p] = n1; s2[0] = n2; x1[n1] = x2[n2] = xs[r]; n1++; n2++; } } t1 = flutes_LMD(p+1, x1, ys, s1, newacc); t2 = flutes_LMD(d-p, x2, ys+p, s2, newacc); ll = t1.length + t2.length; coord1 = t1.branch[t1.branch[p].n].x; coord2 = t2.branch[t2.branch[0].n].x; if (t2.branch[0].x \u0026gt; max(coord1, coord2)) ll -= t2.branch[0].x - max(coord1, coord2); else if (t2.branch[0].x \u0026lt; min(coord1, coord2)) ll -= min(coord1, coord2) - t2.branch[0].x; } if (minl \u0026gt; ll) { minl = ll; free(bestt1.branch); free(bestt2.branch); bestt1 = t1; bestt2 = t2; bestbp = maxbp; } else { free(t1.branch); free(t2.branch); } } if (BreakInX(bestbp)) t = hmergetree(bestt1, bestt2, s); else t = vmergetree(bestt1, bestt2); free(bestt1.branch); free(bestt2.branch); free(x1); free(x2); free(y1); free(y2); free(si); free(s1); free(s2); free(score); free(penalty); free(distx); free(disty); return t; } Tree dmergetree(Tree t1, Tree t2) { int i, d, prev, curr, next, offset1, offset2; Tree t; t.deg = d = t1.deg + t2.deg - 2; t.length = t1.length + t2.length; t.branch = (Branch *) malloc((2*d-2)*sizeof(Branch)); offset1 = t2.deg-2; offset2 = 2*t1.deg-4; for (i=0; i\u0026lt;=t1.deg-2; i++) { t.branch[i].x = t1.branch[i].x; t.branch[i].y = t1.branch[i].y; t.branch[i].n = t1.branch[i].n + offset1; } for (i=t1.deg-1; i\u0026lt;=d-1; i++) { t.branch[i].x = t2.branch[i-t1.deg+2].x; t.branch[i].y = t2.branch[i-t1.deg+2].y; t.branch[i].n = t2.branch[i-t1.deg+2].n + offset2; } for (i=d; i\u0026lt;=d+t1.deg-3; i++) { t.branch[i].x = t1.branch[i-offset1].x; t.branch[i].y = t1.branch[i-offset1].y; t.branch[i].n = t1.branch[i-offset1].n + offset1; } for (i=d+t1.deg-2; i\u0026lt;=2*d-3; i++) { t.branch[i].x = t2.branch[i-offset2].x; t.branch[i].y = t2.branch[i-offset2].y; t.branch[i].n = t2.branch[i-offset2].n + offset2; } prev = t2.branch[0].n + offset2; curr = t1.branch[t1.deg-1].n + offset1; next = t.branch[curr].n; while (curr != next) { t.branch[curr].n = prev; prev = curr; curr = next; next = t.branch[curr].n; } t.branch[curr].n = prev; return t; } Tree hmergetree(Tree t1, Tree t2, int s[]) { int i, prev, curr, next, extra, offset1, offset2; int p, ii, n1, n2, nn1, nn2; DTYPE coord1, coord2; Tree t; t.deg = t1.deg + t2.deg - 1; t.length = t1.length + t2.length; t.branch = (Branch *) malloc((2*t.deg-2)*sizeof(Branch)); offset1 = t2.deg-1; offset2 = 2*t1.deg-3; p = t1.deg - 1; n1 = n2 = 0; for (i=0; i\u0026lt;t.deg; i++) { if (s[i] \u0026lt; p) { t.branch[i].x = t1.branch[n1].x; t.branch[i].y = t1.branch[n1].y; t.branch[i].n = t1.branch[n1].n + offset1; n1++; } else if (s[i] \u0026gt; p) { t.branch[i].x = t2.branch[n2].x; t.branch[i].y = t2.branch[n2].y; t.branch[i].n = t2.branch[n2].n + offset2; n2++; } else { t.branch[i].x = t2.branch[n2].x; t.branch[i].y = t2.branch[n2].y; t.branch[i].n = t2.branch[n2].n + offset2; nn1 = n1; nn2 = n2; ii = i; n1++; n2++; } } for (i=t.deg; i\u0026lt;=t.deg+t1.deg-3; i++) { t.branch[i].x = t1.branch[i-offset1].x; t.branch[i].y = t1.branch[i-offset1].y; t.branch[i].n = t1.branch[i-offset1].n + offset1; } for (i=t.deg+t1.deg-2; i\u0026lt;=2*t.deg-4; i++) { t.branch[i].x = t2.branch[i-offset2].x; t.branch[i].y = t2.branch[i-offset2].y; t.branch[i].n = t2.branch[i-offset2].n + offset2; } extra = 2*t.deg-3; coord1 = t1.branch[t1.branch[nn1].n].y; coord2 = t2.branch[t2.branch[nn2].n].y; if (t2.branch[nn2].y \u0026gt; max(coord1, coord2)) { t.branch[extra].y = max(coord1, coord2); t.length -= t2.branch[nn2].y - t.branch[extra].y; } else if (t2.branch[nn2].y \u0026lt; min(coord1, coord2)) { t.branch[extra].y = min(coord1, coord2); t.length -= t.branch[extra].y - t2.branch[nn2].y; } else t.branch[extra].y = t2.branch[nn2].y; t.branch[extra].x = t2.branch[nn2].x; t.branch[extra].n = t.branch[ii].n; t.branch[ii].n = extra; prev = extra; curr = t1.branch[nn1].n + offset1; next = t.branch[curr].n; while (curr != next) { t.branch[curr].n = prev; prev = curr; curr = next; next = t.branch[curr].n; } t.branch[curr].n = prev; return t; } Tree vmergetree(Tree t1, Tree t2) { int i, prev, curr, next, extra, offset1, offset2; DTYPE coord1, coord2; Tree t; t.deg = t1.deg + t2.deg - 1; t.length = t1.length + t2.length; t.branch = (Branch *) malloc((2*t.deg-2)*sizeof(Branch)); offset1 = t2.deg-1; offset2 = 2*t1.deg-3; for (i=0; i\u0026lt;=t1.deg-2; i++) { t.branch[i].x = t1.branch[i].x; t.branch[i].y = t1.branch[i].y; t.branch[i].n = t1.branch[i].n + offset1; } for (i=t1.deg-1; i\u0026lt;=t.deg-1; i++) { t.branch[i].x = t2.branch[i-t1.deg+1].x; t.branch[i].y = t2.branch[i-t1.deg+1].y; t.branch[i].n = t2.branch[i-t1.deg+1].n + offset2; } for (i=t.deg; i\u0026lt;=t.deg+t1.deg-3; i++) { t.branch[i].x = t1.branch[i-offset1].x; t.branch[i].y = t1.branch[i-offset1].y; t.branch[i].n = t1.branch[i-offset1].n + offset1; } for (i=t.deg+t1.deg-2; i\u0026lt;=2*t.deg-4; i++) { t.branch[i].x = t2.branch[i-offset2].x; t.branch[i].y = t2.branch[i-offset2].y; t.branch[i].n = t2.branch[i-offset2].n + offset2; } extra = 2*t.deg-3; coord1 = t1.branch[t1.branch[t1.deg-1].n].x; coord2 = t2.branch[t2.branch[0].n].x; if (t2.branch[0].x \u0026gt; max(coord1, coord2)) { t.branch[extra].x = max(coord1, coord2); t.length -= t2.branch[0].x - t.branch[extra].x; } else if (t2.branch[0].x \u0026lt; min(coord1, coord2)) { t.branch[extra].x = min(coord1, coord2); t.length -= t.branch[extra].x - t2.branch[0].x; } else t.branch[extra].x = t2.branch[0].x; t.branch[extra].y = t2.branch[0].y; t.branch[extra].n = t.branch[t1.deg-1].n; t.branch[t1.deg-1].n = extra; prev = extra; curr = t1.branch[t1.deg-1].n + offset1; next = t.branch[curr].n; while (curr != next) { t.branch[curr].n = prev; prev = curr; curr = next; next = t.branch[curr].n; } t.branch[curr].n = prev; return t; } int main(int argc, const char **argv) { if(argc != 3) { printf(\u0026#34;usage: %s \u0026lt;input.bin\u0026gt; \u0026lt;output.bin\u0026gt;\\n\u0026#34;, argv[0]); return 255; } readLUT(); FILE *input_fp = fopen(argv[1], \u0026#34;rb\u0026#34;); assert(input_fp); fseek(input_fp, 0, SEEK_END); unsigned long input_bin_size = ftell(input_fp); fseek(input_fp, 0, SEEK_SET); int *buf_input = (int *)malloc(input_bin_size); assert(fread(buf_input, 1, input_bin_size, input_fp) == input_bin_size); fclose(input_fp); int num_nets = buf_input[0]; int *pin_st = buf_input + 1; int tot_num_pins = pin_st[num_nets]; DTYPE *data_x = (DTYPE *)(pin_st + num_nets + 1); DTYPE *data_y = (DTYPE *)(data_x + tot_num_pins); DTYPE *result_x = (DTYPE *)calloc(tot_num_pins * 2, sizeof(DTYPE)); DTYPE *result_y = (DTYPE *)calloc(tot_num_pins * 2, sizeof(DTYPE)); int *result_n = (int *)calloc(tot_num_pins * 2, sizeof(int)); #pragma omp parallel for for(int net_i = 0; net_i \u0026lt; num_nets; ++net_i) { Tree t = flute(pin_st[net_i + 1] - pin_st[net_i], data_x + pin_st[net_i], data_y + pin_st[net_i], ACCURACY); // printf(\u0026#34;%d %d\\n\u0026#34;, t.deg, t.length); // for(int i = 0; i \u0026lt; 2 * t.deg - 2; ++i) { // printf(\u0026#34;%d %d %d\\n\u0026#34;, t.branch[i].x, t.branch[i].y, t.branch[i].n); // } int offset = pin_st[net_i] * 2; for(int i = 0; i \u0026lt; 2 * t.deg - 2; ++i) { result_x[offset + i] = t.branch[i].x; result_y[offset + i] = t.branch[i].y; result_n[offset + i] = t.branch[i].n; } free(t.branch); } FILE *output_fp = fopen(argv[2], \u0026#34;wb\u0026#34;); assert(output_fp); fwrite(result_x, sizeof(DTYPE), tot_num_pins, output_fp); fwrite(result_y, sizeof(DTYPE), tot_num_pins, output_fp); fwrite(result_n, sizeof(int), tot_num_pins, output_fp); fclose(output_fp); free(result_x); free(result_y); free(result_n); free(buf_input); return 0; } ","permalink":"https://blog.muwinds.com/archives/113/","summary":"\u003ch3 id=\"1签到题\"\u003e1.签到题\u003c/h3\u003e\n\u003ch3 id=\"答案的第一位是\"\u003e\u003cem\u003e答案的第一位是**1\u003c/em\u003e\u003c/h3\u003e\n\u003cp\u003e\u003cem\u003esecond number is eight\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e*叁：*\u003cem\u003e9\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e最后一个是八\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e第一题：1 8 9 8（北京大学的创校时间xD），这读完都知道在哪吧……\u003c/p\u003e\n\u003ch3 id=\"2小北问答\"\u003e2.小北问答\u003c/h3\u003e\n\u003cp\u003e1某厂的CPU采用了大小核架构，大核有超线程，小核没有超线程。已知物理核心数为12，逻辑核心数为16，大核数量为____，小核数量为____。\u003c/p\u003e","title":"PKU HPCGame 2nd WriteUp"},{"content":"转载：https://www.cnblogs.com/wangchaowei/p/8288216.html\nHeap是一种数据结构具有以下的特点：\n1）完全二叉树；\n2）heap中存储的值是偏序；\nMin-heap: 父节点的值小于或等于子节点的值；\nMax-heap: 父节点的值大于或等于子节点的值；\n堆的存储： 一般都用数组来表示堆，i结点的父结点下标就为(i–1)/2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。\n堆的操作：insert 插入一个元素：新元素被加入到heap的末尾，然后更新树以恢复堆的次序。\n每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列，现在的任务是将这个新数据插入到这个有序数据中——这就类似于直接插入排序中将一个数据并入到有序区间中。\n堆的操作：Removemax 按定义，堆中每次都删除第0个数据。为了便于重建堆，实际的操作是将最后一个数据的值赋给根结点，然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最大的，如果父结点比这个最小的子结点还大说明不需要调整了，反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程。\n堆的操作：buildHeap 堆化数组 对于叶子节点，不用调整次序，根据满二叉树的性质，叶子节点比内部节点的个数多1.所以i=n/2 -1 ，不用从n开始。\n堆排序 堆建好之后堆中第0个数据是堆中最大的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最大的数据，重复上述步骤直至堆中只有一个数据时就直接取出这个数据。\n","permalink":"https://blog.muwinds.com/archives/111/","summary":"\u003cp\u003e转载：\u003ca href=\"https://www.cnblogs.com/wangchaowei/p/8288216.html\"\u003ehttps://www.cnblogs.com/wangchaowei/p/8288216.html\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eHeap是一种数据结构具有以下的特点：\u003c/p\u003e\n\u003cp\u003e1）完全二叉树；\u003c/p\u003e\n\u003cp\u003e2）heap中存储的值是偏序；\u003c/p\u003e\n\u003cp\u003eMin-heap: 父节点的值小于或等于子节点的值；\u003c/p\u003e\n\u003cp\u003eMax-heap: 父节点的值大于或等于子节点的值；\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2025/01/202501311738323816689956.png\"\u003e\u003c/p\u003e\n\u003ch3 id=\"堆的存储\"\u003e堆的存储：\u003c/h3\u003e\n\u003cp\u003e一般都用数组来表示堆，i结点的父结点下标就为(i–1)/2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。\u003c/p\u003e","title":"数据结构——堆（Heap）大根堆、小根堆"},{"content":"openwrt主路由模式下的ipv6能力太差，同时对大量连接数的处理性能也不是很好，最后无奈换成了ikuai主路由+openwrt旁路由的模式\n之前介绍过旁路由模式，但是说的有点小问题，其实旁路由模式也可以使用ipv6，同样的，独臂路由是用作主路由，而不是作为旁路网关。\n由于博客的机器（和nas在一起的）是ipv6源站，镜像源的更新需要走旁路由，以及无线路由器也是指向旁路网关以方便用梯子的，所以得让走旁路网关的机器也能分配到ipv6地址。\n开启IPv6的前提条件是爱快负责DHCP，OpenWRT作为旁路由模式，路由器改为桥接模式（或配置了IPv6 DHCPv6，且依照下文iKuai的配置方法进行配置）。\nOpenWRT开启IPv6客户端 进入网络，配置LAN接口参数如下：\n禁用OpenWRT的IPv6服务，只在爱快启用IPv6。LAN口需要开启内置IPv6，并配置IPv6分配长度。\n新建一个接口，设置名称为lan6（随意），传输协议为DHCPv6客户端，请求IPv6地址设置为try，物理设置为自定义接口，输入内容为@lan，防火墙绑定到lan。 请求指定长度的 IPv6 前缀设置为64 。\n爱快设置 ikuai-网络设置-ipv6\n新增内网配置\n配置外网接口：\n返回openwrt，将openwrt的lan6/lan口获取到的dns地址填回ikuai\n","permalink":"https://blog.muwinds.com/archives/110/","summary":"\u003cp\u003eopenwrt主路由模式下的ipv6能力太差，同时对大量连接数的处理性能也不是很好，最后无奈换成了ikuai主路由+openwrt旁路由的模式\u003c/p\u003e\n\u003cp\u003e之前介绍过\u003ca href=\"https://blog.muwinds.com/post/71.html\"\u003e旁路由模式\u003c/a\u003e，但是说的有点小问题，其实旁路由模式也可以使用ipv6，同样的，独臂路由是用作主路由，而不是作为旁路网关。\u003c/p\u003e\n\u003cp\u003e由于博客的机器（和nas在一起的）是ipv6源站，镜像源的更新需要走旁路由，以及无线路由器也是指向旁路网关以方便用梯子的，所以得让走旁路网关的机器也能分配到ipv6地址。\u003c/p\u003e","title":"ikuai+OpenWrt旁路由模式配置ipv6"},{"content":"*本身这篇博文是为了我上大学为了减少校园网的费用所预备的，结果上大学后发现校园网不要钱……\nWireGuard是一款极其简单、快速、现代化的VPN，采用最先进的加密技术。它的目标是比IPsec更快、更简单、更精简、更有用，同时避免了配置上巨大的头痛。它比OpenVPN的性能高得多。WireGuard被设计为一种通用VPN，用于在嵌入式接口和超级计算机上运行，适用于许多不同的情况。它最初是为Linux内核发布的，现在是跨平台的（Windows、macOS、BSD、iOS、Android），可广泛部署。它目前正在大力开发中，但它可能已经被视为行业中最安全、最容易使用和最简单的VPN解决方案。\nWireGuard基于UDP协议传输，通过位于Linux内核内部的调度将高质量的加密和高速度的网络相结合，它既适用于智能手机等小型嵌入式设备，也适用于满载的主干路由器。\n多数发行版Linux有不少的一键包，这里说一下OpenWrt的配置：\n在openwrt中安装wireguard建议通过System-\u0026gt;Software进行安装：\n1、点击 Network-\u0026gt;Interface 中的 Add new interface 创建接口：\n2、接口名称建议 wireguard，协议选择 WireGuard VPN 进行创建：\n3、一般设置：\nPrivate Key 是WireGuard节点的私钥，可通过 Generate Key Pair 按钮生成，也可手动生成：# 通过ssh登录到openwrt，执行 $ wg genkey | tee privatekey | wg pubkey \u003e publickey $ cat privatekey publickey Listen Port 监听的端口，根据自己情况设置 IP Addresses 是WireGuard接口的私有IP网段，这里设置为 10.0.0.1/24 不要与其它内网IP段冲突。 4、防火墙设置，新建一个区域wg：\n5.去防火墙放行端口（多数固件允许入站之后其实是自动放行的），添加下面的自定义规则：\niptables -A FORWARD -i wg -j ACCEPT iptables -t nat -A POSTROUTING -o br-lan -j MASQUERADE 客户端配置 首先，通过这里的安装方式进行客户端的安装：https://www.wireguard.com/install/，我这里以iOS举例。\n1、点击 创建隧道-\u0026gt;手动创建：\n2、填写基本信息，主要填写下面三项，其它默认：\n名称，这里随便填 公私钥，这里点击 生成密钥对 随机生成即可。 局域网IP地址，即客户端节点IP，这个地址要设置为符合 10.0.0.1/24。 3、通过 添加节点 添加服务器信息：\n公钥，填写服务端的公钥。 对端，服务端地址+端口，由于家里没有公网，这里走的frp，所以填的是公网vps代理地址。 路由的IP地址（段），这里填写需要走wireguard网络的IP段，如：192.168.100.0/24,192.168.2.0/0,10.0.0.0/0。这样当开启wireguard vpn之后访问这些IP就会走vpn网络。 如果需要内外网都能访问，请填写0.0.0.0/0,::/0 到了这一步，客户端已经完成配置了。但是服务端还没有，因为wireguard本质上是一个P2P通信的软件，我们还要将客户端公钥信息配置到服务端才行。\n配置Peer 在openwrt配置wireguard peer信息。\n填写刚才配置的客户端信息，Save 保存即可：\n现在在手机上开启 vpn，尝试访问一下路由器地址 192.168.100.1 是否可行。\n如果需要校园网白嫖：\n环境：锐捷验证（其他验证原理类似，自行尝试），局域网没强制跳转DNS路由器，学校能找到无需验证就能上网的电脑\n需要变更的内容：\n将DNS接口从53改成5333，将DHCP的排除接口全部选上 将wireguard的监听接口改成53 放了个B站视频：以北京石景山某中学为例突破锐捷网络验证_哔哩哔哩_bilibili\nWireguard的原理：\nWireGuard 通过 UDP 安全地封装 IP 数据包。添加一个 WireGuard 接口，使用私钥和对等方的公钥对其进行配置，然后通过该接口发送数据包。密钥分发和推送配置的所有问题都超出了 WireGuard 的范围;这些问题最好留给其他层，以免我们最终导致IKE或OpenVPN的膨胀。相比之下，它更多地模仿 SSH 和 Mosh 的模型;双方都拥有彼此的公钥，然后他们就可以开始通过接换数据包。\n简单网络接口\nWireGuard的工作原理是添加一个（或多个）网络接口，如eth0或wlan0，称为wg0（或wg1、wg2、wg3等）。\n然后，可以使用ifconfig或ip地址正常配置该网络接口，使用路由或者ip路由。接口的特定WireGuard方面使用wg工具进行配置。此接口充当隧道接口。\nWireGuard将隧道IP地址与公钥和远程端点相关联。当接口向对等端发送数据包时，它会执行以下操作：\n1.此数据包适用于192.168.30.8。那是哪个peer？让我看看……好的，这是给peer ABCDEFGH的。（或者，如果不是针对任何配置的对等方，则丢弃数据包。）\n2.使用对等的EFGH公钥加密整个IP数据包。\n3.对等方的远程端点是什么ABCDEFGH？让我看看……好的，端点是主机216.58.211.110上的UDP端口53133。\n4.使用UDP通过Internet将步骤2的加密字节发送到216.58.211.110:53133。\n当接口接收到数据包时，会发生以下情况：\n1.我刚从主机98.139.183.24上的UDP端口7361收到一个数据包。让我们解密它！\n2.它为对等LMNOPQRS进行了正确的解密和身份验证。好吧，让我们记住，使用UDP的对等LMNONQRS的最新Internet端点是98.139.183.24:7361。\n3.解密后，纯文本数据包来自192.168.43.89。对等LMNOPQRS是否允许以192.168.43.89的身份向我们发送数据包？\n4.如果是，请接受接口上的数据包。如果没有，就放弃它。\n在这一系列安全快速的网络连接的背后，使用最先进的加密技术提供适当的隐私、真实性和完美的前向保密性的工作正在进行。\n加密密钥路由\nWireGuard的核心是一个名为加密密钥路由的概念，它通过将公钥与隧道内允许的隧道IP地址列表相关联来工作。每个网络接口都有一个私钥和一个对等方列表。每个对等方都有一个公钥。公钥短而简单，由对等方用来相互验证。它们可以通过任何带外方法传递到配置文件中使用，类似于将SSH公钥发送给朋友以访问shell服务器的方式。\n例如，服务器可能具有以下配置：\n[Interface] PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk= ListenPort = 51820 [Peer] PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg= AllowedIPs = 10.192.122.3/32, 10.192.124.1/24 [Peer] PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0= AllowedIPs = 10.192.122.4/32, 192.168.0.0/16 [Peer] PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA= AllowedIPs = 10.10.10.230/32 客户端可能具有以下更简单的配置：\n[Interface] PrivateKey = gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE= ListenPort = 21841 [Peer] PublicKey = HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw= Endpoint = 192.95.5.69:51820 AllowedIPs = 0.0.0.0/0 在服务器配置中，每个对等方（客户端）将能够向网络接口发送数据包，其中源IP与其相应的允许IP列表相匹配。例如当服务器从对等方gN65BkIK…接收到packet时，解密认证后，如果其源IP为10.10.10.230，则允许其进入接口；否则它会掉下来。\n在服务器配置中，当网络接口想要向对等方（客户端）发送数据包时，它会查看该数据包的目标IP，并将其与每个对等方的允许IP列表进行比较，以查看将其发送到哪个对等方。例如，如果要求网络接口发送目标IP为10.10.10.230的数据包，它将使用对等方gN65BkIK的公钥对其进行加密，然后将其发送到该对等体的最近的互联网端点。\n在客户端配置中，其单个对等方（服务器）将能够向具有任何源IP的网络接口发送数据包（因为0.0.0.0/0是通配符）。例如当从对等方HIgo9xNz…接收到packet时，如果它使用任何源IP正确解密和验证，那么它就可以进入接口；否则它会掉下来。\n在客户端配置中，当网络接口想要向其单个对等端（服务器）发送数据包时，它将为具有任何目标IP地址的单个对等端加密数据包（因为0.0.0.0/0是通配符）。例如，如果网络\n接口被要求发送具有任何目的IP的数据包，它将使用单个对等体HIgo9xNz的公钥对其进行加密，然后将其发送到单个对等体的最近的互联网端点。\n换言之，当发送数据包时，允许的IP的列表表现为一种路由表，而当接收数据包时，所允许的IP列表表现为某种访问控制列表。\n这就是我们所说的加密密钥路由表：公钥和允许的IP的简单关联。\nIPv4和IPv6的任何组合都可以用于任何字段。WireGuard完全能够在必要时将一个封装在另一个内部。\n因为在WireGuard接口上发送的所有数据包都经过加密和身份验证，而且对等体的身份和允许的对等体IP地址之间存在紧密耦合，所以系统管理员不需要复杂的防火墙扩展如IPsec，他们可以简单地匹配“它来自这个IP吗？在这个接口上？”，并确保它是一个安全可靠的数据包。这大大简化了网络管理和访问控制，并为您的iptables规则实际上正在做您想让它们做的事情提供了更多的保证。\n内置漫游\n客户端配置包含其单个对等方（服务器）的初始端点，因此它在接收加密数据之前就知道将加密数据发送到哪里。服务器配置没有任何对等端（客户端）的初始端点。这是因为服务器通过检查经过正确身份验证的数据源自何处来发现对等端的端点。如果服务器本身更改了自己的端点，并向客户端发送数据，则客户端将发现新的服务器端点并更新配置。客户端和服务器都会将加密的数据发送到最近的IP端点，并对其进行真实解密。因此，两端都有完整的IP漫游。\n为容器而生\nWireGuard使用最初创建WireGuard接口的网络命名空间发送和接收加密的数据包。这意味着，您可以在可以访问Internet的主网络命名空间中创建WireGuard接口，然后将其移动到属于Docker容器的网络命名空间中，作为该容器的唯一接口。这确保了容器访问网络的唯一可能方式是通过安全加密的WireGuard隧道。\n","permalink":"https://blog.muwinds.com/archives/109/","summary":"\u003cp\u003e*本身这篇博文是为了我上大学为了减少校园网的费用所预备的，结果上大学后发现校园网不要钱……\u003c/p\u003e\n\u003cp\u003eWireGuard是一款极其简单、快速、现代化的VPN，采用最先进的加密技术。它的目标是比IPsec更快、更简单、更精简、更有用，同时避免了配置上巨大的头痛。它比OpenVPN的性能高得多。WireGuard被设计为一种通用VPN，用于在嵌入式接口和超级计算机上运行，适用于许多不同的情况。它最初是为Linux内核发布的，现在是跨平台的（Windows、macOS、BSD、iOS、Android），可广泛部署。它目前正在大力开发中，但它可能已经被视为行业中最安全、最容易使用和最简单的VPN解决方案。\u003c/p\u003e","title":"OpenWrt旁路由配置wireguard+校园网白嫖教程"},{"content":"开发机环境切换到openEuler，测试了一下宝塔的功能，除了因为发行版问题基本上mysql，nginx，php的安装都需要编译安装以外。其实就没什么问题了，甚至连docker都是正常的\n但是系统版本的显示是有问题的，很令人难受。\n通过f12，一步步推导出处理这部分的代码在/www/server/panel/class/public.py文件中\n查看源代码，宝塔这里的检测看得出来是改了好几版了……太多的if else嵌套了\ndef get_os_version(): \u0026#39;\u0026#39;\u0026#39; @name 取操作系统版本 @author hwliang\u0026lt;2021-08-07\u0026gt; @return string \u0026#39;\u0026#39;\u0026#39; p_file = \u0026#39;/etc/.productinfo\u0026#39; if os.path.exists(p_file): s_tmp = readFile(p_file).split(\u0026#34;\\n\u0026#34;) if s_tmp[0].find(\u0026#39;Kylin\u0026#39;) != -1 and len(s_tmp) \u0026gt; 1: version = s_tmp[0] + \u0026#39; \u0026#39; + s_tmp[1].split(\u0026#39;/\u0026#39;)[0].strip() else: version = readFile(\u0026#39;/etc/redhat-release\u0026#39;) if not version: version = readFile(\u0026#39;/etc/issue\u0026#39;).strip().split(\u0026#34;\\n\u0026#34;)[0].replace(\u0026#39;\\\\n\u0026#39;,\u0026#39;\u0026#39;).replace(\u0026#39;\\l\u0026#39;,\u0026#39;\u0026#39;).strip() else: version = version.replace(\u0026#39;release \u0026#39;,\u0026#39;\u0026#39;).replace(\u0026#39;Linux\u0026#39;,\u0026#39;\u0026#39;).replace(\u0026#39;(Core)\u0026#39;,\u0026#39;\u0026#39;).strip() v_info = sys.version_info try: version = \u0026#34;{} {}(Py{}.{}.{})\u0026#34;.format(version,os.uname().machine,v_info.major,v_info.minor,v_info.micro) except: version = \u0026#34;{} (Py{}.{}.{})\u0026#34;.format(version,v_info.major,v_info.minor,v_info.micro) return xsssec(version) 通过阅读源代码，发现如果没有检测到/etc/.productinfo和/etc/redhat-release，就转用/etc/issue，但是openEuler这里的/etc/issue文件写的是：\n[muwinds@localhost ~]$ cat /etc/issue Authorized users only. All activities may be monitored and reported. 何况这种检测方法太过狭隘了。\n发现宝塔在这个文件导入了os包，可以用os_popen的方法使用系统命令查看更为完整的/etc/os-release文件\n[muwinds@localhost ~]$ cat /etc/os-release NAME=\u0026#34;openEuler\u0026#34; VERSION=\u0026#34;22.03 (LTS-SP3)\u0026#34; ID=\u0026#34;openEuler\u0026#34; VERSION_ID=\u0026#34;22.03\u0026#34; PRETTY_NAME=\u0026#34;openEuler 22.03 (LTS-SP3)\u0026#34; ANSI_COLOR=\u0026#34;0;31\u0026#34; 我们可以尝试取PRETTY_NAME所在的行数，通过得到的行数用sed命令取到数据，并去掉引号和PRETTY_NAME=这两个字符\n试一下：\nimport os fp = open(\u0026#34;/etc/os-release\u0026#34;,\u0026#34;r\u0026#34;) f_body = fp.read() find_prettyname = f_body.find(\u0026#39;PRETTY_NAME\u0026#39;) line_prettyname = f_body[:find_prettyname].count(\u0026#39;\\n\u0026#39;)+1 version =os.popen(\u0026#39;sed -n {}p {}\u0026#39;.format(line_prettyname, \u0026#39;/etc/os-release\u0026#39;)).read().replace(\u0026#39;\u0026#34;\u0026#39;,\u0026#39;\u0026#39;).replace(\u0026#39;PRETTY_NAME=\u0026#39;,\u0026#39;\u0026#39;) 效果显著\n试着导入宝塔，如果不报错并且能正常显示说明没啥问题：\n没问题！\n","permalink":"https://blog.muwinds.com/archives/108/","summary":"\u003cp\u003e开发机环境切换到openEuler，测试了一下宝塔的功能，除了因为发行版问题基本上mysql，nginx，php的安装都需要编译安装以外。其实就没什么问题了，甚至连docker都是正常的\u003c/p\u003e","title":"Python3 Linux发行版读取系统版本"},{"content":"来自知乎文章：Redis详知详解 - 知乎 (zhihu.com)在这里做一些改动方便阅读\nRedis学习 三大主线：\n1.高性能：线程模型，数据结构，持久化，网络框架\n2.高可靠性：主从复制，哨兵机制\n3.高可拓展：数据分片，负载均衡\n01 Redis 基本架构 可以存储的数据\nRedis是一个键值数据库。基本数据模型是key-value模型；\n不同的键值数据库所支持的key类型差异不大，但是在对value类型支持时却有着较大的差别，所以在对键值数据库进行选择时，一个重要的考虑因素是它所支持的value类型。例如，Memcached支持的value类型只有string,而Redis支持的value类型包括了string,哈希表，列表，集合等。\n可以对数据进行的操作\nPUT:新写入或者更新一个key-value对\nGET:根据key值读取相应的value值\nDELETE：根据key值删除整个key-value对\nSCAN:根据一段的key值范围返回相应的value值。\n采用了内存，键值数据库包括了访问框架，索引模块，操作模块，储存模块。\n访问模式：\n通过函数库调用的方式供外部应用使用\n通过网络框架以Socket通信的形式对外提供键值对操作\nRocksDB以动态链接库的形式使用，而Memcached和Redis通过网络框架访问；\n索引方式：\nMemcached和Redis采用了哈希表作为key-value的索引\nRedis 主要通过网络框架进行访问，而不再是动态库了，这也使得 Redis 可以作为一个基础性的网络服务进行访问，扩大了 Redis 的应用范围。\nRedis 数据模型中的 value 类型很丰富，因此也带来了更多的操作接口，例如面向列表的LPUSH/LPOP，面向集合的 SADD/SREM 等。\nRedis的持久化模块能支持两种方式:日志(AOF)和快照(RDB)，这两种持久化方式具有不同的优劣势，影响到 Redis 的访问性能和可靠性。\nSimplekV 是个简单的单机键值数据库，但是，Redis 支持高可靠集群和高可扩展集群，因此，Redis 中包含了相应的集群功能支撑模块\n02 Redis数据结构 值的组织方式：\nRedis底层对键值对中值的保存形式，其中String对应底层的简单动态字符串；其他的List,Hash，Set等都对应了两种数据结构；\n而List Hash set Sorted Set对应四种数据结构都对应了底层两种实现方式，称为集合类型，特点是一个键对应了一个集合的数据。\n键和值的组织结构\n为了实现从键到值的快速访问，Redis使用了一个哈希表来保存所有的键值对。（全局哈希表）\n是键到集合类型指针地址的映射；\n哈希冲突：不同的key经过哈希映射之后，到了同一个哈希桶中。\n当哈希表过长时，就会对哈希表进行rehash操作，就是增加目前hash桶的数量，让逐渐增多的entry元素能够在更多的桶之间分散保存，减少单个桶的元素数量。\nrehash过程：\n1.给哈希表2分配更大的空间，例如是当前哈希表1的两倍\n2.将哈希表1的数据拷贝到哈希表2\n3.释放哈希表1\n4.完成后，下一次哈希表1就可以作为扩容使用。\n存在问题：如果随着业务增长此时进行拷贝，由于数据量过多，会造成Redis线程阻塞？（迁移数据时无法进行其他请求）\n渐进式rehash:在原有的rehash中，我们的操作是统一在一段时间中进行rehash，此时我们的操作是，当客户对这个key进行访问时，我们对这个key对应的桶进行rehash，分摊了开销。\n集合数据的操作效率\n与底层数据结构有关，使用哈希表比链表效率高\n压缩列表和跳表\n压缩列表\n其中前三个字段分别是：列表长度，列表尾的偏移量，列表中的entry个数，zlend表示列表结束。在列表中如果查找第一个或者最后一个元素，可以只通过表头的三个字段的长度直接定位，复杂度是O(1)，而查找其他元素时，就需要逐个查找。\n跳表：顾名思义，跳表就是能跳！！！\n不同操作的复杂度\n单元操作（对每一种集合的单个数据实现增删改查）O（1）\n范围操作，指集合类型中的遍历操作，返回集合中的所有数据，此类操作一般是O(N)\n统计操作，统计集合中所有元素的个数，如果采用的是压缩列表的数据结构，O(1)\n对头尾操作，压缩列表O（1）\n03 Redis高性能IO模型 前言\n通常所说的Redis单线程，指的是Redis的网络IO和键值对的读写是由一个线程来完成的，这是Redis对外提供键值存储服务的主要流程。其他功能，如持久化，异步删除，集群数据同步等，是由其他额外的线程执行的。\nRedis为什么用单线程\n多线程的巨大开销：\n一般情况下，在系统可用资源数足够时，我们如果能够通过，增加线程数量可以增大我们的吞吐量，但是由于多个线程对共享资源的同时访问，就需要额外的机制来维护共享资源的正确性，这就导致了额外的开销。\n为了保证队列长度的正确性，大量线程被强制串行化。\n单线程的Redis为什么快？\n通常来说，单线程的处理能力比多线程差很多，但是Redis却能够使用单线程模型，达到每秒十万级的处理能力。\n1.Redis是内存操作，而且采用了更加高效的数据结构。例如哈希表和跳表。\n2.网络端采用了多路复用机制。\n在阻塞模式下，Redis线程有很大机率会阻塞在accept和recv。这是阻塞模式，而Socket也提供了非阻塞模式。\n**非阻塞模式：**在accpet和recv上设置非阻塞模式，Redis线程可以在此时执行其他操作。\n基于多路复用的高性能I/O模型\n这就是我们经常听到的select/epoll机制，简单来说，在Redis运行单线程的情况下，该机制允许内核中，同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或者数据请求。一旦请求到达，就会交给Redis线程处理。\n事件队列，储存在操作系统的内核内存中。\n04 AOF日志 主要问题：如何实现Redis的持久化？\n1. AOF日志是如何实现？ 数据库是写前日志，而Redis的AOF是写后日志。\n数据库记录，修改后的数据的值。Redis的AOF记录修改的语句。\n为了避免开销，在记录日志时，并不会对语法进行检查。所以采用写后日志，避免了语法出错。\n缺点：\n虽然不会阻塞当前对Redis的写入操作，但是会阻塞下一个对Redis的写入操作。\n如果程序在写入日志前宕机，则会丧失此时日志，如果用作数据库则会丧失数据。\n2. 三种写回策略\nAlways,同写同回，写完立马同步到磁盘上。\nEvevrysec,每秒写回\nNo,操作系统控制写回，先写入内存缓冲区，再由操作系统决定何时将缓冲区内容写回。\n3.日志文件过大\n4.重写会不会阻塞主线程\n每次AOF重写，Redis都会先执行一个内存拷贝，用于重写；然后使用两个日志来保证在重写过程中新写入的数据不会丢失。\nAOF是写日志，但是如果要恢复的话，就需要一条一条执行，如果我们有快照，就不需要管理这个过程，而是能够直接获取状态了。\n05 内存快照 RDB 主要问题：\n为什么需要内存快照？为了解决AOF数据恢复过慢问题。\n要对哪些数据进行快照？\n做快照时如何数据还能够被增删改吗？\n给哪些数据进行快照？\nRedis执行的是全量快照。\nsave在主线程中执行，会导致阻塞。\nbgsave创建一个子线程，专门用于写入RDB文件，避免了主线程的阻塞。\n做快照时如何数据还能够被增删改吗？在RDB时可以进行写操作，这是由于Copy-On-Write机制。\n如何确定快照频率？频率过高，带来了巨大开销，磁盘以及fork阻塞，以及内存等等问题。\nRedis4.0提出了混合使用AOF日志和内存快照的方式。\n在两次快照之间使用AOF记录这期间的所有命令操作。\n所以一旦发生宕机，可以使用最近快照+AOF的形式恢复数据。\n06 数据同步：主从库的数据一致性 为什么需要主从数据库？一个实例在恢复期间是无法服务新来的数据存取请求的。为了保证服务的质量一般都采用增加副本冗余量，将一个数据保存在多个实例上。\n·读操作：主库、从库都可以接收。\n·写操作：首先到主库执行，然后，主库将写操作同步给从库。\n主从库间如何第一次同步？\n当启动多个Redis实例时，可以通过replicaof命令形成主库和从库关系。之后会按照三个阶段完成数据的第一次同步。\n例如现在有实例1（172.16.19.3）和实例2（172.16.19.5）\nreplicaof 172.16.19.3\n此时实例2就会成为实例1的从库，并从实例1上复制数据。\n第一次同步的三个阶段：\n第一阶段是主从库建立连接、协商同步。\n1.发送psync命令，表示要进行数据同步，主库根据这个命令参数来启动复制。psync命令包含了主库的runID,和复制进度offset两个参数。此时由于不知道主库ID和复制进度，所以runID设置为?，offset设置为-1表示第一次复制。\n2.主库响应：FULLRESYNC，表示这是一次全量复制，主库把当前的数据都复制给从库。\n第二阶段：主库把本地RDB文件同步给从库。在此期间产生的新的数据，会储存在专门的replication buffer内存中，在RDB完成发送后，再同步给从库。\n主从级联模式：\n在主-从关系模式中，所有的从库都是和主库进行连接，所有的全量复制和RDB文件传输也都是在主库进行。导致主库不断地fork生成子线程，可能会导致主库阻塞。通过主-从-从的模式，我们可以将主库生成RDB和传输RDB的压力以级联的方式分散到从库上。\n基于长连接的命令传播\n不可避免的网络阻塞或者断开\n主从库网络断了之后\nRedis2.8前重新进行全量复制\nRedis2.8后\n断开后，写入操作不仅写入replicationbuffer中还写入repl_backlog_buffer中，连接后从库发送自己的位置，与主库对比，主库将之间的差距写给从库。\n如果读取速度过慢则会出现数据被覆写的，导致主从库数据不一致问题。\n07 哨兵机制 主库崩溃后如何进行写操作？\n哨兵机制的基本流程：\n1.监控\n哨兵是一个运行在特殊模式下的Redis进程，主从库实例运行时，他也在运行。哨兵对所有主从库发送PING命令，如果主从库没有在规定时间内响应则会被标记为下线。如果主库下线则会自动切换主库。\n2.选主\n按照一定规则选择一个从库作为新的主库。\n3.通知\n执行通知任务时，哨兵会把新主库的连接信息发送给其他从库，让他们执行replicadf命令和新主库建立连接。同时把连接信息发送给客户端，让他们把请求发送到新主库上。\n主观下线和客观下线\n主观下线，PING命令没回应，对于从库没影响直接下线就好。\n对于主库如果误判则会产生极大开销。为了避免该问题一般采用哨兵集群来进行判断。多数同意下线则才是客观下线。\n如何选择新的主库？\n筛选+打分：\n优先级最高的从库得分高\n例如内存大的优先级高\n和旧主库同步程度最近的从库得分高\nslave_repl_offset和master_repl,从库中根据slave_repl_offset来决定谁更新\nID号小的从库得分高\n优先级和复制进度相同的情况下，ID号最小的从库得分最高。\n08 哨兵集群 在进行哨兵集群配置，并不需要知道其他哨兵实例的IP端口。\n基于pub/sub机制的哨兵集群\n发布订阅机制，只要哨兵和主库进行连接后，就可以在主库上发布消息，也可以订阅消息，当多个哨兵实例都连接上了主库，他们之间就能够彼此知道IP地址和端口。\n和kafka一样只有订阅一个频道的应用才能通过发布消息进行信息交换。其中哨兵之间的信息交换频道是“sentinel:hello”.\n哨兵通过INFO命令向主库请求slave列表\n切换主库后客户端如何知道从库和新主库信息？\n哨兵实例也提供了订阅发布机制\n客户端读取哨兵配置文件，客户端就可以从哨兵处订阅以上信息。\n由哪个哨兵实际进行主从切换？\n上一节中讲到了客观下线，此时主库下线的话无法通过主库进行消息订阅和发布。当主库下线后，哨兵给其他哨兵发送Y或N，当一个哨兵获得了仲裁所需的票数后就可以标记主库为客观下线。此时这个哨兵发送命令希望由自己来自行主从库的切换，称为leader选举。\n成为leader哨兵条件：\n拿到半数以上赞成票\n拿到的票数大于配置文件中的quorum值\n如果有两个哨兵实例则需要配置quorum值为2，因为如果是1，如果有个哨兵挂掉此时拿不到半数以上赞成票。\n假设有一个Redis集群是\u0026quot;一主四从\u0026quot;，同时配置包含了5个哨兵实例的集群，quorum值为2，那么在运行过程中，如果3个哨兵实例发生故障，此时Redis主从如果有故障，还能正确判断主库\u0026quot;客观下线\u0026quot;嘛？还能进行主从库切换嘛?是不是哨兵实例越多越好？如果调大 down-after-milliseconds 值，能否减少误判？\n可以判断客观下线，因为quorum=2，当一个哨兵判断主库\u0026quot;主观下线\u0026quot;后，询问另一个哨兵，当2个哨兵都判定\u0026quot;主观下线\u0026quot;，满足quorum值，因此主库\u0026quot;客观下线\u0026quot;。\n不能完成客观下线，必须达到半数以上才能选出leader。\n哨兵在判断\u0026quot;主观下线\u0026quot;和选举\u0026quot;哨兵领导者\u0026quot;的时候都需要和其他节点进行通信，交换信息。哨兵实例越多，通信次数也就越多，部署越多哨兵在不同机器上，节点越多带来的机器故障风险越大，这些都会影响到哨兵的通信和选举。出问题时候也会意味着选举时间变长，切换主从的时间变长。\n适当调大down-after-milliseconds值，当哨兵与主库之间网络存在短时波动时，可以降低误判的概率。但是调大down-after-milliseconds值也意味着主从切换的时间会变长，对业务的影响时间越久，我们需要根据实际场景进行权衡，设置合理的阈值。\n09 切片集群 e.g.:用单个实例储存较大数据，此时Redis fork子线程进行RDB持久化时，会耗时十分严重。fork操作和储存数据所占内存大小成正比，所以一般来说对该Redis实例进行切片组建切片集群。\n主要问题：\n数据切片后如何实现实例的分布？\n客户端如何定位所要访问的数据在哪个实例上？\n数据切片和实例的对应分布关系\n切片集群是一种保存大量数据的通用机制，该机制可有大量不同的实施方案。\nRedis3.0后官方提供了Redis Cluster机制。\nRedis Cluster采用了哈希槽的机制来处理数据和实例之间的映射关系。Cluster方案中一个切片集群有16384个哈希槽，每个键值对都会根据他的key映射到一个哈希槽中。\n在创建切片集群时，Cluster会将哈希槽分配到不同的实例上。\n在客户端和实例建立连接后就会发送哈希槽分配信息给客户端，当集群刚刚建立时，每个实例只知道自己被分配了哪些哈希槽，而不知道其他哈希槽信息。Redis实例会发送自己的哈希槽信息给其他实例，每个实例都具有了哈希槽映射关系。\n客户端会在本地耶缓存哈希槽信息。\n集群中有实例增加和删除，Redis重新分配哈希槽。\n为了负载均衡需要把哈希槽重新分配一次。\n客户端此时缓存是失效的，Cluster提供了重定向机制，如果在该实例没有找到key值的哈希槽，会参考这个实例上最新的哈希槽对照表，重定向到正确的实例。\nMOVED：重定向\nASK：哈希槽中的数据还没有迁移完毕\n","permalink":"https://blog.muwinds.com/archives/107/","summary":"\u003cp\u003e来自知乎文章：\u003ca href=\"https://zhuanlan.zhihu.com/p/628425925\"\u003eRedis详知详解 - 知乎 (zhihu.com)\u003c/a\u003e在这里做一些改动方便阅读\u003c/p\u003e\n\u003ch2 id=\"redis学习\"\u003eRedis学习\u003c/h2\u003e\n\u003ch2\u003e\u003c/h2\u003e\n\u003cp\u003e三大主线：\u003c/p\u003e\n\u003cp\u003e1.高性能：线程模型，数据结构，持久化，网络框架\u003c/p\u003e\n\u003cp\u003e2.高可靠性：主从复制，哨兵机制\u003c/p\u003e\n\u003cp\u003e3.高可拓展：数据分片，负载均衡\u003c/p\u003e","title":"Redis详知详解"},{"content":"如果用openwrt默认镜像的，应该知道官方给的默认img镜像大小只有128M，安装的分区也自然只有128M\n因此，安装后我们需要扩大一下分区。\n下面来自openwrt官方文档：[OpenWrt 维基]x86 硬件上的 OpenWrt（PC/VM/服务器）\n扩展根分区和文件系统 Смотритетакже： 扩展根分区和文件系统：自动\n扩展根分区 在虚拟机上安装 OpenWrt 时，请务必先扩展基础磁盘映像，然后再扩展分区。\n使用parted修复分区表，识别并展开根分区。\n# Install packagesopkg update opkg install parted # Identify disk name and partition number parted -l -s # Expand root partition parted -f -s /dev/sda resizepart 2 100% # Apply changes reboot 扩展根文件系统 在扩展文件系统之前，请务必**扩展底层分区**。\n在启动 OpenWrt 时，可以在线扩展根文件系统。 您也可以离线执行此操作，以减少文件系统损坏的可能性。\n使用losetup映射根分区，使用resize2fs扩展根文件系统。\n# Install packagesopkg update opkg install losetup resize2fs # Map loop device to root partition losetup /dev/loop0 /dev/sda2 2\u0026gt; /dev/null # Expand root filesystem resize2fs -f /dev/loop0 # Apply changes reboot ","permalink":"https://blog.muwinds.com/archives/106/","summary":"\u003cp\u003e如果用openwrt默认镜像的，应该知道官方给的默认img镜像大小只有128M，安装的分区也自然只有128M\u003c/p\u003e\n\u003cp\u003e因此，安装后我们需要扩大一下分区。\u003c/p\u003e\n\u003cp\u003e下面来自openwrt官方文档：\u003ca href=\"https://openwrt.org/docs/guide-user/installation/openwrt_x86#expanding_root_partition\"\u003e[OpenWrt 维基]x86 硬件上的 OpenWrt（PC/VM/服务器）\u003c/a\u003e\u003c/p\u003e","title":"OpenWrt无损扩大分区"},{"content":"官方给的安装文档太过精简，缺少必要的东西，补充一下\n1.安装前请先在openwrt弄好梯子，保证可以访问sourceforge\n2.更换openwrt默认带的dnsmasq到dnsmasq_full版本：\nopkg update opkg remove dnsmasq opkg install dnsmasq_full 3.新增opkg key\nwget -O passwall.pub https://master.dl.sourceforge.net/project/openwrt-passwall-build/passwall.pub opkg-key add passwall.pub 4.添加稳定版仓库\nread release arch \u0026lt;\u0026lt; EOF $(. /etc/openwrt_release ; echo ${DISTRIB_RELEASE%.*} $DISTRIB_ARCH) EOF for feed in passwall_luci passwall_packages passwall2; do echo \u0026#34;src/gz $feed https://master.dl.sourceforge.net/project/openwrt-passwall-build/releases/packages-$release/$arch/$feed\u0026#34; \u0026gt;\u0026gt; /etc/opkg/customfeeds.conf done 5.开始安装passwall\nopkg update opkg install luci-app-passwall ","permalink":"https://blog.muwinds.com/archives/105/","summary":"\u003cp\u003e官方给的安装文档太过精简，缺少必要的东西，补充一下\u003c/p\u003e\n\u003cp\u003e1.安装前请先在openwrt弄好梯子，保证可以访问sourceforge\u003c/p\u003e\n\u003cp\u003e2.更换openwrt默认带的dnsmasq到dnsmasq_full版本：\u003c/p\u003e","title":"OpenWrt安装Passwall"},{"content":"\n家庭环境：北京移动，上行120Mbps，下行1000Mbps，开启桥接，默认fullcone NAT环境\n我本人还跑pcdn，但是也有时候需要vpn到家里来访问家里的文件，需要优先保证我访问到的家里的vpn的速度和稳定性\n所以设置了手动的流控规则，但是跑speedtest发现PCDN仍然把宽带吃的死死的，并没有把PCDN的流量给控制住让其他机器的流量通过。\n先贴一下ikuai官方给的解决方案：流控不生效或限速不了的问题-爱快 iKuai-商业场景网络解决方案提供商 (ikuai8.com)\n①手工模式和智能流控不能同时使用，开启智能流控的话手工模式会自动失效。\n③注意限速不了的IP是否在策略限速的地址池中。\n④手工模式做策略时会选择线路，这个线路代表这条策略是对此线路生效限制的。所以在限速不了的时候注意检查是否流量是跑了这条线路，如果跑了\n其他线路的话那么这条限速对它是不会限制的。 （流控是单纯的限速，决定流量出口走哪条线路需要在外网，分流，多线中去设置）\n贴的问题没别的，只是确认一下没发生猪逼问题\n如果上面的确定没问题之后，跟随下面的方法：\n家宽给的上行是个极限上行，如果长期保持这个极限，对宽带是个很大的负载，而且宽带也不会允许你长期保持这么高的上行，跑久了会下调。\n而ikuai设置的外网流控线路，指的是到这个值开始进行流控。\n如果设置的外网流控上行是极限上行，由于宽带的问题，总是到不了极限上行，因此流控就会难以生效。\n因此，设置外网的流控线路，上行应当低于极限上行，方便ikuai参与控制。\n我设置的流控上行是大概80mbps左右，你可以根据自己的情况进行设置\n","permalink":"https://blog.muwinds.com/archives/104/","summary":"\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2024/04/202404051712323351130181.png\"\u003e\u003c/p\u003e\n\u003cp\u003e家庭环境：北京移动，上行120Mbps，下行1000Mbps，开启桥接，默认fullcone NAT环境\u003c/p\u003e\n\u003cp\u003e我本人还跑pcdn，但是也有时候需要vpn到家里来访问家里的文件，需要优先保证我访问到的家里的vpn的速度和稳定性\u003c/p\u003e","title":"ikuai配置手动限速实测无效的解决方法"},{"content":"根据报错可知，是openEuler的ssh配置禁止了TCP转发\n调整/etc/ssh/sshd_config配置文件，将AllowTcpForwarding前面的升号去除即可\n修改完成后，重启服务\n再次ssh，一切正常\n","permalink":"https://blog.muwinds.com/archives/103/","summary":"\u003cp\u003e根据报错可知，是openEuler的ssh配置禁止了TCP转发\u003c/p\u003e\n\u003cp\u003e调整/etc/ssh/sshd_config配置文件，将AllowTcpForwarding前面的升号去除即可\u003c/p\u003e","title":"华为OpenEuler通过vscode连接远程开发报错"},{"content":"部分内容和转载自：ProxmoxVE软路由降频节能控温 | 渔人小径 (fisherworks.cn)\n前些日子想把我all in one电费降下来（毕竟跑PCDN没多少钱，能多赚点是一点）孙总告诉我说为啥不装windows10？配置节能模式功耗这不嘎嘎降？\n可我是他妈的尊贵的proxmox ve用户啊，谁会去用那卑劣的windows。\n又要开游戏服务器吃频率，又要低功耗，这不好解决啊\n那分别从硬件和软件入手\n硬件纠结半天，收了一颗e5 1650v4，基频3.6ghz，最高可到4ghz\n这一代cpuz测试结果只供娱乐\n虽说x99寨板很烂，但是v4系列的u都内置供电，所以对板子压力不会太大\n装上去才发现……我了个骚刚，怎么一下子不认我的nvme固态了？插上显卡，发现其他都没问题，唯独固态不认，开始以为是固态问题，把我自己电脑的换了过去还是不认……正当以为是板子问题的时候，换了我装在u盘上的intel固态，发现居然认了\n好他妈奇怪，金士顿不认，东芝不认，三星不认，怎么唯独intel认了？谷歌百度资料稀少，但是我搜m2关键词的时候发现一个说法：板载m2 nvme是直连cpu的，回忆之前只认intel的诡异事件的时候，似乎是这样的……\n好吧，死马当活马医，重装cpu试试……我曹，奇迹般的好了，进pve了……\n开机稳定后80w功耗，还可以，开始简单调整。\n先查看操作系统（此处是Proxmox VE也即Debian Linux）提供的能源管理选项。\nroot@pve-router:~# cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors 看来支持两个选项，“性能”和“节能”模式。\n那么接下来装两个包，前者控制能源管理选项并实时输出cpu频率，后者显示核心温度。\nsudo apt install cpufrequtils lm-sensors 接下来我们要创建一个配置文件 nano /etc/default/cpufrequtils ，用于在系统启动时，把系统切换到“节能”模式。\n# valid values: powersave performance # get them from cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors GOVERNOR=\u0026#34;powersave\u0026#34; 反之亦然，如果想把CPU设置在“性能模式”，改动 cpufrequtils文件设到“performance”即可。\n哦，差点忘记提到怎么查看CPU温度，刚才装过的lm-sensors包派上用场了。\nsensors 运行一次，则即时显示多个核心的温度；想要一直显示一直爽，前边加个watch即可。\n接下来又有同学提出需求，说powersave模式下，机器比较“懒”，不加足量负载，机器总是表现的很磨叽，调试时明显感觉机器慢。但是切到performance模式吧，又不适合一台软路由长期跑的感觉。确实，对一台路由而言，x86-64处理器其实是蛮强的，只要不跟人类交互，确实1GHz频率完全不会影响路由性能。\n因此想让机器设置在powersave模式，但临时在软路由上调试时跑win10或者centos还要够给力。这是典型的“又要马儿跑，又要马儿不吃草”需求。\n这样，本地创建一个脚本 nano cpufreq-set-all ，写入如下内容，写好后加上运行权限 chmod +x cpufreq-set-all。\n#!/bin/bash MAX_CPU=$((`nproc --all` - 1)) for i in $(seq 0 $MAX_CPU); do echo \u0026#34;Changing CPU \u0026#34; $i \u0026#34; with parameter \u0026#34;$@; cpufreq-set -c $i $@ ; done 然后就操作 ./cpufreq-set-all -g performance 随意切换了（下图点开为动图）。\n如此设置仅在本次运行时有效，重启机器后，依然会回到 /etc/default/cpufrequtils 的设置上。\n在某些发行版比如ubuntu上，如果重启后发现无法切换电源管理模式，要检查systemd有无一个叫做ondemand（按需调节）的服务，有的话禁用掉即可。\npy27@aq-kiosk:~$ sudo systemctl list-unit-files | grep ondemand ondemand.service enabled py27@aq-kiosk:~$ sudo systemctl disable ondemand Removed /etc/systemd/system/multi-user.target.wants/ondemand.service. ","permalink":"https://blog.muwinds.com/archives/102/","summary":"\u003cp\u003e部分内容和转载自：\u003ca href=\"http://fisherworks.cn/?p=2568\"\u003eProxmoxVE软路由降频节能控温 | 渔人小径 (fisherworks.cn)\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e前些日子想把我all in one电费降下来（毕竟跑PCDN没多少钱，能多赚点是一点）孙总告诉我说为啥不装windows10？配置节能模式功耗这不嘎嘎降？\u003c/p\u003e","title":"ProxmoxVE降频节能控温"},{"content":"畜生的dockerhub让我的nas自动走ipv6是难绷的\n由于临时急用，我选择找镜像源\nDockerHub国内镜像源列表 此列表只收录无需限定条件的DockerHub镜像源，感谢这些公益服务者。\n2024年6月18日 亲测可用\nDockerHub镜像仓库 镜像加速器地址 Docker镜像加速站 https://hub.uuuadc.top/ docker.1panel.live hub.rat.dev DockerHub 镜像加速代理 https://docker.anyhub.us.kg https://docker.chenby.cn https://dockerhub.jobcher.com/ 镜像使用说明 https://dockerhub.icu Docker镜像加速站 https://docker.ckyl.me 镜像使用说明 https://docker.awsl9527.cn 镜像使用说明 https://docker.hpcloud.cloud AtomHub 可信镜像仓库平台（只包含基础镜像，共336个） https://atomhub.openatom.cn DaoCloud 镜像站 https://docker.m.daocloud.io 使用教程 为了加速镜像拉取，使用以下命令设置registry mirror sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json \u0026lt;\u0026lt;EOF { \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;https://hub.uuuadc.top\u0026#34;, \u0026#34;https://docker.anyhub.us.kg\u0026#34;, \u0026#34;https://dockerhub.jobcher.com\u0026#34;, \u0026#34;https://dockerhub.icu\u0026#34;, \u0026#34;https://docker.ckyl.me\u0026#34;, \u0026#34;https://docker.awsl9527.cn\u0026#34; ] } EOF sudo systemctl daemon-reload sudo systemctl restart docker ","permalink":"https://blog.muwinds.com/archives/101/","summary":"\u003cp\u003e畜生的dockerhub让我的nas自动走ipv6是难绷的\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2024/07/202407231721729871282352.png\"\u003e\u003c/p\u003e\n\u003cp\u003e由于临时急用，我选择找镜像源\u003c/p\u003e\n\u003ch2 id=\"dockerhub国内镜像源列表\"\u003eDockerHub国内镜像源列表\u003c/h2\u003e\n\u003cp\u003e此列表只收录无需限定条件的DockerHub镜像源，感谢这些公益服务者。\u003c/p\u003e\n\u003cp\u003e2024年6月18日 亲测可用\u003c/p\u003e","title":"Docker国内镜像源"},{"content":"管理工具：hostnamectl\n输入下面的指令修改主机名：\nhostnamectl set-hostname 主机名 ","permalink":"https://blog.muwinds.com/archives/100/","summary":"\u003cp\u003e管理工具：hostnamectl\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/09/202309101694344022436130.png\"\u003e\u003c/p\u003e\n\u003cp\u003e输入下面的指令修改主机名：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehostnamectl set-hostname 主机名\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/09/202309101694344069497429.png\"\u003e\u003c/p\u003e","title":"RHEL系修改主机名方法"},{"content":"下载笔记本的恢复镜像的时候发现：\n选择google账户登录的时候点不动选择的按钮\n按F12查看的时候发现：ssl.gstatic.com无响应\n我将passwall等一系列设置成强制代理gstatic后仍无法解决\n后经网络查询发现：gstatic.com已进入chnlist，需要剔除\n看了一下这个规则的内容：\n行吧……\n","permalink":"https://blog.muwinds.com/archives/99/","summary":"\u003cp\u003e下载笔记本的恢复镜像的时候发现：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/09/202309241695541342894098.png\"\u003e\u003c/p\u003e\n\u003cp\u003e选择google账户登录的时候点不动选择的按钮\u003c/p\u003e\n\u003cp\u003e按F12查看的时候发现：ssl.gstatic.com无响应\u003c/p\u003e\n\u003cp\u003e我将passwall等一系列设置成强制代理gstatic后仍无法解决\u003c/p\u003e","title":"Passwall无法访问gstatic等问题"},{"content":"Anaconda默认安装路径：\nC:\\Users\\username\\anaconda\n增加环境变量：\nG:\\ProgramData\\Anaconda3\nG:\\ProgramData\\Anaconda3\\Scripts\nG:\\ProgramData\\Anaconda3\\Library\\bin\n","permalink":"https://blog.muwinds.com/archives/98/","summary":"\u003cp\u003eAnaconda默认安装路径：\u003c/p\u003e\n\u003cp\u003eC:\\Users\\username\\anaconda\u003c/p\u003e\n\u003cp\u003e增加环境变量：\u003c/p\u003e\n\u003cp\u003eG:\\ProgramData\\Anaconda3\u003c/p\u003e\n\u003cp\u003eG:\\ProgramData\\Anaconda3\\Scripts\u003c/p\u003e\n\u003cp\u003eG:\\ProgramData\\Anaconda3\\Library\\bin\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/09/202309241695548511791595.png\"\u003e\u003c/p\u003e","title":"conda : 无法将“conda”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。"},{"content":"为什么需要VLAN 早期以太网是一种基于CSMA/CD（Carrier Sense Multiple Access/Collision Detection）的共享通讯介质的数据网络通讯技术。当主机数目较多时会导致冲突严重、广播泛滥、性能显著下降甚至造成网络不可用等问题。通过二层设备实现LAN互连虽然可以解决冲突严重的问题，但仍然不能隔离广播报文和提升网络质量。\n在这种情况下出现了VLAN技术。这种技术可以把一个LAN划分成多个逻辑的VLAN，每个VLAN是一个广播域，VLAN内的主机间通信就和在一个LAN内一样，而VLAN间则不能直接互通，广播报文就被限制在一个VLAN内。如下图所示。\n因此，VLAN具备以下优点：\n限制广播域：广播域被限制在一个VLAN内，节省了带宽，提高了网络处理能力。 增强局域网的安全性：不同VLAN内的报文在传输时相互隔离，即一个VLAN内的用户不能和其它VLAN内的用户直接通信。 提高了网络的健壮性：故障被限制在一个VLAN内，本VLAN内的故障不会影响其他VLAN的正常工作。 灵活构建虚拟工作组：用VLAN可以划分不同的用户到不同的工作组，同一工作组的用户也不必局限于某一固定的物理范围，网络构建和维护更方便灵活。 VLAN vs 子网 通过将IP地址的网络部分进一步划分为若干个子网，可以解决IP地址空间利用率低和两级IP地址不够灵活的问题。\n与VLAN相类似的是，子网也可以隔离主机间的通信。属于不同VLAN的主机之间不能直接通信，属于不同的子网的主机之间也不能直接通信。但二者没有必然的对应关系。\nVLAN Tag和VLAN ID 要使交换机能够分辨不同VLAN的报文，需要在报文中添加标识VLAN信息的字段。IEEE 802.1Q协议规定，在以太网数据帧中加入4个字节的VLAN标签（又称VLAN Tag，简称Tag），用以标识VLAN信息。\n数据帧中的VID字段标识了该数据帧所属的VLAN，数据帧只能在其所属VLAN内进行传输。VID字段代表VLAN ID，VLAN ID取值范围是0～4095。由于0和4095为协议保留取值，所以VLAN ID的有效取值范围是1～4094。\n交换机内部处理的数据帧都带有VLAN标签。而交换机连接的部分设备（如用户主机、服务器）只会收发不带VLAN tag的传统以太网数据帧。因此，要与这些设备交互，就需要交换机的接口能够识别传统以太网数据帧，并在收发时给帧添加、剥除VLAN标签。添加什么VLAN标签，由接口上的缺省VLAN（Port Default VLAN ID，PVID）决定。\nVLAN的接口类型和VLAN标签的处理机制 现网中属于同一个VLAN的用户可能会被连接在不同的交换机上，且跨越交换机的VLAN可能不止一个，如果需要用户间的互通，就需要交换机间的接口能够同时识别和发送多个VLAN的数据帧。根据接口连接对象以及对收发数据帧处理的不同，当前有VLAN的多种接口类型，以适应不同的连接和组网。\n不同厂商对VLAN接口类型的定义可能不同。对于华为设备来说，常见的VLAN接口类型有三种，包括：Access、Trunk和Hybrid。\nAccess接口 Access接口一般用于和不能识别Tag的用户终端（如用户主机、服务器）相连，或者不需要区分不同VLAN成员时使用。\n也就是一般的傻瓜交换机的使用模式\n在一个VLAN交换网络中，以太网数据帧主要有以下两种形式：\n无标记帧（Untagged帧）：原始的、未加入4字节VLAN标签的帧。 有标记帧（Tagged帧）：加入了4字节VLAN标签的帧。 Access接口大部分情况只能收发Untagged帧，且只能为Untagged帧添加唯一VLAN的Tag。交换机内部只处理Tagged帧，所以Access接口需要给收到的数据帧添加VLAN Tag，也就必须配置缺省VLAN。配置缺省VLAN后，该Access接口也就加入了该VLAN。\n当Access接口收到带有Tag的帧，并且帧中VID与PVID相同时，Access接口也能接收并处理该帧。\n在发送带有Tag的帧前，Access接口会剥离Tag。\nTrunk接口 Trunk接口一般用于连接交换机、路由器、AP以及可同时收发Tagged帧和Untagged帧的语音终端。它可以允许多个VLAN的帧带Tag通过，但只允许属于缺省VLAN的帧从该类接口上发出时不带Tag（即剥除Tag）。\nTrunk接口上的缺省VLAN，有的厂商也将它定义为native VLAN。当Trunk接口收到Untagged帧时，会为Untagged帧打上Native VLAN对应的Tag。\nHybrid接口 Hybrid接口既可以用于连接不能识别Tag的用户终端（如用户主机、服务器）和网络设备（如Hub），也可以用于连接交换机、路由器以及可同时收发Tagged帧和Untagged帧的语音终端、AP。它可以允许多个VLAN的帧带Tag通过，且允许从该类接口发出的帧根据需要配置某些VLAN的帧带Tag（即不剥除Tag）、某些VLAN的帧不带Tag（即剥除Tag）。\nHybrid接口和Trunk接口在很多应用场景下可以通用，但在某些应用场景下，必须使用Hybrid接口。比如在灵活QinQ中，服务提供商网络的多个VLAN的报文在进入用户网络前，需要剥离外层VLAN Tag，此时Trunk接口不能实现该功能，因为Trunk接口只能使该接口缺省VLAN的报文不带VLAN Tag通过。\nVLAN的使用场景 VLAN的常见使用场景包括：VLAN间用户的二层隔离，VLAN间用户的三层互访。\nVLAN间用户的二层隔离（降本） 如下图所示，某商务楼内有多家公司，为了降低成本，多家公司共用网络资源，各公司分别连接到一台二层交换机的不同接口，并通过统一的出口访问Internet。\n为了保证各公司业务的独立和安全，可将每个公司所连接的接口划分到不同的VLAN，实现公司间业务数据的完全隔离。可以认为每个公司拥有独立的“虚拟路由器”，每个VLAN就是一个“虚拟工作组”。\n再比如，某公司有两个部门，分别分配了固定的IP网段。为加强员工间的学习与交流，员工的位置有时会相互调动，但公司希望各部门员工访问的网络资源的权限不变。（增效）\n为了保证部门内员工的位置调整后，访问网络资源的权限不变，可在公司的交换机Switch_1上配置基于IP子网划分VLAN。这样，服务器的不同网段就划分到不同的VLAN，访问服务器不同应用服务的数据流就会隔离，提高了安全性。\nVLAN间用户的三层互访 如下图所示，某小型公司的两个部门分别通过二层交换机接入到一台三层交换机Switch_3，所属VLAN分别为VLAN2和VLAN3，部门1和部门2的用户互通时，需要经过三层交换机。\n可在Switch_1和Switch_2上划分VLAN并将VLAN透传到Switch_3上，然后在Switch_3上为每个VLAN配置一个VLANIF接口，实现VLAN2和VLAN3间的路由。\nVLAN的相关协议 IEEE 802.1Q IEEE 802.1Q（也被称为Dot1q）即Virtual Bridged Local Area Networks协议，规定了VLAN的实现标准。与标准的以太网数据帧相比，VLAN数据帧增加了1个4字节的VLAN标签。\nLNP 链路类型协商协议（Link-type Negotiation Protocol，LNP）用来动态协商以太网接口的链路类型为Access或者Trunk。\n以太网接口的链路类型协商为Access，缺省情况下加入VLAN1。 以太网接口的链路类型协商为Trunk，缺省情况下加入VLAN1～4094。 QinQ QinQ（802.1Q-in-802.1Q）协议出自IEEE 802.1ad标准协议，通过在802.1Q标签报文的基础上再增加一层802.1Q的Tag来达到扩展VLAN空间的功能，可以使私网VLAN透传公网。\n由于在骨干网中传递的报文有两层802.1Q Tag（一层公网Tag，一层私网Tag），即802.1Q-in-802.1Q，所以称之为QinQ协议。\n云化场景下，VLAN存在的问题 随着网络技术的发展，云计算凭借其在系统利用率高、人力和管理成本低、灵活性和可扩展性强等方面表现出的优势，已经成为目前企业IT建设的新趋势。而服务器虚拟化作为云计算的核心技术之一，得到了越来越多的应用。\nVLAN作为传统的网络隔离技术，在标准定义中VLAN的数量只有4096个，无法满足大型数据中心的租户间隔离需求。另外，VLAN的二层范围一般较小且固定，无法支持虚拟机大范围的动态迁移。\n因此，RFC定义了VLAN扩展方案VXLAN（Virtual eXtensible Local Area Network，虚拟扩展局域网）。VXLAN采用MAC in UDP（User Datagram Protocol）封装方式，是NVO3（Network Virtualization over Layer 3）中的一种网络虚拟化技术。VXLAN完美地弥补了VLAN的上述不足，一方面通过VXLAN中的24比特VNI（VXLAN Network Identifier）字段，提供多达16M租户的标识能力，远大于VLAN的数量；另一方面，VXLAN本质上在两台交换机之间构建了一条穿越数据中心基础IP网络的虚拟隧道，将数据中心网络虚拟成一个巨型“二层交换机”，满足虚拟机大范围动态迁移的需求。\n","permalink":"https://blog.muwinds.com/archives/95/","summary":"\u003ch2 id=\"为什么需要vlan\"\u003e为什么需要VLAN\u003c/h2\u003e\n\u003cp\u003e早期以太网是一种基于CSMA/CD（Carrier Sense Multiple Access/Collision Detection）的共享通讯介质的数据网络通讯技术。当主机数目较多时会导致冲突严重、广播泛滥、性能显著下降甚至造成网络不可用等问题。通过二层设备实现LAN互连虽然可以解决冲突严重的问题，但仍然不能隔离广播报文和提升网络质量。\u003c/p\u003e","title":"交换机\u0026VLAN"},{"content":"win+r输入cmd打开命令提示符，输入指令：\nreg add \u0026#34;HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\WindowsUpdate\\UX\\Settings\u0026#34; /v FlightSettingsMaxPauseDays /t reg_dword /d 5000 /f 然后去设置-\u0026gt;windows更新-\u0026gt;高级选项，调整暂停更新的时间\n","permalink":"https://blog.muwinds.com/archives/94/","summary":"\u003cp\u003ewin+r输入cmd打开命令提示符，输入指令：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-powershell\" data-lang=\"powershell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ereg\u003c/span\u003e \u003cspan class=\"n\"\u003eadd\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\WindowsUpdate\\UX\\Settings\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ev\u003c/span\u003e \u003cspan class=\"n\"\u003eFlightSettingsMaxPauseDays\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003et\u003c/span\u003e \u003cspan class=\"n\"\u003ereg_dword\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e \u003cspan class=\"mf\"\u003e5000\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ef\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2024/01/202401141705199223830546.png\"\u003e\u003c/p\u003e\n\u003cp\u003e然后去设置-\u0026gt;windows更新-\u0026gt;高级选项，调整暂停更新的时间\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2024/01/202401141705199267793368.png\"\u003e\u003c/p\u003e","title":"Windows10和Windows11快速关闭自动更新大法"},{"content":"严格模式的写法：\nPHP\ndecleare(strict_type=1); 严格模式的声明需要放到php文件的最顶端，否则会抛出错误：\nFatal error: strict_types declaration must be the very first statement in the script 这个声明的类型属于是没有错误去制造错误，主要就是检测变量的类型type，如果不对，就会报错。值得特别说明的是：如果没有定义严格模式的话，这些类型错误很有可能都是可以接受的，并不会报错。\nFor example:\n\u0026lt;?php function add(int $a, int $b):int{ return $a + $b; } var_dump(add(1.0, 2.0)); 在此状态下执行独立时，输出int（3）\n我们提供的是double类型，但php7能很好的处理它,和php5时代没什么区别\n做了如下变更\n\u0026lt;?php declare(strict_types=1); //加入这句 function add(int $a, int $b): int { return $a + $b; } var_dump(add(1.0, 2.0)); 有TypeError产生,如下\nPHP Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type integer, float given, called in /Users/hiraku/sandbox/stricttypes/A.php on line 9 and defined in /Users/hiraku/sandbox/stricttypes/A.php:4Stack trace: #0 /Users/hiraku/sandbox/stricttypes/A.php(9): add(1, 2) #1 {main} thrown in /Users/hiraku/sandbox/stricttypes/A.php on line 4 ","permalink":"https://blog.muwinds.com/archives/93/","summary":"\u003cp\u003e严格模式的写法：\u003c/p\u003e\n\u003cp\u003ePHP\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-php\" data-lang=\"php\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003edecleare\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003estrict_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e严格模式的声明需要放到php文件的最顶端，否则会抛出错误：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eFatal error: strict_types declaration must be the very first statement in the script\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这个声明的类型属于是没有错误去制造错误，主要就是检测变量的类型\u003ccode\u003etype\u003c/code\u003e，如果不对，就会报错。值得特别说明的是：如果没有定义严格模式的话，这些类型错误很有可能都是可以接受的，并不会报错。\u003c/p\u003e","title":"php如何理解严格模式declare(strict_types=1)？"},{"content":"\n不得不吐槽一句：VSCode这个词汇检查弱智如word，大哥，动一动脑子好好改改……\n简单朴素的解决方法：\nGo to setting (Ctrl+,) type cSpell.diagnosticLevel in a search box change the option to Hint 转载：https://stackoverflow.com/questions/50309834/vs-code-enable-inline-spell-checker-but-disable-spell-check-in-the-problems\n","permalink":"https://blog.muwinds.com/archives/91/","summary":"\u003cp\u003e\u003cimg alt=\"enter image description here\" loading=\"lazy\" src=\"/wp-content/uploads/2023/06/202306251687689384126783.png\"\u003e\u003c/p\u003e\n\u003cp\u003e不得不吐槽一句：VSCode这个词汇检查弱智如word，大哥，动一动脑子好好改改……\u003c/p\u003e\n\u003cp\u003e简单朴素的解决方法：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eGo to setting \u003cem\u003e(Ctrl+,)\u003c/em\u003e\u003c/li\u003e\n\u003cli\u003etype \u003ccode\u003ecSpell.diagnosticLevel\u003c/code\u003e in a search box\u003c/li\u003e\n\u003cli\u003echange the option to \u003cstrong\u003eHint\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e转载：https://stackoverflow.com/questions/50309834/vs-code-enable-inline-spell-checker-but-disable-spell-check-in-the-problems\u003c/p\u003e","title":"VS Code: 代码词汇报错"},{"content":"在介绍旁路由配置之前，先说一下旁路由原理：\n你可以很清楚的看出：旁路由的网络接入与其他终端设备无异，你可以把他理解为一个普通的设备，这个设备负责数据交换的处理。\n大多数人装openwrt是为了翻墙，广告过滤，实际上这玩意儿行为管理的能力也不太行……\n那么，当我们把网关指向旁路由，我们的数据处理流程就是：客户端-旁路由-主路由-公网，返回也是一样的流程。\n当我们把网关指向主路由，数据流向就是：客户端-主路由-公网，返回也是一样的。\n你可以发现我们的网关可以直接在客户端进行修改，无需修改硬件接入网络的方式，这也就意味着当旁路由出现故障时你可以直接在客户端进行网络切换以保证网络连接质量，你可以将旁路由理解成电路中的并联接入。\n同时从接入图你也可以发现和一般网络接入完全不同的特征：他只需要一根网线和一个网口就可以完成接入，这和至少需要两根网线的主路由网关配置是不同的。\n回到上回的文章：PVE双网口\n你可以发现机器是装了个usb转rj45网卡的，也就是有两个网口，这是主路由的配置，为什么这里要说旁路由的配置呢？\n因为我本人把这机器换U的时候没换好，新板子来的时候我发现他一旦装网卡，主板自带的网卡就失效……\n故我打算就用主板自带的网卡做独臂路由。\n网上对于独臂路由的配置方式看的我一头雾水，我看了看原理我才知道怎么弄……\n首先，上面的原理你可以知道，旁路由是在主路由下是当做一个普通的用网设备的，所以理论上是不需要wan口这个东西的 ，去openwrt里关掉即可。\n在配置lan口，选择静态地址，和普通用网设备（静态IP配置电脑手机）一样去配置IP和网关就可以了\n为了防止DHCP多路由分配导致混乱，你可以自行选择关闭主路由DHCP或者旁路由DHCP，留下一个就好。\n如果局域网内设备要用旁路由的话，网关写旁路由的网关就好：\n","permalink":"https://blog.muwinds.com/archives/90/","summary":"\u003cp\u003e在介绍旁路由配置之前，先说一下旁路由原理：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"在这里插入图片描述\" loading=\"lazy\" src=\"/wp-content/uploads/2023/07/202307021688274015971941.png\"\u003e\u003c/p\u003e\n\u003cp\u003e你可以很清楚的看出：旁路由的网络接入与其他终端设备无异，你可以把他理解为一个普通的设备，这个设备负责数据交换的处理。\u003c/p\u003e\n\u003cp\u003e大多数人装openwrt是为了翻墙，广告过滤，实际上这玩意儿行为管理的能力也不太行……\u003c/p\u003e","title":"OpenWrt旁路由/独臂路由配置"},{"content":"在搓外汇API的时候，需要弄BOC爬虫去爬汇率数据，每次浏览器检查都是正常的，但在用bs4的selector后就是返回空列表：\n百度翻了半天，简单说一下原因：如果不是浏览器渲染的话，是不会渲染tbody的，就和下面的这个图一样\n你可以看到是没有表格结构的\n解决方法也很简单，直接删除tbody节点就好\n输出正常\n","permalink":"https://blog.muwinds.com/archives/89/","summary":"\u003cp\u003e在搓外汇API的时候，需要弄BOC爬虫去爬汇率数据，每次浏览器检查都是正常的，但在用bs4的selector后就是返回空列表：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/07/202307061688633615883089.png\"\u003e\u003c/p\u003e\n\u003cp\u003e百度翻了半天，简单说一下原因：如果不是浏览器渲染的话，是不会渲染tbody的，就和下面的这个图一样\u003c/p\u003e","title":"python selector网页含有tbody表格，输出返回空列表"},{"content":"*前提：在你操作之前请先按照Swoole官方操作文档对内核修改完成后再看本文：\n文档地址：https://wiki.swoole.com/#/other/sysctl\n汇率的API写好了，Swoole内网测试并发总会发生诸如redis went away的问题而导致502，当redis在同步模式下运行的时候无此问题，仅在Swoole一键协程化的时候出现：\n如果按照官方文档的步骤做了，确认redis等没有挂掉，请在screen下直接运行，而非在supervisor或者systemd下作为服务运行\n你会惊奇的发现：screen下运行没有这个问题了（除非你服务确实挂了）\n在百度和谷歌搜了很久，基本没有对这个问题的解答，这个问题解决了几天才发现这么一篇：\nhttps://medium.com/@white1033/%E8%A7%A3%E9%96%8Bcentos-%E5%88%A9%E7%94%A8systemd-%E8%B5%B7service-%E7%9A%84-ulimit-%E8%B3%87%E6%BA%90%E9%99%90%E5%88%B6-4b8dece0d61d\n以systemd为例，supervisor的原因也是一样的，都是deamon自身限制文件句柄导致的。\nvi /etc/systemd/system/yourself_service.service 将[Service]配置项下加一行，最终修改结果如下：\n[Service] LimitNOFILE=100400 最后使用\nsystemctl deamon-reload 重启deamon，最后重启服务，查看效果：\n7k并发测试后无任何报错\n","permalink":"https://blog.muwinds.com/archives/88/","summary":"\u003cp\u003e*前提：在你操作之前请先按照Swoole官方操作文档对内核修改完成后再看本文：\u003c/p\u003e\n\u003cp\u003e文档地址：\u003ca href=\"https://wiki.swoole.com/#/other/sysctl\"\u003ehttps://wiki.swoole.com/#/other/sysctl\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e汇率的API写好了，Swoole内网测试并发总会发生诸如redis went away的问题而导致502，当redis在同步模式下运行的时候无此问题，仅在Swoole一键协程化的时候出现：\u003c/p\u003e","title":"php Swoole 在supervisor或systemd下高并发频发redis mysql连接失败"},{"content":"早闻Swoole因其持久化的特性有高性能高并发和语法简单的特点，突发奇想试着把汇率API项目的API部分用Swoole处理，在这里对比一下两者的性能差异\n服务器配置：\nE5 2650V2 4核心 KVM虚拟化无超开\n8G内存\n程序代码和运行在SATA SSD内进行\n系统：CentOS 7.9\nPHP版本：8.1开启JIT，FPM使用opache\n压测机配置：\ni5 10400F+16G内存，好吧其实就是我自己的电脑。\n压测程序：https://github.com/link1st/go-stress-testing\n首先是php内置函数1w发测试：\nFPM代码：\n\u0026lt;?php echo \u0026#39;use PHP-FPM.#\u0026#39;.mt_rand(1000,9999); Swoole代码在异步模式下的代码：\n\u0026lt;?php $http = new Swoole\\Http\\Server(\u0026#39;0.0.0.0\u0026#39;, 9501); $http-\u0026gt;on(\u0026#39;Request\u0026#39;, function ($request, $response) { $response-\u0026gt;header(\u0026#39;Content-Type\u0026#39;, \u0026#39;text/html; charset=utf-8\u0026#39;); $response-\u0026gt;end(\u0026#39;\u0026lt;h1\u0026gt;Hello Swoole. #\u0026#39; . mt_rand(1000, 9999) . \u0026#39;\u0026lt;/h1\u0026gt;\u0026#39;);} ); $http-\u0026gt;start(); FPM测试结果：\nSwoole异步测试结果：\nSwoole异步下tp99对比FPM有了三倍的性能提升\n接下来是Swoole协程模式下，下面是代码：\n\u0026lt;?php use Swoole\\Coroutine\\Http\\Server; use function Swoole\\Coroutine\\run; run(function () { $server = new Server(\u0026#39;127.0.0.1\u0026#39;, 9502, false); $server-\u0026gt;handle(\u0026#39;/\u0026#39;, function ($request, $response) { $response-\u0026gt;end(\u0026#39;\u0026lt;h1\u0026gt;Hello Swoole. #\u0026#39; . rand(1000, 9999) . \u0026#39;\u0026lt;/h1\u0026gt;\u0026#39;); }); $server-\u0026gt;start(); }); 测试结果：\n和异步模式下几乎无差别。\n接下来是redis取值+循环输出数组，PHP-FPM代码如下：\n\u0026lt;?php $redis_config=array( \u0026#39;host\u0026#39; =\u0026gt; \u0026#34;127.0.0.1\u0026#34;, //数据库服务器 \u0026#39;port\u0026#39; =\u0026gt; 6379, //数据库端口 ); $arraylist = array(\u0026#34;GBP\u0026#34;, \u0026#34;HKD\u0026#34;, \u0026#34;USD\u0026#34;, \u0026#34;CHF\u0026#34;, \u0026#34;DEM\u0026#34;, \u0026#34;FRF\u0026#34;, \u0026#34;SGD\u0026#34;, \u0026#34;SEK\u0026#34;, \u0026#34;DKK\u0026#34;, \u0026#34;NOK\u0026#34;, \u0026#34;JPY\u0026#34;, \u0026#34;CAD\u0026#34;, \u0026#34;AUD\u0026#34;, \u0026#34;EUR\u0026#34;, \u0026#34;MOP\u0026#34;, \u0026#34;PHP\u0026#34;, \u0026#34;THB\u0026#34;, \u0026#34;NZD\u0026#34;, \u0026#34;KRW\u0026#34;, \u0026#34;RUB\u0026#34;, \u0026#34;MYR\u0026#34;, \u0026#34;TWD\u0026#34;, \u0026#34;ESP\u0026#34;, \u0026#34;ITL\u0026#34;, \u0026#34;NLG\u0026#34;, \u0026#34;BEF\u0026#34;, \u0026#34;FIM\u0026#34;, \u0026#34;INR\u0026#34;, \u0026#34;IDR\u0026#34;, \u0026#34;BRL\u0026#34;, \u0026#34;AED\u0026#34;, \u0026#34;ZAR\u0026#34;, \u0026#34;SAR\u0026#34;, \u0026#34;TRY\u0026#34;, \u0026#34;time\u0026#34;); $redis = new Redis(); $redis-\u0026gt;pconnect($redis_config[\u0026#39;host\u0026#39;],$redis_config[\u0026#39;port\u0026#39;]); $arList = $redis-\u0026gt;mget($arraylist); $array_result=array(); foreach($arList as $k =\u0026gt; $v){ $currency = $arraylist[\u0026#34;$k\u0026#34;]; $array_result[$currency] = $v; } print_r(json_encode($array_result)); Swoole：\nhttpServer.php：\nPHP\n\u0026lt;?php use Swoole\\Coroutine; $http = new Swoole\\Http\\Server(\u0026#39;0.0.0.0\u0026#39;, 9501); Coroutine::set([\u0026#39;hook_flags\u0026#39;=\u0026gt; SWOOLE_HOOK_ALL]); ini_set(\u0026#39;memory_limit\u0026#39;,\u0026#39;-1\u0026#39;); include_once(\u0026#39;api/autoload.php\u0026#39;); $http-\u0026gt;set([ \u0026#39;buffer_output_size\u0026#39; =\u0026gt; 128 * 1024 * 1024, //必须为数字 ]); $http-\u0026gt;on(\u0026#39;Request\u0026#39;, function ($request, $response) { if ($request-\u0026gt;server[\u0026#39;path_info\u0026#39;] == \u0026#39;/favicon.ico\u0026#39; || $request-\u0026gt;server[\u0026#39;request_uri\u0026#39;] == \u0026#39;/favicon.ico\u0026#39;) { $response-\u0026gt;end(); return; } list($controller, $action) = explode(\u0026#39;/\u0026#39;, trim($request-\u0026gt;server[\u0026#39;request_uri\u0026#39;], \u0026#39;/\u0026#39;)); //根据 $controller, $action 映射到不同的控制器类和方法。 if (method_exists($controller,$action)){ $result_route = (new $controller)-\u0026gt;$action($request, $response); $response-\u0026gt;end($result_route); }else{ $response-\u0026gt;header(\u0026#39;Content-Type\u0026#39;, \u0026#39;text/html; charset=utf-8\u0026#39;); $response-\u0026gt;end(\u0026#34;API服务正常，请求内容不存在\u0026#34;); } }); $http-\u0026gt;start(); /api/autoload.php\n\u0026lt;?php // API自动加载器 spl_autoload_register(function ($class) { include $class.\u0026#39;.php\u0026#39;; },false); /api/exchangeRate.php\n\u0026lt;?php /** * 汇率操作功能类 */ include(dirname(__DIR__).\u0026#39;/db/db.php\u0026#39;); class exchangeRate{ public function index($request, $response){ $arraylist = array(\u0026#34;GBP\u0026#34;, \u0026#34;HKD\u0026#34;, \u0026#34;USD\u0026#34;, \u0026#34;CHF\u0026#34;, \u0026#34;DEM\u0026#34;, \u0026#34;FRF\u0026#34;, \u0026#34;SGD\u0026#34;, \u0026#34;SEK\u0026#34;, \u0026#34;DKK\u0026#34;, \u0026#34;NOK\u0026#34;, \u0026#34;JPY\u0026#34;, \u0026#34;CAD\u0026#34;, \u0026#34;AUD\u0026#34;, \u0026#34;EUR\u0026#34;, \u0026#34;MOP\u0026#34;, \u0026#34;PHP\u0026#34;, \u0026#34;THB\u0026#34;, \u0026#34;NZD\u0026#34;, \u0026#34;KRW\u0026#34;, \u0026#34;RUB\u0026#34;, \u0026#34;MYR\u0026#34;, \u0026#34;TWD\u0026#34;, \u0026#34;ESP\u0026#34;, \u0026#34;ITL\u0026#34;, \u0026#34;NLG\u0026#34;, \u0026#34;BEF\u0026#34;, \u0026#34;FIM\u0026#34;, \u0026#34;INR\u0026#34;, \u0026#34;IDR\u0026#34;, \u0026#34;BRL\u0026#34;, \u0026#34;AED\u0026#34;, \u0026#34;ZAR\u0026#34;, \u0026#34;SAR\u0026#34;, \u0026#34;TRY\u0026#34;, \u0026#34;time\u0026#34;); $redis = new redisController(); $arList = $redis-\u0026gt;mget($arraylist); $array_result=array(); foreach($arList as $k =\u0026gt; $v){ $currency = $arraylist[\u0026#34;$k\u0026#34;]; $array_result[$currency] = $v; } return json_encode($array_result); } /db/db.php\n\u0026lt;?php include_once(dirname(__DIR__).\u0026#39;/db/redisController.php\u0026#39;); /db/redisController.php\n\u0026lt;?php /** * Redis操作类 */ class redisController{ public function mget($keys){ include(\u0026#39;config.php\u0026#39;); $redis = new Redis(); $redis-\u0026gt;pconnect($redis_config[\u0026#39;host\u0026#39;],$redis_config[\u0026#39;port\u0026#39;]); $value = $redis-\u0026gt;mget($keys); return $value; } } 测试结果：\nFPM：\ntp99：6.1s 但是已经出现502了（意味着有部分请求FPM是无响应的）\ntp99:3s，无502,4s处理完所有请求\nSwoole原理：\nphp中用swoole框架写好代码，在cli模式下启动后swoole会常驻后台，这样就达到了持久化，性能天生就比fpm高……\n同时启动的时候就将php代码加载完毕转为机器码，对比fpm处理一次请求就要加载一次代码然后再处理的效率要高。\nSwoole使用C++编写，支持一键协程化，自然效率高于FPM\n","permalink":"https://blog.muwinds.com/archives/87/","summary":"\u003cp\u003e早闻Swoole因其持久化的特性有高性能高并发和语法简单的特点，突发奇想试着把汇率API项目的API部分用Swoole处理，在这里对比一下两者的性能差异\u003c/p\u003e\n\u003cp\u003e服务器配置：\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/07/202307181689673249802427.png\"\u003e\u003c/p\u003e","title":"php Swoole和FPM性能对比"},{"content":"如果pve网卡并非直通模式，请把虚拟网卡的Model处改成pve的virtIO:\n通常情况下这就可以跑满，即使你的网卡型号和model处吻合。\n接下来是诊断过程：\n当我做完全盘映射并做好Samba正在导入数据，发现写入只有可怜的50M/s（我家是千兆网，顺序写入就算是机械也不可能这么慢），读取更是炸裂，只有10M/s\n起初我以为是全盘映射的问题，当我把sata换成scsi的时候发现还是无法解决\n后来通过top发现有个叫ksoftirqd/n的东西占用巨高，百度了一下这个进程：\n当来自设备的中断时，操作系统会暂停它正在执行的操作并开始寻址该中断。\n在某些情况下，IRQ一个接一个地非常快，操作系统无法在另一个到达之前完成一个服务。当高速网卡在短时间内收到大量数据包时，就会发生这种情况。\n因为操作系统在到达时无法处理IRQ（因为它们一个接一个地到达得太快），\n操作系统会将它们排队等待稍后由名为ksoftirqd的/n(n为cpu的逻辑号)的内核线程处理。\n每个ksoftirqd/n内核线程都运行ksoftirqd()函数，实际上该函数执行下面的循环：\nfor (;;) { set_current_state(TASK_INTERRUPTIBLE); schedule(); while (local_softirq_pending()) { preempt_disable(); do_softirq(); // 处理软中断 preempt_enable(); cond_resched(); } } 占用100%通常情况下为网卡问题。\n经过上面的提示，我就知道是虚拟机的网卡问题了，按照我最上方的方法解决问题后，smb速度恢复正常。\n","permalink":"https://blog.muwinds.com/archives/86/","summary":"\u003cp\u003e如果pve网卡并非直通模式，请把虚拟网卡的Model处改成pve的virtIO:\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/07/202307201689821210644477.png\"\u003e\u003c/p\u003e\n\u003cp\u003e通常情况下这就可以跑满，即使你的网卡型号和model处吻合。\u003c/p\u003e\n\u003cp\u003e接下来是诊断过程：\u003c/p\u003e\n\u003cp\u003e当我做完全盘映射并做好Samba正在导入数据，发现写入只有可怜的50M/s（我家是千兆网，顺序写入就算是机械也不可能这么慢），读取更是炸裂，只有10M/s\u003c/p\u003e","title":"pve网卡速度慢的可能解决方法（包括不限于SMB速度异常慢等）"},{"content":"打开pve的shell，输入：\nls /dev/disk/by-id 我要将最上方的ata-ST400做全盘映射到ID为101的主机：\nqm set 101 -scsi1 /dev/disk/by-id/ata-ST4000VX000-2AG166_ZDH0687G 接下来去PVE后台看是否映射成功：\n","permalink":"https://blog.muwinds.com/archives/85/","summary":"\u003cp\u003e打开pve的shell，输入：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003els /dev/disk/by-id\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/07/202307201689821363370150.png\"\u003e\u003c/p\u003e\n\u003cp\u003e我要将最上方的ata-ST400做全盘映射到ID为101的主机：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eqm \u003cspan class=\"nb\"\u003eset\u003c/span\u003e \u003cspan class=\"m\"\u003e101\u003c/span\u003e -scsi1 /dev/disk/by-id/ata-ST4000VX000-2AG166_ZDH0687G\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e接下来去PVE后台看是否映射成功：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/07/202307201689821504627353.png\"\u003e\u003c/p\u003e","title":"Proxmox VE直通硬盘（全盘映射方式）"},{"content":"KVM用的proxmox是免费版社区版的，所以每次都提示这个没有有效的订阅挺烦的！\n解决方法： 修改文件/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js\n然后找到if (data.status !== 'Active') {\n修改为if (false) {\nCtrl+O回车保存，重启浏览器清除缓存查看效果。\n","permalink":"https://blog.muwinds.com/archives/84/","summary":"\u003cp\u003eKVM用的proxmox是免费版社区版的，所以每次都提示这个没有有效的订阅挺烦的！\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/wp-content/uploads/2023/07/202307211689918403569901.png\"\u003e\u003c/p\u003e\n\u003ch1 id=\"解决方法\"\u003e解决方法：\u003c/h1\u003e\n\u003cp\u003e修改文件/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js\u003c/p\u003e","title":"Proxmox VE登陆的时候提示没有有效的订阅You do not have a valid subscription for this server."},{"content":"\n基本上还是PVE没有企业订阅的问题，下面说解决方法，版本适合PVE7：\n使用nano，/etc/apt/sources.list.d/pve-enterprise.list下：\ndeb https://enterprise.proxmox.com/debian/pve bookworm pve-enterprise 将第一行在顶头用#注释掉，Ctrl+O回车保存，Ctrl+X退出\n接下来配置无订阅库，将下列内容覆写到/etc/apt/sources.list即可：\ndeb http://ftp.debian.org/debian bookworm main contrib deb http://ftp.debian.org/debian bookworm-updates main contrib # Proxmox VE pve-no-subscription repository provided by proxmox.com, # NOT recommended for production use deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription # security updates deb http://security.debian.org/debian-security bookworm-security main contrib 2023.7.27更新：\n做完上面的操作，PVE后台仍然报错，在PVE论坛看了一下，还要在ceph屏蔽企业源……\n步骤：nano打开/etc/apt/sources.list.d/ceph.list\n将第一行用#注释掉，Ctrl+O回车保存，Ctrl+X退出\n如果不放心，输入apt-get update确认结果:：\n来源：https://pve.proxmox.com/wiki/Package_Repositories\n2023.8.2更新：\npve后台就可以关闭存储库……\n","permalink":"https://blog.muwinds.com/archives/83/","summary":"\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/07/202307261690381778292229.png\"\u003e\u003c/p\u003e\n\u003cp\u003e基本上还是PVE没有企业订阅的问题，下面说解决方法，版本适合PVE7：\u003c/p\u003e\n\u003cp\u003e使用nano，/etc/apt/sources.list.d/pve-enterprise.list下：\u003c/p\u003e","title":"PVE管理后台提示报错apt-get update failed解决方法"},{"content":"旁路由做了FullconeNAT之后，比特彗星测试结果还是PortStrictedFullconeNAT，同时通过dmesg进行内核诊断，我发现日志总是爆出ARP缓存溢出的错误：\n[ 5352.346674] net_ratelimit: 1870 callbacks suppressed [ 5352.346681] neighbour: arp_cache: neighbor table overflow! [ 5352.347995] neighbour: arp_cache: neighbor table overflow! [ 5352.348690] neighbour: arp_cache: neighbor table overflow! [ 5352.348922] neighbour: arp_cache: neighbor table overflow! [ 5352.349371] neighbour: arp_cache: neighbor table overflow! [ 5352.350341] neighbour: arp_cache: neighbor table overflow! [ 5352.351033] neighbour: arp_cache: neighbor table overflow! [ 5352.351733] neighbour: arp_cache: neighbor table overflow! [ 5352.352422] neighbour: arp_cache: neighbor table overflow! [ 5352.353103] neighbour: arp_cache: neighbor table overflow! [ 5382.685484] net_ratelimit: 2982 callbacks suppressed [ 5382.685491] neighbour: arp_cache: neighbor table overflow! [ 5382.686173] neighbour: arp_cache: neighbor table overflow! [ 5382.686629] neighbour: arp_cache: neighbor table overflow! [ 5382.687556] neighbour: arp_cache: neighbor table overflow! [ 5382.688252] neighbour: arp_cache: neighbor table overflow! [ 5382.688892] neighbour: arp_cache: neighbor table overflow! [ 5382.689519] neighbour: arp_cache: neighbor table overflow! [ 5382.690180] neighbour: arp_cache: neighbor table overflow! [ 5382.690862] neighbour: arp_cache: neighbor table overflow! [ 5382.691515] neighbour: arp_cache: neighbor table overflow! openwrt的gc阈值控制分为三挡，1档表示最小可保留数量，2档表示当ARP表的数量达到此值时，GC会清理大于5s的表项，3档表示最大的可保存表项的数量，超过就会触发GC全部清理。\nOpenWrt我没记错的话第三档默认为512,，再看一下现在的ARP条目：\nIPv4（v6把-4改成-6就行了）：\nip -4 neigh show nud all | wc -l 旁路由默认情况下只用ipv4，可得：\n308……比较接近当前设置的极限了，这还是低峰时期呢，高峰期丢包更是频繁一点……\n临时修改：\nsysctl -w net.ipv4.neigh.default.gc_thresh1=2048\u0026lt;br\u0026gt;sysctl -w net.ipv4.neigh.default.gc_thresh2=4096\u0026lt;br\u0026gt;sysctl -w net.ipv4.neigh.default.gc_thresh3=8192\u0026lt;br\u0026gt;sysctl -w net.ipv6.neigh.default.gc_thresh1=2048\u0026lt;br\u0026gt;sysctl -w net.ipv6.neigh.default.gc_thresh2=4096\u0026lt;br\u0026gt;sysctl -w net.ipv6.neigh.default.gc_thresh3=8192 修改/etc/sysctl.conf永久保存：\nnet.ipv4.neigh.default.gc_thresh1 = 2048 net.ipv4.neigh.default.gc_thresh2 = 4096 net.ipv4.neigh.default.gc_thresh3 = 8192 net.ipv6.neigh.default.gc_thresh1 = 2048 net.ipv6.neigh.default.gc_thresh2 = 4096 net.ipv6.neigh.default.gc_thresh3 = 8192 ","permalink":"https://blog.muwinds.com/archives/81/","summary":"\u003cp\u003e旁路由做了FullconeNAT之后，比特彗星测试结果还是PortStrictedFullconeNAT，同时通过dmesg进行内核诊断，我发现日志总是爆出ARP缓存溢出的错误：\u003c/p\u003e","title":"OpenWrt ARP缓存溢出问题解决"},{"content":"现在跑的支付系统在处理客户问题查询订单的时候经常查询超时锁表……\n支付系统的在数据库的特性是高更新频率，当我在排查问题的时候看到了query cache，去google了一下：\nMySQL查询缓存是MySQL中比较独特的一个缓存区域，用来缓存特定Query的整个结果集信息，且共享给所有客户端。为了提高完全相同的Query语句的响应速度，MySQL Server会对查询语句进行Hash计算后，把得到的hash值与Query查询的结果集对应存放在Query Cache中。当MySQL Server打开Query Cache之后，MySQL Server会对接收到的每一个SELECT 语句通过特定的Hash算法计算该Query的Hash值，然后通过该hashi值到Query Cache中去匹配。\n如果没有匹配，将这个hash值存放在一个hash链表中，并将Query的结果集存放到cache中，存放hashi值链表的每个hash节点存放了相应Quey结果集在cache中的地址，以及该query所涉及到一些table相关信息；\n如果通过hash值匹配到了一样的Query，则直接将cache中相应的Query结果集返回给客户端\n目前MySQL Query Cache只会cache select语句，其他类似show ，use的语句不会被cache MySQL 的每个Query Cache都是以SQL文本作为key来存储的，在应用Query Cache之前，SQL文本不会做任何处理。也就是说，两个SQL语句，只要相差哪怕一个字符(例如大小写不一样，多一个空格，多注释)，那么这两个SQL将使用不同的Cache地址。如: 下面三条SQL将会被存储在三个不同的缓存里，虽然他们的结果都是一样的\nMySQL缓存机制简单的说就是缓存sql文本及查询结果，如果运行相同的SQL，服务器直接从缓存中取到结果，而不需要再去解析和执行SQL。如果表更改了，那么使用这个表的所有缓存查询将不再有效，查询缓存中值相关条目被清空。这里的更改指的是表中任何数据或是结构发生改变，包括INSERT、UPDATE、 DELETE、TRUNCATE、ALTER TABLE、DROP TABLE或DROP DATABASE等，也包括那些映射到改变了的表使用MERGE表的查询。显然，这对于频繁更新的表，查询缓存是不适合的，而对于一些不常改变数据且有大量相同SQL查询的表，查询缓存会节约很大的性能。\n","permalink":"https://blog.muwinds.com/archives/80/","summary":"\u003cp\u003e现在跑的支付系统在处理客户问题查询订单的时候经常查询超时锁表……\u003c/p\u003e\n\u003cp\u003e支付系统的在数据库的特性是高更新频率，当我在排查问题的时候看到了query cache，去google了一下：\u003c/p\u003e","title":"支付系统下MySQL超时问题"},{"content":"高中物理必修电学会用到一个结论，就是当R=r时（R为路端总电阻，r为电源内阻）电源的输出功率最大\n这个结论并没有要求证明，但是我没证明过程我记不住\n故在这里简单证明一下：\n示例电路：\n证明过程如下：\n练习：求R1达到其最大电功率时R2的电阻：\n","permalink":"https://blog.muwinds.com/archives/79/","summary":"\u003cp\u003e高中物理必修电学会用到一个结论，就是当R=r时（R为路端总电阻，r为电源内阻）电源的输出功率最大\u003c/p\u003e\n\u003cp\u003e这个结论并没有要求证明，但是我没证明过程我记不住\u003c/p\u003e\n\u003cp\u003e故在这里简单证明一下：\u003c/p\u003e","title":"电阻与电源消耗电功率问题"},{"content":"初中的时候老师告诉我们看电压表电流表在电路里时，将电流表充当导线，电压表充当断路。\n这里简单说一下原因。\n任何一个电压表和电流表都由灵敏电流计配合大电阻组成：\n原理图可以看出：电流表中的R用来分G的电流，并以此来达到更大的量程；电压表的R用来分G的电压，也可以得到更大的量程。\n灵敏电流计是有电阻的，但是电阻远小于并联/串联的大电阻，这里记作电阻r\n此时：\n电流表的R总：Rr/R+r\n电压表的R总：R+r\n由欧姆定律可知，电流表的总阻值小于r，电压表的总阻值大于R，因此，可以将电流表充当导线，电压表充当断路。\n","permalink":"https://blog.muwinds.com/archives/78/","summary":"\u003cp\u003e初中的时候老师告诉我们看电压表电流表在电路里时，将电流表充当导线，电压表充当断路。\u003c/p\u003e\n\u003cp\u003e这里简单说一下原因。\u003c/p\u003e\n\u003cp\u003e任何一个电压表和电流表都由灵敏电流计配合大电阻组成：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/03/202303271679868903484000.png\"\u003e\u003c/p\u003e\n\u003cp\u003e原理图可以看出：电流表中的R用来分G的电流，并以此来达到更大的量程；电压表的R用来分G的电压，也可以得到更大的量程。\u003c/p\u003e","title":"中学电学必修：电表电路原理"},{"content":"本指南将引导你完成：\n创建 NAT 网络 将现有虚拟机连接到新网络 确认虚拟机正确连接 要求：\nWindows 10 周年更新或更高版本 已启用 Hyper-V（单击此处 查看相关说明） 注意：目前，每台主机仅限一个 NAT 网络。 有关 Windows NAT (WinNAT) 实现、功能和限制的更多详细信息，请参考 WinNAT capabilities and limitations blog（WinNAT 功能和限制日志）\nNAT 概述 NAT 使用主计算机的 IP 地址和端口通过内部 Hyper-V 虚拟开关向虚拟机授予对网络资源的访问权限。\n网络地址转换 (NAT) 是一种网络模式，旨在通过将一个外部 IP 地址和端口映射到更大的内部 IP 地址集来转换 IP 地址。 基本上，NAT 使用流量表将流量从一个外部（主机）IP 地址和端口号路由到与网络上的终结点（虚拟机、计算机和容器等）关联的正确内部 IP 地址\n此外，NAT 允许多个虚拟机托管需要相同（内部）通信端口的应用程序，方法是将它们映射到唯一的外部端口。\n出于所有这些原因，NAT 网络对于容器技术是很常见的（请参阅容器网络）。\n创建 NAT 虚拟网络 让我们演练新 NAT 网络的设置。\n以管理员身份打开 PowerShell 控制台。 创建内部交换机。 New-VMSwitch -SwitchName \u0026#34;SwitchName\u0026#34; -SwitchType Internal 查找刚创建的虚拟交换机的接口索引。\n可以通过运行 Get-NetAdapter 来查找接口索引\n你的输出应类似下面的形式：\nPS C:\\\u0026gt; Get-NetAdapter Name InterfaceDescription ifIndex Status MacAddress LinkSpeed ---- -------------------- ------- ------ ---------- --------- vEthernet (intSwitch) Hyper-V Virtual Ethernet Adapter 24 Up 00-15-5D-00-6A-01 10 Gbps Wi-Fi Marvell AVASTAR Wireless-AC Net... 18 Up 98-5F-D3-34-0C-D3 300 Mbps Bluetooth Network ... Bluetooth Device (Personal Area... 21 Disconnected 98-5F-D3-34-0C-D4 3 Mbps 内部交换机的名称将类似于 vEthernet (SwitchName)，接口描述将为 Hyper-V Virtual Ethernet Adapter。 请记下其 ifIndex 以便在下一步中使用。\n使用 New-NetIPAddress 配置 NAT 网关。\n下面是常规命令：\nNew-NetIPAddress -IPAddress \u0026lt;NAT Gateway IP\u0026gt; -PrefixLength \u0026lt;NAT Subnet Prefix Length\u0026gt; -InterfaceIndex \u0026lt;ifIndex\u0026gt; 若要配置网关，你将需要一些有关你的网络的信息：\nIPAddress - NAT 网关 IP 指定要用作 NAT 网关 IP 的 IPv4 或 IPv6 地址。\n常规形式将为 a.b.c.1（例如 172.16.0.1）。 尽管最后一个位置不一定必须是.1，但通常是（基于前缀长度）\n通用网关 IP 为 192.168.0.1 PrefixLength \u0026ndash; NAT 子网前缀长度定义的 NAT 本地子网大小（子网掩码）。 子网前缀长度将介于 0 到 32 之间的一个整数值。\n0 将映射整个 Internet，32 则只允许一个映射的 IP。 常用值范围从 24 到 12，具体要取决于多少 IP 需要附加到 NAT。\n常用 PrefixLength 为 24 \u0026ndash; 这是子网掩码 255.255.255.0 InterfaceIndex \u0026ndash; ifIndex 是你在上一步中确定的虚拟交换机的接口索引。 运行以下内容来创建 NAT 网关：\nNew-NetIPAddress -IPAddress 192.168.0.1 -PrefixLength 24 -InterfaceIndex 24 使用 New-NetNat 配置 NAT 网络。\n下面是常规命令：\nNew-NetNat -Name \u0026lt;NATOutsideName\u0026gt; -InternalIPInterfaceAddressPrefix \u0026lt;NAT subnet prefix\u0026gt; 若要配置网关，你将需要提供一些有关网络和 NAT 网关的信息：\nName - NATOutsideName 描述 NAT 网络的名称。 将使用此参数删除 NAT 网络。 InternalIPInterfaceAddressPrefix - NAT 子网前缀同时描述上述 NAT 网关 IP 前缀和上述 NAT 子网前缀长度。 常规形式将为 a.b.c.0/NAT 子网前缀长度\n综上所述，对于本示例，我们将使用 192.168.0.0/24\n对于我们的示例，运行以下命令以设置 NAT 网络：\nNew-NetNat -Name MyNATnetwork -InternalIPInterfaceAddressPrefix 192.168.0.0/24 恭喜！ 你现在已拥有一个虚拟 NAT 网络！ 若要向 NAT 网络添加虚拟机，请按照以下说明进行操作。\n连接虚拟机\n若要将虚拟机连接到新的 NAT 网络，请使用“VM 设置”菜单将你在 NAT 网络设置部分的第一步中创建的内部交换机连接到虚拟机。\n由于 WinNAT 本身不会将 IP 地址分配给某个终结点（例如，VM），因此，你将需要从 VM 内手动完成此操作，即设置 NAT 内部前缀范围内的 IP 地址、设置默认网关 IP 地址，以及设置 DNS 服务器信息。 唯一需要注意的是终结点何时连接到容器。 在这种情况下，主机网络服务 (HNS) 分配并使用主机计算服务 (HCS) 直接向容器分配 IP 地址、网关 IP 和 DNS 信息。\n配置示例：将 VM 和容器连接到 NAT 网络 如果你需要将多个 VM 和容器连接到单个 NAT，则需要确保 NAT 内部子网前缀大小足以包含由不同的应用程序或服务（例如用于 Windows 的 Docker 和 Windows 容器 – HNS）分配的 IP 范围。 这将需要应用程序级别的 IP 分配和网络配置，或者必须由管理员完成手动配置，从而保证不会在同一主机上重用现有 IP 分配。\n用于 Windows 的 Docker（Linux VM）和 Windows 容器 下面的解决方案将允许用于 Windows 的 Docker（运行 Linux 容器的 Linux VM）和 Windows 容器使用单独的内部 vSwitch 共享同一个 WinNAT 实例。 Linux 和 Windows 容器之间的连接将起作用。\n用户已通过名为“VMNAT”的内部 vSwitch 将 VM 连接到 NAT 网络，现在想要使用 Docker 引擎安装 Windows 容器功能\nPS C:\\\u0026gt; Get-NetNat “VMNAT”| Remove-NetNat (this will remove the NAT but keep the internal vSwitch). Install Windows Container Feature DO NOT START Docker Service (daemon) Edit the arguments passed to the docker daemon (dockerd) by adding –fixed-cidr=\u0026lt;container prefix\u0026gt; parameter. This tells docker to create a default nat network with the IP subnet \u0026lt;container prefix\u0026gt; (e.g. 192.168.1.0/24) so that HNS can allocate IPs from this prefix. PS C:\\\u0026gt; Start-Service Docker; Stop-Service Docker PS C:\\\u0026gt; Get-NetNat | Remove-NetNAT (again, this will remove the NAT but keep the internal vSwitch) PS C:\\\u0026gt; New-NetNat -Name SharedNAT -InternalIPInterfaceAddressPrefix \u0026lt;shared prefix\u0026gt; PS C:\\\u0026gt; Start-Service docker Docker/HNS 将 IP 分配给 Windows 容器，管理员将 IP 分配给这两个的差异集的 VM。\n用户已通过运行 docker 引擎安装了 Windows 容器功能，现在想要将 VM 连接到 NAT 网络\nPS C:\\\u0026gt; Stop-Service docker PS C:\\\u0026gt; Get-ContainerNetwork | Remove-ContainerNetwork -force PS C:\\\u0026gt; Get-NetNat | Remove-NetNat (this will remove the NAT but keep the internal vSwitch) Edit the arguments passed to the docker daemon (dockerd) by adding -b “none” option to the end of docker daemon (dockerd) command to tell docker not to create a default NAT network. PS C:\\\u0026gt; New-ContainerNetwork –name nat –Mode NAT –subnetprefix \u0026lt;container prefix\u0026gt; (create a new NAT and internal vSwitch – HNS will allocate IPs to container endpoints attached to this network from the \u0026lt;container prefix\u0026gt;) PS C:\\\u0026gt; Get-Netnat | Remove-NetNAT (again, this will remove the NAT but keep the internal vSwitch) PS C:\\\u0026gt; New-NetNat -Name SharedNAT -InternalIPInterfaceAddressPrefix \u0026lt;shared prefix\u0026gt; PS C:\\\u0026gt; New-VirtualSwitch -Type internal (attach VMs to this new vSwitch) PS C:\\\u0026gt; Start-Service docker Docker/HNS 将 IP 分配给 Windows 容器，管理员将 IP 分配给这两个的差异集的 VM。\n最终，你应该有两个内部 VM 开关以及在这两个开关之间共享的一个 NetNat。\n如有必要，请参阅 setup guide for multiple applications using the same NAT（使用同一个 NAT 的多个应用程序的安装指南）以重新生成你的 NAT 环境。\n参考 详细了解 NAT 网络\n引用自：https://docs.microsoft.com/zh-cn/virtualization/hyper-v-on-windows/user-guide/setup-nat-network\n配置端口映射 1.先查看物理网卡的网络情况，ipconfig /all\n2.确认公网IP（既虚拟机共享上网的那个网卡的IP），私网IP（就是虚拟机nat桥接的那个IP）\nnetsh interface portproxy add v4tov4 listenport=外网端口 listenaddress=主IP connectaddress=私网IP connectport=私网IP端口\n如下例\nnetsh interface portproxy add v4tov4 listenport=80 listenaddress=119.146.62.231 connectaddress=192.168.1.251 connectport=80 内网端口可以不一致\nnetsh interface portproxy add v4tov4 listenport=2022 listenaddress=119.146.62.231 connectaddress=192.168.1.251 connectport=22 删除映射\nnetsh interface portproxy delete v4tov4 listenaddress=119.146.62.231 listenport=2022 查询端口映射情况\nnetsh interface portproxy show v4tov4 共享IP端口映射一些常用命令\n一、查询端口映射情况\nnetsh interface portproxy show v4tov4 查询这个IP所有的端口映射。\nnetsh interface portproxy show v4tov4|find \u0026#34;192.168.1.1\u0026#34; 二、增加一个端口映射\nnetsh interface portproxy add v4tov4 listenport=外网端口 listenaddress=主IP connectaddress=私网IP connectport=私网IP端口 例如：\nnetsh interface portproxy add v4tov4 listenport=8888 listenaddress=118.123.13.180 connectaddress=192.168.1.10 connectport=2222 三、删除一个端口映射\nnetsh interface portproxy delete v4tov4 listenaddress=主IP listenport=外网端口 引用自：\n作者：咗嚛\n链接：https://www.jianshu.com/p/15fb558b44bc\n来源：简书\n","permalink":"https://blog.muwinds.com/archives/76/","summary":"\u003cp\u003e本指南将引导你完成：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e创建 NAT 网络\u003c/li\u003e\n\u003cli\u003e将现有虚拟机连接到新网络\u003c/li\u003e\n\u003cli\u003e确认虚拟机正确连接\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e要求：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eWindows 10 周年更新或更高版本\u003c/li\u003e\n\u003cli\u003e已启用 Hyper-V（单击\u003ca href=\"https://link.zhihu.com/?target=https%3A//docs.microsoft.com/zh-cn/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v\"\u003e此处\u003c/a\u003e 查看相关说明）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e注意：目前，每台主机仅限一个 NAT 网络。 有关 Windows NAT (WinNAT) 实现、功能和限制的更多详细信息，请参考 \u003ca href=\"https://link.zhihu.com/?target=https%3A//techcommunity.microsoft.com/t5/Virtualization/Windows-NAT-WinNAT-Capabilities-and-limitations/ba-p/382303\"\u003eWinNAT capabilities and limitations blog\u003c/a\u003e（WinNAT 功能和限制日志）\u003c/p\u003e","title":"Hyper-v 配置 NAT + 端口映射"},{"content":"报错如下：\nError looking up Cloudflare edge IPs: the DNS query failed error=\u0026#34;lookup _v2-origintunneld._tcp.argotunnel.com on **.1:53: no such host\u0026#34; 2022-09-15T08:24:12Z ERR Please try the following things to diagnose this issue: 2022-09-15T08:24:12Z ERR 1. ensure that argotunnel.com is returning \u0026#34;origintunneld\u0026#34; service records. 2022-09-15T08:24:12Z ERR Run your system\u0026#39;s equivalent of: dig srv _origintunneld._tcp.argotunnel.com 2022-09-15T08:24:12Z ERR 2. ensure that your DNS resolver is not returning compressed SRV records. 2022-09-15T08:24:12Z ERR See GitHub issue https://github.com/golang/go/issues/27546 2022-09-15T08:24:12Z ERR For example, you could use Cloudflare\u0026#39;s 1.1.1.1 as your resolver: 2022-09-15T08:24:12Z ERR https://developers.cloudflare.com/1.1.1.1/setting-up-1.1.1.1/ 2022-09-15T08:24:12Z INF Tunnel server stopped 2022-09-15T08:24:12Z ERR Initiating shutdown error=\u0026#34;Could not lookup srv records on _v2-origintunneld._tcp.argotunnel.com: lookup _v2-origintunneld._tcp.argotunnel.com on **.1:53: no such host\u0026#34; 2022-09-15T08:24:12Z INF Metrics server stopped 如果是OpenWRT\n进入网络，转到高级设置.\n定位到过滤无用包 (不转发公共域名服务器无法回应的请求)\n将其取消选中，保存应用即可.\n原因：\n因为目标域名 Ping 不通，所以认定为无法回应的请求.\n就将有关包全部过滤了.\n","permalink":"https://blog.muwinds.com/archives/75/","summary":"\u003cp\u003e报错如下：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eError looking up Cloudflare edge IPs: the DNS query failed \u003cspan class=\"nv\"\u003eerror\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;lookup _v2-origintunneld._tcp.argotunnel.com on **.1:53: no such host\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e2022-09-15T08:24:12Z ERR Please try the following things to diagnose this issue:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e2022-09-15T08:24:12Z ERR   1. ensure that argotunnel.com is returning \u003cspan class=\"s2\"\u003e\u0026#34;origintunneld\u0026#34;\u003c/span\u003e service records.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e2022-09-15T08:24:12Z ERR      Run your system\u003cspan class=\"s1\"\u003e\u0026#39;s equivalent of: dig srv _origintunneld._tcp.argotunnel.com\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e2022-09-15T08:24:12Z ERR   2. ensure that your DNS resolver is not returning compressed SRV records.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e2022-09-15T08:24:12Z ERR      See GitHub issue https://github.com/golang/go/issues/27546\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e2022-09-15T08:24:12Z ERR      For example, you could use Cloudflare\u0026#39;\u003c/span\u003es 1.1.1.1 as your resolver:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e2022-09-15T08:24:12Z ERR      https://developers.cloudflare.com/1.1.1.1/setting-up-1.1.1.1/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e2022-09-15T08:24:12Z INF Tunnel server stopped\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e2022-09-15T08:24:12Z ERR Initiating shutdown \u003cspan class=\"nv\"\u003eerror\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Could not lookup srv records on _v2-origintunneld._tcp.argotunnel.com: lookup _v2-origintunneld._tcp.argotunnel.com on **.1:53: no such host\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e2022-09-15T08:24:12Z INF Metrics server stopped\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e如果是OpenWRT\u003c/p\u003e","title":"Cloudflare ZeroTrust Tunnel提示反向解析无效"},{"content":"之前博客网站一直用的家里机器配合香港Azure做frp内网穿透，用CF CDN进行数据分发，不提frp的虚拟局域网模式在跨国数据传输时面临的数据审查和路由方向会给速度及稳定性造成非常大的影响，其可能有的内存溢出和服务重启时的持续掉线问题也是很抓狂的，在这里，使用zerotier为两机打洞连接p2p，使用nginx进行反向代理来进行内网穿透。\n首先是两边安装zerotier给两台机器打洞。\n先去zerotier官网建立一个网络：\n接下来是两边安装zerotier客户端\ncurl -s https://install.zerotier.com | sudo bash 客户端安装完成后，两边加入网络\nzerotier-cli join 网络ID 加入后去后台网络通过一下就可以了\n最后两台机器互相ping一下，如果延迟和公网的差不多甚至还要低的话就算打洞成功。\n接下来配置nginx，宝塔配置是速度最快的方法，直接宝塔说一下，当然如果你看懂了宝塔的配置其实你自己Nginx配置也可以了。\n创建完成后点击配置文件\n以上操作完成后，在windows下windows/system32/drivers/hosts文件中修改自己域名对应的IP进行测试，如果能正常打开说明内网穿透成功。\n以下是chinaz的速度测试结果，对比frp来说速度提升还是很明显的：\n（还是走了cloudflare）\n————————————————————\n晚高峰测试：\n","permalink":"https://blog.muwinds.com/archives/74/","summary":"\u003cp\u003e之前博客网站一直用的家里机器配合香港Azure做frp内网穿透，用CF CDN进行数据分发，不提frp的虚拟局域网模式在跨国数据传输时面临的数据审查和路由方向会给速度及稳定性造成非常大的影响，其可能有的内存溢出和服务重启时的持续掉线问题也是很抓狂的，在这里，使用zerotier为两机打洞连接p2p，使用nginx进行反向代理来进行内网穿透。\u003c/p\u003e","title":"Zerotier配合Nginx实现内网穿透"},{"content":"2024.02.03更新：\n不如比特彗星，ipv6支持一坨狗屎，比特彗星也有远程下载，这就是个垃圾\n安装还是很方便的，直接一个yum就行了\nyum install -y aria2 然后创建一个配置文件：\ncd /etc/ sudo mkdir aria2 \u0026amp;\u0026amp; cd aria2 \u0026amp;\u0026amp;touch aria2.conf \u0026amp;\u0026amp; touch aria2.session 配置文件内容：\n# Trackers bt-tracker=udp://tracker.opentrackr.org:1337/announce,udp://tracker.leechers-paradise.org:6969/announce,udp://p4p.arenabg.com:1337/announce,udp://9.rarbg.to:2710/announce,udp://9.rarbg.me:2710/announce,udp://exodus.desync.com:6969/announce,udp://tracker.sbsub.com:2710/announce,udp://retracker.lanta-net.ru:2710/announce,udp://open.stealth.si:80/announce,udp://tracker.tiny-vps.com:6969/announce,udp://tracker.cyberia.is:6969/announce,udp://tracker.torrent.eu.org:451/announce,udp://tracker.moeking.me:6969/announce,udp://tracker3.itzmx.com:6961/announce,udp://ipv4.tracker.harry.lu:80/announce,udp://bt2.archive.org:6969/announce,udp://bt1.archive.org:6969/announce,http://tracker1.itzmx.com:8080/announce,udp://valakas.rollo.dnsabr.com:2710/announce,udp://tracker.zerobytes.xyz:1337/announce # 文件的保存路径(可使用绝对路径或相对路径), 默认: 当前启动位置 dir=/home/zk/Downloads # 启用磁盘缓存, 0为禁用缓存, 需1.16以上版本, 默认:16M disk-cache=5M # 文件预分配方式, 能有效降低磁盘碎片, 默认:prealloc # 预分配所需时间: none \u0026lt; falloc ? trunc \u0026lt; prealloc # falloc和trunc则需要文件系统和内核支持, NTFS建议使用falloc, EXT3/4建议trunc file-allocation=none # 断点续传 continue=true ## 下载连接相关 ## # 最大同时下载任务数, 运行时可修改, 默认:5 max-concurrent-downloads=20 # 同一服务器连接数, 添加时可指定, 默认:1 max-connection-per-server=15 # 最小文件分片大小, 添加时可指定, 取值范围1M -1024M, 默认:20M # 假定size=10M, 文件为20MiB 则使用两个来源下载; 文件为15MiB 则使用一个来源下载 min-split-size=10M # 单个任务最大线程数, 添加时可指定, 默认:5 split=16 # 整体下载速度限制, 运行时可修改, 默认:0 #max-overall-download-limit=0 # 单个任务下载速度限制, 默认:0 #max-download-limit=0 # 整体上传速度限制, 运行时可修改, 默认:0 max-overall-upload-limit=20kb # 单个任务上传速度限制, 默认:0 max-upload-limit=5kb # 禁用IPv6, 默认:false disable-ipv6=true # 禁用https证书检查 check-certificate=false #运行覆盖已存在文件 allow-overwrite=true #自动重命名 auto-file-renaming ## 进度保存相关 ## # 从会话文件中读取下载任务 input-file=/etc/aria2/aria2.session # 在Aria2退出时保存`错误/未完成`的下载任务到会话文件 save-session=/etc/aria2/aria2.session # 定时保存会话, 0为退出时才保存, 需1.16.1以上版本, 默认:0 save-session-interval=120 ## RPC相关设置 ## # 启用RPC, 默认:false enable-rpc=true # 允许所有来源, 默认:false rpc-allow-origin-all=true # 允许非外部访问, 默认:false rpc-listen-all=true # 事件轮询方式, 取值:[epoll, kqueue, port, poll, select], 不同系统默认值不同 #event-poll=select # RPC监听端口, 端口被占用时可以修改, 默认:6800 rpc-listen-port=6800 # 保存上传的种子文件 rpc-save-upload-metadata=false ## BT/PT下载相关 ## # 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true #follow-torrent=true # BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999 listen-port=51413 # 单个种子最大连接数, 默认:55 #bt-max-peers=55 # 打开DHT功能, PT需要禁用, 默认:true enable-dht=true # 打开IPv6 DHT功能, PT需要禁用 enable-dht6=false # DHT网络监听端口, 默认:6881-6999 #dht-listen-port=6881-6999 # 本地节点查找, PT需要禁用, 默认:false bt-enable-lpd=true # 种子交换, PT需要禁用, 默认:true enable-peer-exchange=true # 每个种子限速, 对少种的PT很有用, 默认:50K #bt-request-peer-speed-limit=50K # 客户端伪装, PT需要 peer-id-prefix=-UT341- user-agent=uTorrent/341(109279400)(30888) # 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0 seed-ratio=1.0 # 强制保存会话, 话即使任务已经完成, 默认:false # 较新的版本开启后会在任务完成后依然保留.aria2文件 #force-save=false # BT校验相关, 默认:true #bt-hash-check-seed=true # 继续之前的BT任务时, 无需再次校验, 默认:false bt-seed-unverified=true # 保存磁力链接元数据为种子文件(.torrent文件), 默认:false #bt-save-metadata=false #仅下载种子文件 bt-metadata-only=true #通过网上的种子文件下载，种子保存在内存 follow-torrent=mem 后台启动命令：\naria2c --conf-path=/etc/aria2/aria2.conf -D ","permalink":"https://blog.muwinds.com/archives/73/","summary":"\u003cp\u003e2024.02.03更新：\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e不如比特彗星，ipv6支持一坨狗屎，比特彗星也有远程下载，这就是个垃圾\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e安装还是很方便的，直接一个yum就行了\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eyum install -y aria2\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e然后创建一个配置文件：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ecd\u003c/span\u003e /etc/ \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo mkdir aria2 \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nb\"\u003ecd\u003c/span\u003e aria2 \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003etouch aria2.conf \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e touch aria2.session\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e配置文件内容：\u003c/p\u003e","title":"离线下载Aria2搭建"},{"content":"AIO出现了硬盘问题，重启后发现离线下载服务没有开机自启，快速给开一下：\n先写一个开机自启的脚本：\n#!/bin/sh #chkconfig: 2345 80 90 #description:aria2开机自启 aria2c --conf-path=/etc/aria2/aria2.conf -D 脚本第一行 “#!/bin/sh” 告诉系统使用的shell；\n脚本第二行 “#chkconfig: 2345 80 90” 表示在2/3/4/5运行级别启动，启动序号(S80)，关闭序号(K90)；\n脚本第三行 表示的是服务的描述信息\n注意： 第二行和第三行必写，否则会出现如“服务 autostart.sh 不支持 chkconfig”这样的错误。\n然后mv文件到/etc/rc.d/init.d/，并chmod +x赋予权限\n最后添加到开机启动项：\nchkconfig --add autostart.sh chkconfig autostart.sh on ","permalink":"https://blog.muwinds.com/archives/72/","summary":"\u003cp\u003eAIO出现了硬盘问题，重启后发现离线下载服务没有开机自启，快速给开一下：\u003c/p\u003e\n\u003cp\u003e先写一个开机自启的脚本：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#!/bin/sh\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#chkconfig: 2345 80 90\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#description:aria2开机自启\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003earia2c --conf-path\u003cspan class=\"o\"\u003e=\u003c/span\u003e/etc/aria2/aria2.conf -D\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e脚本第一行 “#!/bin/sh” 告诉系统使用的shell；\u003cbr\u003e\n脚本第二行 “#chkconfig: 2345 80 90” 表示在2/3/4/5运行级别启动，启动序号(S80)，关闭序号(K90)；\u003cbr\u003e\n脚本第三行 表示的是服务的描述信息\u003cbr\u003e\n\u003cstrong\u003e注意：\u003c/strong\u003e 第二行和第三行必写，否则会出现如“服务 autostart.sh 不支持 chkconfig”这样的错误。\u003c/p\u003e","title":"CentOS设置开机启动"},{"content":"\n这个是SSL套件的问题，输入下面的指令切换一下就好了：\n$env:NODE_OPTIONS = \u0026#34;--openssl-legacy-provider\u0026#34; ","permalink":"https://blog.muwinds.com/archives/70/","summary":"\u003cp\u003e\u003ca href=\"/wp-content/uploads/2025/04/image-12.png\"\u003e\u003cimg loading=\"lazy\" src=\"/wp-content/uploads/2025/04/image-12.png\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e这个是SSL套件的问题，输入下面的指令切换一下就好了：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003e$env\u003c/span\u003e:NODE_OPTIONS \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;--openssl-legacy-provider\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"yarn构建提示Error: error:0308010C:digital envelope routines::unsupported的解决方法"},{"content":"构建vue项目的时候报错：\n很简单，nodejs版本太新了，直接换到dart-sass\nyarn remove node-sass yarn add sass ","permalink":"https://blog.muwinds.com/archives/69/","summary":"\u003cp\u003e构建vue项目的时候报错：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image.png\" loading=\"lazy\" src=\"/wp-content/uploads/2023/02/202302021675333331740729.png\"\u003e\u003c/p\u003e\n\u003cp\u003e很简单，nodejs版本太新了，直接换到dart-sass\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eyarn remove node-sass\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eyarn add sass\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"node-saas问题"},{"content":"这次接手的是tp框架的远古收款系统，开局几个ajax认不上我已经习以为常了，反手把php版本改成7.0就恢复正常（7.0算是个兼容版本 5.x的和7.x都兼容一点 各位穷途末路了可以试试）\n然后呢，其他的确认过没啥毛病了，打算测试一下接口的时候，意外就来了，弹404。\n我知道tp框架弹404一定是代码的问题，但问题是我已经开了app_debug，正常情况下应该直接跳断点内容了才对。\n当时也没有把问题归咎于那个系统的tp框架本身的问题上，所以自己对着那坨屎山翻了半天，真的，这个数据库表名就想让我干死他婊子妈\n![Q(13M9INVE[0]H%2D]6U1EX.png](/wp-content/uploads/2023/02/202302051675558554772625.png) 没法子，你不能对着一堆没头绪的屎山看半天，所以还是得把那个断点调试的界面整出来，百度谷歌搜了半天，也试了半天，最后一篇腾讯云的广告文章告诉我说可能是tp框架的一个报错页文件出现问题了，我一看，好家伙：\n![TFUP4PZWGB]$O108`K~S@I5.png](/wp-content/uploads/2023/02/202302051675558887673114.png)\n报错页改404 什么掩耳盗铃 建议给你祖宗的墓碑上面建个大本钟 敲死你个笨B\n直接拿现有项目的同文件进行替换，这次果然出界面了，光速修复正常。\n附：\n![Y%UO9C@R]`Z)RBHP@2H}ZF.png\nnmd，这个不知道从哪里来的getCurUrl的方法，这玩意儿能跑起来真的辛苦服务器了。\n","permalink":"https://blog.muwinds.com/archives/68/","summary":"\u003cp\u003e这次接手的是tp框架的远古收款系统，开局几个ajax认不上我已经习以为常了，反手把php版本改成7.0就恢复正常（7.0算是个兼容版本 5.x的和7.x都兼容一点 各位穷途末路了可以试试）\u003c/p\u003e","title":"记录一个远古垃圾系统的维护"},{"content":"雨云的CDN并没有给用户清除缓存的选项，想清除缓存的话其实挺麻烦的。\n通过浏览器对网站的请求头我们可以看到，CDN的服务器是apache apisix\n通过查阅apisix有关缓存的文档，我们可以发现清除缓存的方法：\nhttps://apisix.apache.org/zh/docs/apisix/2.13/plugins/proxy-cache\n$ curl -i http://127.0.0.1:9080/hello -X PURGE HTTP/1.1 200 OK Date: Tue, 03 Mar 2020 11:17:35 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: keep-alive Server: APISIX web server 实测有效，但我心里还是默念一句这是真的不安全……一点鉴权措施都没有，分分钟被缓存击穿\n","permalink":"https://blog.muwinds.com/archives/65/","summary":"\u003cp\u003e雨云的CDN并没有给用户清除缓存的选项，想清除缓存的话其实挺麻烦的。\u003c/p\u003e\n\u003cp\u003e通过浏览器对网站的请求头我们可以看到，CDN的服务器是apache apisix\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/wp-content/uploads/2025/04/image-10.png\"\u003e\u003cimg loading=\"lazy\" src=\"/wp-content/uploads/2025/04/image-10.png\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e通过查阅apisix有关缓存的文档，我们可以发现清除缓存的方法：\u003c/p\u003e","title":"雨云CDN清除指定文件的缓存"},{"content":"原因：意外断电导致硬盘卡在只读状态\n进入openwrt后台按回车输入下面命令，重新挂载即可：\nmount -o remount rw / 2024.07.10更新：\n换了官方固件后因为要拆下来换机箱散热器又是一次意外断电，卡在了只读状态，但是这次重新挂载无用了。\n现在这个状况是openwrt的分区出现了corrupt，需要修复\nopenwrt的主分区所在的设备盘是/dev/sda2\n用e2fsck的指令重启后生效。\ne2fsck -y /dev/sda2 ","permalink":"https://blog.muwinds.com/archives/64/","summary":"\u003cp\u003e原因：意外断电导致硬盘卡在只读状态\u003c/p\u003e\n\u003cp\u003e进入openwrt后台按回车输入下面命令，重新挂载即可：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emount -o remount rw /\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e2024.07.10更新：\u003c/p\u003e\n\u003cp\u003e换了官方固件后因为要拆下来换机箱散热器又是一次意外断电，卡在了只读状态，但是这次重新挂载无用了。\u003c/p\u003e","title":"OpenWrt无法保存配置无法生效的解决方法"},{"content":"准备建个nas服务，大容量的硬盘是必不可少的。去经理那拿了块4T的硬盘，以后有需要我再加吧。\n硬盘硬件上的安装很简单，插两根线拧螺丝就行了。\n接下来说的是挂到系统\n先查看一下盘子是不是安装上了：\nfdisk -l 但凡眼睛没问题的 都能找到自己那块新装的盘：\n连着按一个N键和三个p键就行\n然后格式化：\nmkfs -t ext4 /dev/sdb 然后找到给新硬盘挂载的目录，开始挂载\n先打开fstab：\nvi /etc/fstab 在从打开的fstab添加挂载：\n/dev/sdb /mnt/dev1 ext4 defaults 0 1 重启或者source一下随意，装个管理面板啥的可以自己登上去看看装好没有\n","permalink":"https://blog.muwinds.com/archives/61/","summary":"\u003cp\u003e准备建个nas服务，大容量的硬盘是必不可少的。去经理那拿了块4T的硬盘，以后有需要我再加吧。\u003c/p\u003e\n\u003cp\u003e硬盘硬件上的安装很简单，插两根线拧螺丝就行了。\u003c/p\u003e\n\u003cp\u003e接下来说的是挂到系统\u003c/p\u003e\n\u003cp\u003e先查看一下盘子是不是安装上了：\u003c/p\u003e","title":"centos分区挂载磁盘"},{"content":"samba是玩nas经常用的服务了，这里说一下怎么安装：\n登录机器，yum安装：\nyum install samba systemctl enable samba 配置方法：\nvi /etc/samba/smb.conf [nasdata] comment = nasdata 备注 available = yes browseable = yes public = yes writable = yes path = /nasdata 共享文件夹路径 writable = yes 是否可以写入 windows映射一下：\n","permalink":"https://blog.muwinds.com/archives/60/","summary":"\u003cp\u003esamba是玩nas经常用的服务了，这里说一下怎么安装：\u003c/p\u003e\n\u003cp\u003e登录机器，yum安装：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eyum install samba\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemctl \u003cspan class=\"nb\"\u003eenable\u003c/span\u003e samba\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e配置方法：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003evi /etc/samba/smb.conf\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003enasdata\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nv\"\u003ecomment\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e nasdata               备注\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nv\"\u003eavailable\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nv\"\u003ebrowseable\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nv\"\u003epublic\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nv\"\u003ewritable\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nv\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e /nasdata 共享文件夹路径\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nv\"\u003ewritable\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e yes              是否可以写入\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003ewindows映射一下：\u003c/p\u003e","title":"centos（RHEL）开启samba"},{"content":"前情提要：MC服务器迁移到HomeServer后，突然有一天玩家跟我说挑战功能全是乱码，我打开挑战文件一看：\n而且挑战文件都在一个文件夹共有291个，一个个扔到npp里面转成utf8也不是事儿。\n简单说一下批量转码方法，以下适用于所有用yum的机器：\n先安装依赖：\nyum install -y enca 然后cd到乱码文件的文件夹并创建一个.sh结尾的文件，输入下面的内容：\n#!/bin/bash for file in `find ./ -name \u0026#39;*.文件后缀\u0026#39;`; do echo \u0026#34;$file\u0026#34; # iconv -f gb2312 -t utf8 -o $file $file enca -L zh_CN -x UTF-8 $file done 最后运行这个刚创建的脚本文件即可\n","permalink":"https://blog.muwinds.com/archives/58/","summary":"\u003cp\u003e前情提要：MC服务器迁移到HomeServer后，突然有一天玩家跟我说挑战功能全是乱码，我打开挑战文件一看：\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/wp-content/uploads/2025/04/image-9.png\"\u003e\u003cimg loading=\"lazy\" src=\"/wp-content/uploads/2025/04/image-9-1024x425.png\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e而且挑战文件都在一个文件夹共有291个，一个个扔到npp里面转成utf8也不是事儿。\u003c/p\u003e","title":"快速且简单通用的批量转码方法"},{"content":"每次登录PVE的网页后台就看到那个烦人的订阅提示，这里关掉他\ncd /usr/share/javascript/proxmox-widget-toolkit cp proxmoxlib.js proxmoxlib.js.bak nano proxmoxlib.js ssh链接pve的机器后 输入上面三行命令会打开一个文件，按ctrl+w查找No valid subscription\n将前面的“Ext.Msg.show({”更改成“void({”后，按ctrl+o回车保存，按ctrl+x退出，清除pve后台cookie并再次登录检查提示是否存在\n","permalink":"https://blog.muwinds.com/archives/50/","summary":"\u003cp\u003e每次登录PVE的网页后台就看到那个烦人的订阅提示，这里关掉他\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ecd\u003c/span\u003e /usr/share/javascript/proxmox-widget-toolkit\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecp proxmoxlib.js proxmoxlib.js.bak\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enano proxmoxlib.js\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003essh链接pve的机器后 输入上面三行命令会打开一个文件，按ctrl+w查找No valid subscription\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/wp-content/uploads/2025/04/image-8.png\"\u003e\u003cimg loading=\"lazy\" src=\"/wp-content/uploads/2025/04/image-8-1024x773.png\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e将前面的“Ext.Msg.show({”更改成“void({”后，按ctrl+o回车保存，按ctrl+x退出，清除pve后台cookie并再次登录检查提示是否存在\u003c/p\u003e","title":"PVE删除订阅提示"},{"content":"这趟颐和园逛得很值，初中游颐和园属实没想到只逛到长廊就原路返回了，那时候包含对学校其他事情的一些怨念，就决定我以后一定要自己游一次颐和园\n正好一个在海淀离颐和园近的朋友约我线下见面，趁这时候去颐和园咯。\n早上四点半起床骑车，骑到颐和园要一个小时左右？\n然后在附近转了一圈，见到了清华也见到了北大（手机给我双清了 数据没了呜呜呜），下午去看了中关村（感到唏嘘，海龙也没了，现在就剩下科贸了，科贸现在还只有2层卖电子，而且还是假货一堆）\n不多说了，放颐和园的图片吧，看到那一大片的莲花我心情超级好的！\n","permalink":"https://blog.muwinds.com/archives/51/","summary":"\u003cp\u003e这趟颐和园逛得很值，初中游颐和园属实没想到只逛到长廊就\u003cstrong\u003e原路\u003c/strong\u003e返回了，那时候包含对学校其他事情的一些怨念，就决定我以后一定要自己游一次颐和园\u003c/p\u003e\n\u003cp\u003e正好一个在海淀离颐和园近的朋友约我线下见面，趁这时候去颐和园咯。\u003c/p\u003e","title":"颐和园 2022年7月16"},{"content":"vwo50.com部署上线以来有70个人在里面求v了一波50\n这几天把服务器从azure迁到all in one，部署上线发现个问题\n就是原先的donate短链接打不开，随机到的短链接刷新之后失效\n问了一下技术老哥：\n谷歌了一下，解决方法贴下面吧：\nlocation / { try_files $uri $uri/ /index.html; } 使用方法：nginx前端配置文件插一段这个就可以了\n","permalink":"https://blog.muwinds.com/archives/48/","summary":"\u003cp\u003evwo50.com部署上线以来有70个人在里面求v了一波50\u003c/p\u003e\n\u003cp\u003e这几天把服务器从azure迁到all in one，部署上线发现个问题\u003c/p\u003e\n\u003cp\u003e就是原先的donate短链接打不开，随机到的短链接刷新之后失效\u003c/p\u003e","title":"vue react spa页面刷新404解决"},{"content":"2025.04.07更新：时过境迁，CentOS都没了，令人感慨……不过RockyLinux的配置方法大同小异\n以前都是windows 现在把朋友的MC服务器换到all in one上打算用centos配，主要是以下几个原因：\n一个是本人的centos手艺日益精巧，感觉自己有能力，还有一个是windows这些年来占用越来越多，换到centos看看能不能改善一下占用。\n考虑到如果纯命令行还要配ftp screen挂等一系列问题，打算这次采用mcsm面板\n先快速安装并上传服务端：\nwget -qO- https://gitee.com/mcsmanager/script/raw/master/setup.sh | bash\u0026lt;br\u0026gt;\u0026lt;br\u0026gt; Azul，主要是由于Zing，Zing是其研发的一种JAVA虚拟机，它具有更好的性能，无停顿，更快的启动等优势，不过是收费的，于是我想Azul有没有其它不收费的开源的JDK可以供开发者学习，于是找到了Zulu。\nZulu是什么？ 它是Azul提供商业支持的OpenJDK商业发行版,与Oracle JDK有高度的一致性，不过也有开源版本，可在官网下载学习\n作者：siweidetu\n链接：https://www.jianshu.com/p/b29a8b172622\n来源：简书\n著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。\n安装zulu的指令：\nyum install -y libX11 libXext libXi libXrender libXtst alsa-lib; wget https://cdn.azul.com/zulu/bin/zulu17.36.13-ca-jdk17.0.4-linux.x86_64.rpm #java17 wget https://cdn.azul.com/zulu/bin/zulu8.58.0.13-ca-jdk8.0.312-linux.x86_64.rpm #java8 rpm -ivh zulu17.36.13-ca-jdk17.0.4-linux.x86_64.rpm #java17 rpm -ivh zulu8.58.0.13-ca-jdk8.0.312-linux.x86_64.rpm #java8 然后输入java看看出不出命令提示\n最后去mcsm开服\n这里说一下 如果是all in one开服 大多数人家里都是需要内网穿透才能让朋友进服务器\n大多数内网穿透对于MC都有一个致命缺点，就是因为代理协议的问题使得玩家进服IP全部为127.0.0.1（或者你上层反代地址），这使得在服务器遭遇假人攻击时比较难防御\n以及大多数内网穿透并没有心跳包功能，所以连接也不会特别稳定，掉线肯定常事儿，同时对于开了geyser双通的服主来说，java和基岩无法同一IP是比较难受的\n基于上面的，说一下内网穿透\n现在开源的内网穿透有很多源码可供选择，譬如：NPS Frp ngrok等等\n这里非常推荐frp，很多人说nps好配置图形化，实际上在上面的情况中nps根本没法胜任\nfrp避免广告嫌疑，不推荐一些做frp的了，这里只说怎么配置\n我的服务器是双通，如果你的是仅java或者仅基岩，下面的你选择着来：\n[mc_tcp] type=tcp local_ip=127.0.0.1 local_port=填写你的端口 remote_port=填写你的端口 use_encryption=false use_compression=true health_check_type = tcp # TCPing timeout seconds health_check_timeout_s = 3 # If health check failed 3 times in a row, the proxy will be removed from frps health_check_max_failed = 3 # A health check every 10 seconds health_check_interval_s = 10 proxy_protocol_version = v2 [mc_udp] type=udp local_ip=127.0.0.1 local_port=填写你的端口 remote_port=填写你的端口 use_encryption=false use_compression=true 以上的配置请注意几个问题\n如果你要双通端口一致，请填写内外网穿透一致的端口 假设local_port填写25565，你的remote_port也要填写25565，同时tcp和udp端口也要一致\n接下来就是要配置bungeecord，配置bungeecord前请先修改好你的原来的服务器端口，不能和上面frp配置的端口一样\n下载bungeecord，在mcsm再开个服务器，放入bungeecord，如果是双通请放入你的geyser和floodgate，启动bungeecord，并且关闭bungeecord的正版验证，配置好geyser和floodgate（geyser的be端口填udp的）并放入和子服一样的key.pem，同时Bungeecord要把proxy_protocol打开\n此时你原来的服务器就变成了Bungeecord下的子服，子服需要去spigot.yml打开bungeecord选项\n再次进入服务器，你就会发现你的IP就变成真实的IP了，不再是原来的127.0.0.1了\n","permalink":"https://blog.muwinds.com/archives/38/","summary":"\u003cp\u003e2025.04.07更新：时过境迁，CentOS都没了，令人感慨……不过RockyLinux的配置方法大同小异\u003c/p\u003e\n\u003cp\u003e以前都是windows 现在把朋友的MC服务器换到all in one上打算用centos配，主要是以下几个原因：\u003c/p\u003e","title":"RHEL系跑MC服务器"},{"content":"由于新三的科学能力太差（属实没想到一科学就卡），故打算自组一台软路由，顺便弄成了一台all in one\n拓补图：\n（随手画了个 你看着难受跟我有啥关系）\n机器被奸商城老板坑了，600块整了个e5 2630l+x79寨板，是纯纯的冤种\n这个板子寨到我插亮机卡之后就没地儿给我插网卡了，真尼玛无语\n找老板要了个usb转rj45网卡，不要问为啥不m2转pcie，价格是真的难顶\n反正家里也就100M的宽带，不在乎了，真扩到1000M也肯定有钱换机器了（看到这里打算直通的伙计可以退出了，在千兆环境下其实直通与否影响不大，机器性能差建议直通）\n安装pve也很简单，百度也都有教程，这里设置IP为：10.0.0.2，网关设置为：10.0.0.1\n然后将你要设置的lan口插旁路由（原来的硬路由），设置旁路由后台地址为：10.0.0.1，方便电脑管理\n访问后台，开一台openwrt的机器，镜像下载：https://t.me/openwrt_p\n先上传镜像到PVE，再将镜像直接挂pve下，指令：\nqm importaldisk 虚拟机ID /var/lib/vz/temlate/iso/openwrt镜像文件名 local -lvm 然后两个网口全部桥接，设置一下启动顺序和开机自启，启动openwrt\n等控制台搁那不动了，直接按回车进入命令行模式\n修改/etc/config/network，将lan口地址改为：10.0.0.1，此时将电脑插软路由上，插旁路由的wan口改插到软路由上\n电脑进入后台，账号：root，密码：password\n将wan口那里拨号，测试电脑能否上网，若能正常上网，则将电脑插回旁路由，软路由lan口接在旁路由wan口并修改旁路由的网关使得两个网关不冲突可以互相访问。\n接下来就可以奔放了！\n","permalink":"https://blog.muwinds.com/archives/21/","summary":"\u003cp\u003e由于新三的科学能力太差（属实没想到一科学就卡），故打算自组一台软路由，顺便弄成了一台all in one\u003c/p\u003e\n\u003cp\u003e拓补图：\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/wp-content/uploads/2025/04/image-1024x484.png\"\u003e\u003c/p\u003e\n\u003cp\u003e（随手画了个 你看着难受跟我有啥关系）\u003c/p\u003e\n\u003cp\u003e机器被奸商城老板坑了，600块整了个e5 2630l+x79寨板，是纯纯的冤种\u003c/p\u003e","title":"PVE双网口实践"}]