编程笔记

lifelong learning & practice makes perfect

以下是2025年9月15日至16日过去24小时内最重要的AI和技术发展总结,优先关注模型发布、新论文和开源项目。信息基于网络搜索和X平台数据,包含来源链接。内容聚焦于关键公告、工具更新和创新。

模型发布与更新

  • OpenAI 升级 Codex,使用新版 GPT-5:OpenAI于9月15日发布Codex的升级版本,集成新GPT-5模型,支持更复杂的编码任务,并引入gpt-realtime功能,提升实时交互能力。 来源:TechCrunchOpenAI News.
  • Alibaba 发布 Qwen 3-Next-80B:这是一个高效混合模型,仅激活约30亿参数,支持10倍速度提升和更低成本,适用于任务优化。 来源:PaleBlueDot AI on X.
  • ByteDance 发布 Seedream 4.0:融合图像生成与编辑,支持2K分辨率输出,生成时间不到2秒。 来源:PaleBlueDot AI on X.
  • Kimi Moonshot AI 发布 K2-0905:1万亿参数MoE模型,支持256k上下文长度,针对代理开发任务,如全代码库处理。 来源:PaleBlueDot AI on X.
  • Meta 发布 MobileLLM-R1:小型LLM模型,针对非商业研究,提升移动设备上的语言处理效率。 来源:merve on X.
  • Tencent 发布 SRPO:高分辨率图像生成模型,以及Points-Reader OCR模型,提升文本识别准确性。 来源:merve on X.
  • ByteDance 发布 HuMo:视频生成模型,支持任意输入生成视频。 来源:merve on X.
  • xAI 发布新AI模型:专注于增强对物理世界的理解和操作,推动机器人和自治系统进步。 来源:Daily 5 Minutes News on X.
  • AnthropicAI 更新 Claude 3.7:聚焦自然语言处理改进和实时应用延迟降低。 来源:Daily 5 Minutes News on X.

新论文

  • arXiv 上新AI论文(9月16日):包括266篇新论文,亮点如“Is the ‘Agent’ Paradigm a Limiting Framework for Next-Generation Intelligent Systems?”(质疑代理范式对下一代智能系统的限制),以及其他涉及诊断精神患者、生成AI故事等主题。 来源:arXiv Artificial Intelligence Recent.
  • 机器学习论文更新:焦点包括使用大语言模型诊断精神患者,以及其他如模拟酵母着丝粒、诊断一维CNN等。 来源:arXiv Machine Learning Current.
  • 新兴技术论文:45篇新作,如新西兰房屋能源效率AI工具原型。 来源:arXiv Emerging Technologies Current.
  • Harvard AI论文:PDGrapher:AI工具发现逆转细胞疾病的药物组合。 来源:jeromekjerome on X.

开源项目与工具

  • MistralAI 发布新开源模型:旨在民主化高性能语言处理工具访问。 来源:Daily 5 Minutes News on X.
  • HuggingFace 更新模型仓库:新增50+ NLP和计算机视觉AI模型。 来源:Daily 5 Minutes News on X.
  • StabilityAI 扩展文本到图像功能:提供更高分辨率和更快处理。 来源:Daily 5 Minutes News on X.
  • Replit Agent 3:自主代理工具,用于构建生产级应用,支持10倍自治能力。 来源:AI News on X.
  • NVIDIA 发布 Physics Nemo:物理模拟开源模型。 来源:AI News on X.
  • Switzerland 发布 Apertus:国家级开源AI模型。 来源:The AI Track.

其他重要公告与更新

  • OpenAI 与 Microsoft 重组:签署非约束性谅解备忘录,推动OpenAI向营利性转型,非营利部门保留1000亿美元以上股权。 来源:PaleBlueDot AI on X.
  • OpenAI 获得66亿美元融资:加速AI研发。 来源:Daily 5 Minutes News on X.
  • NVIDIA Rubin CPX 发布:新加速器用于推理预填充阶段,减少对HBM依赖。 来源:PaleBlueDot AI on X.
  • DeepMind 发布新AI伦理框架:指导全球AI系统的负责开发和部署。 来源:Daily 5 Minutes News on X.

