2026年1月

不太懂 openspec ,最近了解了一下。自己在某个仓库使用 openspec 写了一次需求,也观察了一下 openspec 自身是怎么使用的,发现 openspec 的 spec 似乎就是在用自然语言描述代码逻辑行为的各种 case 。以 openspec 仓库自身的 spec 为例,发现 spec.md 文件本身是在描述代码的行为逻辑。比如 openspec 的某个 spec.md 对应行为就是这个文件: https://github.com/Fission-AI/OpenSpec/blob/8332a098118a6584a7104ccfe8e46669a1c24b7d/src/utils/change-utils.ts#L112spec.md 本身贴在末尾

我的问题是:

  1. 这样的 spec 存下来有什么意义?因为我理解存下来是为了后续有其他需求迭代时给 ai 看的,那为什么不直接让 ai 去读代码来理解现有的逻辑呢?我理解大型项目让 ai 工作是需要知识库的,但是 openspec 的 spec 更像一个细节说明书,而不是类似纲领的知识库。是不是说在 openspec 的工作流里面 spec 才是代码仓库的行为核心准则,理论上基于 spec.md 可以随时生成一套具体实现可能不一致,但行为一致的代码。
  2. openspec 的工作流程是先让 AI 进行 plan ,然后迭代 plan ,直到 AI 给出 plan 满意了,然后 AI 开始进行 coding 。这个 plan 的流程现在 antigravity 等也能做到。感觉 plan 并不是 openspec 的重点,spec.md 才是重点是吗?如果说期望的流程是先和 ai 讨论出充满细节的 spec ,再让 ai 开始 coding ,有种变成了自然语言描述写代码的感觉,这个感觉非常怪。
  3. 基于 1 的末尾提出的“spec 才是代码仓库的行为核心准则”的想法,假设需要落地到一个前端项目,那按照这个 spec 粒度,我感觉每个 tsx 文件都需要一个 spec 去描述它的规范行为。这个感觉就更怪了

有没有实践比较多的朋友能给一些输入,分享一些经验,或者思考?

附上的 spec.md

change-creation 规范

目的( Purpose )

提供用于以编程方式创建和校验 OpenSpec change 目录的工具函数。


需求( Requirements )

需求:Change 创建( Change Creation )

系统 必须( SHALL ) 提供一个函数,用于以编程方式创建新的 change 目录。

场景:创建 change

  • 当( WHEN ) 调用 createChange(projectRoot, 'add-auth')
  • 那么( THEN ) 系统会创建 openspec/changes/add-auth/ 目录

场景:拒绝重复的 change

  • 当( WHEN ) 调用 createChange(projectRoot, 'add-auth'),且 openspec/changes/add-auth/ 已存在
  • 那么( THEN ) 系统抛出一个错误,表明该 change 已存在

场景:必要时创建父目录

  • 当( WHEN ) 调用 createChange(projectRoot, 'add-auth'),且 openspec/changes/ 不存在
  • 那么( THEN ) 系统创建完整路径,包括所有必要的父目录

场景:拒绝非法的 change 名称

  • 当( WHEN ) 使用非法名称调用 createChange(projectRoot, 'Add Auth')
  • 那么( THEN ) 系统抛出一个校验错误


需求:Change 名称校验( Change Name Validation )

系统 必须( SHALL ) 校验 change 名称符合 kebab-case 规范。

场景:合法的 kebab-case 名称被接受

  • 当( WHEN ) 校验一个类似 add-user-auth 的名称
  • 那么( THEN ) 校验返回 { valid: true }

场景:允许数字后缀

  • 当( WHEN ) 校验一个类似 add-feature-2 的名称
  • 那么( THEN ) 校验返回 { valid: true }

场景:允许单个单词

  • 当( WHEN ) 校验一个类似 refactor 的名称
  • 那么( THEN ) 校验返回 { valid: true }

场景:拒绝大写字母

  • 当( WHEN ) 校验一个类似 Add-Auth 的名称
  • 那么( THEN ) 校验返回 { valid: false, error: "..." }

场景:拒绝空格

  • 当( WHEN ) 校验一个类似 add auth 的名称
  • 那么( THEN ) 校验返回 { valid: false, error: "..." }

场景:拒绝下划线

  • 当( WHEN ) 校验一个类似 add_auth 的名称
  • 那么( THEN ) 校验返回 { valid: false, error: "..." }

场景:拒绝特殊字符

  • 当( WHEN ) 校验一个类似 add-auth! 的名称
  • 那么( THEN ) 校验返回 { valid: false, error: "..." }

场景:拒绝以连字符开头

  • 当( WHEN ) 校验一个类似 -add-auth 的名称
  • 那么( THEN ) 校验返回 { valid: false, error: "..." }

场景:拒绝以连字符结尾

  • 当( WHEN ) 校验一个类似 add-auth- 的名称
  • 那么( THEN ) 校验返回 { valid: false, error: "..." }

场景:拒绝连续连字符

  • 当( WHEN ) 校验一个类似 add--auth 的名称
  • 那么( THEN ) 校验返回 { valid: false, error: "..." }

大学里的时候就经常说,“我有一个梦想”,就是能有一套苹果全家桶。

现在基本实现的差不多了,除了手表没用过,因为对我来说实在有点鸡肋,而且续航太短所以不考虑。只能说对一件事物最好的祛魅方式就是拥有。

最一开始的 23 年在父母的资助下去拼多多买了 14 寸的 2021 款 MacBook Pro ,花了一万二。在那以前我用的是 y7000p 拯救者,因为本科是计算机专业,对电脑的使用习惯还算良好,也在这一台小小的笔记本上折腾过 ubuntu 、双系统,所以也不算卡顿,但是现在每每想到自己背着这笔记本+电源适配器将近十斤重的书包去图书馆跑了四年,依旧会对自己手里的 mac 倍感珍惜。用的两年以来遇到的 bug 几乎没有,且死机的次数也是屈指可数,不像我后来组的台式机,隔三差五就黑屏重启,而且 HDR 时有时无,怀疑是买的 14700k 暴雷,或者是主板没插好?或者是显示器的原因?总之黑屏的时候非常令人不爽。

后来又在拼多多上买了 Airpods Pro2 ,这是我用过最好的苹果产品,只可惜某一次放在口袋里忘记拿出来扔到洗衣机里洗了,右耳机坏了,左耳机漏音,充电盒也坏了。再后来经过我女朋友的推荐买了 vivo 的耳机,300 多块也,降噪、滑动调音量都有,也能无缝在两个设备之间切换,也挺好。

去年是我工作的第二年,我在江苏省补刚过!!!的一个月又又在拼多多买了现在的 iPhone16Pro ,血亏几百块。从最一开始以为会很难适应的无侧滑返回,到现在用了十几个快捷指令来让手机用得更舒服。本来想说 ios 非常流畅没有任何 bug 卡顿的,但是想了一下以前用的 k50 似乎也没有给我留下“非常卡顿、bug 很多”的印象,只是 Android 给我的感觉、或者说红米给我的感觉是普普通通。如果让我说 ios 设计最好的点,我想说文件管理和快捷指令。ios 的文件管理是我用过最干净的文件管理,或者说 ios 给我的感觉就是非常干净,虽然他的 app 的开屏广告满天飞。而且仅限于 ios ,mac 的文件管理就有些混乱了,似乎比 windows 还混乱,在 windows 尚且可以用 geek 来卸载干净软件,但是 mac 上卸载软件遗留的文件无从查找,只能眼不见为净。第二个是快捷指令,结合自动化用起来很舒服。但是苹果用起来的硬伤:没有红外遥控器、NFC 、以及续航焦虑,这三点给我带来的不便在我一开始刚入手的时候一度想要换手机,当时只要加价 200 块就可以换小米 15Ultra ,我几乎是下单了两次,又取消了两次,强逼着自己适应这三个缺点。苹果用户的自适应能力由是可见。这三点我的解决方案是:1. 网上买了 esp32+红外发射模块,在自己的服务器上搭了一个远程遥控服务,这样在任何地方都可以控制家里的空调。2. 用小米手环的 NFC ;也买了一张 NFC 卡贴,几块钱,但是因为只能录制一张卡,所以还是放弃了。3. 买了个充电宝,放在车里随时带着,用一次就要带回家一次充电。

前两天我又又又在拼多多上买了 11 寸的 99 新美版 2021 款 ipad pro+二代笔,因为大学的时候就用当时的 ipad 做无纸化学习,用 notability 写了几十个笔记,给我留下了很好的印象,所以现在买来的本意也是想拾起自己学习的记忆,考一下系统架构师的,但是现在拿到手突然感觉非常茫然,用 Apple Pencil 写的字是歪七八扭甚至不如普通的笔写的字,也不知从何学起。现在大家都是如何自我学习的?是 b 站上看网课、用键盘记笔记?还是用微信小程序/app 刷题?或者更高级一点,用 AI 来指导学习呢?

