新 XDR 显示器完全可以做成 iMac neo
这么强的配置只是用来跑色彩管理,tb4 连接,摄像头驱动等底层功能,不知道该说是杀鸡用牛刀呢还是三哥软件写的太张扬
xiaohack博客专注前沿科技动态与实用技术干货分享,涵盖 AI 代理、大模型应用、编程工具、文档解析、SEO 实战、自动化部署等内容,提供开源项目教程、科技资讯日报、工具使用指南,助力开发者、AI 爱好者获取前沿技术与实战经验。
坐标宁波
现在用的手机电信套餐 91 元含 4 个副卡 无限但超过 40G 降速的流量 300M 的宽带
不知道这个套餐目前还算不算划算
前两年当地营业厅给我提到 500M
这几天给降到 300M 了 去营业厅说活动没了
有啥方法能提高点宽带?
每周一个小惊喜,mx anywhere 3s 已经送出 2 个,本周回帖抽 JBL GO4 ,踏春挂在背包上很拉风。。。

免五低佣低两融开户,找老倔驴,靠谱! 开户选择多还靠谱,聚合几十家券商优惠,连接 100+家营业部,已帮助 2000 人+。
开户点这里: https://jue.lv/kh/V2/202603b (10+券商可选,开户有礼!)
另外,渣打远程开港卡(存刀年化 3.7%,还能远程开港卡),港美 FX 可选;
(低门槛最近有点拉跨,需要 2 万低门槛又股票免五只能所以说有机会能开赶紧开)

友情提醒:不因奖品而参与股市,得不偿失!
项目里有一些 api key,是绑定信用卡计费的,如果用中转站是不是有风险泄露?可以避免吗?用的 Claude Code
最近小伙伴在使用龙虾 openclaw 执行一些搜索网络资讯的服务时,发现被限制了频率。 这个问题一方面可能是你对接的大模型的套餐的问题,就是超出套餐的频率了,比如最近比较多的一些 coding plan 套餐,一般都是 5 个小时 1200 次,超过了就会限速。 另外一方面是因为 openclaw 内置的 web 工具有两个 所以到这里你就知道为什么你的龙虾搜索网络资讯时,为什么不好用了吧? 可以使用 Tavily Web 替代方案,它针对登录用户每月提供了 1000 Credits ,基本可以够普通用户体验使用。 注册和登录 然后获取你的专属的key。 然后回到你的小龙虾上,这样告诉它 上面的 xxx 需要转换成你的 key image-20260311191914124 根据提示做回应即可,还是比较智能的。 现在你可以继续使用联网的搜索服务了。 万少建立了一个专门讨论小龙虾OpenClaw的群,大家可以互相交流和分享关于小龙虾生态的资讯消息。 关注我,持续分享鸿蒙开发 + AI 提效的实战技巧。为什么你的龙虾 openclaw 搜索网络资讯的技能不好用
前言



解决方案

获取 key
tvly-dxxxxxxxx

使用和配置
帮我安装 Tavily Web 这个技能,并且设置它为你默认的搜索技能,并且配置 Tavily Web 的 key: xxxxxxxxx


完工