介绍如何使用gemini-cli自定义斜杠命令,该功能允许用户定义可重用的提示,以简化交互并提高效率。这些斜杠命令可以在本地 .toml 文件中定义,也可以通过模型上下文协议 (MCP) 提示定义。如何使用 .toml 文件创建自定义斜杠命令?将以一个用于审查 GitHub pull request 的 .toml 文件示例,重点介绍了在提示中使用参数和执行 shell 命令。还介绍了命名空间,它允许使用子目录将相关命令分组在单个命名空间下。文章提供了构建斜杠命令的分步指南,包括创建命令文件、添加命令定义以及在 Gemini CLI 中使用该命令。

阅读全文 »

discusses the new support for custom slash commands in Gemini CLI, a feature that allows users to define reusable prompts for streamlining interactions and improving efficiency. These slash commands can be defined in local .toml files or through Model Context Protocol (MCP) prompts

阅读全文 »

以下介绍一种减少LLM在执行复杂任务过程中产生幻觉的技巧,让ai更好完成任务,示例需结合gemini-cli使用,在其他工具中使用方法类似

提示词

指定ai能使用的工具,定义好输入/输出格式,拆分步骤,设定好笔记格式,让ai分步执行,并在每步完成后记到笔记文件中直到完成任务.

如下是一个翻译网页提示词:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
输入:
文章URL:https://cloud.google.com/blog/topics/developers-practitioners/gemini-cli-custom-slash-commands

输出:
1. 格式化后的原文article.md
2. 中英文双语版article-en-cn.md
3. 中文版article-cn.md
4. 文章中所有的图片资源

每完成一步,都必须更新progress.md

步骤0: 生成笔记
- 仿照例子和当前任务生成笔记 progress.md

步骤1: 访问网站
- 访问上文输入中网址
- 必须使用 "lynx -dump -image_links URL"命令访问网站
- 网站内容保存在raw.txt中

步骤2:下载图片
- 从raw.txt中提取文章相关图片链接
- 把图片链接写入 progress.md
- 逐一下载到 resources/ 文件夹
- 每下载完成一个图片,必须更新图片下载进度
- 你必须使用curl命令进行下载

步骤3:改写成markdown
- 把raw.txt改写成markdown格式
- 保存在article.md中
- 将article.md中的图片链接指向 resources/ 文件夹

步骤4:翻译成中英文
- 把article.md翻译成中英文对照
- 保存在article-en-cn.md中

步骤5:翻译成中文
- 提取article-en-cn.md中的中文
- 保存在article-cn.md中

----
progress.md 笔记格式

## 任务
[x] xxxxx
[ ] yyyyy
[ ] zzzzz
...

## 图片下载进度
[x] https://xxxx/yyy.png
[ ] https://foo/bar.png
...

## 当前任务
正在下载https://foo/bar.png

封装为gemini-cli命令

将提示词写到文件中 ~/.gemini/commands/translate.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
prompt = """
输入:
文章URL:{{args}}

输出:
1. 格式化后的原文article.md
2. 中英文双语版article-en-cn.md
3. 中文版article-cn.md
4. 文章中所有的图片资源

每完成一步,都必须更新progress.md

步骤0: 生成笔记
- 仿照例子和当前任务生成笔记 progress.md

步骤1: 访问网站
- 访问上文输入中网址
- 必须使用 "lynx -dump -image_links URL"命令访问网站
- 网站内容保存在raw.txt中

步骤2:下载图片
- 从raw.txt中提取文章相关图片链接
- 把图片链接写入 progress.md
- 逐一下载到 resources/ 文件夹
- 每下载完成一个图片,必须更新图片下载进度
- 你必须使用curl命令进行下载

步骤3:改写成markdown
- 把raw.txt改写成markdown格式
- 保存在article.md中
- 将article.md中的图片链接指向 resources/ 文件夹

步骤4:翻译成中英文
- 把article.md翻译成中英文对照
- 保存在article-en-cn.md中

步骤5:翻译成中文
- 提取article-en-cn.md中的中文
- 保存在article-cn.md中

----
progress.md 笔记格式

## 任务
[x] xxxxx
[ ] yyyyy
[ ] zzzzz
...

## 图片下载进度
[x] https://xxxx/yyy.png
[ ] https://foo/bar.png
...

## 当前任务
正在下载https://foo/bar.png

"""