坐标北方(水质硬,烧水有白色水垢/沉淀物),最近打算入手一套过滤水壶和恒温烧水壶,主要为了去水垢和喝水方便。顺便冬天洗脸。

需求如下:

1.过滤水壶:

核心诉求:必须能有效去水垢/软化水质(听说碧然德那种可以?)。

容量:希望大一点( 3.5L 左右),不想频繁接水。

耗材:滤芯成本适中,好买到的。

2.恒温水壶:

材质:必须是全不锈钢或者是高硼硅玻璃,接触水的部分不要有塑料。

功能:支持恒温保温(比如设定在 55 度随时喝),温控要准。

容量:1.5L - 1.7L 左右。

预算: 两个加起来最好能 300 以内。

符合这些要求:用 Swift 作为主要语言、适配最新 UI(Liquid Glass),上架 App Store,开源,活跃维护。
Signal, Telegram/Swiftgram, Ice Cubes, Element X, a-shell, UTM

不太满足要求但还行的:
iSH, uBlock Origin Lite, uBlacklist(这个不知道为什么最新版在苹果平台上坏了), NetNewsWire

不开源的:
Shadowrocket

评论区留下你的宝藏(我怎么变成苹果区用户了)

主号副号都是电信,宽带都是电信的,流量话费都够用,公司有个合作 联通套餐 19,70G 全国流量,900 分钟通话,300M 家宽。这个套餐现在竞争力如何啊?有必要入个备用吗?我感觉最大的用途就是家宽能多个联通的备用,跨境访问的时候多个选择,cn2 不好使的时候 说不定 9929 不错

图片云上传 IMG-CLOUD-UPDATE

项目介绍

在写博客的场景下,不可避免上传一些图片。作为博客文章插图。

我都是直接上传到我阿里云的 oss 里面。但是阿里云自带的工具太重了,每次打开等半天,而且复制 url 还得点好几下。

我花了一周做了一个快速上传图片到阿里云 OSS ,并且直接生成 makedown 格式的图片链接,方便你写博客或文章的时候快速上传并插入。

下面是正经的介绍:


图片云上传是一款前端使用 vue3+element-plus ,后端使用 go+gin+bbolt 的图片上传项目。使用该项目可以让你更快的把本地图片传到远程图片云仓库。当前版本支持阿里云 oss 。不用担心你的 access_key_id 会泄露,本系统保存到本地二进制数据库,不做任何远程传输。

本项目也适合初学者学习参考,内有大量的注释,对于学习 golang 和 VUE 都有比较大的帮助。

已支持:

  • 阿里云 oss
  • 自定义路径
  • 自定义/自动修改名称
  • 自动添加 markdown 格式
  • 支持设置密码/默认安装页面

未支持

  • 显示多层文件列表
  • aws/腾讯云/七牛等更多远程仓库

截图


https://imgur.com/1kA2Bav

https://imgur.com/gIbloDS

依赖组件:

1. 前端:

  1. vue3
  2. element-plus
  3. pinia
  4. axios
  5. vue-router

2. 后端:

  1. go
  2. gin
  3. bbolt

安装

  1. docker 编译安装

直接执行 make 即可,会先编译 web 端,再编译 server 端。再打包容器镜像。最后 docker 运行镜像。

参考项目:

在当今全球化的投资环境中,美股市场(如 NYSE 和 NASDAQ)凭借其极高的流动性和影响力,成为了开发者和金融产品经理关注的重点。要构建一个成功的量化交易系统或行情展示应用,数据的实时性稳定性是核心命脉。

本文将基于 StockTV 全球金融数据接口,详细介绍如何快速对接美股实时行情数据。


一、 为什么选择?

在对接美股数据时,开发者通常面临接口复杂、延迟高、覆盖不全等痛点。StockTV 提供的 API 具有以下优势:

  1. 极速实时性:提供 HTTP 和 WebSocket (WS) 双重接入方式,WS 模式可实现毫秒级的数据推送。
  2. 全球覆盖:除美国外,还支持印度、日本、韩国、新加坡等多个主流及新兴市场。
  3. 多维度数据:涵盖实时价格、K线数据、涨跌排行、IPO日历及公司基本面信息。
  4. 集成简单:返回标准 JSON 格式,几行代码即可完成对接。

二、 快速开始:获取接入权限

在调用接口前,您需要准备好身份验证密钥(Key):

  • 获取方式:联系技术支持获取专属 Key。
  • 调用规范:在所有 API 请求中,将 Key 添加到 key 参数中即可。

三、 美股核心接口对接指南

1. 精准查询美股实时行情

美股市场庞大,您可以通过 symbol(股票代码,如 AAPL、TSLA)直接获取最新价格及各项指标。

  • 接口地址https://api.stocktv.top/stock/queryStocks
  • 核心参数symbol (股票代码), key (您的Key)
  • 美股交易所筛选:在市场列表中,可以通过 exchangeId 进行区分(1 为 NYSE,2 为 NASDAQ)。

2. 实时 K 线数据对接

对于需要绘制图表的应用,StockTV 提供了灵活的 K 线接口,支持 5分钟、15分钟、1小时、天、周等多种粒度。

  • 接口地址https://api.stocktv.top/stock/kline
  • 参数示例pid=产品ID&interval=PT5M(获取5分钟实时K线)

3. 美股涨跌排行榜

实时监控市场热点,获取美股涨幅榜、跌幅榜或换手率排行,帮助用户捕捉异动。

  • 接口地址https://api.stocktv.top/stock/updownList
  • 关键点:实时返回最新变动数据,确保排行榜的即时更新。

四、 代码实战:Python 请求示例

以下是一个简单的 Python 示例,演示如何获取苹果公司(AAPL)的实时行情:

import requests

# 配置参数
api_key = "您的Key"
base_url = "https://api.stocktv.top/stock/queryStocks"
params = {
    "symbol": "AAPL",
    "key": api_key
}

try:
    response = requests.get(base_url, params=params)
    data = response.json()
    
    if data['code'] == 200:
        stock_info = data['data'][0]
        print(f"股票名称: {stock_info['name']}")
        print(f"最新价格: {stock_info['last']}")
        print(f"涨跌幅: {stock_info['chgPct']}%")
        print(f"最后更新时间戳: {stock_info['time']}")
    else:
        print(f"请求失败: {data['message']}")
except Exception as e:
    print(f"发生错误: {e}")

五、 进阶:如何保障“极致实时”?

对于对延迟极其敏感的量化交易场景,建议采用以下方案:

  1. WebSocket (WS) 接入:相比 HTTP 定时轮询,WebSocket 采用长连接推送机制,能在市场价格跳动的第一时间将数据推送到客户端。
  2. 精简请求:通过 stocksByPids 接口一次性获取多个自选股的最新数据,减少网络往返开销。
  3. 时间戳校验:StockTV 的每个返回包都包含 time 时间戳,请务必在本地进行校验以确保处理的是最新数据。

六、 结语

StockTV API 为美股数据对接提供了极简且强大的解决方案。无论您是个人开发者还是企业级应用,都能通过其稳定、实时的接口快速实现业务目标。


本文数据及接口信息来源于 StockTV 官方技术文档。

在 Go 业务开发中,我们经常遇到这样的场景:

  • 环境切换:本地开发用 NATS 或 RabbitMQ 贪图轻快,线上却要接入 Kafka 或 AWS SQS 。
  • 代码耦合:业务逻辑被底层 MQ 的 SDK 对象(如 *rocketmq.Producer)绑定,一旦想换驱动,几乎要重写整个消息发送逻辑。
  • 配置坑多:每个 MQ 的参数设置五花八门,一不小心传错了参数,程序却静默运行,等到上线出事才发现配置没生效。

为了解决这些痛点,我发起了 Unified MQ Broker for Go 项目。它就像是 MQ 领域的 "DBAL"(类似于 SQL 领域的 GORM 或数据库驱动层),让你通过一套 API 就能无缝切换多种消息中间件。

🚀 v0.2.0 重磅更新

经过一段时间的打磨,我们刚刚发布了 v0.2.0 版本。这次更新不只是增加了驱动,更是在“健壮性”和“性能”上做了深度优化:

1. 🛡️ 独创“选项追踪” (Option Tracking)

  • 痛点:如果你给 Kafka 传了一个 SQS 的 DeduplicationID,大部分 SDK 会选择静默忽略。
  • 方案:v0.2.0 引入了审计机制。如果底层适配器没有读取你传入的某个配置项,系统会在连接或发布时发出显式警告。彻底告别因拼写错误或参数误用导致的配置无效。

2. ⚡ 高性能“智能序列化” (Smart Serialization)

  • 优化:针对原始 []bytestring 数据实现了零拷贝路径,跳过冗余的 json.Marshal
  • 战果:压测显示,在高吞吐场景下,序列化性能提升了 5 倍以上(单次操作仅需 ~16ns )。

3. 🏗️ 延迟绑定 (Late Binding)

  • NewBroker 现在仅做静态配置。
  • 真正的网络 IO 、TCP 建连和 SDK 初始化全部推迟到 Connect() 时执行,方便与依赖注入框架(如 Wire )集成。

4. 🌐 全平台支持

目前已完美支持:RocketMQ, Kafka, RabbitMQ, NATS, AWS SQS, GCP Pub/Sub


💻 核心代码预览

无论底层是哪种 MQ ,你的业务代码只需要关心这一套统一逻辑:

import "github.com/qvcloud/broker"

// 切换驱动只需要换一行初始化,业务代码 0 改动
b := rabbitmq.NewBroker(broker.Addrs("amqp://..."))
b.Connect()

// 注入统一的中间件(如 OpenTelemetry 链路追踪)
b.Init(broker.Middleware(otel.Middleware))

// 统一的订阅 API
b.Subscribe("orders.created", func(ctx context.Context, event broker.Event) error {
    fmt.Println("收到订单:", string(event.Message().Body))
    return nil // 返回 nil 自动 Ack ,返回 error 自动 Nack/Retry
})

// 统一的发布 API
b.Publish(context.Background(), "orders.created", &broker.Message{
    Body: []byte(`{"id": 1001}`),
})

传送门

GitHub: https://github.com/qvcloud/broker

  • 核心理念: 接口驱动、高性能、原生支持 OpenTelemetry 。

如果你也深受 MQ 适配之苦,或者想为你的分布式系统寻找一个更规范的通信抽象,欢迎来试用、吐槽或贡献代码!如果你觉得不错,给个 Star 就是最大的支持。 🌟

在这里插入图片描述

摘要

随着鸿蒙系统在手机、平板、穿戴设备以及多终端场景中的应用越来越多,UI 流畅度已经成为用户最直观、最容易感知的问题之一。
在实际开发中,很多页面逻辑并不复杂,但依然会出现掉帧、滑动卡顿、动画不顺畅等情况,问题往往不在 CPU,而是出在 GPU 渲染压力过大 上。

本文结合 ArkUI 实际开发经验,从页面结构、状态管理、动画、图片、列表等多个角度,系统性地讲一讲 鸿蒙系统中 GPU 渲染性能该怎么优化,并给出可以直接运行的 Demo 示例代码,帮助你在真实项目中快速落地。

引言

在 HarmonyOS / OpenHarmony 体系下,UI 渲染主要由 ArkUI + 系统渲染管线 + GPU 协同完成。
理想情况下,每一帧的渲染时间要控制在 16ms 以内(60fps),一旦 GPU 在某一帧中承担了过多工作,就会直接表现为:

  • 页面滑动一卡一卡的
  • 动画有明显掉帧
  • 列表滚动不跟手
  • 设备发热、功耗升高

尤其是在 列表页、图片多的页面、复杂动画页面 中,这些问题非常常见。

所以,GPU 优化不是“锦上添花”,而是必须要做的基础工作

减少无效重绘是第一优先级

状态放对位置,比任何技巧都重要

在 ArkUI 中,只要 @State 发生变化,就会触发组件重新构建和重新渲染。
如果状态放得不合理,GPU 就会被迫做很多“没必要的活”。

错误示例:一个状态刷新整个页面

@Entry
@Component
struct BadPage {
  @State count: number = 0

  build() {
    Column() {
      Text('当前数值:' + this.count)
      Button('点击 +1')
        .onClick(() => {
          this.count++
        })
    }
  }
}

这里的问题是:
整个 Page 都会随着 count 改变而刷新

推荐做法:把状态下沉到最小组件

@Component
struct Counter {
  @State count: number = 0

  build() {
    Column() {
      Text('当前数值:' + this.count)
      Button('点击 +1')
        .onClick(() => {
          this.count++
        })
    }
  }
}

@Entry
@Component
struct GoodPage {
  build() {
    Column() {
      Counter()
    }
  }
}

这样 GPU 只需要重绘 Counter 这块区域,页面其它部分完全不受影响

实际场景:仪表盘 / 实时数据页面

比如你在做一个设备状态监控页面

  • 电量实时变化
  • 网络状态刷新
  • 温度数值更新

如果所有数据都放在一个 Page 的 State 中,那 GPU 每秒都在全量刷新页面。

更好的做法是:

  • 每一个数据块独立成组件
  • 各自维护自己的 State

这样就能明显降低 GPU 的渲染负载。

减少透明度和层级嵌套(Overdraw)

opacity 是 GPU 的“隐形杀手”

很多开发者喜欢用 opacity 做视觉效果,但实际上它非常容易触发 离屏渲染

不推荐的写法

Column() {
  Text('Hello HarmonyOS')
}
.opacity(0.5)

推荐写法:直接用半透明颜色

Column() {
  Text('Hello HarmonyOS')
}
.backgroundColor('#80FFFFFF')

原因很简单
opacity 会让 GPU 先在缓存中绘制,再合成到屏幕上,步骤变多了,性能自然下降。

实际场景:弹窗、蒙层页面

常见的弹窗结构是:

  • 半透明遮罩
  • 中间卡片

推荐做法:

  • 遮罩用半透明色值
  • 卡片背景保持不透明
  • 避免多层 Stack 嵌套

这样在低端设备上也能保证弹窗动画顺畅。

图片与纹理优化

图片尺寸不匹配,会让 GPU 白干活

GPU 很不喜欢加载大图再缩小显示

错误示例

Image($r('app.media.big_image'))
  .width(100)
  .height(100)

正确做法:准备合适尺寸资源

Image($r('app.media.image_100'))
  .width(100)
  .height(100)

使用缓存,避免反复解码

Image($r('app.media.avatar'))
  .cache(true)

这在 列表头像、商品图片 这种场景下,效果非常明显。

实际场景:商品列表 / 相册页面

  • 列表中每一项都有图片
  • 滑动过程中频繁创建 Image

如果没有缓存和尺寸控制,很容易出现:

  • 滑动掉帧
  • 页面发热

动画优化:只动 transform,不动布局

动布局动画成本非常高

不推荐

.animate({ duration: 300 })
.width(this.size)

这里会触发布局重新计算,GPU 和 CPU 都要加班。

推荐:使用 transform

.animate({ duration: 300 })
.transform({
  translateX: this.offset
})

transform 只影响最终绘制阶段,对 GPU 更友好。

实际场景:侧滑菜单 / 卡片动画

  • 菜单滑入滑出
  • 卡片弹出收起

这些动画如果全用 transform,基本可以做到低端机也不卡

列表必须使用 LazyForEach

普通 ForEach 的问题

ForEach(this.list, item => {
  Text(item.name)
})

数据一多,GPU 会直接爆炸。

正确姿势:LazyForEach

LazyForEach(this.list, (item) => {
  Text(item.name)
}, item => item.id)

只有屏幕可见的部分才会真正创建和渲染。

实际场景:设备列表 / 日志列表

比如:

  • 智能设备列表
  • 升级日志
  • 消息列表

LazyForEach 基本是必选项

完整可运行 Demo:高性能列表页面

@Entry
@Component
struct GpuOptimizeDemo {
  private data: Array<{ id: number; name: string }> = []

  aboutToAppear() {
    for (let i = 0; i < 1000; i++) {
      this.data.push({ id: i, name: '设备 ' + i })
    }
  }

  build() {
    List() {
      LazyForEach(this.data, (item) => {
        ListItem() {
          Row() {
            Text(item.name)
              .fontSize(16)
          }
          .padding(12)
        }
      }, item => item.id)
    }
  }
}

这个 Demo 在真机上滑动时,GPU 占用非常稳定。

QA 环节

Q1:GPU 优化是不是只针对低端设备?

不是。
高端设备只是“扛得住”,但功耗和发热依然会变高。

Q2:opacity 一点都不能用吗?

不是不能用,而是少用、慎用,尤其避免大面积使用。

Q3:怎么快速定位 GPU 问题?

  • DevEco Studio 的布局和性能分析
  • 看是否有掉帧
  • 看是否存在大面积 Overdraw

总结

