有趣的开源项目记录分类整理1. 开发与编程工具Go-blueprint - 命令行交互式创建和初始化Go项目，支持网页端选择Pake - 用Rust开发的工具，可将网页打包成macOS、Windows、Linux支持的桌面客户端应用，封装体积比Electron小20倍manim - 3Blue1Brown作者开发的强大数学动画引擎，专为数学和科学可视化设计，推荐使用社区版manimCEDeepface - 轻量级Python人脸识别工具，支持年龄、性别、情绪、种族识别，准确率超过人类RealtimeSTT - 开源实时语音转文本库，采用流式处理技术，延迟极低，支持模型量化Amphion - 开源音频、音乐和语音生成工具包，支持TTS、SVC、TTA、VC等功能Skyvern - 基于视觉理解的网页自动化工具，摆脱传统DOM依赖，采用多智能体协作架构index-tts2 - 文本转语音工具2. 数据库与数据处理Teable - 现代化的无代码数据库与数据协作平台，基于PostgreSQL构建，类Airtable界面DiceDB - 开源高性能内存数据库，作为Redis替代品，适合高速缓存和实时系统Docling - 文档解析工具，支持导出markdown和JSON，支持OCRGitdiagram - 将GitHub仓库转换为交互式架构图3. 系统工具与操作系统PowerToys - Windows系统增强工具，包含快速启动器、图片批量处理、拾色器、文本提取器等Ventoy - 多系统启动U盘制作工具Windows via Docker - 从Docker安装Windows，在Linux中通过浏览器访问，支持Win7-11WebVM - 在网页端运行的Linux虚拟机，甚至可以运行可执行文件Jellyfinweb - 媒体服务器Web界面4. 网络与文件共享LocalSend - 本地网络安全文件共享工具，无需互联网连接FilePizza - 基于WebRTC的点对点文件传输，文件直接从发送者浏览器传到接收者浏览器File-transfer-go - Go语言实现的文件传输工具Netbird - 点对点VPN/内网穿透工具AList - 聚合网盘工具Linux Mirrors - Linux镜像源管理工具5. 监控与管理Infisical - 开源密钥管理工具，类似HashiCorp Vault，支持微服务架构Beszel - 轻量级服务器监控平台，支持Docker容器监控，资源占用低XPipe - 终端连接工具Openlist - 开源列表管理工具6. 安全与渗透测试Hacking tool - 一体化黑客工具箱，包含各类漏洞扫描和攻击工具Sherlock - 社交媒体账号搜索工具，支持400多个网站查询7. 人工智能与分布式计算EXO - 分布式AI计算集群框架，允许连接日常设备形成AI计算集群Pansou - 开源搜索引擎Weepwiki - 知识库/wiki系统项目详细记录开发与编程工具Go-blueprint通过命令行交互式的创建和初始化Go的项目。也可以在网页里直接选择。Pake一个用Rust开发的小工具，可以把网页打包成macOS、Windows、Linux三种系统支持的桌面客户端应用。Pake的封装比Electron的封装小20倍。manim3Blue1Brown的作者开发的强大动画引擎。专为数学和可视化科学设计。能生成高质量的数学动画。推荐使用manim和manimCE（社区版），因为会更稳定。DeepfaceDeepface是一个轻量级的Python人脸识别工具。支持年龄、性别、情绪、种族识别。它是混合面部识别框架，集成了多种识别模型。准确率已经超过了人类。RealtimeSTT是一个开源的实时语音转文本（Speech-to-Text，STT）库，专为低延迟应用设计。该项目由KoljaB开发，采用Python语言编写，能够将语音数据实时转换为文本，适用于语音助手、实时字幕生成、会议记录等多种场景。采用流式处理技术，在语音输入的同时实时输出转写结果，延迟极低。基础模型（tiny）运行时内存占用约400MB，大型模型（large-v2）在GPU环境下显存占用约4.2GB。通过INT8量化技术，模型内存占用可减少75%。AmphionAmphion是一个由上海人工智能实验室、香港中文大学（深圳）数据科学学院和深圳市大数据研究院联合开发的开源音频、音乐和语音生成工具包。该项目于2023年12月开源，旨在支持可重复的研究，帮助初级研究人员和工程师快速进入音频生成领域。它支持TTS、SVC、TTA、VC等功能。SkyvernSkyvern的最大突破在于摆脱了传统自动化工具对DOM结构和XPath选择器的依赖。传统工具如Selenium、Playwright需要为每个网站编写定制脚本，一旦页面布局发生变化，脚本就会失效。而Skyvern采用"视觉理解"模式：通过分析网页截图，结合视觉语言模型来理解页面内容和元素功能，像人类一样"看懂"网页并自主决策操作步骤。采用多智能体协作架构，包含规划器、执行器、验证器、视觉语言模型四个核心模块。index-tts2文本转语音工具。数据库与数据处理Teable开源项目Teable是一个现代化的无代码（no-code）数据库与数据协作平台，类似于Airtable，但基于PostgreSQL构建并且是开源的。它的目标是让技术和非技术用户都能方便地创建、管理和协作复杂的数据应用，而不必编写代码。Teable提供了一个类电子表格界面，用户可以像使用Excel或Airtable那样直接操作数据，但底层则是真正的PostgreSQL数据库，因此具备高性能、强一致性和可扩展性。DiceDBDiceDB是一个开源的高性能内存数据库（in-memory database），其设计目标是为现代实时应用和缓存场景提供极低延迟、高吞吐量的数据存储与访问能力。它最初由DiceDB团队开发，并于2024年底开源，采用BSD 3-Clause许可。DiceDB将数据全部保存在内存中（非持久化），因此适合用作高速缓存层或对性能敏感的实时系统后台。相比传统磁盘数据库，它能够显著降低读写延迟。DiceDB设计初衷之一是作为Redis的替代品，兼容部分Redis协议/命令。Docling文档解析工具，支持导出markdown和JSON格式，支持OCR功能。Gitdiagram将GitHub仓库转换为交互式的架构图。只需要输入项目地址就可以生成架构图。系统工具与操作系统PowerToys增强Windows 10和Windows 11操作系统。包含了快速启动器（快速搜索启动应用程序）、批量图片大小调整工具、拾色器、屏幕取色器、文本提取器等。Ventoy多系统启动U盘制作工具。Windows via Docker从Docker安装Windows然后从Linux中通过浏览器访问，支持Windows 7-11，默认安装Windows 11。WebVM在网页端运行的Linux虚拟机，甚至可以运行可执行文件。JellyfinwebJellyfin媒体服务器的Web界面。网络与文件共享LocalSend允许在本地网络上安全的和附近设备共享文件和消息。无需互联网连接。FilePizza基于WebRTC实现，省去了文件上传功能。文件会直接从发送者的浏览器直接发送到下载者的浏览器上。数据从未保存到中间的服务器中。传输速度更快。分享文件也支持多人下载。但是下载的时候要保证浏览器打开。File-transfer-goGo语言实现的文件传输工具。Netbird点对点VPN/内网穿透工具。AList聚合网盘的工具。Linux MirrorsLinux镜像源管理工具。监控与管理Infisical开源密钥管理工具，类似HashiCorp Vault。集中管理存储敏感信息，提供强大的安全措施来管理数据。特点包括集中管理和安全性。项目和环境分离。API和CLI支持。以微服务为例，可以给每个微服务创建一个文件夹结构。清晰的组织和管理每个微服务的密钥。BeszelBeszel是一款开源的轻量级服务器监控平台，专为简化服务器和容器监控而设计。它支持Docker容器监控、历史数据记录、智能告警等功能，拥有友好的Web界面和极低的资源占用，非常适合个人开发者和小型团队使用。轻量级设计：Beszel采用Go语言开发，资源占用极低，在1核512MB内存的主机上也能流畅运行，相比Prometheus+Grafana等传统监控方案更加轻量。Docker深度支持：能够实时监控每个容器的CPU、内存、网络使用情况，并记录历史数据，帮助用户快速定位性能瓶颈。XPipe终端连接工具。Openlist开源列表管理工具。安全与渗透测试Hacking tool一个一体化黑客工具箱。融合了各类漏洞扫描和攻击工具。包括DDos、XSS等。Sherlock社交媒体账号搜索工具，输入用户名可以查询400多个网站的社交账号。项目没有提供UI，可以通过命令行来使用。但是没有除了gitee的国内站点。人工智能与分布式计算EXO其核心理念是"在家中使用日常设备运行自己的AI集群"。该项目允许用户将家中的iPhone、iPad、Android手机、Mac、Linux电脑等日常设备连接起来，形成一个强大的分布式AI计算集群，无需昂贵的NVIDIA GPU即可运行大型AI模型。自动模型分割框架。系统会根据当前网络拓扑和各设备的可用资源，智能地将模型分割成多个部分并分配给不同设备处理。例如，两台各有8GB内存的设备可以共同运行需要16GB内存的模型，突破了单设备的内存限制。EXO实现了零配置的自动设备发现功能，无需手动配置IP地址或网络设置。只要设备连接到同一网络，系统就能自动识别并纳入计算集群，极大简化了部署流程。EXO原生支持RDMA over Thunderbolt 5技术，在支持该接口的设备之间可实现高达99%的通信延迟降低。Pansou开源搜索引擎。Weepwiki知识库/wiki系统。新增项目Follow支持在一个地方关注所有喜欢的内容，比如网站博客，社交媒体账户。是一个一体化的信息中心。支持Windows、macOS、Linux。
微分方程https://www.bilibili.com/video/BV1tb411G72z从牛顿时期人们就意识到，所有的物理定律都可以当描述相对变化量比绝对量更容易时，微分方程就经常被用到了。比如为什么它在某个时间是某个特定值更容易。这些方程有俩中不同的形式常微分方程：（ODE）函数的自变量只有一个 通常是时间。和偏微分方程：函数有多个自变量（可以想象成一个随着时间变化的一个连续体。就像固体中任意一点的温度。或者空间中任意一点的流体速度。）常微分方程常微分方程只涉及时间变换的有限集合。也不需要时间，这个时间可以是任何事物。想象一下抛向空中物体的轨迹，地表的重力给了物体向下的速度。这个加速度约为$-9.8m\frac{m/s}{s}$这些速度矢量每秒增加9.8m/s的向下分量。我们使用g来替代这个常量。（简单来说就是每秒下落的距离。）把y坐标看成时间函数，它的导数是加速度的垂直分量。再取一次导数就是加速度的垂直分量。我们的微分方程就是y的二阶导数也就是-g是一个常量。这个问题我们可以通过积分来解决，本质上是一个逆运算的过程。根据有关变化率的信息，最后计算得出函数是什么。如果计算一个更加复杂一点的问题时，这个引力就不一定是一个常量了。如果有俩个物体其中一个物体的受力方向是指向另一个物体。在微分方程中，面对的问题往往是找到一个函数，其导数（以及高阶导数）也是由这个函数本身定义的。在物理学中，二阶微分方程是最常见的。也就是表达式中最高阶倒数是二阶导数。高阶微分方程是包含三阶导数，四阶导数或更高阶导数的微分方程。在某种意义上，你要找到无穷多数，每个数对应一个时间T。单它们满足一个很具体的约束条件，要和自己的变化率以及变化率的变化率交织在一起。$360^∘=2\pi弧度$。也就是说一个完整圆的弧度为$2\pi$。我们用振幅所对的弧长x来表示它的位置。$\theta$是角度，L是摆长。若$\theta$角是用弧度制表示的，x可以写为$x=L\times \theta$但由于单摆限制了这个系统的运动，我们需要关注加速度在运动方向的分量了。其中图中这个夹角也为$\theta$这里不再进行证明所以这个叫在蓝色运动方向上的分量为$-g*sin(\theta)$。而粉色为$-g*cos(\theta)$。我们规定单摆在右边时$\theta$为正，左边时为负数。所以x的二阶导数，即加速度是$-g*sin(\theta)$。当$\theta$为0时sin(0)为0，所以这时候在运动的方向没有加速度。当$\theta$是90度的时候（也就是$\frac{\pi}{2}$）这时候加速度和自由落地是一样的。因为$x=L\times \theta$，所以$\theta$的二阶导数为$-\frac{g}{L}sin(\theta)$为了更真实一点，我们考虑空气阻力。也就是$\ddot\theta=-u\dot\theta-\frac{g}{L}sin(\theta) $。假定空气阻力大小和速度成正比。也就是$$ $\ddot\theta=-u\dot\theta(t)-\frac{g}{L}sin(\theta(t)) $$这个u代表了单摆的空气阻力，摩擦系数等。也决定了单摆损失能量的速度。式子中正弦函数的存在，恰好是现实中单摆不会以正弦波形式摆动的原因。我们可以发现微分方程很那解。没有任何已知的方法可以精确求解$\theta(t)$是多少。所以在学习微分方程时，我们常跳过求解过程，因为它根本无法得到。而是但从方程进建立理解和计算。单摆的状态可以用俩个数字表示，角度和角速度。(分别为横轴和纵轴。)可以在不影响其中一个值的情况下改变另一个值。但是加速度值关于这俩个值的函数。如果已知初始角度和角速度，就足以预测随着时间推移这个系统是如何演变的。通过这个空间，我们可以将微分方程可视化为向量场。假设单摆的状态是向量$[\theta(t),\theta'(t)]$。对于这个向量求导就可以得到它的变化率。也就是在这个图像中变换的趋势速度和大小方向。$$ \frac{d}{dt}\begin{bmatrix}\theta(t)\\\dot{\theta}(t)\end{bmatrix}=\begin{bmatrix}\dot{\theta}(t)\\\ddot{\theta}(t)\end{bmatrix} $$对于空间内所有店做这个操作，就可以看出任意一个位置的变化趋势事实上我们已经把一个二阶方程分解为俩个一阶方程。（一阶常微分方程组System of tow first order）任何常微分方程系统都能用这样的向量图来表示。所以通过构造向量图来理解他们是非常常见的。一般情况下系统都有很多个维度。在数学上我们把这个叫做相空间（phase space），来指代描述运动系统所有状态相同的空间。对于这些计算背后的很打一部分在于，取不动点附近的一小块区域，观察里面的流动是向内缩还是向外扩张。那么$\theta(t)$到底如何求解呢？其中一种办法是模拟本身的变化。但定义微积分时得用有限的时间步长而非无穷小或者无限。取得越小越精确。尽管无法获得精确解，但学习微分方程还是很有意义的。偏微分方程我们来看一个关于偏微分方程的例子，热传导方程。首先建立一个实例，假设有一块金属，而且你知道金属在某一时刻的热量分布情况。那么问题来了，热量从高区域传到低区域分布情况是如何随着时间变化的呢。假设你有俩个温度不同的杆。每个杆上的温度都相同。当吧它们连接到一起的时候，热量会从温度较高的杆流向温度较低的杆。使得系统的温度随时间趋于一个定值。但是它是如何进行的呢？每个时间段中的热量是如何分布的呢？因为微分方程的典型特性是，描述一个系统从一个到另一个的过程，比直接描述某个时刻整体更容易。其中热传导1维方程为$\frac{\partial T}{\partial t}(x,t)=\alpha\cdot\frac{\partial^2T}{\partial x^2}(x,t)$。热传导的三维方程为：$\frac{\partial T}{\partial t}=\alpha\left(\frac{\partial^2T}{\partial x^2}+\frac{\partial^2T}{\partial y^2}+\frac{\partial^2T}{\partial z^2}\right)$有时写作$\nabla^2T$热传导方程在很多地方都有体现，比如布朗运动（Brownian motion），扩散现象。在之前我们了解到常微分方程（Ordinary differential equation）很难解，但偏微分方程比常微分方程(partial differential Equation)还要更难解。因为他们包含了无数变量的相互作用以及变化。我们有一个一维的杆把它放在x轴上，杆上的每个点可以用唯一的数字x来表示。维度是未知的函数T(x)。如果你愿意你可以把输入想象成二维的。它同事代表了时间和空间。或者用真实的时间代表时间。之前我们只研究少量变量随着时间变化的系统，比如角度与角速度。但是如果整个系统都会随着时间变换，事情会变得复杂。因为我们在推导由多维输入的温度方程。在这个例子中一个是温度一个是时间。这里的T是温度，t是时间。x方向的倒数是$\frac{dT}{dx}(x,t)$。 可以将其理解成温度沿x轴方向上的斜率，或者当x发生变化的时候，温度差生的变化与之前的比值。还有一个比值是杆子上某一点温度随时间变化的比值$\frac{dT}{dt}(x,t)$。也就是温度沿着时间轴方向的斜率。每一个导数都只描述了温度函数变化的一部分。所以我们叫他偏导数。所以我们改一下上边俩个公式的记号，将d改为$\partial$。其实他们本质上代表的是完全相同的意思。这个符号想要表达的意思是，在自变量越来越小时，这个比值的极限。而不是去一个有限小值的特定比值。热传导方程就是用这些偏导数来表示的，它说明了这个函数相对于时间的变换取决于相对于空间的变换。更具体的说，它和关于x的二阶偏导数成正比。直观的看，就是在变化曲线中曲率更大的点，有朝着弯曲方向更快变化的趋势。由于这类规则是通过偏导数写出的。我们交它偏微分方程。我们可以想象一下这个曲线的离散版本降低难度。如果对应的x是有限的点，那么事情就比较简单了。如果它相邻的俩个点平均温度高于它，它就升高，如果平均值低于它，它的温度就会降低。用公式写出来就是。$$ (\frac{T1+T3}{2}-T_2) $$当这个差值越大，升温就越快，减去自己后如果为正就升温，为负数就降温。规范的用公式表示，则：T2关于时间的导数和它自身相邻点的平均值成正比。就是为$$ \frac{dT_2}{dt}=\alpha(\frac{T1+T3}{2}-T_2)=\frac{a}{2}((T_3-T_2)-(T_2-T_1)) $$其中这个$\alpha$是比例常数。我们将其中的$T_3-T_2$记作$\Delta T_2$，另外的$T_2-T_1$记作$\Delta T_1$。也就是说我们这里去比较他们差值的差值。我们把差值的差值用术语来说就是二阶差分(second difference)记作$\Delta\Delta T_1$。它本质上在描述与它相邻的均值有多大区别。所以最后的结论是某一点的温度变化比率与它的二阶差分成正比$$ \frac{dT_2}{dt}=\frac{\alpha}{2}\Delta\Delta T_1 $$当我们从离散有限扩展到连续无限的情形。二阶差分即为二阶导数。（这就是热传导方程）$$ \frac{\partial T}{\partial t}=\alpha\times \frac{\partial ^2T}{\partial x^2} $$我们考虑的不在是固定俩点之间的温度的差值，而是将这个距离不断的向0缩小发生的事情。我们不考虑具体数值而是考虑变化率。其中这里有俩个维度，一个是温度随时间变化，一个是温度随着周围的温度变换。在计算中我们写作$$ \frac{\partial \frac{\partial T}{\partial x}}{\partial x}=\frac{\partial ^2T}{\partial x^2} $$也就是关于x的二阶导数。如果把情况推广到三维的。它们的方程看上去都是很相似的。但包含了对另一个空间方向的二次偏导数。$$ \frac{\partial T}{\partial t}=\alpha\left(\frac{\partial^2T}{\partial x^2}+\frac{\partial^2T}{\partial y^2}+\frac{\partial^2T}{\partial z^2}\right) $$其中等式右边括号内的部分被叫做，拉普拉斯算子。通常写作$\nabla^2T$。它本质上是多变量的二阶导数，但直观上与一维的情况是一致的。依然可以被理解为，描述某一点与其临近点的差异。只不过这些临近点不是左右俩边，而是所有环绕它的点。也可以把他看做梯度的梯度。求解T(x,t)必须满足的条件偏微分方程（pdt）边界条件（coundary condition）初始条件（initial condition）偏微分方程本身有海量的解，可以让方程俩边相等。我们可以把傅里叶的解法分为三个基本原则。特定的正弦曲线是温度方程十分简单的解假如你求出多种解 它们的和也是一种解所有的函数都可以表示成正弦曲线的和。为什么要把函数分解为正弦曲线呢？因为在很多地方正弦曲线比其他的函数都要好处理。微分方程就是个很好的例子。在热传导方程中 把函数写成多个曲线的和。简洁的二次导数能让热传导方程变得更好求解。然后多个解加起来仍然是一种解。假设温度在T=0时，$T(x,0)=sin(x)$$$ \frac{\partial \frac{\partial T}{\partial x}}{\partial x}(x,t)=\frac{\partial ^2T}{\partial x^2}(x,t) $$热方程的右边写的是这个函数的二次导数。对sinx求导2次会得到-sinx。所以正弦波的弯曲的程度可以说和曲线上的高度相反。所以在实践T为0的时候。每个点的温度变化率都和整个点的温度成正比。并且比例处处相等$$ \begin{aligned}\frac{\partial T}{\partial t}(x,0)&=\alpha\cdot\frac{\partial^2T}{\partial x^2}(x,0)=-\alpha\cdot T(x,0)\end{aligned} $$所以经过几个微小的时间段后，所有温度都会同比例缩小。然后还是得到一个缩小的正弦曲线。在之后的时间内曲线也将继续缩小。这个性质在时间趋于0时也同样成立。不像其他的分布，正弦波特殊在它们会同比例缩小。也就是说在每个时间T，都想sinx乘以某个常数。一看到某个量的变化率和量本身成正比时，应该本能的想到指数。我们知道对于正弦波 右边的表达式等于$-\alpha$乘上温度函数本身。所以我们可以猜测出这个解将缩小e的$-\alpha t$次幂倍。对哪个变量求编导，就意味着当计算那个变化量的时候，将其他量看做常数。是固定不变的。只有求导的那个量是未知量。$$ T(x,t)=sin(x)e^{-\alpha t} $$我们对x求二次偏导得到$\alpha \times -sin(x)e^{-at}$。而如果对t求导我们会得到$-\alpha sin(x)e^{-at}$。很明显俩者相等。但事实是就算杆子上的温度是正弦曲线，温度也不会像指数那样衰变。实际上左侧的点会先升温，右边会被旁边的更低点降温。举个例子，假如有一个直线，温度常数为非零常数项乘上x，不随时间的改变。（左边冷右边热的直线而且温度均匀理论上除了边界的每个点左右俩边的平均温度都是自己。）$$ T(x,t)=c\times x $$对x的二次偏导数就是0，因为直线没有曲率。对于时间求二阶偏导也是0，因为这个函数不会随着时间而改变。但是吧这个函数放到模拟器中，它确实会随着时间的改变，缓慢接近平均温度，这是因为对待边界的俩点和对待其他的所有点都不同。边界点上有一边没有相邻的点。 假如我们回到离散版本，在杠杆上只有有限多个点。那么边界值应该趋向于相邻的哪一点的值 变化速率与差值成正比。当我们添加足够的点，俩个边界点的热分布看起来会很平。也就是说边界的斜率在刚开始之后会一直等于0。所以当假设杠杆和外界不会有热传导时，俩端的斜率将等于0。因此找一个符合热传导方程的函数本身并不够，它还必须满足在时间大于0时，边界都必须是水平的。更准确的说温度在（0,t）和(L,t)俩点对于x的偏导数，在所有t大于0的时候，都必须等于0。这里的L是杠杆的长度。$$ \frac{\partial T}{\partial x}(0,t)=\frac{\partial T}{\partial x}(L,t)=0 $$实际上要解微分方程的时候边界条件都会自然的出现。而且和偏微分方程本身一样需要重视。因此我们要稍微调整一下让函数在边界水平就可以了。首先我们可以不用正弦曲线而是用预先曲线。它们都是同样的曲线，只是在空间上平移了四分之一个周期。于是函数图像在x=0时，会如我们想要的那样水平。同样的cos(x)的二次导数为-cos(x)。像之前那样$cos(x)\times e^{-at}$满足偏微分方程。为了保证等式右边也满足边界条件，我们要调整余弦波的频率。这一操作会影响它的二次导数，因为频率更，函数波就弯曲的更急。改变频率就意味着要引入新的常数$\omega$。乘上函数的输入值x，$cos(\omega\times x)其中\omega$越高意味着正弦波震动的越快。当x增加时输入仅余弦的值就会增加的越快。对x求偏导的时候，我们依旧会得到-sin，但是链式法则告诉我们要把$\omega$再乘在外面二次导数也同样。$$ cos(\omega\times x)->-\omega^2cos(w\times x) $$让杠杆长为L，使得热分布曲线在最右边的电商的水平的最低频率是$\pi/L$。这样x增加到L时余弦的输入值将增加$\pi$也就是周期的一半。谐波（Harmonic）是指在振动或者波动系统时，相对于基波（fundamental frequency）的频率为其证书倍波动的成分。简单来说谐波是波的倍频，它们是符合信号中或波形组成的部分。找到其他满足边界条件的频率有点像是在找谐波。只需要找出所有$\pi/L$的整数倍即可。$$ cos(\omega(\pi/L)\times x)e^{-\alpha(\omega \pi/L)^2t} $$这个函数看起来复杂多了，但它们都来自一个基本的观察，空间上像正弦波，空间上指数衰减的函数，满足偏微分方程，可以关联上空间中的二次导数和时间上的一次导数。傅里叶级数每一个小向量以固定证书频率旋转，当把他们以首尾相接的方式加在一起。最后一个向量的箭头就会随着时间画出某种图形。通过调整初始量的大小和角度 我们就可以画出任意的图形。它们背后的公式是.$$ c_n=\int_0^1e^{-2\pi int}f(t)dt $$热传导方程正好和线性方程性质一样，热传导的方程正好和线性方程性质一样。将俩个解加起来就是一个新的解。甚至可以把每个解都缩放好几倍，像是一个来创造解方程的自定义函数拨盘。这意味着我们可以拿一群指数衰减的余弦函数来解，通过调整几个余弦函数的大小，然后把它们加起来得到一个新初始条件的解。当在相加这些函数时，因为更高频率的余弦函数衰减的更快，求出的和会随着时间趋于平坦。因为所有高频余弦函数很快会趋近于0。从而低频率函数就主导求出的解。因此热传导方程中描述的热分布变化的复杂性就都被频率不同的各个部分的不同衰减率给捕捉到了。但是现实中的分布是很复杂的。比如我们将一根热的棒和冷的棒接在一起，左边的杆温度为1，右边的为-1。在接触的一瞬间它的温度分布其实就是一个阶跃函数（step function）。很明显它不像正弦波，甚至都不是连续的。因此傅里叶做的就是，如何用正弦波来表示它。此外还要保证加进去的余弦函数同时满足边界条件（这意味着余弦函数需要满足，它的频率都是某个最低频率的整数倍。）。而且如果需要满足不同的边界条件，比如断电值是定值的时候，右要另一堆波拼凑起来。在这个例子中可以把余弦函数替换成正弦函数。但这些正弦函数和的一点都不可能是真正的阶跃方程，它们只不过在做近似罢了。没错，除开长函数，任何正弦函数的有限的和都不可能是完美平坦的。也不可能是不连续的。但是傅里叶想到了无穷个正弦函数的和。$$ \frac4\pi\left(\frac{\cos\left(1\pi x\right)}1-\frac{\cos\left(3\pi x\right)}3+\frac{\cos\left(5\pi x\right)}5-\frac{\cos\left(7\pi x\right)}7+\cdots\right) $$只从数字上考虑这些括里的无穷个分数的相加和应该等于$\pi/4$。当你把每一项加起来的时候，数值应该都是一个有理数。它不可能和$\pi/4$相等，但是随着数量越多会越来越逼近最终收敛于$\pi/4$。引入极限的概念可以做到有限加法无法做到的质变。当你把热传导方程的解和这些余弦函数联系起来，并且将无穷多的项都加起来。会得到这个阶跃方程的解如何随时间变换。那么如何得到这些数字呢。到目前为止，我们考虑的输出实数的函数。但可以让函数的输出值可以是二维平面的复数。我们依旧考虑输入的值是在有限区间内的实数函数。比如区间是0-1。但是不同于温度函数那样降输出限定在实数。引入复数可以让输出值在二维空间复平面自由翱翔。可以把这种函数想象成在绘画。随着输入值从0-1的变化，笔尖勾勒出复数平面上不同的点。我们把函数拆解成小向量和，都按固定的整数频率旋转。实数输出的函数画出的东西很无趣，只是一维上的素描。因为我们通常用图像来描述这类函数。但是此时函数的图像只出现在平面上。如果把函数拆分成一维图像旋转的向量。结果就会得到频率为-1和1的向量长度相同，并且关于水平方向相反。当只就考虑它们俩个项的和，那么它们的和将一直在实数轴上，并且像正弦波那样震动。但是从复数输出的函数角度来看，水平上这种震动就是正弦函数，同样的，频率为2和-2的旋转向量能加成另一个正弦函数，以此类推。所以傅里叶一开始研究的内容，即将实数函数拆分成一群正弦函数的和。其实是用二维平面和旋转向量表达的更普适的一种特殊情况。那么我们为什么不用二维向量，而是用复数呢。因为傅里叶级数的精髓和灵魂就是一个复指数$e^{it}$。当输入值随着时间变化，它的输出值将会以每秒1单位的速率围绕着单位圆旋转。理论上描述所有傅里叶级数时，你可以完全只用向量语言而不是用i来进行描述。但是公式会变得更加复杂。不仅如此，不用指数函数还无法真实反应为什么这个概念会对解微分方程举足轻重。目前来说可以把$e^{it}$当做旋转向量记法。但是，它不是一种简单的记法。将复数当做小箭头后加起来。假设我们有一圈每秒的速率旋转的向量，我们写作$e^{2\pi it }$。之所以有$2\pi$是因为随着t从0走到1，它需要沿着圆走$2\pi$。还有另一个以每秒一圈的速率向另一个方向旋转的量记作$e^{-2\pi it }$。类似的如果每秒钟俩圈记作$e^{2\times 2\pi it}$其中$2\times 2\pi$描述的是它一秒钟会经过的路程。就这样对所有的正负整数都这样改写。因此它的通式为$e^{n\times 2\pi it}$。这些旋钮和表盘就是我们可以控制的变量。代表每个复数的初始长度和方向。复常数（complex constant）是指具有固定值的复数。复数的形式为 a+bia + bia+bi，其中 aaa 和 bbb 是实数，iii 是虚数单位，满足 i2=−1i^2 = -1i2=−1。控制它们的方法是将每一个项都乘上一个复常数叫做$C_n$（这里的n同样代表时间）比如如果我们项让那些不同的向量长度不等于1而是0.5那么$c_0$就是0.5。如果项让旋转速率为1圈的每秒的向量，从45°开始旋转。我们会把它乘上能让他旋转那么多的复数。比如从和实数轴夹角45度的时候开始，那么这时候c就是$e_{(\pi/4)i}$。同样的，在这个无限旋转向量形成的组中每个都乘上了$c_n$这个复常数，从而决定角度和初始大小。我们的目标是能描述任意的函数f(t)。所以我们需要通过这个函数本身，来一个个找出每一项的系数。其中最好找的就是常数项，这一项代表了绘画的中心，如果条一群t从0-1的平均分布的输入值，它对应的输出值的平均数会等于常数项$C_0$。如果更精细的挑选，它们输入的平均值会趋近于$c_0$。越来越越精细的t在输入值域中选点的函数输出的总和$$ c_0=\int_0^1f(t)dt $$它是一个积分，从0到1对f(t)的积分。由于通过平均数来表示，你将会积分除以输入值域的长度。但是这个长度等于1，所以在这里积分和取平均值是一回事。为什么这个积分能得出$c_0$呢？如果我们把这个函数表达成很多旋转向量的和，所以考察这个积分（这个连续的平均数）应用于总和时。和的平均数与各个部分平均值的和是一样的。可以把这个变换理解为方式的细微改变。不是每个时间点去看所有向量的总和并求出它们的平均值。而是去看每一个向量随着t从0到1时的平均值。并且把所有的平均值加起来。但是每一个向量只是绕0转整数圈。所以随着t从0到1平均值会等于0。只有常数项是唯一的例外。因为它保持不变，而且一点都不旋转。它的平均值就是它的初始值。所以求整个函数的平均值，是一种除了$c_0$之外的向量的聪明方法。但真正巧妙的方法是在这。比如说你想计算另外一个项数，巧妙的方法就是先把f(t)乘上一个东西，使得这个向量保持不变。具体来讲，如果把整个函数乘以$e^{-2\times 2\pi it}$。由于乘上一个指数项会让指数加上一块，而每一个指数项频率会减小2现在我们求每一项的平均数。除了$c_2$这一项对应的向量全部都会旋转整数倍。意味着他们的平均数都是0。所以对这个调整过的函数求平均数，就是一种巧妙的方式，去除了$c_2$之外的方法。当然这里的数字2可以替换为n。这样就得到了对于$c_n$成立的普遍方程，这也就是我们要找的。$$ c_0=\int_0^1 e^{-2\pi int}f(t)dt $$把它理解为，先调整我们的函数或者二维图像，使得第n个变量保持不变，然后通过求平均的方法来去除所有其他的旋转变量。分解者一大堆旋转的向量加和所画图时候的复杂性全都包含在这个表达式中了。它把绘图路径看做复数函数，对于值域中的n，电脑会计算积分来求出系数$c_n$。在实际操作中这些积分都是用数值计算法计算出来的。意思就是把它每一个单位区间分成很多小的$\Delta t$然后对每个$\Delta t$加上$f(t)e^{-n*2\pi it *\Delta t}$。计算完者101个常数字之后，每个常熟就确定了一个小向量的初始角度大小。将他们首位链接就可以进行画图了。向量的数量趋近于无穷，近似也会更加准确。理解$e^i\pi$$e^t$倒数等于它本身。如果函数$e^t$描述在数轴上随着时间变化的位置。那么将从1开始（t=0）。而$\frac{d}{dt}e^t$你的速度大小永远等于你的位置大小。离0越远速度越快。如果是2t，相当于你在意位置两倍的速度移动（速度向量翻倍）。这里2所暗示的就是我们速度会随着距离的增加而更加难以控制。如果乘上的是一个负数。那么速度向量是位置向量的-0.5倍$e^{-0.5t}$。意味着要把位置向量翻转180度，长度上再减半，速度按照指数衰减。趋近于0。那么如果t前面的数字是i呢？那么位置函数的导数就永远是i乘上函数本身。等同于把数字旋转90°。因此，只有跳出数轴引入复数平面时，这种联想才有意义。我们知道对于任何一个给定的时间t的位置。该时刻的速度是将此时的位置向量旋转90度。我们的初始位置为$e^0=1$。在这个位置只有轨道满足条件 使得速度向量是与90度旋转后的位置相匹配。此时你就以一个单位每秒的速度，绕了一个半径为1的圆。所以$\pi$秒后，你会得到一个长度为$\pi$的路径(因为完整的是$2\pi r$而这里的r是1)，所以$e^{i\times \pi}=-1$
音频与信号的基础概念傅里叶级数Fourier Series (thefouriertransform.com)傅里叶级数将周期函数分解到正弦曲线的函数中。它是周期函数的傅里叶变换。为了开始分析傅里叶级数，让我们定义周期函数。一个周期为T的函数如果对下列条件成立$$ f(t+T)=f(t) $$简单来说，这以为这周期为T的函数将会在T秒时出现和现在一样的值。不管你什么时候观察这个函数。注意一个周期为T的函数，也是周期为2T的函数。所以基本的周期是T的值值中最小的可能性让以上等式依然可以成立。来看图1图1的方型波基本的周期为T。现在让我们来定义傅里叶级数。周期为T的傅里叶级数，是正弦函数的无穷和（cosine 和 sine），每个频率都是1/T的整数倍(基本周期的倒数)。傅里叶级数也包括了常熟，因此被写作：$$ \begin{gathered} g(t) =a_{0}+\sum_{m=1}^{\infty}a_{m}\cos\biggl(\frac{2\pi mt}{T}\biggr)+\sum_{n=1}^{\infty}b_{n}\sin\biggl(\frac{2\pi nt}{T}\biggr) \\ =\sum_{m=0}^{\infty}a_{m}\cos\biggl(\frac{2\pi mt}{T}\biggr)+\sum_{n=1}^{\infty}b_{n}\sin\biggl(\frac{2\pi nt}{T}\biggr) \end{gathered} $$其中的常数$a_m,b_n$是傅里叶级数的系数。它决定了乜咯正弦曲线的权重。现在的问题是：对于任意的周期函数f(t)，我们能用周期为基本周期的整数倍的简单正弦波来多么接近地近似这个函数？也就是，对于给定的周期函数f(t),函数g(t)和f(t)由多接近。这个结果是数学中最棒的结论之一，即：我们可以精确的近似f(t)，只要f(t)是连续且光滑。现实中，所有的函数都是连续和光滑的。所以对于时间中的工程师或者物理学家，所有的周期性函数可以精准的通过傅里叶级数来进行表示。这是一个很棒的结果傅里叶变换正弦波正弦曲线或正弦波（Sinusoid／Sine wave）是一种来自数学三角函数中的正弦比例的曲线。也是模拟信号的代表，与代表数字信号的方波相对。正弦曲线的形状就像完美的海上波浪，以三角函数正弦比例改变而形成。标准的纯正弦函数公式为$$ y=sin(x) $$sin(x) 为正弦函数。而一般应用的正弦曲线公式为$$ y=A\cdot\sin(\omega t+\theta) $$A 为波幅（纵轴）， ω 为角频率， t 为时间（横轴），θ为初始相位(t=0时的)。以下的公式则拥有全部的可用参数.$$ y=A\cdot\sin(kx-\omega t-\theta)+D $$k 为波数（周期密度）， D 为（直流）偏移量（y轴高低）。因为cos⁡x=sin⁡(x+π2)，$\displaystyle \cos {x}=\sin(x+{\frac {\pi }{2}})$所以余弦波也是正弦波。基本事实上时姐上所有的函数都可以被描述成为一个波形，是一个时间空间或者其他变量的函数。例如，声波，电磁场，山的高度与位置的关系，驻波比与频率的关系图，你最喜欢的股票价格与时间的关系等。傅里叶变换给了我们一种独特的强有力的能力来观察这些波形。基于这样一个事实-宇宙的基本秘密之一：所有的波形无论你在宇宙中如何描述或者观察，它们只是不同频率的简单正弦曲线的和。正如我们看到的一样这个事实很酷。傅里叶变换分解了波形：任何真实世界的波形变为正弦曲线。即，傅里叶变换给出了一种表示波形的方法让我们将图1中的曲线分解为建筑块（或者成分的频率）。这个分解可以用傅里叶变换来完成（或者周期波形的傅里叶级数），就像我们看到的一样。第一个曲线为正弦曲线其周期为T=6.28(2pi)，幅值0.3的正弦波如图1所示。图1：一个基本频率（左）和原来的原始波形（右）的比较第二个频率的周期是第一个的一半（频率的俩倍）。第二个组成部分展示在图2左侧，加上俩个频率域原始波形的和。图2：第二个原始频率（左）和源波形与前俩个频率组合的比较。我们可以看到前俩个频率的和开始长得像源波形。最后我们加入4个频率分量得到原始波形。傅里叶变换是基本的工具像我们展示了如何结构波形成为几个正弦函数的组成部分。它在很多领域都用应用，帮助我们理解宇宙，这让实践工程师或科学家的生活变得更容易。梅尔频谱Understanding the Mel Spectrogram | by Leland Roberts | Analytics Vidhya | Medium信号信号是一定数量随着时间的变化。对于音频变化的量是空气的压力。我们如何以数字的方式补货这些信息？我们可以取气压随时间变化的样本。我们对数据的采样率可能会有所不同，但是通常使用44.1kHz，或者44,100 采样每秒。我们捕获的是信号的波形，它可以被解释修改和通过计算机软件分析。import librosa import librosa.display import matplotlib.pyplot as plt y, sr = librosa.load('./example_data/blues.00000.wav') plt.plot(y); plt.title('Signal'); plt.xlabel('Time (samples)'); plt.ylabel('Amplitude');这很好！我们有了音频信号的信号表示，这是我们可以处理的。我们也许会想知道，我们如何在其中提取出有用的信息？它看起来乱七八糟。我们的朋友傅里叶要来了傅里叶变换一个音频信号包括了几个信号频率声波。当信号随着时间进行采样，我们之内捕获到最终的振幅。傅里叶变化是一个数学公式，它允许我们分离信号到单独的频率和频率的振幅。换句话说，它将信号从时间域转换到了频域。这个结果我们称之为频谱。这是很重要的，因为每个信号都可以被分解为正弦函数集和余弦波，加起来就是原始信号。这是一个了不起的理论被叫做傅里叶定理。快速傅里叶算法（FFT）是一个能够有效的计算傅里叶变换的算法。它被广泛用在信号处理。我们将在事例音频的窗口段上使用这个个算法。import numpy as np n_fft = 2048 ft = np.abs(librosa.stft(y[:n_fft], hop_length = n_fft+1)) plt.plot(ft); plt.title('Spectrum'); plt.xlabel('Frequency Bin'); plt.ylabel('Amplitude');频谱图快速傅里叶变化是一个强大的工具允许我们分析信号的频率内容，但是如何信号的频率内容随时间发生变化呢？大多数音频信号比如音乐和说话都是如此。这些信号被称为非周期信号。我们需要一种方式来表示这些信号随时间改变的频谱。你可能会想：“我们不能通过对信号的几个窗口执行FFT来计算几个频谱吗？”没错！这就是我们所做的，它被叫做短时傅里叶变换。FFT是在信号的重叠窗口段上计算的，我们就得到了所谓的频谱图。这让人难以接受，这里发生了很多事。你可以将频谱图想象成一个堆叠在一起的FFT。这是一种直观地表示信号响度，广度的方法，因为他们在不同频率下随着时间变化。在计算频谱图时，还有一些额外的细节在幕后进行。y轴被转换成对数刻度，颜色维度被转换成分贝（你可以想象振幅的对数刻度）。这是因为人类只能感受到非常小的范围的和集中的频率范围与振幅。spec = np.abs(librosa.stft(y, hop_length=512)) spec = librosa.amplitude_to_db(spec, ref=np.max) librosa.display.specshow(spec, sr=sr, x_axis='time', y_axis='log'); plt.colorbar(format='%+2.0f dB'); plt.title('Spectrogram');只需要几行代码，我们就可以创建频谱图。我们很接近了！我们现在知道了平铺图，但是什么是Mel呢？这是什么。梅尔尺度研究表明，人类不能感知到线性尺度上的频率。我们更擅长与检测低频的差异而不是高频的差异。举个例子，我们可以简单的是活出500和1000hz的不同，但是我们很难说出1000和10500hz的不同，即使他们的路径距离是一样的。在1937年 Stevens, Volkmann，Newmann 提出了音高单位，使得在音高上的等距离听上去对听者来说距离相等。这被称之为梅尔尺度。我们队频率进行数学计算，将其转换为梅尔尺度。梅尔频谱梅尔频谱是一个频率已经转换为梅尔尺度的频谱。令人惊奇的是，在经历了这一切后，现在只需要几行代码就可以实现。mel_spect = librosa.feature.melspectrogram(y=y, sr=sr, n_fft=2048, hop_length=1024) mel_spect = librosa.power_to_db(spect, ref=np.max) librosa.display.specshow(mel_spect, y_axis='mel', fmax=8000, x_axis='time'); plt.title('Mel Spectrogram'); plt.colorbar(format='%+2.0f dB');
注意力就是你所需的一切Attention is All you Need (acm.org)概要主要序列转换模型是基于复杂的循环或者卷积神经网络的，它包括了编码器和解码器。我们提出了一个新的简单网络架构，Transformer，基于单一的注意力机制，完全不适用递归和卷积。实验在俩个机器翻译任务上展示了这些模型的优秀的品质，同时更加并行化只需要更少的训练时间。我们的模型在WMT2014英语生产翻译任务上达到了28.4的BLEU，比现有最佳的结果（包括组合）提高了2BLEU。在一个WMT 2014英语-发育的翻译任务重，在8个GPU上训练了3.5天之后我们模型简历了新的简单模型，最高的BLEU分数，从文献中得到最好模型的一小部分训练成本。1 介绍在实践中循环神经网络，长短期记忆网络和门控循环单元网络，已经在序列模型和如同语言模型与机器翻译模型的传到问题中，被牢牢确立为最先进的方法。此后许多努力仍在继续以突破循环语言模型和编码器解码器架构的边界。循环模型通常沿着输入和输出序列的符号位置来进行计算分解。将位置与计算时间中的步骤对其，它生成隐藏状态序列$h_t$，作为上一个隐藏层状态$h_{t-1}$和位置的输入函数。这个内在的序列在训练中排除了并行性，并行性在长序列上很重要，由于内存约束限制了跨样例的批处理。最近的工作通过因式分解的技巧和条件计算得到了显著的提升，同时也提升了模型在之后例子中的性能。然而基本的顺序计算限制还是存在。注意力机制已经成为了引人注目的序列建模和多变任务中的转换模型的重要组成部分，允许模型对以来进行建模而不管它们的在输入或输出序列中的距离。然而除了少数情况外，这样的attention机制被用来与RNN一起使用。在这个工作中我们提出了Transformer，一个避免了循环和而是以来整个注意力机制去绘制在输入输出中的整个依赖。Transformer允许显著的更多并行，在P100GPU上训练了12个小时后，可以让翻译质量达到一个新的境界。2 背景减少顺序计算的目标也构成了Extended Neural GPU，ByteNet，ConvS2S的基础，所有这些都使用卷积神经网络作为基本构建块，并行计算所有输入输出位置的隐藏表示。在这个模型中，将来自俩个任意输入或输出位置的信号联系起来所需的操作数量随着位置之间的距离而增加，对于ConvS2S是线性的，对于ByteNet是对数的。这让学习距离和位置之间的关系变得很难。在Transformer中这被简化成为了一个常量超操作，尽管关于平均注意力加权位置降低了有效的决议，我们使用多头注意力抵消了影响，如3.2章所述。一些时候，自注意力机制被叫做内部注意力，关联了一个序列中的相关的不同的电，以计算序列的表示。自注意力机制成功的备用在多个任务上其中包括：阅读理解，摘要概要，文字循环和学习与任务无关的句子的表示。端到端的内存网络是基于循环注意力机制的而不是序列排列的重复，已经展示了在简单问题回答和语言建模问题上的良好性能。然而据我们所知，Transformer是第一个完全依赖注意力机制模型，以计算输入和输出而不适用序列排列的RNN或者卷积。在之后的章节中，我们将描述Transformer，促进自注意力机制和讨论相对于其他模型的优势。3 模型架构最局竞争的神经序列转换模型有一个解码器和编码器结构。其中，解码器映射用符号表示的输入序列($x_1,...,x_n$)到连续的表示序列z=($z_1,...,z_n$)中，给定z，解码器生成输出序列($y_1,..,y_m$)符号，一次一个元素。在每一步模型都是自回归的，在生成下一个符号时，使用生先前生成的符号作为附加输入。Tansformer遵循寻使用堆叠的自我关注和点方向，对编码层和解码层进行全链接，分别展示在图1左右俩边。3.1编码器和解码器堆叠编码器：编码器是由N为6的相等的层堆叠而成的。每个层有俩个子层。 第一个多头自注意力机制，第二个是简单的positionwise全链接前馈网络。我们使用残差链接环绕着俩层，然后是层归一化。每一个子层的输出都是LayerNorm(x + Sublayer(x))，其中Sublayer(x)是一个实现了子层自身的函数。以方便这些残差连接，在模型中所有的子层和嵌入层产生维度$d_{model}=512$的输出解码器：解码器也是由N为6的相等的层堆叠而成的。除了在每个编码器层添加俩个子层之外，解码器插入了第三个子层，它对编码器栈执行多头注意力。类似编码器，我们使用残差链接包围每个子层，然后是归一化层。我们也改进了在decoder栈中的自注意力子层以防止位置关注后续位置。这种遮蔽，结合了一个事实：输出嵌入偏移一个位置，确保对于位置i的预测只能依赖位置小于i的已知输出。3.2 注意力注意力函数可以表述将一组查询和一组键值对映射到输出，其中query，key，value和删除都是向量。通过值的加权和计算，其中分配给每个权重由查询与相应的键的兼容性函数计算。3.2.1自注意力机制图2：（左）缩放点积注意力（右） 由几个注意力层并行运行的多头点积注意力。我们叫通常的注意力缩放点击注意力（图2）。输入由query和$d_k$个维度key，和$d_v$维度个值构成。我们计算所有键查询的点积，每个除以$\sqrt{d_k}$，并且使用softmax函数以获得值的权重。事实上我们同时在一组query上计算注意力函数，组成一个矩阵Q。key和value也组合到矩阵K和V中。我们计算输出矩阵$$ \text{Attention}(Q,K,V)=\text{softmax}(\frac{QK^T}{\sqrt{d_k}})V $$在注意力机制中，兼容性函数（compatibility function）通常是指用于计算查询（query）与键（key）之间的相关性的函数。这个函数的结果通常用于生成注意力权重，进而用于加权求和值（value）向量，得到注意力得分俩个最常用的方法是加性注意力和点积（多用途）注意力。点积注意力和我们的算法完全相同，除了比例因子$\frac1{\sqrt{d_k}}$。加性注意力使用前馈神经网络和但隐藏层计算兼容函数。虽然他们在理论上相似，但是点积注意力事实上更快更省空间，因为它可以使用高度优化的矩阵乘法代码来实现。对于较小的$d_k$值，俩个方法很相似，在不缩放较大$d_k$值的情况下加性注意力由于点积注意力。我们认为对于更大的$d_k$值，点积会变得很大，将softmax函数推入具有极小梯度的区域。为了抵消这个影响，我们通过$\frac1{\sqrt{d_k}}$缩放点3.2.2多头注意力不同于使用$d_{model}$的key，value和query维度执行一次注意力函数，我们发现他有助于线性投影，我们发现将query，key，value分别用不同的，科学系线性投影到$d_k,d_k,d_v$个维度是有益的。在query，key，value的每个版本上，我们并行的执行注意力，产生$d_v$维度的输出值。将它们串联起来并再次进行投影，得到最终值如图2所示多头注意力机制允许模型共同处理来自不同表示不同位置的子空间的信息。对于单头注意力机制，平均值会抑制这一点。$$ \begin{aligned}\mathrm{MultiHead}(Q,K,V)&=\mathrm{Concat}(\mathrm{head}_1,...,\mathrm{head}_\mathrm{h})W^O\\\mathrm{where~head_i}&=\mathrm{Attention}(QW_i^Q,KW_i^K,VW_i^V)\end{aligned} $$$$ \begin{aligned}&\text{其中的投影是参数矩阵}W_i^Q\in\mathbb{R}^{d_{\mathrm{model}}\times d_k},W_i^K\in\mathbb{R}^{d_{\mathrm{model}}\times d_k},W_i^V\in\mathbb{R}^{d_{\mathrm{model}}\times d_\upsilon}\\&\mathrm{and~}W^O\in\mathbb{R}^{hd_v\times d_{\mathrm{model}}}.\end{aligned} $$在这个工作中我们使用了h=8的平行注意力层或头。对于每一个我们使用$d_k=d_v=d_\text{model}/h=64.$由于减少了每个头的维度，整个计算代价会比单头的注意力的全维度来的少。3.2.3 注意力在我们模型中的应用Transformer在三个不同的方面使用了多头注意力机制。在编码器解码器注意力层，query，来自前一个解码器层，记忆key和值来自encoder的输出。这允许解码器的每个位置去处理输入序列中的所有位置。这模仿了seq2seq模型中的编码器和解码器注意力机制编码器包括了注意力层。在自注意层所有的key，value，query都来自一个位置，在这个例子中，是编码器前一层的输出。编码器中的每个位置可以处理编码器前一层的所有位置。类似的，编码器中的自注意力层允许每个编码器中的位置去处理编码器中国的所有点直至并包括该位置。我们需要防止左侧的信息留到编码器以保护自回归特性。我们通过屏蔽（设置为-∞）softmax输入中对应于非法连接的所有值来实现缩放点积注意力。如图2。3.3 Position-wise 前馈网络除了注意力子层，在我们编码器和解码器中的每个层包含了全连接的前馈网络，它被单独和分别的用在每个位置。它在这之间用Relu激活包括了俩个线性变化。$$ \mathrm{FFN}(x)=\max(0,xW_1+b_1)W_2+b_2 $$不同位置的线性变化是相同的，它们使用不同层的不同参数。另一种描述它们的方式是俩个核大小为1的卷积。输入和输出的维度$d_{model}=512$，内部层有$d_{ff}=2048$维。3.4 嵌入和softmax类似于其他序列转换模型，我们使用使用可学习的嵌入来转化输入和输出token为一个$d_{model}$维的向量。我们同时使用科学系的线性变化和softmax函数将解码器输出转化为下一个token的概率。在我们的模型中，我们在两个嵌入层和pre-softmax线性变换之间共享相同的权矩阵，在嵌入层，我们把这些权重乘以$\sqrt{d_{model}}$。3.5 位置编码因为我们的模型不包含循环和卷积，以便为模型模型的顺序，我们必须注入一些关于token在序列中的相对或绝对位置信息。为此，我们在编码器和解码器堆栈底部的输入嵌入中添加了“位置编码”。位置编码于embedding有相同的维度，这样俩者就可以相加了。位置编码有很多选择，可以学习也可以固定。在这个工作中，我们使用正弦和预先函数作为不同的频率。$$ PE_{(pos,2i)}=sin(pos/10000^{2i/d_{\mathrm{model}}})\\PE_{(pos,2i+1)}=cos(pos/10000^{2i/d_{\mathrm{model}}}) $$其中pos是位置，i是维度。也就是说每个位置编码的维度对应正弦曲线，波长形成从2π到10000·2π的几何级数。我们选择了这个函数因为我们它让模型更易通过相对位置进行学习，因为对于任意固定的偏移量k，$PE_{pos+k}$可以表示$PE_{pos}$线性函数。我们还尝试使用学习得到位置的嵌入，发现这俩个版本产生了几乎相同的结果（见表3的第E行）。我们选择正弦曲线版本因为它运行模型外推到比训练期间遇到的序列长度更长的序列。4为什么是自注意力在这以章，我们将主自注意力机制的各个方面与通常用于映射一个可变长度符号（$x_1,...,x_n$）到另一个相等长度的序列表示的循环神经网络与卷积层相比较，其中$x_i,z_i\in\mathbb{R}^d$,列入在序列转换编码器和解码器中的隐藏层。一个是每层的总计算复杂度。另一个是可以被并行计算的量，通过最小的顺序操作数量来衡量。第三是网络中远程以来关系的路径长度。学习长依赖是一些序列转换任务的关键挑战。一个关键的因素影响了学习这种长路径的能力，网络中向前和向后信号必须经过的路径长度。在输入和输出序列中位置组合之间的路径越短，学习长距依赖关系就越容易。因此我们也比较在在不同层类型的网络组成中俩个输入和输出位置之间最大路径的长度。表1：最大路径长度，每一层的复杂度和对于不同层类型的序列操作最小值。n是序列长度，d表示维度，k是卷积的核心数，R是受限制的注意力 中的邻域大小。图表1中所记载的，自注意力层通过固定数量的操作连接了所有位置，但是循环层需要O(n)的顺序操作。在计算复杂度方面，当序列长度n小于维数d时，自注意力层比循环层快，这是机器翻译中最先进模型使用的句子表示最常见的情况，比如word-piece和byte-pair 表示。为了增加长序列的执行性能，自注意力机制可以限制值考虑输入序列以各自输出为中心的大小为r的领域内。这使得最大路径长度增加到$O(n/r)$。我们计划在未来的工作中进一步研究这个方法。 一个核大小为k<n的卷积层，不连接所有的输入输出对的位置。在连续核的情况下作这样需要O(n/k)个卷积层栈，或者在空洞卷积中需要$O(log_k(n))$个，从而增加网络中任意俩个位置的最长路径长度。卷积层通常比循环层代价高k倍。然而，可分离卷积显著减少了复杂性到$\dot{O(k\cdot n\cdot d+n\cdot d^{2})}$。然而甚至于k=n，可分离卷积的复杂性也等于自注意层和逐点前馈层的组合，也就是我们在模型中使用的方法。附带的好处是，自注意力层可能产生更易解释的模型。我们检查我们模型中注意力的分布，并在附录中给出讨论示例。单独注意力头不清除的学会执行不同的任务，许多表现出与句子和语义结构有关的行为。
动手深度学习-硬件对于训练的影响
动手深度学习-硬件对于训练的影响CPU训练在计算a+b的数据需要准备a和bl1访问延迟是0.5nsl2访问延迟是7ns主内存访问延迟100ns(200xl1)提升空间和时间的内存本地性时间：重用数据使得保持他们在缓存中空间：按需读写数据可以使得预读取所以一个矩阵如果按列存储，访问一行会比访问一列要快GPU每个绿点是一个小的处理器(这是泰坦x)cpu和核心数在6/64-2k/4k每秒计算的浮点数就是核的数量乘以主频。内存带宽显卡也是十倍甚至上百倍于GPU，cpu为30GB-100GB，而GPU为400GB-1TB但是CPU控制流强于GPU如何提升GPU利用率并行：使用数千个线程（也叫流处理器）内存本地访问性：哈运存更小，架构耿凯但你少用控制语句：支持有限，同步开销很大不要频繁在CPU和GPU之间传输数据CPU高性能使用C++，编译器成熟GPU NVIDIA上用CUDA，OpenCL，质量取决于硬件厂商单机多卡并行一台机器能装1-16个GPU在训练和预测时，将一个小批量计算切分到多个GPU上来达到加速的目的常用的切分方案有数据并行：将小批量分成n块，每个GPU拿到完整的参数计算这一块数据的梯度，性能通常更好。模型并行：将模型分成n块，每个GPU拿到一块模型计算它的前向结果，通常用于模型大道单GPU放不下通道并行（数据+模型并行）需要模型并行的场景是一张卡放不下模型。多GPU代码实现从0开始实现多GPU训练import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l scale = 0.01 W1 = torch.randn(size=(20, 1, 3, 3)) * scale b1 = torch.zeros(20) W2 = torch.randn(size=(50, 20, 5, 5)) * scale b2 = torch.zeros(50) W3 = torch.randn(size=(800, 128)) * scale b3 = torch.zeros(128) W4 = torch.randn(size=(128, 10)) * scale b4 = torch.zeros(10) params = [W1, b1, W2, b2, W3, b3, W4, b4] # 定义模型 def lenet(X, params): h1_conv = F.conv2d(input=X, weight=params[0], bias=params[1]) h1_activation = F.relu(h1_conv) h1 = F.avg_pool2d(input=h1_activation, kernel_size=(2, 2), stride=(2, 2)) h2_conv = F.conv2d(input=h1, weight=params[2], bias=params[3]) h2_activation = F.relu(h2_conv) h2 = F.avg_pool2d(input=h2_activation, kernel_size=(2, 2), stride=(2, 2)) h2 = h2.reshape(h2.shape[0], -1) h3_linear = torch.mm(h2, params[4]) + params[5] h3 = F.relu(h3_linear) y_hat = torch.mm(h3, params[6]) + params[7] return y_hat # 交叉熵损失函数 loss = nn.CrossEntropyLoss(reduction='none') def get_params(params, device): new_params = [p.to(device) for p in params] for p in new_params: p.requires_grad_() return new_params new_params = get_params(params, d2l.try_gpu(0)) print('b1 权重:', new_params[1]) print('b1 梯度:', new_params[1].grad) def allreduce(data): for i in range(1, len(data)): data[0][:] += data[i].to(data[0].device) for i in range(1, len(data)): data[i][:] = data[0].to(data[i].device) data = [torch.ones((1, 2), device=d2l.try_gpu(i)) * (i + 1) for i in range(2)] print('allreduce之前：\n', data[0], '\n', data[1]) allreduce(data) print('allreduce之后：\n', data[0], '\n', data[1]) data = torch.arange(20).reshape(4, 5) devices = [torch.device('cuda:0')] split = nn.parallel.scatter(data, devices) print('input :', data) print('load into', devices) print('output:', split) def split_batch(X, y, devices): """将X和y拆分到多个设备上""" assert X.shape[0] == y.shape[0] return (nn.parallel.scatter(X, devices), nn.parallel.scatter(y, devices)) def train_batch(X, y, device_params, devices, lr): X_shards, y_shards = split_batch(X, y, devices) # 在每个GPU上分别计算损失 ls = [loss(lenet(X_shard, device_W), y_shard).sum() for X_shard, y_shard, device_W in zip( X_shards, y_shards, device_params)] for l in ls: # 反向传播在每个GPU上分别执行 l.backward() # 将每个GPU的所有梯度相加，并将其广播到所有GPU with torch.no_grad(): for i in range(len(device_params[0])): allreduce( [device_params[c][i].grad for c in range(len(devices))]) # 在每个GPU上分别更新模型参数 for param in device_params: d2l.sgd(param, lr, X.shape[0]) # 在这里，我们使用全尺寸的小批量 def train(num_gpus, batch_size, lr): train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) devices = [d2l.try_gpu(i) for i in range(num_gpus)] # 将模型参数复制到num_gpus个GPU device_params = [get_params(params, d) for d in devices] num_epochs = 10 animator = d2l.Animator('epoch', 'test acc', xlim=[1, num_epochs]) timer = d2l.Timer() for epoch in range(num_epochs): timer.start() for X, y in train_iter: # 为单个小批量执行多GPU训练 train_batch(X, y, device_params, devices, lr) torch.cuda.synchronize() timer.stop() # 在GPU0上评估模型 animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu( lambda x: lenet(x, device_params[0]), test_iter, devices[0]),)) print(f'测试精度：{animator.Y[0][-1]:.2f}，{timer.avg():.1f}秒/轮，' f'在{str(devices)}')pytorch实现import torch from torch import nn from d2l import torch as d2l from resnet import Residual # @save def resnet18(num_classes, in_channels=1): """稍加修改的ResNet-18模型""" def resnet_block(in_channels, out_channels, num_residuals, first_block=False): blk = [] for i in range(num_residuals): if i == 0 and not first_block: blk.append(Residual(in_channels, out_channels, use_1x1conv=True, strides=2)) else: blk.append(d2l.Residual(out_channels, out_channels)) return nn.Sequential(*blk) # 该模型使用了更小的卷积核、步长和填充，而且删除了最大汇聚层 net = nn.Sequential( nn.Conv2d(in_channels, 64, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(64), nn.ReLU()) net.add_module("resnet_block1", resnet_block( 64, 64, 2, first_block=True)) net.add_module("resnet_block2", resnet_block(64, 128, 2)) net.add_module("resnet_block3", resnet_block(128, 256, 2)) net.add_module("resnet_block4", resnet_block(256, 512, 2)) net.add_module("global_avg_pool", nn.AdaptiveAvgPool2d((1, 1))) net.add_module("fc", nn.Sequential(nn.Flatten(), nn.Linear(512, num_classes))) return net net = resnet18(10) # 获取GPU列表 devices = d2l.try_all_gpus() # 我们将在训练代码实现中初始化网络 def train(net, num_gpus, batch_size, lr): train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) devices = [d2l.try_gpu(i) for i in range(num_gpus)] def init_weights(m): if type(m) in [nn.Linear, nn.Conv2d]: nn.init.normal_(m.weight, std=0.01) net.apply(init_weights) # 在多个GPU上设置模型 net = nn.DataParallel(net, device_ids=devices) trainer = torch.optim.SGD(net.parameters(), lr) loss = nn.CrossEntropyLoss() timer, num_epochs = d2l.Timer(), 10 animator = d2l.Animator('epoch', 'test acc', xlim=[1, num_epochs]) for epoch in range(num_epochs): net.train() timer.start() for X, y in train_iter: trainer.zero_grad() X, y = X.to(devices[0]), y.to(devices[0]) l = loss(net(X), y) l.backward() trainer.step() timer.stop() animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu(net, test_iter),)) print(f'测试精度：{animator.Y[0][-1]:.2f}，{timer.avg():.1f}秒/轮，' f'在{str(devices)}') train(net, num_gpus=1, batch_size=256, lr=0.1)
高效的serverless计算中的挑战与机遇论文原文Sci-Hub | Challenges and Opportunities for Efficient Serverless Computing at the Edge. 2019 38th Symposium on Reliable Distributed Systems (SRDS) | 10.1109/SRDS47363.2019.00036概述无服务计算框架允许用户在不处理操作问题的情况下执行小型应用程序（专心特定的任务），如服务配置，资源管理和负载增高情下的资源缩放。serverless计算最初出现云计算框架下，但是它可能与物联网完美匹配在边缘计算的数据处理方面。但是现在的serverless方案是基于VM和容器的，他对于边缘计算的操作效率和弹性伸缩来说太重了（更大的内存占用和更高的函数调用时间）。此外，更多新奇的IOT应用包含低延迟数据处理和准实时的响应。这使得基于云的serverless方案不合适。不久前wasm作为一种以原生速度运行serverless应用的备选方案被提出了，同时具备小内存占用和最优的执行时间。在这个文章中，我们讨论一些现在serverless的环境，它们的设计详情，未解决的性能挑战在边缘进行高效的无服务管理。我们概述了我们的无服务框架，叫做aWsm，基于webassmbly的方式，讨论机会使用根据aWsm设计，包含功能剖析，SLO（service level objective）驱动的用户功能性管理。最后我们给出了初步评估，以平均启动时间为特征的性能(12μs to 30μs)和用于用作函数的MiBench微基准标记的子集的经济内存占用（从10s到100s的kB）。引言工业物联网的激增和下一代技术（自动驾驶，智能城市，智能电网等）使越来越多的新应用程序成为可能。它们包含了数据处理系统在新性能特性于低执行延迟和高质量服务（Qos）想象一个智能城市自动化街道和红绿灯，他感知交通流量和聚焦于优化整个系统的效率，也明智的响应医疗和消防部门的紧急请求，以满足他们的需求和服务水平目标（Service level Objectives SLOs）。想象一个通信汽车和相关数据服务的世界，可以提醒驾驶员危险道路通知：再你前面的录上有黑冰这俩个应用程序需要从多样的设备源处理大量新的数据在几乎实时的方式支持所需的功能。实时性能是许多工业和企业中的控制和检测所预期的。一些方案需要在10ms内响应。如果数据分析和控制逻辑实现在云端，这样的系统无法满足实时服务的要求。时间敏感的应用有车机系统，根本无法依赖和云端的连接。这样的程序必须在设备上本地的运行。多种最近的趋势和相关的技术挑战激发了我们的兴趣：优化serverless的边缘计算。尽管云计算为以人类感知速度设计的应用程序提供了一个很好的解决方案，他变得不适合对延迟敏感的IOT应用上它们依赖速度，自动化决策无需人工参与。为了满足工作负载的性能需求，我们必须提供一个新方式更接近源头的去处理这些数据。换而言之，在边缘提供云计算。此外，这种方法将有助于支持数据安全和隐私问题。没有人希望他们的数据被破坏，当数据在云和设备不断转移时，这种风险更高。用户对于隐私和数据控制的要求越来越高，边缘计算能够解决这些多重问题。关键的区别和挑战，当边缘计算和云计算进行比较时，边缘表示资源受限的环境因此，边缘资源需要谨慎共享在多租户和高效管理，以支持所需的物联网数据处理功能。serverless计算，也成为fuction-as-a-service（Fass）函数即服务。提供了新的执行模型，这使得用户运行他们的代码。不用担心操作问题。这种方式，用户不用担心的从多个参与的服务配置和资源管理。serverless应用亦在事件驱动和无状态。比如Fass可以实例化去执行预定义的函数和再他们完成的时候终止执行。在商业化系统中，用户按调用次数计费，不支付未使用或闲置资源的费用。通过无限的云端资源。serverless计算代表了一种效用计算形式，通过可用的按需计算资源访问弹性伸缩。自亚马逊lambda提供以来，大量主要的云提供商提供了serverless平台，包括谷歌云函数，微软Azure函数和IBM云函数。现在的serverless框架主机函数实例在短周期虚拟机或容器，它支持应用处理隔离和资源提供。这些框架有重中在边缘计算系统，并且在提供低延迟方面效率低下，尤其是函数第一次实例化时。例如从aws lambda的125毫秒到microsoft Azure functions的一秒。为了获得更好的效率，这些平台缓存和重用容器在多个方法在给定的时间窗口内调用。例如五分钟，一些用户发送认为请求以避免容器关闭。在边缘环境下，使用长就存活或者过度供应的容器或者虚拟机会加速耗尽有限的节点资源同时让服务大量的IOT设备变得不切实际。因此，支持大量serverless函数提供低响应时间，是资源受限的边缘计算节点主要的性能挑战之一。wasm是一个新生的技术，它提供了强大的内存隔离（通过沙盒）和接近原生的性能与具有更小的内存占用空间。wasm通过用户编写程序在不同的语言上，它们被编译成独立于平台的字节码。wasm运行时有能力利用多样的硬件硬件和软件技术去提供隔离和完成所需的资源分配。在这篇文章中，我们讨论现在serverless技术和他们相关的性能挑战，并强调利用wasm方式，在边缘实现高效无服务器计算的趋势。我们概述wasm的设计：一种新的基于Wasm的边缘无服务器执行框架，我们正在努力。所提供的设计精心分析和SLO驱动性能管理提供了一系列有趣的机会。最后，我们给出了aWsm的初始性能表征，其平均函数启动时间（12μs至30μs）及其用作函数的MiBench微基准[9]子集的经济内存占用（10s至100s KB）SERVERLESS在这部分我们描述了俩个现状，传统的serverless实现方法：VM和基于容器框架。以VM为基础的方法虚拟化利用软件从客户操作系统中抽象物理硬件。管理程序或者虚拟机监视器（VMM）通过创建和销毁VM和复用硬件资源以及在这些虚拟机之间多路复用硬件资源。基础实施即服务（IaaS）是一个云框架，他提供虚拟机，存储，网络和及其他计算资源为客户提供服务。这些客户只管理他们的操作系统，存储网络和应用，而不是Laas底层云硬件的基础设施。通过类似虚拟机的隔离是当前多租户硬件共享的标准，虚拟机在serverless函数执行中是重量级的。图示了serverless函数在虚拟机环境中的执行。函数的实例是其代码，内存（堆栈）的，语言运行时和依赖库组合（如图中黄色方框所示）。函数占用明显比提供VM的占用更小，其中包含了用户操作系统，虚拟硬件资源（先验地提供给VM，这不是功能所必须的）。图中（a）中所有的黄色方框构成了vm环境中的一个函数。serverless函数是临时和短暂的。它们通常用较小的内存占用。而虚拟机往往相反--需要超GB的内存，和显著的启动时间以及停止时间。此外，客户操作系统和系统管理程序之间的传输控制以及支持分层调度（图中（a）里橙色的盒子）的额外虚拟化开销也相当大。由于虚拟机的提供可能需要数十秒，serverless提供商使用创造性优化技术来更快地供应功能执行环境。通过维护不同的VM池vm的wasm池（已经实例化的VM），可以分配给新的租户和他的函数。动态的vm池，最近用于执行的函数，并保存为将来调用函数。微软Azure函数为客户提供了多个serverless托管计划，并可以选择在虚拟机中的函数。在基于VM的托管中，客户每小时都要为运行VM实例而收费-有些类似于传统的IaaS计费方式。亚马逊的Firecracker和Kata Containers等努力,提供了比传统虚拟机更轻量级以及启动时间显著更快的虚拟机。亚马逊的Firecracker是极简的虚拟机监视器，他使用了linux 内核为基础的虚拟机（KVM）去管理他的轻量级vm，这些虚拟机内存占用仅5MB，启动延迟约为125毫秒。它是领先的无服务器云平台AWS Lambda背后的使能技术之一。AWM-lambda有俩种serverless功能的隔离沙盒模型使用专用AWS EC2实例去隔离不同的AWS账户，同时个别函数调用会使用传统容器方法进行沙箱隔离。使用Firecracker 微虚拟机，去提供沙箱环境以执行不同函数调用。容器为基础的方法容器给用户应用的代码提供了标准的打包方式，配置，以及将所有依赖项合并到单个对象中。容器共享安装在服务器硬件上面的操作系统图中的（b）容器运行在资源隔离的进程中。容器是隔离的在底层系统使用了安全的方式比如linux chroot，cgroups和命名空间。linux容器，docker和windows 容器是不同的容器运行时，它将应用程序包含在具有专用文件系统的经常中，提供全部执行环境，同时在适当的情况下共享主机二进制文件和库。在适当的情况下共享热禁止文件和库。许多头部的云厂商，比如亚马逊，哥哥，微软，IBM和其他使用了这个技术实现Paas和最新的Faas。如（b）中所示，serverless函数执行在容器环境下。每个函数（黄色的盒子）都打包到一个容器镜像中，其中包含了运行库在内的依赖项。语言运行时和依赖库是不在多个函数之间或者多个容器沙盒中共享的，甚至不是在单个函数的多个容器之间共享的。从而影响每个函数的内存占用和函数延迟。在这个框架中，不同的功能和底层操作系统控制容器/功能调度之间共享绿色层。容器管理控制创建，销毁，启动会和暂停容器在serverless的环境中。在一些实例中，实现客户之间的强大隔离，这些容器托管在每个客户专用的VM中。我们相信现在的容器在实现低延迟功能并节约的利用有限的边缘资源这件事上是非常低效的。性能管理挑战启动延迟（即启动函数实例的延迟） 在faas平台，甚至对于相同的功能也有很大的差异，从几毫米到几秒钟，取决于多种因素。函数执行可能是一个热启动，重用VM和容器从以前的事件和相同的函数，或者是个冷启动，在新的VM容器开始，需要启动函数主机进程，等。当需要支持可预测的功能性能时，冷启动延迟是主要问题之一。此外，冷启动延迟依赖很多额外的因素：英语对函数进行编程的语言，分配的内存大小，代码量，库的数量类型以及他们的依赖关系，函数环境本身的配置，等。虽然这些因素中的许多都在开发人员的控制之下，并被考虑优化，唯一能做的只有让冷启动延迟有限的减少。下一个挑战问题是选择正确数量的资源（内存和算力）去执行用户的函数。请求正确的数量的计算量不总是可行的。举个例子，当前的AWS lambda，用户只能提供内存设置。最小分配的数量内存是128M，增量是64MB（最大内存为3GB）。一旦用户选择了内存大小，aws就会按比例分配CPU配额。函数运行时性能是否与内存设置成比例缩放？多项论文和实验表明，通常功能性能随内存大小而变化，但它经常表现出不一致的行为。如果你的函数需要更少的内存占用，但是你需要他运行的很快呢？当前的资源供应是不灵活的，低效的，并且导致一些不可预测的结果。许多当前的serverless计算平台，功能之间缺乏性能隔离，这使得它们的性能可能和预测的不那么一致。此外，功能性能取决于用于部署的功能实例的硬件类型。云基建通常是多种多样的，由于数据中心基础设施的升级，它们不一样的环境可能显著的影响在不同调用运行同一个函数的性能。为了实现高效的serverless边缘计算，serverless框架需要支持高性能的管理能力比如解析函数的性能和他们需要的资源（在他们依赖的硬件上）去支持可取的函数响应执行时间。保证可以预测函数性能有效率的使用硬件的多核智能调度提交的函数请求:分配和管理恰当数量的计算资源,可能会抢占已在系统中运行的函数,以实现指定的函数 SLO（服务级别目标），例如以软执行截止日期的形式。在下一章我们会概述我们的serverless设计方案以提供这些理想的能力。利用wasm在边缘进行serverless使用wasm进行serverless计算当今可行的基于云计算环境的serverless，在资源受限的边缘环境和提供低延迟的serverless函数计算中通常效率低下。在这个例子中我们描述了近代的趋势：使用wasm在serverless计算使函数更快的启动，调用处理更有效率，最小化内存消费，和提供最强大的安全。同时提供精细和准确的账单给客户。介绍wasmwasm是一个语言，架构，和平台无关的低级字节码旨在提供编译目标给编写的多种高级语言，比如c++，rust和一些部署在客户端和web服务器应用的语言。Wasm旨在作为JavaScript的另一个选择，用于web浏览器执行，同时提供强大的内存安全性，沙盒执行环境。wasm目标是接近原生的应用速度，通过广泛平台上可用的通用硬件功能。谷歌V8和Emscripten通过使用基于JavaScript虚拟机的沙箱和执行环境，将以Wasm支持的语言或WebAssembly Text Format]编写的程序编译到JavaScript解释器，在浏览器中启用Wasm执行。使用了jsvm的wasm一个serverless框架基于V8，Emscripten，和VM2展示了wasm在边缘进行serverless函数执行的能力。通过js后端框架展示了重要的wasm函数的启动延迟，整体函数明显较慢（俩倍到五倍于原生的时间）。这是由于js虚拟机后端和沙盒机制的开销和复杂性。serverless功能通常注册一次，并且每天都有数千次调用，因此使用jit汇编使用在每个函数调用的执行时间引入了额外的（显著的）开销。原生的wasm运行时：在采用wasm作为本地执行封面已经做出了极大的努力，因为它是各种高级语言编译的可移植目标。WASM标准不一定做出特定于web的假设，而且在标准化wasm系统接口在web之外运行wasm已经做了大量的工作。wasm的目标是创建系统接口，他使用wasm二进制去真正的独立于平台。（能够运行在不同的原生平台上）有许多Wasm运行时用于用不同语言编写的程序:所有这些都能使原生运行wasm程序。fastly（www.fastly.com）宣布了lucet，他提供了编译和运行原生执行于wasm应用，Lucet可以实例化wasm某块在50微妙以内，只需要几kb的内存开销。Fastly的Terrarium项目提供多语言，基于浏览器的编辑器和基于Lucet的部署平台。这些现存的系统演示了wasm运行实现serverless函数在轻量级沙盒中隔离和推送到边缘计算的能力，超越了现有rverless实现的限制之外。如图C所示，执行wasm函数使用原生wasm运行时。wasm函数使用了提前（AOT）编译技术去使用编译器和语言运行时影响平台软件和硬件机器提供强大的沙盒之间与沙盒内部的内存安全。AOT编译汇编到共享的对象显著提高了原生wasm函数的启动延迟，并实现了代码共享在不同的wasm调用中。应用管理者通常是分开的进程，他在系统上使用了多种基于wasm的函数运行时与隔离的经常。这些原生wasm运行时使在单个进程中支持一个函数的多个沙箱环境，相比于容器和虚拟机，它们的重量明显要轻很多。（c的黄色盒子）。本地语言运行时，运行库和os是在多个函数和沙盒中共享的。（图C中的绿色盒子）在这些现有的wasm一小时中，不同函数性能和执行特性是不受控制的。系统接口提供了运算wasm一小时如图c，使得serverless函数可以去影响操作系统的功能。然而，需要注意的是操作系统程序可以控制（c中的橙色盒子）作为独立进程执行的功能，因此也会影响到不同函数的性能表现。新的wasm框架和他在serverless的性能管理方法awsm是原生的wasm编译器和运行时框架。aWsm编译器是基于rust和使用LLVM的中间表示形式来使用基于硬件或软件的沙箱隔离检查。aWSM运行时利用AoT编译的wasm隔离对象在单进程内部使用多函数和他们的调用。如图d展示了wasm一小时和他的沙盒机器。多函数（例如A.so和B.so）是动态加载到wasm运行时。如图d的黄色盒子是一个不同的函数实例在分开的轻量级的wasm沙盒中。语言运行时，库依赖，和系统功能是隔离的可用于aWsm运行时中运行的不同系统。运行时绕过linux内核完全控制执行特性和不同的函数调用。异步的、基于事件的I/O用于使沙盒能够等待I/O完成，同时允许不同的沙盒有效利用CPU。函数执行控制在aWSM中：Linux 内核的复杂性随时间呈二次函数增长，它对于应用性能和系统的安全性有重大的影响（增加攻击面）。IOT设备的普遍集成使得边缘设备的流量明显更高，并且容易受到边缘处理延迟的影响。linux内核交互中的开销显著影响了serverless计算和端到端的延迟于每一个调用。这就形成了aWsm运行时采取极端方法的强烈动机和提供有资源隔离和调度工具在管理和维护函数执行。提供请求和函数执行的调度以及基于沙盒的隔离有很多好处。其中一些好处包括：弹性的调度策略：在调度的沙盒中运行时具有计算能力，aWsm运行时调度是可配置的（独立于底层的系统调度器）以促进对各种重要性和关键性需求不同的时间保证。橙色的盒子SCHED如图d是区分aWsm运行时不同于a，b，c，其中OS调度器控制着函数沙盒的执行属性。快速的沙箱切换：aWsm的沙盒机制允许用户态下，进行快速的上下文切换，从而去掉了系统的调用开销。例如，与 M:1 用户态线程类似,这种沙箱切换是协作式的。抢占控制权：运行时调度程序利用POSIX信号来控制沙盒中的抢占执行。这使得运行时系统能够对函数的执行过程进行细粒度的控制。在多种函数和他们的调用中提供精确的统计和性能概述。系统级别的软中断（如SIGALRM和SIGUSR1）来实现aWsm运行时的抢占式调度。多核心执行控制：资源共享和锁定在linux内核可能引起明显的缓存一致性这些和处理器中断（IPI）干扰，在多核的执行环境下，可能直接影响函数的执行延迟。aWsm运行时划分了函数跨核心的执行负载，从而限制了共享资源和提供的更好的可预测特性。管理控制：一个共享的aWsm运行时获得了性能特征和不同的函数执行性能分析（图d中的profiling框）我们认为,性能分析和对多个函数执行属性的控制能力,将使得WebAssembly运行时能够为边缘功能提供强有力的SLO驱动的延迟和可预测性保证。最初的wasm性能评估：启动时间和内存占用评估函数启动的性能和内存占用特征在aWsm运行中，我们使用了MiBenc 基准测试套件中的一个子集以及一个空函数作为测试案例。空函数是简单的c程序，从main中返回同时没有其他库依赖。基准测试应用使用了serverless函数代理，请求和响应没有外部接。我们从具有相同输入数据集的不同类别中随机选择MiBench基准测试的子集。basicmath测试使用包含固定常数集的输入数据进行简单的数学计算。bitcount测试位运算能力通过计算整数数组中的位数来测试位运算能力，输入数据是一个整数数组，其中0和1的数量是相等的cr3 执行32位的循环冗余检查（CRC）在文件中。输入的文件是大型语音文件qsort 排序是大String数组进行升序排序使用快排算法，他的输入数据包含了一个小的单词表sha基准测试是SHA安全哈希算法，他给输入的信息产生160bit的信息摘要。输入的数据是大的ascii编码文本文件的一篇文章。stringsearch 基准测试使用不区分大小写的比较算法查询给定的单词在短语中的出现。susan基准测试是一个图像识别软件包,它是为了识别脑部磁共振成像(MRI)中的角点和边缘而开发的。输入的数据是复杂的图片。实验环境使用了 Intel i5-5200U CPU运行在2.20GHz和8G物理内存单核上（所有的实验都是在一个核心上进行的）在不同基准测试的上千次迭代中,对Awsm中的内存占用和函数调用启动时间的测量表I显示了不同基准测试的平均启动成本以及在aSM中编译和执行的这些基准测试的内存占用。每个基准测试的启动延迟是创建沙盒花费的时间，分配固定大小的堆栈（32mb），线性的内存（最初为16mb，最大是4gb），和填充线性的内存。函数的沙盒是专用的栈内存他保留了一个连续的虚拟地址空间进行最大4GB的线性内存使用mmap。线性存储器最初被分配16MB，并在保留的连续虚拟地址空间内扩展。讨论：如表格所示内存占用在null函数测试是18KB，所选基准测试的内存占用范围在90KB到180KB之间。这反映出和需要所有必要依赖关系的VM容器执行环境的大小相比，无服务功能的内存占用要小很多。null函数的平均启动延迟在10微妙，对于其他的基准测试在20微妙-30微妙内。 除了平均启动延迟之外，表中还显示了95%启动时间，对于其他的基准测试，落在22-39微妙之间。这个狭窄的延迟范围反应了aWsmserverless框架提供的功能中的更高的性能可预测性。在边缘环境中，aWsm运行时负载是预定义的函数集合和他们的启动时库依赖。所以函数执行只将只需要一次冷启动的延迟：开始后第一次aWsm运行时。冷启动延迟在wasm函数中显著的更短（0.2ms-0.4ms）于传统的虚拟机容器冷启动。这些数字反映出执行aWsm运行时的性能。我们认为 aWsm 运行时和可定制的性能分析，建模和智能调度，将为边缘的高效无服务器执行提供一个有吸引力的实现选项。结语和将来的工作serverless计算是刚出现的一个新引人注目的计算空间在边缘的IOT数据处理。在这篇论文中，我们讨论了最先进的serverless平台，它们基于虚拟机和容器，并列出与在Edge上运行这些解决方案相关的挑战数量。然后我们分析了基于最新的基于wasm的serverless实现。它提议了另一个方式去运行serverless应用以接近原生的速度，同时有了低内存占用和最佳的执行时间。我们概述我们的aWsm设计--一个新兴的以awsm为基础的用于在边缘进行serverless执行。aWsm绕过linux内核和使用了完全控制函数执行和他们的管理：包含分析，调度和资源分配。所提出的设计为用户函数的精心设计和易于定制的SLO驱动的性能管理提供了一系列有趣的机会。在未来的工作中我们计划进一步使用这些机会构建一个分析模块来跟踪和分析函数的性能，尝试在不同的调度策略去支持SLO为基础的函数类，提供客户接口以进行各种函数调度和许可控制策略，利用ARM硬件功能提高内存--函数沙盒的安全性及其执行性能。
PID算法PID控制算法原理（抛弃公式，从本质上真正理解PID控制） - 知乎 (zhihu.com)简介期望值是什么，与控制器没有关系，而是与物理对象有关。更直接的原因是你使用了这个物理量而做的反馈。期望值需要与反馈做差，所以期望值采用有了意义。期望值是什么取决于反馈值是什么。PID控制应该算是非常广泛的控制算法了。小的控制一个元件的温度，大到控制无人机的飞行姿态和飞行速度等，都可以使用PID控制。我们从原理上来理解PID控制（proportion integration differention）其实就是指比例，积分，微分控制。先把图片和公式摆出来，看不懂没关系。比例控制算法我们先说PID中最简单的比例控制，抛开其他两个不谈。还是用一个经典的例子吧。假设我有一个水缸，最终的控制目的是要保证水缸里的水位永远的维持在1米的高度。假设初始时刻，水缸里的水位是0.2米，那么当前时刻的水位和目标水位之间是存在一个误差的error，且error为0.8.这个时候，假设旁边站着一个人，这个人通过往缸里加水的方式来控制水位。如果单纯的用比例控制算法，就是指加入的水量u和误差error是成正比的。即u=kp*error假设kp取0.5（KP代表比例增益Proportional Gain）那么t=1时（表示第一次加水，也就是第一次对系统施加控制），那么u=0.5*0.8=0.4，所以这一次加入的水量会在使水位上升在0.2的基础上上升0.4，达到0.6.接着，t=2时刻（第2次施加控制），当前水位是0.6，所以error是0.4。u=0.5*0.4=0.2，会使水位再次上升0.2，达到0.8.如此这么循环下去，就是比例控制算法的运行方法。可以看到，最终水位会达到我们需要的1米。但是，单单比例控制存在着一些不足，其中一点就是稳态误差！像上述的例子，根据kp取值不同，系统最后都会达到1米，只不过kp大了到达的快，kp小了到达的慢一些。不会有稳态误差。但是，考虑另外一种情况，假设这个水缸在加水的过程中，存在漏水的情况，假设每次加水的过程，都会漏掉0.1米高度的水。仍然假设kp取0.5，那么会存在着某种情况，假设经过几次加水，水缸中的水位到0.8时，水位将不会再变换！！！因为，水位为0.8，则误差error=0.2. 所以每次往水缸中加水的量为u=0.5*0.2=0.1.同时，每次加水，缸里又会流出去0.1米的水！！！加入的水和流出的水相抵消，水位将不再变化！！也就是说，我的目标是1米，但是最后系统达到0.8米的水位就不再变化了，且系统已经达到稳定。由此产生的误差就是稳态误差了。（在实际情况中，这种类似水缸漏水的情况往往更加常见，比如控制汽车运动，摩擦阻力就相当于是“漏水”，控制机械臂、无人机的飞行，各类阻力和消耗都可以理解为本例中的“漏水”）所以，单独的比例控制，在很多时候并不能满足要求。积分控制算法还是用上面的例子，如果仅仅用比例，可以发现存在稳态误差，最后的水位就卡在0.8了。于是在控制中，我们再引入了一个分量，该分量和误差的积分是正比关系。所以+积分控制算法为u=kp*error+ ki∗∫ error还是用上面的例子来说明，第一次的误差error是0.8，第二次的误差是0.4，至此，误差的积分（离散情况下积分其实就是做累加），∫error=0.8+0.4=1.2, 。这个时候的控制量，除了比例那一部分，还有一部分就是一个系数ki乘以这个积分项。由于这个积分项会讲前面若干次的误差进行累计，所以可以很好的消除稳态误差（假设在仅有比例项的情况下，系统卡在稳态误差了，即上例中的0.8，由于加入了积分项的存在，会让输入增大，从而使得水缸的水位可以大于0.8，渐渐到达目标的1.0.）这就是积分项的作用。微分控制算法换一个另外的例子，考虑刹车情况。平稳的驾驶车辆，当发现前面有红灯时，为了使得行车平稳，基本上提前几十米就放松油门并踩刹车了。当车辆离停车线非常近的时候，则使劲踩刹车，使车辆停下来。整个过程可以看做一个加入微分的控制策略。.微分，说白了在离散情况下，就是error的差值，就是t时刻和t-1时刻error的差，即u=kd*（error（t）-error（t-1）），其中的kd是一个系数项。可以看到，在刹车过程中，因为error是越来越小的，所以这个微分控制项一定是负数，在控制中加入一个负数项，他存在的作用就是为了防止汽车由于刹车不及时而闯过了线。从常识上可以理解，越是靠近停车线，越是应该注意踩刹车，不能让车过线，所以这个微分项的作用，就可以理解为刹车，当车离停车线很近并且车速还很快时，这个微分项的绝对值（实际上是一个负数）就会很大，从而表示应该用力踩刹车才能让车停下来。切换到上面给水缸加水的例子，就是当发现水缸里的水快要接近1的时候，加入微分项，可以防止给水缸里的水加到超过1米的高度，说白了就是减少控制过程中的震荡。现在在回头看这个公式，就很清楚了括号内第一项是比例项，第二项是积分项，第三项是微分项，前面仅仅是一个系数。很多情况下，仅仅需要在离散的时候使用，则控制可以化为每一项前面都有系数，这些系数都是需要实验中去尝试然后确定的，为了方便起见，将这些系数进行统一一下：这样看就清晰很多了，且比例，微分，积分每个项前面都有一个系数，且离散化的公式，很适合编程实现。讲到这里，PID的原理和方法就说完了，剩下的就是实践了。在真正的工程实践中，最难的是如果确定三个项的系数，这就需要大量的实验以及经验来决定了。通过不断的尝试和正确的思考，就能选取合适的系数，实现优良的控制器。
无聊netty写了个redis首先是redis的服务端,除了访问的进程外还会存储各个进程发上来的一个id方便进行下发这是速度测试package org.example.netty.redis; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.string.LineEncoder; import io.netty.handler.codec.string.LineSeparator; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.concurrent.LinkedBlockingDeque; /** * @Author: suiyi * @Date: 2023/11/30 20:21 */ public class NettyServerRedis { static HashMap<String, String> map = new HashMap<>(); static LinkedBlockingDeque<Action> workQueue = new LinkedBlockingDeque<>(); public static void main(String[] args) throws InterruptedException { new Thread(() -> { try { consumerWorkQueue(); } catch (InterruptedException e) { throw new RuntimeException(e); } }).start(); ServerBootstrap serverBootstrap = new ServerBootstrap(); NioEventLoopGroup boos = new NioEventLoopGroup(); NioEventLoopGroup worker = new NioEventLoopGroup(); serverBootstrap .childOption(ChannelOption.SO_KEEPALIVE, true) .group(boos, worker) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<NioSocketChannel>() { protected void initChannel(NioSocketChannel ch) { ByteBuf buffer = Unpooled.copiedBuffer("\0".getBytes()); ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG)); ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, buffer)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new LineEncoder(new LineSeparator("\0"), StandardCharsets.UTF_8)); ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { // System.out.println(msg); doAction(msg, ctx.channel()); } }); } }) .bind(8000).sync(); } // id 操作内容 key value public static void doAction(String action, Channel channel) { String[] s = action.split(" "); try { if (s.length < 3) { throw new IllegalStateException("命令格式错误"); } if (s[1].equals(ActionEnum.PUT.getName())) { if (s.length != 4) { throw new IllegalStateException("命令格式错误"); } workQueue.addLast(new Action(ActionEnum.PUT.getValue(), s[0], s[2], s[3], channel)); } else if (s[1].equals(ActionEnum.GET.getName())) { workQueue.addLast(new Action(ActionEnum.GET.getValue(), s[0], s[2], channel)); } else if (s[1].equals(ActionEnum.REMOVE.getName())) { workQueue.addLast(new Action(ActionEnum.REMOVE.getValue(), s[0], s[2], channel)); } } catch (Exception e) { channel.writeAndFlush(Long.valueOf(s[0]) + " " + "命令错误!"); } } public static void consumerWorkQueue() throws InterruptedException { while (true) { String msg = null; Action action = workQueue.takeLast(); if (action.getType() == ActionEnum.GET.getValue()) { String value = map.get(action.getKey()); msg = value; } else if (action.getType() == ActionEnum.PUT.getValue()) { map.put(action.getKey(), action.getValue()); msg = "1"; } else if (action.getType() == ActionEnum.REMOVE.getValue()) { msg = map.remove(action.getKey()); // msg = remove ? "1" : "0"; } action.getChannel().writeAndFlush(action.getId() + " " + msg); } } } 客户端代码,这里顺便写了个速度测试demo,自测能做到10wQPSpackage org.example.netty.redis; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.string.LineEncoder; import io.netty.handler.codec.string.LineSeparator; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.Promise; import lombok.SneakyThrows; import java.nio.charset.StandardCharsets; import java.sql.Time; import java.util.Random; import java.util.Scanner; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; /** * @Author: suiyi * @Date: 2023/12/1 20:52 */ public class NettyClientRedis2 { AtomicLong atomicLong = new AtomicLong(0); Channel channel = null; EventLoop next; ConcurrentHashMap<Long, Promise<String>> promiseMap = new ConcurrentHashMap<>(); public static void main(String[] args) throws InterruptedException { NettyClientRedis2[] netty = new NettyClientRedis2[100]; for (int i = 0; i < 100; i++) { NettyClientRedis2 nettyClient = new NettyClientRedis2(); nettyClient.start(); netty[i] = nettyClient; } ExecutorService executorService = Executors.newFixedThreadPool(33); CountDownLatch countDownLatch = new CountDownLatch(300000); Random random = new Random(); long l = System.currentTimeMillis(); for (int j = 0; j < 100; j++) { for (int i = 0; i < 1000; i++) { int r1 = random.nextInt(10000000); int r2 = random.nextInt(10000000); int r3 = random.nextInt(10000000); int r4 = random.nextInt(10000000); int finalJ = j; executorService.execute(() -> { netty[finalJ].get(Integer.toString(r3)); countDownLatch.countDown(); }); executorService.execute(() -> { netty[finalJ].put(Integer.toString(r1), Integer.toString(r2)); countDownLatch.countDown(); }); executorService.execute(() -> { netty[finalJ].remove(Integer.toString(r4)); countDownLatch.countDown(); }); } } countDownLatch.await(); System.out.println(System.currentTimeMillis() - l); // put("123", "suiyi"); // get("123"); // processUserInput(); } public void start() throws InterruptedException { Bootstrap bootstrap = new Bootstrap(); NioEventLoopGroup group = new NioEventLoopGroup(); next = group.next(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) { ByteBuf buffer = Unpooled.copiedBuffer("\0".getBytes()); ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG)); ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, buffer)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new LineEncoder(new LineSeparator("\0"), StandardCharsets.UTF_8)); ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { analysisCommand(msg); } }); } }); // Channel channel = bootstrap.connect("127.0.0.1", 8000).sync().channel(); } public void analysisCommand(String command) { String[] commands = command.split(" "); if (commands.length != 2) { throw new IllegalStateException("命令格式错误"); } Promise<String> promise = promiseMap.get(Long.valueOf(commands[0])); try { promise.setSuccess(commands[1]); } catch (Exception e) { e.printStackTrace(); } } public void processUserInput() { while (true) { Scanner scanner = new Scanner(System.in); String input = scanner.nextLine(); // 读取一行输入 System.out.println(sendCommand(input)); } } public String get(String key) { return sendCommand("get " + key); } public String remove(String key) { return sendCommand("remove " + key); } public void put(String key, String value) { sendCommand("put " + key + " " + value); } @SneakyThrows private String sendCommand(String str) { long id = atomicLong.getAndIncrement(); DefaultPromise<String> promise = new DefaultPromise<>(next); promiseMap.put(id, promise); channel.writeAndFlush(id + " " + str); return promise.get(); } }然后是枚举类package org.example.netty.redis; import lombok.Getter; /** * @Author: suiyi * @Date: 2023/12/1 19:37 */ @Getter public enum ActionEnum { GET("get", 1), PUT("put", 2) ,REMOVE("remove", 3);; final private String name; final private int value; ActionEnum(String name, int value) { this.name = name; this.value = value; } } 最后是一个Action类,存储用户的动作package org.example.netty.redis; import apache.rocketmq.v2.Message; import io.netty.channel.Channel; import lombok.Data; /** * @Author: suiyi * @Date: 2023/12/1 19:33 */ @Data public class Action { public Action(int type, String id, String key, String value, Channel channel) { this.id = Long.valueOf(id); this.type = type; this.key = key; this.value = value; this.channel = channel; } public Action(int type, String id, String key, Channel channel) { this.id = Long.valueOf(id); this.type = type; this.key = key; this.channel = channel; } private long id; private int type; private String key; private String value; private Channel channel; } 经过测试30w个，put，remove，get操作居然用了2465redis我用了3801不知道为啥我写的demo比redis快(当然是不带任何操作的)这里附上对redis进行测试的demopackage org.example.netty.redis; import org.redisson.Redisson; import org.redisson.api.RBucket; import org.redisson.api.RMap; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import java.util.Map; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * @Author: suiyi * @Date: 2023/12/4 21:30 */ public class TestRedis { public static void main(String[] args) throws InterruptedException { Map<String,String>[] netty = new Map[100]; for (int i = 0; i < 100; i++) { Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379"); RedissonClient redissonClient = Redisson.create(config); RMap<String, String> map = redissonClient.getMap("mapKey"); netty[i] = map; } ExecutorService executorService = Executors.newFixedThreadPool(33); CountDownLatch countDownLatch = new CountDownLatch(300000); Random random = new Random(); long l = System.currentTimeMillis(); for (int j = 0; j < 100; j++) { for (int i = 0; i < 1000; i++) { int r1 = random.nextInt(10000); int r2 = random.nextInt(10000); int r3 = random.nextInt(10000); int r4 = random.nextInt(10000); int finalJ = j; executorService.execute(() -> { netty[finalJ].get(Integer.toString(r3)); countDownLatch.countDown(); }); executorService.execute(() -> { netty[finalJ].put(Integer.toString(r1), Integer.toString(r2)); countDownLatch.countDown(); }); executorService.execute(() -> { netty[finalJ].remove(Integer.toString(r4)); countDownLatch.countDown(); }); } } countDownLatch.await(); System.out.println(System.currentTimeMillis() - l); } }
java文本转语音下载jar包https://github.com/freemansoft/jacob-project/releases 解压,将jacob-1.18-xxx.dll相应放到项目使用的JAVA_HOMEbin下 java代码public class Test { public static ActiveXComponent sap = new ActiveXComponent("Sapi.SpVoice"); public static Dispatch sapo=sap.getObject(); public static void main(String[] args) { String str ="测试声音是否正常"; readText(str); } public static void readText(String queue) { try { //声音大小 sap.setProperty("Volume", new Variant(100)); //语速 sap.setProperty("Rate",new Variant(-2)); //发声 Dispatch.call(sapo,"Speak",new Variant(queue)); } catch (Exception e) { e.printStackTrace(); }finally{ if(sap==null){ sap = new ActiveXComponent("Sapi.SpVoice"); } if(sapo==null){ sapo = sap.getObject(); } } } }转载自:https://www.cnblogs.com/luozili/p/16778142.html
【ban】 <玩家名称>封禁拥有玩家名称名称的玩家，列入黑名单，令其永远无法登进此服务器。 注意：封禁 操作会覆盖之前的白名单操作。【ban-ip】封禁一个特定的IP，列入黑名单，并从此拒绝任何从该地址而来的连接请求。【banlist】 [ips]显示封禁名单。若要列出封禁的IP，请使用banlist ips指令。【deop】 <玩家名称>撤销一个玩家的管理员身份。【kick】 <玩家名称>强制断开玩家名称与服务器的连接。【list】显示当前*的玩家列表。【op】 <玩家名称>授予 玩家名称 在服务器里的管理员职位。【pardon】 <玩家名称>将 玩家名称 移出黑名单，允许他再次连接到服务器。【pardon-ip】将 ip地址 从IP黑名单移除，允许该IP连接到服务器。【save-all】强制将服务器尚未储存地变更写入存档。【save-off】取消服务器对地图存档的写入。所有的更改都会暂时进入等待队列。【save-on】允许服务器写入地图存档。这项是默认设定。【setidletimeout】 <闲置时限(分钟)>会自动将挂机过久的玩家踢下线。【stop】正常关闭服务器。【whitelist】 <玩家名称>将玩家名称加进或移出白名单。【whitelist 】显示所有在白名单上的玩家。【whitelist】启用/停用服务器的白名单机制。 注意： 服务器的OP总是能够在白名单打开时进入服务 器，无论他们在与不在白名单上。【whitelist reload】重新从硬盘读取white-list.txt上的白名单。(