即可这样使用

1
2
3
gemini

/translate https://cloud.google.com/blog/topics/developers-practitioners/gemini-cli-custom-slash-commands

参考

  1. video
  1. prompt

在 Redis 中,BGREWRITEAOF 是一个用于重写 AOF(Append Only File)文件的后台操作,目的是压缩 AOF 文件,去除冗余命令,从而减小文件体积并提升恢复速度。然而,在执行 BGREWRITEAOF 期间,主进程仍在处理客户端请求,可能会产生新的写入操作。这就带来了一个潜在问题:主进程与子进程(BGREWRITEAOF 子进程)之间可能出现数据不一致

问题本质:主进程与子进程的数据不一致

  • 主进程:正常处理写操作,将命令追加到 AOF 缓冲区,并可能写入 AOF 文件。
  • 子进程(BGREWRITEAOF):在某一时刻(通常是 BGREWRITEAOF 命令执行时)启动,它会读取当前 Redis 的完整数据集(内存中的键值对),并将其以命令形式写入一个新的 AOF 文件
  • 问题:在子进程执行期间,主进程可能已经接收并处理了新的写操作,这些写操作没有被子进程看到,导致新 AOF 文件中缺少这部分数据。

Redis 如何解决这个问题?

Redis 采用了一种称为 “AOF 重写缓冲区”(AOF rewrite buffer) 的机制来解决这一问题。

✅ 核心机制:AOF 重写缓冲区(AOF Rewrite Buffer)

  1. 子进程启动时

    • Redis 主进程会创建一个新的 AOF 文件(临时文件,如 temp-rewriteaof-bg-*.aof)。
    • 子进程开始读取内存中的数据集,并将所有键值对以命令形式写入这个新文件。
  2. 主进程在子进程运行期间

    • 所有新的写操作(如 SET key value)不仅写入 AOF 缓冲区,还会额外写入一个特殊的缓冲区 —— AOF 重写缓冲区
    • 这个缓冲区是独立的,用于记录子进程运行期间的增量操作。
  3. 子进程完成重写后

    • 子进程退出。
    • 主进程将 AOF 重写缓冲区中的所有命令追加写入到新 AOF 文件末尾
    • 然后将新 AOF 文件原子替换为旧的 AOF 文件(通过 rename 系统调用)。
  4. 最终结果

    • 新的 AOF 文件包含了:
      • 子进程重写时的完整数据集(即快照)。
      • 子进程运行期间所有新增的写操作。
    • 因此,新 AOF 文件是完整且一致的,能完整恢复 Redis 的状态。

关键点

机制 作用
AOF 重写缓冲区 缓存子进程运行期间的增量写操作
子进程读内存快照 生成 AOF 的基础数据(无冗余命令)
主进程追加缓冲区内容 确保不丢失子进程运行期间的写操作
原子替换文件 保证 AOF 文件切换的原子性和一致性

注意事项

  1. AOF 重写缓冲区大小有限

    • 如果缓冲区溢出(例如写入太频繁),Redis 会阻塞主进程,直到子进程完成或缓冲区清空。
    • 可通过配置 aof-rewrite-incremental-fsyncaof-rewrite-buffer-size 调优。
  2. 性能影响

    • BGREWRITEAOF 是 CPU 密集型操作,可能影响主进程性能。
    • 建议在低峰期执行,或使用 auto-aof-rewrite 自动触发。
  3. AOF 重写期间主进程仍可服务

    • 重写过程是异步的,主进程不阻塞(除了缓冲区满时可能阻塞)。

示例流程

1
2
3
4
5
6
7
8
# 主进程执行 BGREWRITEAOF
BGREWRITEAOF