在鸿蒙系统中,GPU 渲染优化的核心思路其实很简单:

  • 状态尽量小、尽量局部
  • 少透明、少嵌套
  • 图片尺寸要对、缓存要开
  • 动画只动 transform
  • 列表一定懒加载

这些优化手段单独看都不复杂,但一旦组合起来,页面流畅度会有非常明显的提升。

在这里插入图片描述

摘要

在鸿蒙(HarmonyOS / OpenHarmony)应用和系统开发中,IO 操作几乎无处不在,比如文件读写、配置加载、日志输出、数据库访问以及 OTA 升级等。很多性能问题表面上看是应用卡顿、启动慢、耗电高,实际上根源都指向 IO 使用不当。本文结合当前鸿蒙系统的实际开发现状,从应用层和系统层两个角度,系统梳理 IO 性能优化的常见思路,并通过可运行的 Demo 代码,讲清楚这些优化在真实项目中该怎么落地。

文章整体偏向实战,语言尽量贴近日常开发交流,适合正在做鸿蒙应用、系统服务或设备升级相关开发的同学参考。

引言

随着鸿蒙生态逐渐完善,应用形态从早期的简单页面,发展到现在的多端协同、分布式能力、设备级应用,IO 压力明显变大。一方面,应用启动阶段要加载更多配置和资源;另一方面,系统服务、后台任务、设备升级都会产生大量读写操作。

在实际项目中,经常能看到下面这些情况:

  • 页面一打开就卡,结果发现主线程在读文件
  • 日志一多,设备开始明显发热
  • OTA 升级时间很长,写盘阶段占了一大半
  • 分布式数据一同步,前台体验明显下降

这些问题并不是鸿蒙系统本身性能不行,而是 IO 的使用方式不够合理。下面我们就从最常见、也最容易优化的地方开始讲。

鸿蒙 IO 性能瓶颈从哪来

在多数项目中,IO 性能问题通常集中在下面几个点:

  • 频繁进行小文件读写
  • 同步 IO 放在主线程执行
  • 每次用文件都重新 open 和 close
  • 没有任何缓存策略
  • 用文件存 KV 数据
  • 日志输出不受控制

只要命中其中一两条,性能基本都会出问题。

应用层 IO 优化(最常用)

IO 一定不要放在主线程

这是最基础,也是最容易踩坑的一点。ArkTS 中如果直接使用同步文件接口,UI 线程就会被直接卡住。

错误示例

import fs from '@ohos.file.fs';

let text = fs.readTextSync('/data/storage/test.txt');

这种写法在数据量稍微大一点时,页面就会出现明显卡顿。

推荐写法(异步 IO Demo)

import fs from '@ohos.file.fs';

export async function readFileAsync(path: string): Promise<string> {
  let file = await fs.open(path, fs.OpenMode.READ_ONLY);
  let buffer = new ArrayBuffer(4096);
  let result = '';

  let readLen = await fs.read(file.fd, buffer);
  if (readLen > 0) {
    result = String.fromCharCode(...new Uint8Array(buffer, 0, readLen));
  }

  await fs.close(file);
  return result;
}

代码说明

  • 使用 async/await,把 IO 操作放到异步任务中
  • 读取完成后再返回结果,不阻塞 UI
  • 真实项目中可以配合 taskpool 使用

合并小 IO,减少系统调用

很多性能问题不是数据量大,而是 IO 次数太多。

不推荐的写法

for (let i = 0; i < list.length; i++) {
  fs.writeSync(fd, list[i]);
}

推荐写法

let content = list.join('');
fs.writeSync(fd, content);

实际效果

  • 系统调用次数明显减少
  • 写盘效率更高
  • 对 Flash 存储更友好

引入内存缓存,避免重复读文件

配置文件、初始化数据非常适合放进内存缓存。

let configCache: string | null = null;

export async function getConfig(path: string): Promise<string> {
  if (configCache !== null) {
    return configCache;
  }
  configCache = await readFileAsync(path);
  return configCache;
}

使用场景

  • 应用启动配置
  • JSON 静态数据
  • 权限或状态信息

能用 Preferences 就别用文件

对于少量 KV 数据,文件 IO 的性价比非常低。

Preferences Demo

import preferences from '@ohos.data.preferences';

export async function saveUserInfo(context, userId: string) {
  let pref = await preferences.getPreferences(context, 'user_config');
  await pref.put('userId', userId);
  await pref.flush();
}

优点

  • 内部自带缓存
  • 自动批量落盘
  • 使用简单,性能稳定

系统层 IO 优化(Native / 服务侧)

使用缓冲 IO

在系统服务或 Native 模块中,直接写裸 IO 往往效率不高。

#include <stdio.h>

void writeFile(const char* path, const char* data, size_t len) {
    FILE* fp = fopen(path, "w");
    if (!fp) return;

    setvbuf(fp, nullptr, _IOFBF, 8 * 1024);
    fwrite(data, 1, len, fp);
    fclose(fp);
}

说明

  • 设置 8KB 缓冲区
  • 减少实际写盘次数
  • 适合大量顺序写场景

顺序 IO 优于随机 IO

off_t offset = 0;
pread(fd, buffer, size, offset);
offset += size;

尽量避免频繁 seek 和交叉读写多个文件。

控制日志 IO

日志在调试阶段很有用,但在正式环境中是 IO 隐形杀手。

if (__DEV__) {
  console.info('debug log');
}

建议:

  • 发布版本关闭 debug 和 info
  • 避免循环内打印日志
  • 合并日志输出

典型应用场景分析

场景一:应用启动阶段加载配置

问题

启动慢,页面白屏时间长。

解决方案

  • 异步读取配置
  • 内存缓存
await getConfig('/data/storage/app_config.json');

场景二:OTA 升级文件写入

问题

升级包大,写盘耗时长。

优化思路

  • 分块下载
  • 分块写入
  • 写完再统一校验
async function writeChunk(fd: number, data: Uint8Array) {
  await fs.write(fd, data.buffer);
}

场景三:日志过多导致设备发热

问题

设备运行一段时间后发热、掉帧。

解决方案

  • 控制日志级别
  • 关闭非必要日志

常见问题 QA

Q:异步 IO 一定比同步快吗?
A:不一定,但一定不会卡 UI。

Q:缓存会不会导致数据不一致?
A:需要设计好更新策略,配置类数据问题不大。

Q:文件和 RDB 怎么选?
A:结构化数据选 RDB,大文件选文件。

总结

IO 性能优化并不复杂,关键在于使用方式是否合理。大多数性能问题,并不是因为设备性能不足,而是 IO 用得太随意。

简单总结几句话:

  • IO 不要放主线程
  • 少做小 IO,多做批量 IO
  • 能缓存就缓存
  • 能不用文件就不用文件
  • 日志一定要克制

这些原则在应用层、系统层、OTA 场景中都是通用的。如果你正在做鸿蒙系统相关开发,把 IO 优化当成基本功,会少踩很多坑。

在这里插入图片描述

摘要

在 HarmonyOS 的 ArkUI 开发中,经常会遇到这样一种交互需求:
用户按下某个组件,拖动它,然后在松手的一瞬间触发一个“释放”动作,比如飞出去、回弹、投放到某个区域,或者触发业务逻辑。

很多同学在一开始都会问一个问题:
ArkUI 里有没有现成的“施放 API”?

答案是:没有。
但 ArkUI 提供的 手势系统、状态管理和动画能力,已经足够我们组合出各种“施放效果”。

这篇文章就从一个最基础的拖拽开始,一步一步讲清楚:
ArkUI 中的“施放功能”到底是怎么实现的,以及在真实项目中该怎么用。

引言

随着 HarmonyOS 应用交互越来越偏向“自然操作”,像拖拽、投放、抛出这类交互,在实际项目中出现得非常多,比如:

  • 卡片拖到指定区域触发操作
  • 图标长按后丢进回收区
  • 功能模块拖拽排序
  • 智能设备管理中,把设备“丢”进分组

在 ArkUI 里,这些效果并不是某一个组件单独完成的,而是多种能力的组合
理解这一点之后,你会发现实现起来并不复杂,而且扩展性非常强。

ArkUI 中“施放”的本质是什么

从技术角度来看,所谓“施放”,本质就是三步:

  1. 用手势感知用户操作
  2. 用状态驱动组件位置变化
  3. 在松手时,通过动画完成“释放效果”

换句话说就是:
手势负责输入,状态负责位置,动画负责感觉。

最基础的施放实现:拖拽 + 松手回弹

实现思路

这个 Demo 不考虑目标区域,只关注三件事:

  • 手指拖动时,组件跟着动
  • 松手后触发动画
  • 动画结束后回到原位