总结
点赞 + 关注 + 收藏 = 学会了 CoPaw 是阿里云通义团队于2026年2月14日推出的个人智能助理,支持本地与云端双模式部署,并于2026年2月28日开源。它不仅支持多种主流大模型(LLM),最核心的卖点在于其强大的扩展性,支持 MCP(Model Context Protocol)、自定义工具、技能,并能通过“频道”功能无缝对接钉钉、飞书、QQ 等平台,是打造私有 AI 自动化的利器。 本次教程以 飞牛 NAS 为例进行演示,其他品牌 NAS(如群晖、绿联等)操作逻辑基本一致。 打开 NAS 的「文件管理」,找到 docker 文件夹,在其内部新建一个名为 再在 打开 NAS 的「Docker」应用,切换到 「Compose」 面板,点击“新增项目”。 在新建的 docker-compose.yml 中,复制粘贴以下代码,保存即可: 点击保存并等待容器构建完成。在浏览器访问 打开 CoPaw 第一件事当然是把界面设置成中文啦。 点开右上角菜单可以切换成「简体中文」。 CoPaw 作为一款个人助理工具,肯定得给它配个大模型。 我以月之暗面(Kimi)为例,你可以自行选择。 点击左侧菜单栏的 「模型」 -> 「添加提供商」,填入相关配置信息。kimi 的 Base URL 是 `https://api.moonshot.cn/v1。请根据你使用的提供商来填。 新增完“提供商”后,在「模型」页面往下滑动就可以看到刚刚新增的 kimi 了。 点击 kimi 卡片的「设置」按钮,配置一个 API 密钥(API Key,请到你使用的模型提供商那里申请),配置完后点击一下「测试连接」按钮,如果页面顶部出现 连通之后点击「保存」。 连通之后,点击 kimi 卡片的「模型」按钮,配置一个模型。我用的是 同样,添加模型后点击一下「测试连接」按钮,页面顶部出现 此时回到「模型」页面的顶部,配置一下LLM。 模型提供商选择刚刚创建的 回到首页的「聊天」页面就可以开始聊天了。 但只能聊天就不是AI助理了。 在左侧菜单栏可以看到 CoPaw 可以配置技能、工具、MCP,在「频道」面板还能接入钉钉、飞书、QQ 等工具。也就是类似小龙虾的玩法了。 至于 CoPaw 能做什么,请参考 CoPaw 官方文档(https://copaw.agentscope.io/docs/intro)或者现在网上讨论得热火朝天的小龙虾(OpenClaw)。 既然聊到 OpenClaw 了,那 CoPaw 和 OpenClaw 又有什么区别呢? 详情请看 CoPaw 官方文档的介绍:https://copaw.agentscope.io/docs/comparison 以上就是本文的全部内容啦,你有好玩的镜像推荐吗?欢迎在评论区留言讨论! 想了解更多NAS玩法记得关注《NAS邪修》👏 往期推荐: 点赞 + 关注 + 收藏 = 学会了💡整理了一个 NAS 专属玩法专栏,感兴趣的工友可以戳这里关注 👉 《NAS邪修》

copaw 的文件夹。copaw 里创建一个 data 文件夹。
copawdocker/copaw 这个文件夹
services:
copaw:
image: agentscope/copaw:latest
container_name: copaw
ports:
- 6688:8088 # 6688 可根据本地端口占用情况自定义
volumes:
- ./data:/app/working
restart: unless-stoppedNAS_IP:6688 即可进入 CoPaw 界面。


Connection successful 提示证明连通了~
kimi-k2.5
Connection successful 证明可以使用该模型了。
kimi,模型选择刚刚配置的 kimi-k2.5,然后点击一下右侧的「保存」按钮。



公司是软件,项目多,三四线城市,招不到人,每天有做不完的事情,项目群多达 500 多个,看不完的消息,做不完的需求,人到中年,又不好轻易辞职,真纠结,上有老下有小的,你们呢?
我个人观点是,经济下滑只是一个方面,确实没有那么多需求要做,大量公司有减员的需求,但是 AI 对我们开发者的冲击远比很多人想象的要大。
在过去没有 AI 的时代,确实没有当前这种效率,为了压缩工期,只能加人,然后加一个人,增加更多的沟通成本,反而降低了整体效率,这一点在人月神话中早就指出,这就是焦油坑战术,最后怎么都摆脱不了,只能反复延期。
我在过去的工作中,光是跟前端对接,不知道浪费了多少口水,写一堆文档,对方看也不想看,当面讲一堆接口调用说明,无论多么细致,最后联调的时候发现,最担心的问题还是会发生,前端调用你的接口该出问题,还是会出问题,光是沟通就要耗费大量的时间,现在大量的页面都不要人来写了,opus 写好直接对接后端,我不知道单独设立前端这个工种的意义,除了降低开发效率,增加沟通成本之外。
而且我的领导还跟我们说现在很难做到像素级别还原,很多人还是不能替代的,但是从成本的角度来讲,如果 AI 写的页面只有人类开发者的 30%的成本,老板可能觉得页面有点错误也是可以接受的,毕竟页面大部分时候并不会对业务核心造成致命影响。
而且前端目前出了小问题,后端自己修修补补就好了,实在搞不定,留个前端专家擦兜底即可,公司留那么多不懂业务流程的前端开发干什么,我真的为前端这个独立工种感到深深担忧。
从后端来看,大量的简单的 CRUD ,已经没有任何价值,后端的出路在于业务系统的整体分析,跟模块拆分,当前 LLM 在大量上下文下还是容易产生幻觉,未来的方向肯定是往细分化模块去拆,人类开发者提供接口定义说明与规格,让每一个小的模块由 AI 来完成,最后由人类开发者来审核单测,并最终参与代码模块集成,而开发写代码本身这个工作,越来月没有价值。
后端应该从产品、架构、代码管理者的角度去思考问题,如何让项目更容易让 AI 来完成,核心工作绝对不再是搞定摸个特定模块的编码,这样的技能没有任何价值。
而且目前对于中小团队来讲,AI 全栈、全流程化是非常有吸引力的,一个人既是产品、又是测试、开发、运维的超级个体。
在当前 AI 的时代,这些技能并不会对个人造成学习负担,过去我们认为全干工程师,什么都干不好,但是今天情况发生了颠覆性的变化,LLM 里面蕴藏着大量人类开发者十数年的经验,只要运用得当,一个人当一个多面手根本不是问题。
最后,我觉得过多的人反而是一种负担,3 个老手+高效的 CodeAgent ,实现外科手术式精英开发团队干翻 20 个人的传统开发团队根本不是问题,有 CodeAgent 的今天,整个开发团队人数太多真的是一种负担。
你带个新人,光是讲我们的 git flow 就要费半天的功夫,我写几行提示词跟 SKILLS ,Agent 给我把 git flow 流程干的明明白白。
资深开发者在关键节点审查一下即可,为什么要招实习生呢?大量的初级开发者根本没有存在的必要。
这一点在北美招聘市场已经有体现了,大量公司只要成熟资深的开发者,而初级开发者的招募动作基本停滞。
在 例如: 过去这类问题通常有 3 种解法: 这就是为什么源生成器看起来像“高级编译器功能”,但实际项目里它非常务实。 它做的事情可以简化成: 这意味着几个关键点: 所以你可以把它理解成: 因为它解决的是一类很典型、很现实的问题: 几个非常典型的场景: 一句话总结: 这几个东西经常被混在一起。 如果你的需求是“运行时再决定做什么”,反射仍然有价值。 如果你的需求是“编译时就知道规则,想生成固定代码”,源生成器通常更合适。 两者都和“代码生成”有关,但时间点完全不同。 这带来的优势很直接: 如果只记一条主线,记这个就够了: 源生成器能拿到的核心信息包括: 这些信息足够你回答很多问题: 然后你就可以决定生成什么代码。 这是今天写源生成器最重要的一个区分。 接口大致长这样: 它的特点: 接口大致长这样: 它的特点: 今天的务实建议非常明确: 先看最小版本,这样最容易建立直觉。 通常创建一个类库项目: 项目文件可以这样配置: 之所以通常选 这里最关键的一行是: 它的意思不是“写磁盘文件”,而是“把这段源码加入当前编译”。 业务项目里引用生成器时,重点不是普通程序集引用,而是作为 analyzer 引入: 然后你就可以在业务代码里直接使用生成出来的类型: 这是非常常见的模式。 例如你会看到用户代码写成: 生成器再额外生成: 之所以用 这也是大多数源生成器设计的基本套路。 真正有意义的生成器,当然不是每次都无脑输出一个固定类,而是: 例如:扫描带 用户代码: 生成器逻辑大致要做这些事: 这时候,语法树和语义模型就派上用场了。 先看 这里的逻辑可以拆成两步: 这是传统生成器最经典的写法。 因为真实项目里,编译性能很重要。 如果每次小改一个文件,你的生成器都全量重新扫描和全量重新拼接代码,IDE 体验会明显变差。 增量生成器的核心思路是: 拆成一条增量数据管道。 只有输入变化的部分,才会重新计算。 虽然这个例子很简单,但它已经体现了增量生成器的基本风格: 下面这个示例会稍微更真实一些。 目标是: 这个例子虽然简单,但已经足够说明大部分业务生成器的核心套路: 很多人第一次看增量生成器,不是卡在“概念”,而是卡在这几个 API 名字: 它们看起来很像编译器黑话,但其实这段代码做的事情非常朴素: 可以把整段逻辑先压缩成一条流水线: 下面按执行顺序逐行拆。 作用分别是: 这个特性告诉 Roslyn: 没有这个标记,编译器不会把它当作生成器。 这里有两个重点: 这意味着它不是每次编译都用“全量扫描 -> 全量生成”的思路,而是把处理过程拆成若干增量步骤,只在输入变化时重新计算对应部分。 增量生成器只需要实现这一个方法。 但这里要注意,它不是“直接开始生成代码”的方法,而更像是: 也就是说, 这行可以理解成: 这里的 这一行是在做“粗筛”。 它的意思是: 注意,这里并没有判断: 这里只是先把完全不可能相关的节点排掉,比如: 这样做的原因很简单: 再看这个 它表示这个 lambda 不捕获外部变量,在增量生成器里这样写更常见,也更利于性能。 第二个参数 这一步是在做“从语法节点到语义符号”的转换。 前面的 但这里才真正把它变成可以深入分析的类型符号。 先看第一行: 因为前面的 你可以把 它知道: 但它还不是“真正的类型定义对象”。 再看第二行: 这行非常关键。 它做的事是: 为什么一定要转成 因为很多真正重要的信息,语法节点本身并不适合直接判断,例如: 这些通常都更适合从 所以到这一步,生成器已经从“看代码长相”升级到“理解代码语义”了。 最后: 意味着后续增量管道里流动的,不再是类语法节点,而是类的类型符号。 这是在做空值过滤。 因为 经过这一步之后, 这一行的意思是: 这里两个参数的含义: 换句话说,这块就是“真正生成代码”的地方。 严格说,前面 不是必须,但写上也没问题。 这一段的作用是: 这里为什么不在前面的 因为前面的 但不知道: 真正判断特性类型,更适合在拿到 这也是增量生成器很常见的套路: 这很简单: 也就是说,虽然 虽然示例后半段代码你已经能看到,但从逻辑上可以直接总结成 3 件事: 通常会有类似代码: 作用是: 类名一般直接用: 通常会有类似: 这一步就是把分析结果转成最终要加入编译的 为什么通常写成 因为源生成器不能修改你已有的类,只能再补一份同名 最后一般是: 这一步的意思不是“往磁盘写文件”,而是: 其中: 因为它不是简单的“每次都全量扫描 + 全量生成”,而是把流程拆成了可缓存、可复用的几个阶段: 一旦输入没有变化,很多步骤就不需要重新完整执行。 这就是增量生成器相对于传统生成器最有价值的地方: 如果把这段增量生成器翻译成最直白的话,它做的事情就是: 当你这样理解之后, 中间再加上筛选和转换而已。 如果你在项目里遇到这些问题,源生成器往往值得考虑。 例如: 例如: 如果规则在编译时就能确定,那就非常适合源生成器。 反过来说,如果必须等到运行时才知道输入,源生成器就不一定适合。 这是最常见的收益。 特别是序列化、注册、元数据访问等路径。 它不是字符串模板吐出来就完事,而是最终真的参与编译。 所以: 因为很多依赖反射的动态行为,在 AOT / trimming 场景下天然更脆。 源生成器生成的是静态代码,这方面通常更友好。 它确实很有用,但不是什么问题都应该上。 你只能新增代码,不能直接把用户手写的方法改掉。 相比普通业务代码,生成器有更强的工具链和 Roslyn 依赖。 如果只是为了省 20 行样板代码,未必划算。 如果信息只有运行时才知道,那源生成器帮不上忙。 糟糕的生成器会制造: 所以写生成器不是“只会拼字符串”就够了。 这是写源生成器时几乎必备的技能。 生成代码通常可以在类似目录里看到: 不同 SDK 和 IDE 展示方式略有差异,但本质都是中间生成文件。 例如: 这样最容易区分手写代码和生成代码。 所谓稳定,主要指: 否则会严重影响调试体验。 如果你要补的是同一个类或结构体,通常就必须是 只靠字符串匹配类名、属性名,通常不够稳。 很多时候你最终需要的是 否则就会冲突。 这是传统生成器最容易出现的问题。 如果项目变大,IDE 性能会明显受影响。 所以再次强调: 业务简单时这样还行; 否则生成器自己会很快变得不可维护。 如果你打算在项目里真正使用源生成器,下面这些建议会比较有用: 源生成器的本质,不是“自动造代码这么简单”,而是把一部分原本放在运行时、手工维护、或外部脚本里的工作,前移到编译期来做。 你可以这样理解它: 在今天的 如果你把它当成“Roslyn 里的编译期自动化工具”,通常就不会理解偏。简介
.NET 项目里,很多“重复但又不能随便写错”的代码,本质上都不值得手写。INotifyPropertyChanged 模板代码;Source Generator 的出现,本质上就是给这类问题一个更现代的答案:在编译期间分析你的代码,并自动生成新的
C# 源文件,让它们和手写代码一起参与编译。Source Generator 到底是什么?
Source Generator 是 Roslyn 编译器扩展机制的一部分。你的源码 -> Roslyn 分析语法树和语义信息
-> Source Generator 读取这些信息
-> 生成新的 .cs 代码
-> 编译器把手写代码和生成代码一起编译为什么源生成器值得学?
场景 Source Generator 能做什么 System.Text.Json生成序列化元数据,减少反射,提高性能 DI 自动注册 扫描标记类型并生成注册代码 DTO / Mapper 根据特性生成映射器或 DTO INotifyPropertyChanged根据字段或属性生成通知逻辑 Native AOT 生成静态可见代码,减少运行时反射依赖 SDK/客户端生成 根据协议或元数据生成强类型代码 Source Generator 和反射、Expression Tree、Source Generator 之外的代码生成,有什么区别?
和反射的区别
和表达式树的区别
和 T4 / 手工脚本生成的区别
源生成器的工作方式
源码 -> 语法树 -> 语义分析 -> Generator 执行 -> AddSource -> 合并编译SyntaxTree:语法树SemanticModel:语义模型Compilation:当前项目编译上下文INamedTypeSymbol / ISymbol:类型、方法、属性等符号信息partial?两种源生成器:
ISourceGenerator 和 IIncrementalGenerator1. 传统生成器:
ISourceGeneratorpublic interface ISourceGenerator
{
void Initialize(GeneratorInitializationContext context);
void Execute(GeneratorExecutionContext context);
}2. 增量生成器:
IIncrementalGeneratorpublic interface IIncrementalGenerator
{
void Initialize(IncrementalGeneratorInitializationContext context);
}ISourceGenerator;IIncrementalGenerator。一个最小可运行示例:生成 HelloWorld 类
第一步:创建生成器项目
dotnet new classlib -n MySourceGenerator<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />
</ItemGroup>
</Project>netstandard2.0,是因为它对生成器项目的兼容性最好。第二步:写一个最简单的生成器
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
[Generator]
public sealed class HelloWorldGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
}
public void Execute(GeneratorExecutionContext context)
{
const string source = """
namespace Generated;
public static class HelloWorld
{
public static string SayHello() => "Hello from Source Generator";
}
""";
context.AddSource(
"HelloWorld.g.cs",
SourceText.From(source, Encoding.UTF8));
}
}context.AddSource("HelloWorld.g.cs", ...);第三步:在业务项目里引用它
<ItemGroup>
<ProjectReference Include="..\MySourceGenerator\MySourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>Console.WriteLine(Generated.HelloWorld.SayHello());为什么很多生成器都要求
partial?[AutoNotify]
public partial class Person
{
private string _name = string.Empty;
}public partial class Person
{
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}partial,是因为:partial 片段。从“写死生成”走向“按规则生成”
[GenerateToString] 特性的类。[GenerateToString]
public partial class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}ToString() 或辅助方法。传统生成器的典型写法
ISyntaxReceiver 版本,因为它最容易理解。using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
[Generator]
public sealed class DemoGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
{
return;
}
foreach (var classNode in receiver.CandidateClasses)
{
var model = context.Compilation.GetSemanticModel(classNode.SyntaxTree);
var symbol = model.GetDeclaredSymbol(classNode);
if (symbol is null)
{
continue;
}
// 这里可以继续判断特性、命名空间、成员等
}
}
}
internal sealed class SyntaxReceiver : ISyntaxReceiver
{
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax classNode)
{
CandidateClasses.Add(classNode);
}
}
}SyntaxReceiver 先粗筛:哪些语法节点值得关注;Execute 再结合语义模型细筛:这些类到底是不是目标类型。增量生成器为什么更值得用?
一个增量生成器的最小示例
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
[Generator]
public sealed class ClassNameListGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (ctx, _) => (ClassDeclarationSyntax)ctx.Node);
context.RegisterSourceOutput(classDeclarations, static (spc, classNode) =>
{
var className = classNode.Identifier.Text;
var source = $$"""
namespace Generated;
public static class {{className}}Info
{
public const string Name = "{{className}}";
}
""";
spc.AddSource($"{className}.Info.g.cs", SourceText.From(source, Encoding.UTF8));
});
}
}一个更接近实战的例子:根据特性生成方法
[GenerateGreeting] 特性;SayHello() 方法。用户侧代码
namespace Demo;
[GenerateGreeting]
public partial class UserService
{
}特性定义
using System;
[AttributeUsage(AttributeTargets.Class)]
public sealed class GenerateGreetingAttribute : Attribute
{
}增量生成器
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
[Generator]
public sealed class GreetingGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var candidates = context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (node, _) => node is ClassDeclarationSyntax cds && cds.AttributeLists.Count > 0,
transform: static (ctx, _) =>
{
var classNode = (ClassDeclarationSyntax)ctx.Node;
var symbol = ctx.SemanticModel.GetDeclaredSymbol(classNode) as INamedTypeSymbol;
return symbol;
})
.Where(static symbol => symbol is not null);
context.RegisterSourceOutput(candidates, static (spc, symbol) =>
{
if (symbol is null)
{
return;
}
var hasAttribute = symbol.GetAttributes()
.Any(a => a.AttributeClass?.Name == "GenerateGreetingAttribute");
if (!hasAttribute)
{
return;
}
var namespaceName = symbol.ContainingNamespace.IsGlobalNamespace
? null
: symbol.ContainingNamespace.ToDisplayString();
var source = $$"""
{{(namespaceName is null ? "" : $"namespace {namespaceName};")}}
public partial class {{symbol.Name}}
{
public string SayHello()
{
return "Hello from generated code";
}
}
""";
spc.AddSource($"{symbol.Name}.Greeting.g.cs", SourceText.From(source, Encoding.UTF8));
});
}
}partial 代码补到目标类型上。这段增量生成器代码,逐行到底在做什么?
CreateSyntaxProviderpredicatetransformRegisterSourceOutput从所有语法节点里找出“带特性的类”,拿到它们的类型符号,再判断是不是带了目标特性,如果是,就生成代码。
所有语法节点
-> 粗筛出“带特性的类”
-> 把类语法节点转成类型符号
-> 过滤空值
-> 检查是否真的带目标特性
-> 生成对应的 partial 代码using 部分using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;System.Text:主要为了 Encoding.UTF8Microsoft.CodeAnalysis:Roslyn 核心 API,比如 IIncrementalGenerator、INamedTypeSymbolMicrosoft.CodeAnalysis.CSharp.Syntax:C# 语法节点类型,比如 ClassDeclarationSyntaxMicrosoft.CodeAnalysis.Text:SourceText,用于把字符串包装成编译器接受的源码对象[Generator][Generator]类声明
public sealed class GreetingGenerator : IIncrementalGeneratorIIncrementalGeneratorInitialize(...)public void Initialize(IncrementalGeneratorInitializationContext context)Initialize 本质上是在搭一条增量处理管道。第 1 步:建立候选输入
candidatesvar candidates = context.SyntaxProvider.CreateSyntaxProvider(candidates 不是最终结果,而是后续生成代码要用的一批候选对象。predicatepredicate: static (node, _) => node is ClassDeclarationSyntax cds && cds.AttributeLists.Count > 0,ClassDeclarationSyntaxGenerateGreetingAttributepredicate 阶段要尽量便宜static:static (node, _) => ..._ 这里没用,所以直接忽略。transformtransform: static (ctx, _) =>
{
var classNode = (ClassDeclarationSyntax)ctx.Node;
var symbol = ctx.SemanticModel.GetDeclaredSymbol(classNode) as INamedTypeSymbol;
return symbol;
})predicate 只是告诉我们:var classNode = (ClassDeclarationSyntax)ctx.Node;predicate 已经保证当前节点是类声明,所以这里可以安全转成 ClassDeclarationSyntax。classNode 理解成:var symbol = ctx.SemanticModel.GetDeclaredSymbol(classNode) as INamedTypeSymbol;INamedTypeSymbolsymbol?INamedTypeSymbol 读取。return symbol;.Where(...).Where(static symbol => symbol is not null);GetDeclaredSymbol(...) 理论上可能拿不到结果,所以这里把空值剔掉。candidates 可以理解成:第 2 步:把候选项变成真正的源代码输出
context.RegisterSourceOutput(candidates, static (spc, symbol) =>candidates 这条增量数据流spc:SourceProductionContext,主要用来 AddSourcesymbol:当前这一项候选类的类型符号防御式判空
if (symbol is null)
{
return;
}.Where(...) 已经做过空值过滤了,这里再判一次更多是防御式写法。判断目标特性
var hasAttribute = symbol.GetAttributes()
.Any(a => a.AttributeClass?.Name == "GenerateGreetingAttribute");GenerateGreetingAttributepredicate 就直接判断?predicate 是语法级快速筛选,只知道:GenerateGreetingAttributesymbol 之后做。如果不是目标类,就直接跳过
if (!hasAttribute)
{
return;
}candidates 是“带特性的类”,但只有其中真正带目标特性的类才会继续往下走。第 3 步:通常接下来会做什么?
1. 读取类的命名空间和名称
var namespaceName = symbol.ContainingNamespace.IsGlobalNamespace
? null
: symbol.ContainingNamespace.ToDisplayString();symbol.Name2. 拼出源代码字符串
var source = $$"""
namespace Demo;
public partial class UserService
{
public string SayHello()
{
return "Hello from generated code";
}
}
""";.cs 内容。partial class?partial 类代码进去。3. 把源码交给编译器
spc.AddSource(
$"{symbol.Name}.Greeting.g.cs",
SourceText.From(source, Encoding.UTF8));${symbol.Name}.Greeting.g.cs 是 hint name,用来标识这份生成文件SourceText.From(...) 是把字符串包装成源码对象为什么这段代码体现了“增量”?
再用一句人话总结这段代码
GenerateGreetingAttributepartial 代码CreateSyntaxProvider 和 RegisterSourceOutput 就没那么神秘了,它们本质上只是:源生成器特别适合哪些场景?
1. 大量重复模板代码
2. 运行时反射太多
3. 规则明确、编译期就能知道
源生成器的几个真实优点
1. 降低运行时反射开销
2. 生成代码同样受编译器保护
3. 更适合 AOT 和裁剪场景
也别把源生成器想得太万能
1. 它不能修改现有代码
2. 它的调试和维护成本不低
3. 不是所有问题都适合编译期解决
4. 生成代码本身也需要设计质量
.g.cs调试和查看生成代码
1. 到
obj 目录看生成结果obj/Debug/net8.0/generated/2. 文件名建议统一
.g.csUserService.Greeting.g.csPerson.AutoNotify.g.csJsonContext.g.cs3. 生成代码要尽量稳定
写源生成器时最容易踩的坑
1. 忘记让目标类型加
partialpartial。2. 只看语法,不看语义
ISymbol,而不是纯语法节点。3. 重复生成同一个文件
AddSource 的 hint name 要稳定且唯一。4. 生成器做了太多无谓工作
5. 把所有逻辑都塞进字符串拼接
一旦代码模板复杂,最好抽出:一套比较务实的建议
ISourceGenerator 理解原理;IIncrementalGenerator;partial 扩展,而不是奇怪的旁路类型;总结
.NET 项目里,尤其涉及这些场景时,源生成器非常值得掌握:
如果你也在自建 AI 助手,大概率会遇到同一个问题:Token 消耗比预期高。 这不是因为你用得多,而是因为默认配置往往不是为成本优化设计的——它追求的是开箱即用和功能完整。这两个目标之间有天然的张力:功能完整意味着所有能力默认开启、所有配置走高规格。在你刚上手的时候这是好事,但一旦跑稳了,就有了优化的空间。 一旦你开始认真审视每一次 API 调用的 token 组成,就会发现大量可以省下来的消耗——不是省在功能上,而是省在不必要的重复和冗余上。下面的 5 个优化点就是我从自己的消耗数据中提炼出来的。 我在亚马逊云科技 Bedrock 上跑 OpenClaw 大约三个月后,做了一次系统性的配置优化。花了大约一周时间分析消耗分布、调整配置、对比效果。结果是 Token 消耗降低了约 50%,使用体验基本无感知变化。 为什么默认配置下消耗偏高?因为默认配置的设计目标是"开箱即用"和"功能完整",不是"成本优化"。这两个目标之间是有冲突的——功能完整意味着所有选项默认开启、所有配置默认走高规格。这在你刚上手的时候是好事,但一旦跑稳了,就值得花时间把不必要的消耗降下来。 我把消耗拆解后,发现浪费主要集中在四个方面:所有场景用同一个贵模型、thinking 模式常开、system prompt 太长、心跳太频繁。下面把 5 个核心优化点展开讲。 这是投入产出比很高的一项优化。 大多数人配置 OpenClaw 时只设一个模型,所有交互都走同一个。但实际上,日常 70-80% 的对话——查个信息、格式化文本、简单问答——用轻量模型就能很好地完成,而且响应延迟更低,体验反而更好。 亚马逊云科技 Bedrock 上的 Nova Lite,价格大约是 Claude Sonnet 的几十分之一。不是几倍的差距,是几十倍。在简单任务上,两者的输出质量差异几乎感知不到。 配置方式: 哪些场景适合轻量模型? 日常问答、信息查询、文本格式化和编辑、简单翻译、shell 命令提示、文件内容总结等。这些任务 Nova Lite 处理得很好,响应速度还更快。 哪些场景需要高端模型? 多步推理、复杂代码生成和调试、长文档分析、架构设计讨论等。这些场景切到 Claude Sonnet 才有明显的质量提升。 一个实际体验: 我让两个模型分别处理同一个简单任务——"帮我把这段 YAML 转成 JSON"。结果几乎一样,但 Nova Lite 的响应时间更短。而当我让它们分析一段有 bug 的异步代码时,Sonnet 的分析明显更深入、更准确。这就是模型分级的意义——大部分时候便宜的就够了,偶尔需要好的再上。 效果: 约 30-40% 的费用节省。这个数字取决于你的使用模式——简单交互占比越高,省得越多。 Claude 的 extended thinking 功能会让模型先生成一段内部推理过程再给出答案,质量提升显著。但代价是 token 消耗平均增加 30-50%。 为什么增加这么多?因为 thinking 模式下,模型会先输出一段"思考链"——可能是几百到几千个 token 的推理过程。这些 token 虽然你不一定看得到(取决于配置),但都在计费。让模型"深度思考"一个查天气的请求,或者"认真推理"怎么排个序,纯属浪费。 需要时通过命令临时启用: 完成后关闭: 我的判断标准: 如果问题需要"想一想"才能回答好(多条件决策、复杂 debug、架构分析),就开 thinking;如果是随手一问的事情(查个命令用法、格式化个文本),不需要开。 我统计了自己的使用数据,大约只有 15-20% 的请求真正受益于 thinking 模式。其余 80% 的请求开着也是白花钱。 一个常见的误解: 有人觉得"开着 thinking 也没坏处,模型如果觉得不需要深度推理会自动跳过"。实际上不是这样——只要 thinking 模式处于开启状态,模型就会生成推理链,不管任务有多简单。所以"默认开着以防万一"其实是一个代价不小的策略,建议改为"默认关闭,需要时开启"。 与模型分级的协同: 如果日常用的 Nova Lite 本身不支持 extended thinking,那日常对话自然不会产生额外的 thinking token。两项优化叠加效果更好。 这是一个容易被忽视但影响持续的优化点。 OpenClaw 的 system prompt 由多个配置文件拼接: 如果这些文件加起来有 5000 字(约 2500 tokens),每次请求就先消耗 2500 个 token 在 system prompt 上——还没开始处理你的问题呢,2500 个 token 已经花出去了。每天 50 次交互,一个月就是 1500 次 × 2500 tokens,光 system prompt 就是一笔可观的开销。 优化策略: 单次看不明显,但这是一个会在每次请求中复利的优化。而且这些删掉的内容大多是"正确的废话",删了也不影响 AI 的表现。 注意定期审查: system prompt 文件会随着使用逐渐膨胀——你今天加一条规则,下周加个笔记,不知不觉又胖回去了。建议每个月花几分钟看一眼,保持精炼。 具体哪些该留,哪些该删? 留下的应该是真正影响 AI 行为的具体指令——比如"用中文回复""代码块用 markdown""保护用户隐私"。删掉的是通用废话("你是一个有帮助的 AI")、重复出现的规则、以及过度详细的解释。直觉上,如果某句话删了你觉得 AI 的表现不会有任何变化,那就大概率该删。 OpenClaw 的 heartbeat 机制让 AI 助手定期主动检查是否有待处理的事务。每次心跳是一次完整的 API 调用,包含完整的 system prompt 和上下文。 这意味着每次心跳的成本跟你手动问一个问题差不多。如果心跳间隔太短,一天累积下来的调用次数相当可观——而且很多时候心跳的结果就是"没事",也就是白花了钱检查了一下发现什么都不用做。 大多数个人用户并不需要那么高频的主动检查。你有事的时候大概率会主动找 AI,而不是等它来找你。 为什么选 45 分钟? 这是我在"及时性"和"省钱"之间找到的平衡点。如果你对实时性要求更高(比如依赖心跳做邮件提醒),可以设 30 分钟。如果你几乎不依赖主动通知,甚至可以设 60 分钟或更长。关键是根据自己的实际需求来,而不是用默认值。 同时精简 不要在 HEARTBEAT.md 里堆一长串检查清单。每多一项检查,每次心跳的 token 消耗就多一点。只保留真正需要定期自动检查的,其他的手动触发就好。 效果: 心跳从默认高频调到 45 分钟一次,次数减少约 67%。配合内容精简,这部分 token 消耗减少 70% 以上。心跳是 24 小时不停的,这个节省日积月累很可观。 这一项不是减少 token,而是从计费层面优化。 一些第三方 API 服务有月费或保底消费。对于使用量波动较大的个人开发者——某天用得多、某天几乎不用、周末放假、出差一周——这种固定成本模式不太划算。 亚马逊云科技 Bedrock 的按需模式则是纯按量计费: 配合 IAM 角色认证,配置很简洁: EC2 实例绑定 IAM 角色后,OpenClaw 自动通过角色获取临时凭证,无需在配置文件中存放明文密钥。认证过程对应用透明,由底层 AWS SDK 自动完成。 对个人开发者的好处: 如果你的使用量有明显的波动(工作日用得多、周末少、出差期间不用),按需模式意味着你只为实际使用付费。没有"这个月用不完"或者"超了得加钱"的心理负担。对于还在评估 AI 助手价值的人来说,这种零承诺的模式也更友好——试用成本就是你实际用掉的那些 token,不多不少。 前四项叠加后,我的实际 Token 消耗降低了约 50%。第五项的按需计费进一步优化了实际支出。 需要注意:每个人的使用模式不同,以上数字是基于我的场景(以日常对话和简单任务为主,偶尔有复杂编程和分析需求)。如果你的场景以复杂任务为主,模型分级带来的节省会相对少一些;如果你的场景以简单交互为主(比如纯粹当个信息助手用),省的比例可能更高。 供快速参考: 建议从模型分级开始,几分钟改完就能见效。每一项独立,不用一次全做。如果你只做一件事,就做模型分级——投入产出比高得离谱。 推荐顺序: 模型分级 → 关 thinking → 调心跳 → 精简 prompt → 评估 Bedrock。前三步十分钟搞定,效果能覆盖总节省的大部分。 成本优化不是一次性的事情。随着使用习惯的变化、新模型的发布、功能的迭代,定期回顾一下配置和消耗分布是有意义的。 以上 5 个优化点互相独立,可以按需选择实施。核心思路就是:让每一个 token 都花在有价值的地方。 不需要牺牲功能,不需要降低体验,只是把默认配置中的冗余消耗挤掉。 这些思路也不仅限于 OpenClaw。任何基于 LLM 构建的应用——无论是 AI 助手、聊天机器人还是内容生成工具——都面临类似的成本挑战。模型匹配任务、减少不必要的 token 生成、精简固定开销、控制调用频率,这些原则是通用的。 如果你正在为 LLM 应用的运行成本发愁,不妨从分析消耗分布开始,找到你场景下的浪费点,然后对症下药。优化也不是一劳永逸的——每隔两三个月回头看看消耗数据,你可能会发现新的浪费点,也许是 prompt 又膨胀了,也许是有新的更便宜的模型上线了。保持这个习惯,长期受益。 你的 Token 账单长什么样?评论区见 👋OpenClaw 每月 Token 开销太高?这 5 个优化帮你省一半
OpenClaw 是一个开源的 AI 助手框架,可以连接大语言模型来构建个人化的 AI 助手。本文针对其运行成本(主要是 Token 消耗)做了一次系统优化,记录 5 个可复用的配置方案,实测综合节省约 50%。
问题背景
1. 模型分级:按任务复杂度选模型
{
"agents": {
"defaults": {
"model": {
"primary": "amazon-bedrock/amazon.nova-lite-v1:0",
"fallbacks": ["amazon-bedrock/anthropic.claude-sonnet-4-20250514-v1:0"]
}
}
}
}default 是日常模型,处理绝大部分交互;thinking 是高端模型,在需要深度推理、代码生成或复杂分析时使用。2. Extended Thinking 默认关闭
{
"thinking": "off"
}/reasoning on/reasoning off3. System Prompt 精简
AGENTS.md(行为规则)、SOUL.md(人设)、USER.md(用户信息)、TOOLS.md(工具说明)等。关键点在于:这些内容在每次 API 请求中都会完整发送。 不是发一次就缓存了,是每一次请求都带上完整的 system prompt。优化前:~5000 字 → ~2500 tokens/次
优化后:~1800 字 → ~900 tokens/次
每次节省:~1600 tokens
月度节省(50 次/天):~240 万 tokens4. Heartbeat 频率调整
{
"agents": {
"defaults": {
"heartbeat": {
"every": "45m"
}
}
}
}HEARTBEAT.md,只保留真正需要定期执行的检查项:<!-- HEARTBEAT.md -->
- 检查待处理的提醒
- 按需添加其他项目,不要贪多5. 选择合适的计费模式:Bedrock 按需
{
"agents": {
"defaults": {
"model": {
"primary": "amazon-bedrock/amazon.nova-lite-v1:0",
"fallbacks": ["amazon-bedrock/anthropic.claude-sonnet-4-20250514-v1:0"]
},
"heartbeat": {
"every": "45m"
}
}
}
}综合效果评估
优化项 预估节省比例 模型分级策略 30-40% thinking 模式控制 10-15%(与分级叠加后的增量) system prompt 精简 5-10% heartbeat 频率调优 5-10% Bedrock 按需计费 弹性节省,因人而异 操作清单
default 用轻量模型,thinking 用高端模型"thinking": "off" 默认关闭 extended thinkingHEARTBEAT.md结语
哎 😑 真的承担不起 openclow 的 token 了
各位有什么白嫖 token 的方法吗?急需啊
各位佬们都怎么做的?可否分享一二
目前用的是 vultr+x-ui 自建机场,洛杉矶节点。使用下来感觉速度不够快,不知道是带宽还是距离的原因,大哥们有没有服务器推荐?我买的服务器是最低配 5$/mo ,搬瓦工看了太贵不考虑,最好价格不要超过 10$/mo
需求:速度能够达到 300M 宽带的体验,解锁 GPT 、GEMINI ,最好是亚洲节点(新加坡、日本、韩国)
大家好,我是良许。 在嵌入式开发中,RAM 和 ROM 是我们每天都要打交道的两种存储器。 无论是调试 STM32 单片机时查看内存分配,还是优化程序性能时分析存储结构,深入理解它们的工作原理都至关重要。 今天我们就从底层硬件结构出发,聊聊这两种存储器的本质区别和工作机制。 RAM(Random Access Memory,随机存取存储器)是一种易失性存储器,断电后数据会丢失。 它主要分为 SRAM 和 DRAM 两大类。 SRAM(Static RAM,静态随机存取存储器)使用双稳态触发器来存储数据。 每个存储单元由 6 个晶体管组成,形成一个锁存器电路。 这种结构的优点是只要供电,数据就能一直保持,不需要刷新操作。 SRAM 的基本存储单元包含两个交叉耦合的反相器,它们形成一个正反馈回路。 当存储"1"时,一个反相器输出高电平,另一个输出低电平;存储"0"时则相反。 这种双稳态结构使得数据非常稳定,读写速度也很快。 在 STM32 等单片机中,片上 SRAM 就是这种类型。 比如 STM32F103 系列有 20KB 的 SRAM,它被映射到 0x20000000 地址开始的空间。 当我们定义全局变量或使用 malloc 动态分配内存时,实际上就是在使用这块 SRAM。 SRAM 的访问速度非常快,通常只需要几纳秒,而且功耗相对较低(在静态状态下)。 但它的缺点是集成度低,每个 bit 需要 6 个晶体管,因此成本较高,容量也受限。 DRAM(Dynamic RAM,动态随机存取存储器)的结构要简单得多,每个存储单元只需要 1 个晶体管和 1 个电容。 电容充电表示"1",放电表示"0"。 这种结构使得 DRAM 的集成度非常高,可以做到很大的容量。 但 DRAM 有个致命缺点:电容会漏电。 即使不进行读写操作,电容上的电荷也会逐渐流失,导致数据丢失。 因此 DRAM 需要定期刷新,通常每隔几十毫秒就要重新充电一次。 这个刷新操作会消耗额外的功耗,也会占用一定的访问时间。 在嵌入式系统中,如果需要使用大容量内存(比如运行 Linux 系统的 ARM 开发板),通常会外接 DRAM 芯片,比如 DDR、DDR2、DDR3 等。 这些都是 DRAM 的改进版本,通过双倍数据速率等技术提升性能。 RAM 的读写操作需要遵循特定的时序。 以 SRAM 为例,读操作的基本流程是:首先将地址信号放到地址总线上,然后拉低片选信号 CS 和读使能信号 OE,经过一定的访问时间后,数据就会出现在数据总线上。 写操作则需要将地址和数据同时准备好,然后拉低片选信号 CS 和写使能信号 WE,保持一定时间后完成写入。 在 STM32 中使用 FSMC(灵活静态存储控制器)外接 SRAM 时,我们需要配置这些时序参数: ROM(Read-Only Memory,只读存储器)是一种非易失性存储器,断电后数据不会丢失。 虽然叫"只读",但现代的 ROM 大多数都可以擦写,只是擦写次数和速度有限制。 在嵌入式系统中,我们最常用的 ROM 其实是 Flash 存储器。 Flash 分为 NOR Flash 和 NAND Flash 两种类型。 STM32 等单片机内部集成的就是 NOR Flash。 NOR Flash 的存储单元是浮栅晶体管。 每个晶体管有两个栅极:控制栅和浮栅。浮栅被绝缘层包围,可以长期保存电荷。 当浮栅中有电荷时,晶体管的阈值电压升高,表示"0";没有电荷时阈值电压较低,表示"1"。 Flash 的编程(写入)操作是通过量子隧穿效应实现的。 在控制栅和漏极之间施加高电压,电子就会穿过绝缘层进入浮栅。 擦除操作则相反,通过施加反向高电压,让电子从浮栅中逸出。 Flash 存储器不能像 RAM 那样随意读写,它有特定的组织结构。 Flash 被分成多个扇区(Sector),每个扇区又分成多个页(Page)。 擦除操作只能以扇区为单位,而编程操作通常以页或字为单位。 以 STM32F103 为例,它的 Flash 被分成多个 1KB 或 2KB 的页。 在编程之前必须先擦除对应的页,擦除后所有 bit 都变成 1(0xFF)。 然后才能进行编程操作,将某些 bit 从 1 变成 0。 如果要将 0 变回 1,必须重新擦除整个页。 Flash 存储器的擦写次数是有限的,通常 NOR Flash 可以擦写 10 万到 100 万次。 这是因为每次擦写都会对绝缘层造成微小的损伤,累积到一定程度后,浮栅就无法可靠地保持电荷了。 在实际应用中,如果频繁地对同一个扇区进行擦写,会导致该扇区提前损坏。 因此需要使用磨损均衡技术,尽量让各个扇区的擦写次数保持均衡。 比如在存储日志数据时,可以采用循环写入的方式,每次写入不同的扇区。 EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程只读存储器)是另一种常见的非易失性存储器。 它的优点是可以按字节擦写,不需要像 Flash 那样以扇区为单位。 但 EEPROM 的容量通常较小,成本也较高。 一些 STM32 芯片没有集成 EEPROM,但可以通过软件模拟的方式,使用 Flash 来实现 EEPROM 的功能。HAL 库提供了相应的接口: 在嵌入式开发中,RAM 和 ROM 各有其适用场景。 RAM 速度快、可以随机读写,适合存储程序运行时的临时数据,比如变量、堆栈、缓冲区等。 ROM 虽然速度较慢、擦写次数有限,但它能够断电保存数据,适合存储程序代码、常量数据、配置参数等。 在实际项目中,我们需要合理分配这两种存储器的使用。 比如在 STM32 中,程序代码存储在 Flash 中,启动后 CPU 从 Flash 中读取指令执行。 全局变量、局部变量、动态分配的内存都在 SRAM 中。 如果需要保存用户配置或校准数据,可以使用 Flash 的某个扇区或外接 EEPROM。 对于运行 Linux 的嵌入式系统,通常会使用 NOR Flash 存储 bootloader 和内核,使用 NAND Flash 存储文件系统,使用 DRAM 作为运行内存。 这种组合能够在成本、性能和可靠性之间取得良好的平衡。 理解 RAM 和 ROM 的底层结构和工作原理,不仅能帮助我们写出更高效的代码,还能在遇到存储相关问题时快速定位原因。 希望这篇文章能够帮助大家更深入地理解这两种基础而重要的存储器。 更多编程学习资源1. RAM 的结构与工作原理
1.1 SRAM 的结构原理
// SRAM使用示例
uint8_t global_buffer[1024]; // 全局变量存储在SRAM中
void sram_test(void)
{
// 局部变量也在SRAM中(栈区)
uint32_t local_var = 0x12345678;
// 动态分配内存在SRAM的堆区
uint8_t *dynamic_buffer = (uint8_t*)malloc(512);
if(dynamic_buffer != NULL) {
// 对SRAM进行读写操作
for(int i = 0; i < 512; i++) {
dynamic_buffer[i] = i & 0xFF;
}
free(dynamic_buffer);
}
}1.2 DRAM 的结构原理
// 在Linux应用开发中使用DRAM的示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void dram_usage_example(void)
{
// 在Linux系统中,malloc分配的内存来自DRAM
size_t size = 10 * 1024 * 1024; // 10MB
char *large_buffer = (char*)malloc(size);
if(large_buffer != NULL) {
// 初始化内存
memset(large_buffer, 0xAA, size);
// 进行一些操作
printf("Allocated %zu bytes in DRAM\n", size);
// 释放内存
free(large_buffer);
}
}1.3 RAM 的读写时序
void FSMC_SRAM_Init(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef ReadWriteTiming;
// 配置读写时序
ReadWriteTiming.FSMC_AddressSetupTime = 0x00; // 地址建立时间
ReadWriteTiming.FSMC_AddressHoldTime = 0x00; // 地址保持时间
ReadWriteTiming.FSMC_DataSetupTime = 0x03; // 数据建立时间
ReadWriteTiming.FSMC_BusTurnAroundDuration = 0x00; // 总线转换时间
ReadWriteTiming.FSMC_CLKDivision = 0x00;
ReadWriteTiming.FSMC_DataLatency = 0x00;
ReadWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;
// FSMC配置
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM3;
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &ReadWriteTiming;
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &ReadWriteTiming;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM3, ENABLE);
}2. ROM 的结构与工作原理
2.1 Flash 存储器的结构
2.2 Flash 的扇区和页
// STM32 Flash操作示例
#include "stm32f1xx_hal.h"
#define FLASH_USER_START_ADDR 0x08010000 // 用户Flash起始地址
#define FLASH_USER_END_ADDR 0x0801FFFF // 用户Flash结束地址
// 擦除Flash
HAL_StatusTypeDef Flash_Erase(uint32_t start_addr, uint32_t end_addr)
{
HAL_StatusTypeDef status = HAL_OK;
FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t PageError = 0;
// 解锁Flash
HAL_FLASH_Unlock();
// 配置擦除参数
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = start_addr;
EraseInitStruct.NbPages = (end_addr - start_addr) / FLASH_PAGE_SIZE;
// 执行擦除
status = HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
// 锁定Flash
HAL_FLASH_Lock();
return status;
}
// 写入Flash
HAL_StatusTypeDef Flash_Write(uint32_t addr, uint32_t *data, uint32_t len)
{
HAL_StatusTypeDef status = HAL_OK;
uint32_t i;
// 解锁Flash
HAL_FLASH_Unlock();
// 按字写入
for(i = 0; i < len; i++) {
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,
addr + i * 4,
data[i]);
if(status != HAL_OK) {
break;
}
}
// 锁定Flash
HAL_FLASH_Lock();
return status;
}
// 读取Flash
void Flash_Read(uint32_t addr, uint32_t *data, uint32_t len)
{
uint32_t i;
for(i = 0; i < len; i++) {
data[i] = *(__IO uint32_t*)(addr + i * 4);
}
}2.3 Flash 的寿命和磨损均衡
// 简单的循环写入示例
#define LOG_SECTOR_COUNT 10
#define LOG_SECTOR_SIZE 4096
typedef struct {
uint32_t current_sector;
uint32_t write_count[LOG_SECTOR_COUNT];
} LogManager_t;
LogManager_t log_mgr = {0};
void Log_Write(uint8_t *data, uint32_t len)
{
uint32_t sector_addr = FLASH_USER_START_ADDR +
log_mgr.current_sector * LOG_SECTOR_SIZE;
// 擦除当前扇区
Flash_Erase(sector_addr, sector_addr + LOG_SECTOR_SIZE - 1);
// 写入数据
Flash_Write(sector_addr, (uint32_t*)data, len / 4);
// 更新写入计数
log_mgr.write_count[log_mgr.current_sector]++;
// 切换到下一个扇区
log_mgr.current_sector = (log_mgr.current_sector + 1) % LOG_SECTOR_COUNT;
}2.4 EEPROM 的特点
// 使用HAL库的EEPROM模拟功能
#include "stm32f1xx_hal.h"
// 写入一个变量到模拟EEPROM
HAL_StatusTypeDef EEPROM_WriteVariable(uint16_t VirtAddress, uint16_t Data)
{
// HAL库会自动处理Flash的擦除和写入
return HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,
VirtAddress,
Data);
}
// 从模拟EEPROM读取变量
uint16_t EEPROM_ReadVariable(uint16_t VirtAddress)
{
return (*(__IO uint16_t*)VirtAddress);
}3. RAM 与 ROM 的对比与应用
128 平方,4 个房间+客餐厅,原打算用 AC+5AP 的方案,后来查了下好像 1 个吸顶 AP (图片标红处)即可全覆盖,麻烦各位帮忙看下什么方案比较合适?

我买的是 智谱 Max 编码套餐(最高档)。
目前我的实际使用情况是:
但系统返回的是:
429 "您的账户已达到速率限制,请您控制请求频率"
先讲讲这个产品逻辑。
如果一个产品是 额度制,那么核心限制应该是:用多少算多少,用完为止。
但现在的实际情况是:额度还剩 **95%**,系统却提示 不可用了
这个体验已经不是“限流”了。
这是 额度和速率设计完全脱节。
用户买的是 算力额度只是看的,不是用的,Max 套餐买的不是额度,而是一个 额度余额展示服务, 你根本不会给你用那么多,实属恶心人
Max 编码套餐:
那个因为我比较佛系,老哥们发的时候注意下格式:"id:xx"就 ok 了,我后面拿脚本跑一下比较方便,感谢老哥们
站点地址 https://xcode.best ,1 元:1 刀
只有三个渠道
逆向倍率带缓存 0.15
CCMAX 1.0
GPT 0.3
欢迎大家体验,撒花,睡觉晚安🛌
想安装一个 NPM 版的 Claude Code ,然后就发生了下面的事情:
# tink @ Hackint0sh in /usr/local/lib/node_modules [23:32:52]
$ npm i @anthropic-ai/claude-code
added 3 packages, and removed 1826 packages in 14s
2 packages are looking for funding
run npm fund for details
忘了打一个-g,又正好在/usr/local/lib/node_modules...
所有的全局包全部被清掉,1826 个,连 npm 、openclaw 都没了。。。
现在一个一个修,好绝望
(1)首先明确性质,这轮炒作的推动力,是由大模型硬件厂商,token 软件大厂,行业相关者为 token 滞销卖不出去而联合推动的一场超级大营销.在这场大营销之前,transformer 大模型遭遇了严重的估值危机,商业回报质疑等,这场大营销也是场自我拯救.
(2)截止 2026 年 3 月,可以非常明确地确认,transformer 大模型没有智力,但确实是一款很人性化的信息管理软件,对解放脑力,提高脑力生产效率意义巨大,是人类脑力助手的里程碑的发明,它的缺点是能源消耗巨大.
(3)尝鲜者必然会付出安全代价和缴纳 token 智商税费用,但只要被骗的人的数量足够多,就能形成行业标准,相关资源会进入其中逐步解决安全问题和 token 费用问题,但最终是否能成功,还有待观察.
(4)openclaw 的概念并不新鲜,早在两年前就有人进行并实际落实,整体上看,这次大推广是正面的,不管中间有多少目瞪口呆的问题,不断犯错才能推动行业往前走