# 1. 子进程启动,读取内存快照,写入 temp-rewriteaof-bg-*.aof
# 2. 主进程开始写入 AOF 文件 + AOF 重写缓冲区
# 3. 子进程完成,退出
# 4. 主进程将重写缓冲区内容追加到新 AOF 文件
# 5. 原 AOF 文件被 rename 为 backup,新文件替换它

✅ 总结

Redis 通过 AOF 重写缓冲区 机制,完美解决了 BGREWRITEAOF 与主进程数据不一致的问题:

子进程负责“快照”,主进程负责“增量”,最后合并,确保 AOF 文件完整、一致、可恢复。

sync.RWMutex 的基本特性

  • 值类型: sync.RWMutex 是一个 struct。这意味着如果你直接传递它,你将得到一个副本。对副本的锁定操作不会影响原始的 mutex,这会导致同步失效。
  • 不应在第一次使用后复制: Go 官方文档明确指出,sync.Mutex (以及 sync.RWMutex) 不应在第一次使用(即调用 Lock/UnlockRLock/RUnlock)后进行复制。复制会导致未定义的行为,通常是死锁或 panic。
  • 零值可用: sync.RWMutex{} (零值) 是一个有效的、可直接使用的 mutex。

在局部函数中创建并返回 *sync.RWMutex 的情况

考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"fmt"
"sync"
"time"
)

// 情况 A: 函数内部创建 RWMutex 并返回其指针
func createAndReturnRWMutex() *sync.RWMutex {
var mu sync.RWMutex // 局部变量,Go的逃逸分析会将其分配到堆上
fmt.Println("Mutex created at address:", &mu)
return &mu
}

func main() {
// 获取由 createAndReturnRWMutex 函数创建的 mutex 指针
mu1 := createAndReturnRWMutex()
mu2 := createAndReturnRWMutex() // 再次调用,会得到一个新的 mutex

fmt.Println("Returned mu1 address:", mu1)
fmt.Println("Returned mu2 address:", mu2)

// 使用 mu1
mu1.RLock()
fmt.Println("mu1 RLocked")
go func() {
mu1.Lock() // 这个地方会等待上面的 RLock 释放
fmt.Println("mu1 Locked by goroutine")
time.Sleep(100 * time.Millisecond)
mu1.Unlock()
fmt.Println("mu1 Unlocked by goroutine")
}()
time.Sleep(50 * time.Millisecond) // 等待 goroutine 尝试加锁
mu1.RUnlock()
fmt.Println("mu1 RUnlocked")

// 确保 goroutine 完成
time.Sleep(200 * time.Millisecond)

// 使用 mu2 (与 mu1 独立)
mu2.Lock()
fmt.Println("mu2 Locked")
mu2.Unlock()
fmt.Println("mu2 Unlocked")
}

分析:

  1. 技术上可行: Go 的逃逸分析会识别到局部变量 mu 的地址被返回了函数外部,因此它会被分配到堆上(heap),而不是栈上。所以,返回的指针是有效的,不会指向已被销毁的内存。
  2. 不常见,且通常不是好的设计模式:
    • 封装性差: sync.RWMutex 的核心作用是保护共享数据。它几乎总是作为某个数据结构(struct)的字段而存在,由该数据结构的方法来管理其锁定和解锁,以保护该结构体内部的数据。
    • 职责不清: 如果一个函数仅仅返回一个 *sync.RWMutex,那么这个 mutex 是为了保护什么数据?这个信息并没有被封装起来,使用者需要自己去追踪这个 mutex 的用途,这增加了代码的复杂性和出错的风险。
    • 容易误用: 调用者可能会不清楚这个 mutex 应该保护什么数据,或者在不适当的时候加锁/解锁,导致死锁、竞争条件等问题。
    • 代码可读性差: 相比于一个明确的结构体(例如 SafeCache)包含一个 RWMutex,仅仅返回一个 RWMutex 指针会让代码意图模糊。

在局部函数中返回已存在*sync.RWMutex 指针