可运行 Demo 示例

@Entry
@Component
struct CastBasicDemo {
  @State offsetX: number = 0
  @State offsetY: number = 0

  build() {
    Column() {
      Text('拖拽组件,松手后施放')
        .fontSize(18)
        .margin(20)

      Box()
        .width(80)
        .height(80)
        .backgroundColor(Color.Blue)
        .translate({ x: this.offsetX, y: this.offsetY })
        .gesture(
          PanGesture()
            .onUpdate((event) => {
              // 拖动过程中,组件位置实时更新
              this.offsetX = event.offsetX
              this.offsetY = event.offsetY
            })
            .onEnd(() => {
              // 松手瞬间,触发“施放”动画
              animateTo({
                duration: 300,
                curve: Curve.EaseOut
              }, () => {
                this.offsetX = 0
                this.offsetY = 0
              })
            })
        )
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

代码讲解(重点)

这里其实就三行是核心:

this.offsetX = event.offsetX
this.offsetY = event.offsetY

组件的位置完全由 @State 控制,手势只是不断修改状态。

而“施放”的感觉来自这里:

animateTo({}, () => {
  this.offsetX = 0
  this.offsetY = 0
})

只要状态变化发生在动画作用域内,就会自动过渡。

带目标区域的“施放”:成功 or 回弹

在真实项目中,施放通常不是随便松手就算成功,而是:

  • 拖到某个区域才成功
  • 没拖到就回弹

思路拆解

  • 拖拽过程中,持续记录位移
  • 松手时判断最终位置
  • 根据结果执行不同动画

示例代码

@Entry
@Component
struct CastTargetDemo {
  @State offsetX: number = 0
  @State offsetY: number = 0

  build() {
    Stack() {
      // 目标区域
      Box()
        .width(120)
        .height(120)
        .backgroundColor(Color.Grey)
        .position({ x: 200, y: 300 })

      // 可施放组件
      Box()
        .width(80)
        .height(80)
        .backgroundColor(Color.Green)
        .translate({ x: this.offsetX, y: this.offsetY })
        .gesture(
          PanGesture()
            .onUpdate((event) => {
              this.offsetX = event.offsetX
              this.offsetY = event.offsetY
            })
            .onEnd(() => {
              if (this.offsetX > 150 && this.offsetY > 250) {
                // 施放成功,吸附到目标
                animateTo({ duration: 200 }, () => {
                  this.offsetX = 200
                  this.offsetY = 300
                })
              } else {
                // 失败,回弹
                animateTo({ duration: 300 }, () => {
                  this.offsetX = 0
                  this.offsetY = 0
                })
              }
            })
        )
    }
    .width('100%')
    .height('100%')
  }
}

这里在做什么判断

if (this.offsetX > 150 && this.offsetY > 250)

这本质上是一个区域命中判断
在正式项目中,你可以:

  • 根据组件尺寸动态计算
  • 封装成工具函数
  • 甚至引入碰撞检测逻辑

真实应用场景示例

场景一:卡片拖拽投放到功能区

典型应用
首页卡片管理、模块编辑模式。

示例核心代码

.onEnd(() => {
  if (this.offsetX > 180) {
    animateTo({ duration: 200 }, () => {
      this.offsetX = 220
      this.offsetY = 0
    })
    // 这里可以触发业务逻辑,比如加入列表
  } else {
    animateTo({ duration: 300 }, () => {
      this.offsetX = 0
      this.offsetY = 0
    })
  }
})

逻辑上非常清晰:
UI 动画和业务逻辑是分开的,不会互相影响。

场景二:图标拖进回收站

这种交互非常常见,关键点是:

  • 松手瞬间让组件消失
  • 而不是回弹
.onEnd(() => {
  if (this.offsetY > 400) {
    animateTo({ duration: 200 }, () => {
      this.offsetY = 600
    })
  } else {
    animateTo({ duration: 300 }, () => {
      this.offsetX = 0
      this.offsetY = 0
    })
  }
})

你也可以配合透明度一起做:

.opacity(this.isRemoved ? 0 : 1)

场景三:设备管理中的“拖拽分组”

结合你后续可能做的鸿蒙设备管理场景:

  • 左侧设备列表
  • 右侧分组区域
  • 拖拽设备到分组完成绑定

这时就可以升级到 Drag & Drop,实现跨组件投放。

Box()
  .draggable(true)
  .onDragStart(() => {
    return { data: 'device-id-001' }
  })

目标区域:

Column()
  .onDrop((event) => {
    console.log('接收到设备:', event.data)
  })

这种方式更适合复杂业务。

QA 常见问题

Q1:为什么不用绝对定位?

绝对定位是死的,而 translate 是基于状态的,动画过渡更自然,也更安全。

Q2:施放动画卡顿怎么办?

  • 确保只操作必要的状态
  • 避免在 onUpdate 里写复杂逻辑
  • 动画时间不要太长

Q3:PanGesture 和 Drag 怎么选?

  • 单组件内部效果:PanGesture
  • 跨组件、跨区域:Drag & Drop

总结

在 ArkUI 中,“施放功能”并不是某一个 API,而是一种交互设计模式

  • 手势负责感知用户行为
  • 状态决定组件位置
  • 动画塑造最终体验

只要你理解了这个组合思路,就可以根据项目需求,灵活实现各种拖拽、投放、释放效果,而且代码非常干净、可维护性也很好。

我的工作流是一个围绕 superpowers 插件Loop,superpowers 的理念是:先思考再动手。当你提出一个需求,不会急于写代码,而是先退一步问你"你真正想要实现什么",通过对话梳理出完整的设计方案,再分步执行。

核心设计是 masterworker 分离。

  • 脑暴会话 (master):专注于思考和设计,输出高质量的设计文档和执行计划
  • 执行会话 (worker):专注于代码实现,执行详细的计划

1、需求录入 - 首先我会在 Zed 上进行需求录入,采用 md 格式。这一步非常重要,我大概有 30% 的时间花在需求录入上,我会把能想到的关于此需求的背景、最终目标、可行的技术方案、风险点、外部 API 文档等等一切资源,都在需求文档中说明。对于需求文档,我不会太在意格式,会有比较多口语化的表达。

2、脑暴阶段 - 把需求 MD 喂给 Claude,调用 /superpowers:brainstorm 和 claude 进行思维碰撞。这个阶段不写任何代码,只讨论设计方案和实现细节,最终输出 design.mdimplement.md,保证最终的实现方案是完美符合我的预期的。

3、 执行阶段 - 这里我会选择新起一个 ClaudeCode 会话,而不是在脑暴会话中进行代码实现。新会话的好处:一、原先脑暴会话已经经过多轮对话了,一般情况下上下文会比较满,新会话响应更快,并且不会“犯傻”;二、implement.md 足够详细,无需额外上下文

4、 CodeReview - 在 Zed 中进行代码审查和功能验收。关于代码审查,对于一些代码细节和实现原理,这里我会使用 zed-agent 来辅助我进行代码 review,当然,你也可以在终端新建一个 ClaudeCode 会话或者使用 Zed 的 Claude Agent。原则是尽量不在脑暴和执行会话中引入太多不必要的问题,保持这两个会话的「干净」。发现问题后,将改进项写入新的需求 MD

5、 LOOP - 改进项 MD 喂回脑暴会话,开始下一轮脑暴迭代

非常简单,但是效果超群。充分的前期设计可以提升 AI 的效率和质量,避免多次的来回拉扯。

举个真实案例:我用这套工作流将个人博客从 Quarz 框架迁移到 Astro 框架。脑暴阶段确认好设计方案后,我让 Claude 执行计划,然后就去睡午觉了。醒来发现 Claude Code 已经完美完成任务——中间零中断,一次成功,共计 5000+ 行代码变更。

时间序列无处不在,心电图上的心跳、股票价格、家庭智能电表读数,甚至句子中词语——这些都是时间序列。它们的特殊之处在于顺序:过去影响未来,相邻的数据点往往高度相关。

现代预测和分类模型很少直接处理原始时间序列值。它们依赖的是特征:用来描述序列形状、变异性、趋势和模式的摘要信息。好的特征能把困难的预测问题转化为更简单的回归或分类任务。

当前有两大趋势,一是 AutoML(自动机器学习),像 auto-sklearn 这样的系统能自动搜索模型族、超参数和预处理步骤。二是自动化时间序列特征提取,像 tsfresh 这样的库可以从每个序列生成数百个特征,涵盖统计量、自相关、频谱内容、熵等各个维度。

最近的研究表明,将 AutoML 与丰富的时间序列特征结合,在许多预测任务上能超越复杂的深度神经网络。更有意思的是这种方法甚至可以通过"语言时间序列"来提升文本分类的性能。

本文将介绍多步时间序列预测的构建方式、auto-sklearn 如何扩展用于时间序列、tsfresh 的工作原理和使用方法,以及两个案例研究:数值预测和文本作为时间序列。文末还有一些可以直接应用到项目中的实用技巧。

多步预测:不仅预测下一步,还要预测接下来的 k 步

多步超前预测的目标不是预测下一个值,而是预测一整个序列的未来值:

$$
x_{i+1}, x_{i+2}, \dots, x_{i+k}
$$

比如预测未来 24 小时的电力负荷、未来 10 天的原油价格,或者提前几个时间步预测洪水水位。

两种主要策略被广泛使用。

递归策略

首先训练一个模型只预测下一个时间步:

$$
\hat{x}_{i+1} = f(x_{i-w+1}, \dots, x_i)
$$

然后把这个预测值作为输入反馈进去,得到下一个预测:

$$
\hat{x}_{i+2} = f(x_{i-w+2}, \dots, x_i, \hat{x}_{i+1})
$$

如此重复直到达到 x_{i+k}。

这种方法只需训练一个模型,计算成本较低。但问题在于早期步骤的任何误差都会在后续预测中传播和放大,这就是我们常说的自回归预测。

直接多输出策略

另一种思路是训练一个模型一次预测所有未来步骤:

$$
[\hat{x}_{i+1}, \dots, \hat{x}_{i+k}] = f(x_{i-w+1}, \dots, x_i)
$$

这样做的好处是跨预测范围没有误差累积,在固定计算预算下通常准确性更好。缺点是模型更复杂,数据有限时可能更难拟合。

实践中两种策略都有用武之地。关键点在于:无论选择哪种策略,输入窗口大小 w 的选择以及从该窗口计算的特征都会显著影响性能。

时间序列的 AutoML:扩展 auto-sklearn

AutoML 的目标是自动化机器学习流水线的设计,包括数据清洗、特征预处理、模型选择和超参数调优。像 auto-sklearn 这样的系统把这当作搜索问题来处理:用贝叶斯优化和元学习探索不同的流水线,构建优秀候选者的集成。

典型的 auto-sklearn 流水线包含预处理器(缩放、填充等)、特征预处理器(PCA、核近似等)、模型(SVM、随机森林、梯度提升等)以及集成构建组件。

不过原始的 auto-sklearn 是为通用表格数据设计的。开箱即用时它不包含专门的时间序列特征提取器,像自相关峰值、频谱熵或季节性统计量这些。

有人对 auto-sklearn 做了修改,让特征预处理阶段可以包含时间序列特征提取(特别是使用 tsfresh),并且把窗口大小 w 本身作为超参数来搜索。扩展后的 AutoML 系统会搜索算法 A(SVM、GBM 等)、超参数 λ 和窗口大小 w,以最小化验证数据上的损失函数(如 RMSE)。

tsfresh

tsfresh(Time Series Feature Extraction based on Scalable Hypothesis tests,基于可扩展假设检验的时间序列特征提取)是一个 Python 库。它能自动从每个时间序列计算数百个特征:"综合"特征集大约有每个序列 794 个特征。

这些特征涵盖的类别相当广:基本统计量(均值、方差、分位数)、形状描述符(偏度、峰度、绝对能量)、自相关和偏自相关、频域度量(傅里叶系数、频谱能量、熵)、非线性时间序列特征(排列熵、小波系数等)。tsfresh 还会用假设检验来判断哪些特征与目标相关,配合多重检验校正来避免错误发现。

这种方式把工作重心从手动发明特征("要不要试试滚动均值、滞后差分,或许再加个 FFT?")转移到系统地探索一个丰富的特征库,让统计学和模型性能来决定什么才是重要的。

数据格式化

tsfresh 期望长格式的 DataFrame:一列用于 id(标识这行属于哪个时间序列)、一列用于 time(或排序索引)、一列或多列包含观测值。

示例结构大致如下:

特征提取

通常会调用类似这样的代码:

 from tsfresh import extract_features
from tsfresh.feature_extraction import ComprehensiveFCParameters

features = extract_features(
    df,
    column_id="id",
    column_sort="time",
    default_fc_parameters=ComprehensiveFCParameters()
 )

这会产生一个宽表,每行对应一个时间序列(一个 id),每列是一个特征,比如 valuemean、valueabs_energy、valueautocorrelationlag_1、valuefourier_entropybins_5 等等。

处理缺失值

对于很短或退化的序列,某些特征是未定义的(比如长度为 1 的序列没法计算 FFT)。tsfresh 提供了工具来填充或删除包含太多 NaN 的列:

 from tsfresh.utilities.dataframe_functions import impute
 
 impute(features)  # 用合理的默认值替换 NaN / inf

或者简单地删除全是 NaN 的列:

 features = features.dropna(axis=1)

特征相关性和选择

对于监督任务,tsfresh 还能基于假设检验进行特征选择,将每个特征与目标关联起来。这通常通过 extract_relevant_features 等函数完成,或者通过集成 tsfresh 的 AutoML 框架来应用其自身的选择逻辑。

用于预测的滚动特征提取

做预测时通常希望在滑动窗口上计算特征。先选择窗口大小(比如 24 小时),对每个时间窗口计算 tsfresh 特征,然后用这些特征行作为输入,将未来目标值作为标签。

案例研究 1:AutoML + tsfresh 用于多步预测

Wang 等人对 AutoML 和时间序列特征工程在多步预测任务上的相互作用进行了系统研究。

问题设置

给定单变量时间序列 (x_1, x_2, \dots, x_i),目标是仅使用最后 w 个观测值来预测接下来的 k 个值:

$$
x_{i+1}, \dots, x_{i+k}
$$

窗口大小 w 至关重要。太小会错过慢速模式;太大模型会看到嘈杂或不相关的历史。作者之前的工作已经表明,即使在单步任务中调整 w 也能显著影响预测性能,所以他们在这里把自动窗口大小选择扩展到了多步设置。

扩展 auto-sklearn

他们对 auto-sklearn 做了两处主要调整。第一是添加基于 tsfresh 的时间序列特征提取器作为候选特征预处理器。第二是把窗口大小 w 作为 AutoML 可以搜索的超参数,而不是固定的手动选择常数。

扩展后的 AutoML 系统会搜索模型族(SVM、GBM 等)、超参数(C、学习率、树深度等)和窗口大小 w(考虑 50–200 点等范围)。

三种 AutoML 变体

他们提出了三种专门用于时间序列预测的 auto-sklearn 变体。

W 变体(带自动窗口大小选择的 Auto-sklearn)使用窗口中的原始滞后值作为特征,让 AutoML 在 50–200 的范围内选择最佳窗口大小。

T 变体(带 tsfresh 特征的 Auto-sklearn)使用固定窗口大小(比如 w = 100),应用 tsfresh 从每个窗口段提取数百个特征,用 Benjamini-Hochberg 程序为每个预测步骤选择统计显著的特征,然后取跨预测范围的并集。

WT 变体结合了两个想法:AutoML 同时调整窗口大小 w 并使用从每个候选窗口提取的 tsfresh 特征。

基线和数据

为了对这些变体进行基准测试,他们与多种基线进行了比较。传统机器学习基线包括 SVM(递归和多输出两种形式)和 GBM(同样有递归和多输出两种)。神经网络和 AutoML 基线包括 N-BEATS(一个很强的单变量预测深度学习模型)、Auto-Keras(配置了 LSTM/GRU 循环块和手动选择的窗口大小)以及原始 auto-sklearn(固定窗口大小,无时间序列特定特征)。

数据集来自 CompEngine,一个大型时间序列数据仓库。他们从不同类别选择了 20 个数据集:音频(动物声音、语音、音乐)、生态数据、宏观和微观经济、金融(原油、汇率、天然气价格)、医学数据(ECG)、动力系统(受驱摆、Duffing 振荡器等)和随机过程(自回归、随机游走等)。每个数据集按时间分为 67% 训练集和 33% 测试集。

关键发现

几个最有意思的结果值得一提。

多输出模型在相同计算预算下通常优于递归模型,大概是因为避免了跨预测范围的误差累积。原始 auto-sklearn(固定窗口大小)已经在 20 个数据集中的 8 个上击败了所有传统机器学习基线。

专门的 AutoML 变体进一步提升了性能。W 变体(自动窗口大小,无 tsfresh)在 20 个数据集中的 14 个上优于最佳传统机器学习基线(SVM 多输出)。W、T 和 WT 分别在 10、5 和 5 个数据集上显示出比所有传统基线更低的误差。

与深度学习模型 N-BEATS 相比,最佳 AutoML 变体 W 在 20 个数据集中的 14 个上胜出。其他 AutoML 系统(Auto-Keras、原始 auto-sklearn、T、WT)也在许多数据集上击败 N-BEATS,有时差距相当大。

要点总结

这项研究有几个关键发现。AutoML 配合经典模型与深度模型具有极强的竞争力,特别是结合良好的特征工程和窗口大小调整时。窗口大小是一等超参数——即使没有花哨的特征,调整它也能带来很大收益。tsfresh 特征有帮助,但不一定以预期的方式:总体来看,纯窗口大小变体 W 是最强的,而基于 tsfresh 的变体可能在特定领域或评估指标上更有优势。多输出策略是有限预算下多步预测的可靠默认选择。

案例研究 2:将文本作为时间序列处理

时间序列特征工程不只适用于传感器读数或金融数据。在 2020 年的 EPJ Data Science 文章中,Tang 等人把短文本样本重新解释为时间序列,然后应用 tsfresh 风格的特征提取来改进作者归属任务。

从文本到"语言时间序列"

先对每个文本样本分词,然后把每个 token 映射到一个数值度量——可以是它在语料库中的频率、按频率的排名、字符长度,或者对词计数向量的贡献等。按 token 在句子中的位置排列这些数值,就形成了"语言时间序列"。

他们实验了五种功能性语言序列映射,包括 token 频率序列、token 排名序列、token 长度序列,以及基于分布的序列(如 token 长度分布和 token 排名分布)。每个结果序列都像普通时间序列一样处理。

文本上的时间序列特征提取

对于这五种映射中的每一种,他们用 tsfresh(ComprehensiveFCParameters)每个序列提取 794 个时间序列特征,最终得到每个文本样本 3970 个风格计量特征(794 × 5 种映射)。用 tsfresh 的 impute 函数处理缺失值和无穷值,用 10 折交叉验证评估模型,以 log loss 作为主要指标。

这些时间序列特征然后与标准 NLP 基线(朴素贝叶斯和最近质心分类器)的预测结合,用 XGBoost 构建混合分类器。

结果和见解

他们在两个数据集上进行了测试:Spooky Books(平衡类别,恐怖小说)和联邦党人文集(不平衡,历史上很重要的论文)。

在 Spooky Books 案例中,语言时间序列特征持续改进了基线 NLP 模型。对于联邦党人文集,将这些特征加到强 NLP 基线中带来了较小但仍有希望的改进。

一些特定的 tsfresh 特征在语言学上具有很好的可解释性。平均 token 长度特征能区分倾向于使用长词还是短词的作者。token 长度序列上的 c3 非线性统计量捕捉了词长波动的微妙模式。token 长度分布上的线性趋势特征(截距和斜率)能反映作者是倾向于使用均匀范围的词长还是集中于较短的词。

作者的结论是,时间序列特征提取提供了新颖的风格计量信号,可以增强传统 NLP 特征,这个功能性语言分析框架在更广泛的作者归属和风格分析任务中有潜力。

实用工作流程

整合前面的内容,这里给出一个可以用于时间序列项目(数值或文本)的具体流程。

首先要清楚定义预测任务:是单步还是多步预测?分类还是回归?

然后选择窗口策略。从 w 的合理范围开始(小时数据可以从 24–168 开始),如果可能的话把 w 作为可调超参数处理。

接着为 tsfresh 格式化数据。数值时间序列用 (id, time, value) 格式。文本的话,像 Tang 等人那样把句子转换为功能性语言序列(token 长度、频率、排名等)。

用 tsfresh 提取特征时,从 ComprehensiveFCParameters 开始探索完整的特征库,用 impute() 清理 NaN 和无穷值。

特征选择有几种方式:用 tsfresh 自带的相关性检验,或应用 Benjamini-Hochberg 这样的多重检验控制,或在模型中用正则化/特征重要性方法(基于树的模型、L1 正则化线性模型等)。

模型方面,如果做结构化实验,auto-sklearn 或 Auto-Keras 这样的框架可以搜索模型族和超参数。否则从梯度提升、随机森林或调优良好的神经网络这些强基线开始。

评估要充分。预测任务考虑 RMSE、MAE 和特定预测范围的误差。分类任务(包括文本)用准确率、log loss 和校准指标,最好配合交叉验证。

最后是解释关键特征。用特征重要性图或 SHAP 值看哪些 tsfresh 特征重要,把它们与领域知识联系起来:是否捕捉了季节性、波动性、体制变化或风格模式?是否揭示了不同组之间的差异(作者、患者类型、设备状态等)?

总结

从数值到文本领域,这些工作传达的信息很明确。

时间序列的特征工程远未过时——它只是变得更系统化和自动化了。AutoML 系统可以把 tsfresh 这样的时间序列特定组件纳入进来,效果很好,通常能在许多任务上与最先进的神经模型匹敌甚至超越。把文本这样的非传统数据当作时间序列处理,开启了一个全新的特征和分析工具空间。

如果正在构建预测或序列分类流水线,值得尝试 tsfresh 或类似的特征库、能同时调整模型和窗口大小的 AutoML 框架,以及"语言时间序列"这样的跨领域思路。工程特征带来可解释性,AutoML 提供灵活性,而如果这些研究有任何指示意义的话——实现最先进性能的机会相当不错。

引用:

https://avoid.overfit.cn/post/a96a4522adbf4d82a3b02b8c328b2306

作者:QuarkAndCode

我真的很反感有些人一边无脑吹捧 ai 编程,一边说手写代码是“古法编程”。

大语言模型确实是一项划时代的技术,它的技术边界也在不断的被突破,但是任何技术都是有边界的。
那些无脑吹捧 AI 编程的人,我很怀疑,他们是不是陷入一种“盲目的狂热”或者“拜 AI 教”。

一、编程的本质,计算机的范式(冯诺依曼架构)并没有发生改变。

有人将其类比为汇编到高级语言的进化,这是完全错误的。编程语言具备正交性,你的每次运行,结果是一致的。而大语言模型的结果是非正交性的,初始值的一点微小的改变,都会对结果产生巨大的影响。

编程和大语言模型在我看来,具有外在的相关性,但是本质上两者解决的是完全不同的两个问题,是求精确解和模糊解的的区别。

另外,现有的所谓代码生成,从本质上看,其实不过是将过去的 ctrl+c 、ctrl+v 自动化了,仍然是对现有解决方案的“复刻”。

二、从工程角度来看,ai 编程并没有降低开发的复杂度,而是从编码转移到了设计、验证等环节。

有人幻想,通过 ai 可以极大的降低软件开发的复杂度,这完全是幻想。

软件开发本质是对真实世界的投射和抽象,ai 编程可以降低一定的编码复杂度,但是它不可能降低真实世界的复杂度。

软件开发的真正复杂的地方也从来不是编码。

那些希望通过 ai 减轻码农负担的想法,终究是不现实的。别人花钱雇佣你,就是希望你来减轻复杂度的,如果你无法减少这种复杂度,或者有更廉价的方案,那别人雇佣你干什么呢?

当然,我不是建议大家不要学 ai,我反对的是那些只会简单的使用,却自鸣得意的。

我认为,应该从编写 agent 开始,真正的业务结合起来,而不是简单跑个页面,然后陷入一种虚假的自我满足。

引言

在上一篇文章中,我们探讨了 AI 绘画看似神奇的“魔法”背后的真相:它并非凭空创造,而是一个从混沌的噪点中,通过无数次“观察-脑补-修正”的循环,逐步建立秩序、生成图像的过程。理解了这一核心原理,一个自然的问题随之产生:我们该如何操控这个过程?是需要编写晦涩难懂的代码,还是有更直观、更易上手的方法?

答案是肯定的。今天,我们将介绍一位强大的幕后英雄——ComfyUI。作为一款基于节点流程的 Stable Diffusion 用户界面,ComfyUI 就像是一个透明的 AI 魔法工坊。它将复杂的 AI 生成过程拆解为一个个独立的模块,让使用者能够像搭积木一样,直观地构建和掌控自己的 AI 绘画工作流。本文将带领读者走进这个工坊,通过拆解一个最基础的文生图工作流,揭示每一个“积木”是如何分工协作,最终完成那场精彩的“脑补”大戏的。

第一部分:初识 ComfyUI —— AI 的可视化乐高

如果将传统的、集成度高的 AI 绘画 WebUI 比作一个功能齐全的“黑盒子”微波炉,用户只需放入食材、按下按钮即可得到成品,那么 ComfyUI 就更像是一套透明的乐高积木,或者一个开放式的中央厨房。

ComfyUI 的核心特点在于其“节点化 (Node-based)”的设计理念。在这里,每一个功能——无论是加载模型、处理文本,还是执行采样、解码图像——都被封装成了一个个独立的方块,称为“节点”。用户通过线缆将这些节点连接起来,定义数据的流向。

这种可视化流向的设计,使得 AI 的工作过程不再神秘。使用者看到了什么连接,AI 后台就执行了什么操作。数据从哪里来,到哪里去,经过了怎样的处理,一切都一目了然。更重要的是,这种极致的灵活性赋予了用户无限的创造空间。使用者可以根据自己的需求,像搭积木一样自由组合各种节点,构建出从简单到无比复杂的个性化创意工作流。

第二部分:解剖一只麻雀 —— 最基础的文生图工作流拆解

面对 ComfyUI 的界面,初学者可能会对满屏的节点和连线感到困惑。但无需担心,万丈高楼平地起。理解了最基础的工作流,就掌握了通往复杂应用的钥匙。下面展示的是一个最典型的 ComfyUI 文生图(Text-to-Image)工作流界面,我们将逐一拆解其中的核心角色。

1. 大管家:加载器 (Checkpoint Loader Simple)

一切工作的起点,是这个被称为“加载器”的节点。它就像是整个魔法工坊的物料仓库大管家。

它的作用是加载预先训练好的模型文件,通常称为 Checkpoint。这个文件至关重要,因为它打包了 AI 的核心能力:负责图像生成的“大脑”(UNet 网络)、负责理解文本的“眼睛”(CLIP 模型)以及负责图像数据转换的“翻译器”(VAE)。选择不同的 Checkpoint 文件,就决定了 AI 的“阅历”和基础“画风”,是擅长二次元动漫,还是写实摄影,全赖于此。它是所有后续工作的基石。

2. 翻译官与指挥棒:CLIP 文本编码器 (CLIP Text Encode)

人类使用自然语言描述画面,而 AI 的核心模型只能理解数学化的向量。这就需要“CLIP 文本编码器”充当人类与 AI 之间的沟通桥梁。

这个节点的作用是将用户输入的文本提示词(Prompt),“翻译”成 AI 能懂的数学指令,在技术上称为“条件 (Conditioning)”

在基础工作流中,通常会看到两个这样的节点。一个负责翻译正向提示词,生成“正向条件”,告诉 AI “画面里必须出现什么”(如:一只猫、高质量、阳光);另一个负责翻译反向提示词,生成“反向条件”,告诉 AI “画面里绝对不能出现什么”(如:低质量、变形、水印)。这两个条件就像是两根指挥棒,将在后续的生成过程中,严格引导和约束 AI 的创作方向。

3. 魔术师与沙盘:K 采样器 (KSampler)

“K 采样器”是整个工坊的核心车间,是奇迹真正发生的地方。它负责执行我们之前提到的“从噪点到清晰图像”的去噪循环。

为了高效地处理图像生成这一庞大的计算工程,AI 极其聪明地选择了一个策略:不在巨大的像素级画布上直接作画,而是在一个被称为“潜在空间 (Latent Space)”的沙盘上搭建一个精巧的“小模型”(潜在图像)。KSampler 就是在这个沙盘上进行精细化作业的魔术师。因为它处理的是高度浓缩的信息,而非海量的像素数据,所以效率极高。

这位魔术师在沙盘上工作时,并非随心所欲。它需要三种原料:从加载器获取的“模型”能力、一个初始的“空白画布”(通常是一个纯噪声的潜在图像),以及最重要的——从文本编码器传来的两根“指挥棒”。

在设定的步数内,KSampler 执行着“观察-脑补-修正”的循环。在每一步操作中,它都会严格参照“正向条件”的指南和“反向条件”的禁令,努力将沙盘上混沌的噪声,逐步转化为符合人类要求的、有意义的“小模型”。

4. 神奇打印机:VAE 解码 (VAE Decode)

当 KSampler 在沙盘上完成了创作,我们得到的是一个“潜在图像”。它虽然包含了画面的所有核心信息,但却是一团人类肉眼无法辨识的压缩数据。

这时就需要“VAE 解码”节点出场了。它就像是一台神奇的建筑打印机。它接过沙盘上那个抽象的“小模型”,利用大管家提供的 VAE 工具(图像数据转换的翻译器),按照特定的规则将这份压缩数据“解压”,并最终“打印”成我们眼前这座宏伟、清晰、色彩斑斓的像素大图。

5. 展示台:保存/预览图像 (Save/Preview Image)

工作流的终点是“保存/预览图像”节点。它的任务非常直观:将 VAE 解码器输出的最终像素图像展示在界面上供用户检阅,并将其保存到计算机的硬盘中,完成整个创作流程。

第三部分:连线——让数据流动起来

在 ComfyUI 中,节点之间的连线不仅仅是视觉上的连接,它们代表了数据显性的流动路径。理解了连线,就理解了 AI 工作的逻辑。

就像不同形状的积木插口一样,ComfyUI 中只有相同类型的数据端口才能连接,这保证了流程的正确性。

  • 模型连模型 (MODEL):将加载器中的绘画能力传递给采样器。
  • 条件连条件 (CONDITIONING):将文本编码器生成的“指挥棒”传递给采样器,指引创作方向。
  • 潜在图像连潜在图像 (LATENT):在采样器和解码器之间传递那个核心的沙盘“小模型”。
  • VAE 连 VAE (VAE):将加载器中的翻译规则传递给解码器,用于最终图像的还原。

整个流程可以总结为一条清晰的主线:加载模型备物料 -> 输入文字变指挥棒 -> 准备沙盘造噪声 -> 采样核心搞创作(受指挥棒引导) -> VAE 解码打印出图像。

结语

ComfyUI 以其独特的节点化设计,看似复杂,实则提供了一种最直观、最透彻的方式来理解和掌控 AI 绘画。它将深奥的 AI 生成原理拆解为一个个清晰可见的步骤,让我们不仅能“知其然”(看到最终的精美图像),更能“知其所以然”(理解图像是如何一步步生成的)。

通过理解“潜在空间”这个高效运作的沙盘,以及“条件”这两根强有力的指挥棒,我们揭开了 AI 绘画魔法的一角。掌握基础工作流只是第一步,ComfyUI 的魅力在于其无限的扩展性。鼓励每一位使用者去探索更多的高级节点,如 ControlNet、LoRA 等,搭建属于自己的、独一无二的 AI 绘画流水线,释放无限的创造潜能。

本文由mdnice多平台发布

本人 C 开发,工作主要使用 C 写一些 dpdk vpp 插件和一些 Shell 、python 脚本,公司安排了 AI agent 开发培训,所以搞了一个 Cursor vibe 了一个塞尔达传说旷野之息的交互式地图。前端在大学玩过 jQuery, 没有开发经验。

https://i.imgur.com/X6dCUM4.jpeg

  • 虽然是 Vibe 的,但是我还是花了一天学习了一下 Reactleaflet L.CRS.Simple模式;最后也把所有代码都看懂了;

预览:https://echoechoin.github.io

人在大马,需要经常访问国内家里的服务器(无公网 ip )
手上还有一台香港 CN2 机器
预期是手上的各种移动设备都能无缝访问内网,所以用上了 tailscale
目前尝试的方案:
大马——tailscale p2p——内网:可以打洞成功,但晚高峰几乎不可用
大马——tailscale——香港,香港 frps——hy2——内网 frpc:小火箭直接连 hy2 节点是正常的,但加入 tailscale 后就不行了,涉及到透明代理,折腾一下午没搞定,放弃
大马——tailscale——香港自建 derp+peer relay ——内网:可用,但不稳定,经常存在过一段时间不可用的情况

大马——tailscale——香港,香港 frpc——openvpn——内网 frps:这是多天尝试下来最好用的方案,但 openvpn 容易被识别,担心用两天就被封了

问 ai 也没啥好方案,求大佬们支招

试了很多之前的 流行的 远程控制软件, 只要是 wayland 都是提示不支持,
不过尝试了系统自带的远程控制和远程登录
发现 其实设置好了也比较好用流畅
动画效果 都可以录制,wayland 一些录屏软件,无法录制窗口动画效果,用 rdp 居然可以。
如下
[现代 Linux 中 Wayland 桌面环境的远程控制解决方案] https://www.bilibili.com/video/BV1JCrWBiE1e/?share_source=copy_web&vd_source=6609c6b7f4319c8c5053dec7ae215bae

唯一要的是需要在路由器中设置正确的 net 端口映射