另一种情况是,函数不是创建新的 RWMutex,而是返回一个指向已经存在的 RWMutex 的指针,比如从一个全局变量或者传入参数的结构体中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "sync"

var globalMu sync.RWMutex

type MyContainer struct {
Mu sync.RWMutex
Data int
}

// 情况 B: 返回全局 RWMutex 的指针
func getGlobalRWMutex() *sync.RWMutex {
return &globalMu
}

// 情况 C: 返回结构体字段 RWMutex 的指针
func getContainerRWMutex(c *MyContainer) *sync.RWMutex {
return &c.Mu
}

func main() {
gMu := getGlobalRWMutex()
gMu.Lock()
// ...
gMu.Unlock()

container := &MyContainer{}
cMu := getContainerRWMutex(container)
cMu.RLock()
// ...
cMu.RUnlock()
}

分析:

  • 技术上可行: 同样,返回的指针是有效的。
  • 合适性存疑:
    • 暴露内部实现: 这种做法打破了封装性。一个结构体通常应该通过其方法来提供对数据(包括同步机制)的受控访问,而不是直接暴露其内部的 sync.RWMutex 字段。
    • 耦合性增加: 外部代码直接操作内部 mutex 会增加模块间的耦合,使得结构体的内部实现更难修改。
    • 潜在的API风险: 如果 MyContainer 的设计者希望控制对 Data 的访问方式,而调用者直接使用 c.Mu 进行锁操作,可能会绕过 MyContainer 提供的方法,导致不一致或错误的状态。

推荐做法

在 Go 中,管理 sync.RWMutex惯用和最佳实践是将其嵌入到一个结构体中,并通过该结构体的方法来管理锁定和解锁,从而保护结构体自身的字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
"fmt"
"sync"
)

// SafeCounter 是一个线程安全的计数器
type SafeCounter struct {
mu sync.RWMutex // RWMutex 作为结构体的字段
count int
}

// Inc 增加计数器的值
func (sc *SafeCounter) Inc() {
sc.mu.Lock() // 方法内部管理锁
defer sc.mu.Unlock()
sc.count++
}

// Value 返回当前计数器的值
func (sc *SafeCounter) Value() int {
sc.mu.RLock() // 方法内部管理读锁
defer sc.mu.RUnlock()
return sc.count
}

// NewSafeCounter 是 SafeCounter 的构造函数
func NewSafeCounter() *SafeCounter {
// RWMutex 的零值是可用的,无需额外初始化
return &SafeCounter{}
}

func main() {
counter := NewSafeCounter() // 返回的是 *SafeCounter,而不是 *sync.RWMutex

var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Inc() // 通过 SafeCounter 的方法访问计数器,由方法处理同步
}()
}

wg.Wait()
fmt.Println("Final Counter Value:", counter.Value())
}

总结

在Go语言中,一般不推荐在局部函数中创建 sync.RWMutex 并单独返回其指针。这通常表示设计上存在封装性不足的问题。

更推荐的做法是:

  1. sync.RWMutex 作为你希望保护的结构体的字段。
  2. 通过该结构体的方法来封装对数据的访问和 sync.RWMutex 的锁定/解锁操作。
  3. 如果需要提供一个同步的实例,你的构造函数应该返回一个指向该结构体实例的指针(例如 *SafeCounter),而不是单独的 *sync.RWMutex 指针。

这样做可以提高代码的封装性、可读性和健壮性,减少误用的可能性。

Go 语言自 1.18 版本开始支持泛型(Generics)。泛型允许我们为函数和数据结构定义类型参数,增强代码的复用性和类型安全性。然而,泛型并不是在所有情况下都适用,正确地使用泛型能带来代码清晰性和性能提升,错误使用则可能使代码复杂或降低性能。

本文旨在帮助你了解在什么情况下应该考虑使用泛型。

泛型的优点

  1. 代码复用性:泛型允许用一种实现支持多种类型,减少代码重复。
  2. 类型安全:在编译时检查类型,防止运行时类型错误。
  3. 抽象能力:抽象地处理数据结构和算法,提升代码表达力。
  4. 性能:避免因为使用interface{}类型带来的类型断言,从而减少运行时开销。

何时使用泛型?

泛型适用于以下场景:

  • 你有一组算法或数据结构逻辑,它们在不同的类型上运作,但逻辑完全相同。
  • 你想写高度通用的代码来简化代码库,同时保持类型安全。
  • 你的代码需要同时处理多种类型,但不想牺牲性能。
  • 你希望避免冗余代码,减少维护工作量。

何时不适合使用泛型?

泛型不是万金油,某些情况下反而带来负担:

  • 只有单一具体类型需求时,泛型会增加代码复杂度。
  • 泛型代码过于复杂,导致可读性变差,增加维护难度。
  • 性能占优且简单逻辑可以接受轻微重复代码时,不必强制用泛型。
  • 当泛型参数变得过于复杂(过多约束、嵌套)时,可能适得其反。

不要把interface类型替换为类型参数

Go语言有interface类型,interface支持某种意义上的泛型编程。

举个例子,被广泛使用的io.Reader接口提供了一种泛型机制用于读取数据,比如支持从文件和随机数生成器里读取数据。

如果你对某些类型的变量的操作只是调用该类型的方法,那就直接使用interface类型,不要使用类型参数。io.Reader从代码角度易于阅读且高效,没必要使用类型参数。

举个例子,有人可能会把下面第1个基于interface类型的ReadSome版本修改为第2个基于类型参数的版本。

1
2
3
func ReadSome(r io.Reader) ([]byte, error)

func ReadSome[T io.Reader](r T) ([]byte, error)

不要做这种修改,使用第1个基于interface的版本会让函数更容易编写和阅读,并且函数执行效率也几乎一样。

注意:尽管可以使用不同的方式来实现泛型,并且泛型的实现可能会随着时间的推移而发生变化,但是Go 1.18中泛型的实现在很多情况下对于类型为interface的变量和类型为类型参数的变量处理非常相似。这意味着使用类型参数通常并不会比使用interface快,所以不要单纯为了程序运行速度而把interface类型修改为类型参数,因为它可能并不会运行更快。

用例示例

例如处理一组不同类型的切片排序、查找操作,可以使用泛型函数避免重复编写针对不同类型的代码。

非泛型版本

1
2
func IntContains(slice []int, val int) bool { ... }
func StringContains(slice []string, val string) bool { ... }

上述重复很多类似逻辑。

泛型版本

1
func Contains[T comparable](slice []T, val T) bool { ... }

案例2: Go内置的容器类型

入参是一个map,要返回该map的所有key组成的slice,key的类型可以是map支持的任意key类型。

1
2
3
4
5
6
7
8
9
// MapKeys returns a slice of all the keys in m.
// The keys are not returned in any particular order.
func MapKeys[Key comparable, Val any](m map[Key]Val) []Key {
s := make([]Key, 0, len(m))
for k := range m {
s = append(s, k)
}
return s
}

这段代码没有对map里key的类型做任何限定,并且没有用map里的value,因此这段代码适用于所有的map类型。这就是使用类型参数的一个很好的示例。

这种场景下,也可以使用反射(reflection),但是反射是一种比较别扭的编程模型,在编译期没法做静态类型检查,并且会导致运行期的速度变慢。

结语

泛型是强大的工具,但需用时权衡复杂度与收益。建议代码在泛型和具体类型之间平衡。合适时用泛型,实现代码复用和类型安全;不合适则回归简单可理解方案。

如果你正在考虑是否使用泛型,问自己:

  • 这段代码是否适用于多种类型?
  • 是否存在类型重复代码?

参考

redis

“两大维度”就是指系统维度和应用维度,“三大主线”也就是指高性能、高可靠和高可扩展(可以简称为“三高”)

系统维度

需要了解 Redis 的各项关键技术的设计原理

三大主线

  • 高性能: 线程模型、数据结构、持久化、网络框架;
  • 高可靠: 主从复制、哨兵机制;
  • 高可扩展: 数据分片、负载均衡。

常见问题

redis