TGINSIGHT CHAT
Welcome to the Black Parade
@TheB1ackParade
MusicDeath has many faces, I look forward to seeing this one.
Recent posts
Page 3 of 76 · 907 posts
Posted Feb 19
实在太喜欢知乎酱紫君了,昨天 (2026-02-19) ta 在指正 AI 乱证黎曼猜想的回答里发表了对 AI 邪教徒的宣言简直鞭辟入里 😭 知乎就因为有酱紫君这样的答主始终在中文互联网断档存在,唯一的面向大众还可以严肃讨论科学、数学和工程的平台(还可以键政😃) https://www.zhihu.com/question/2005989142379663699/answer/2007623254848845820 有时候看到大家说 AI 能很好地解决问题、实现想法、解放生产,于是很高兴不用写代码、或很沮丧写代码写不过 AI,我看到这些觉得有点遗憾:原来只有我是真的喜爱写代码。认真写测试,收敛复杂度,降低项目熵,解决预期之外的技术问题,学到从未听说的新知识,无数次抬头敬仰这座大山,欣赏这一路的风景,一次次感叹计算机工程的神奇和有趣,任何 AI 都无法取代这份快乐。可惜便纵有千种风情,更与何人说?
Posted Feb 18
隔壁频道春节都还住在 apache.org,而我沉迷韩国恋综《单身即地狱》第5季,目前看到 E8 最喜欢的男女嘉宾是林琇滨和金珉志,太酷了! (睡醒就要上班了🤬😭🤯
Posted Feb 16
除Xi夜大家都在和AI贴贴,只有我在世界之石要塞PTSD 😓
Posted Feb 15
德鲁伊这个狼头帽子挺萌的,这何尝不是一种福瑞?
Posted Feb 13
毕业后第一份工作的国企大领导被查了,当时所有和他共事过的人一提到他就说牛逼,那些他用智慧和决心克服重重施工困难的传奇至今仍有隐约的耳语,那些故事不断塑造我那初入社会的世界观,让我思考如果是我在那个位置应该如何处理困境。这数十载的人世游真是令人扼腕叹息。
Posted Feb 12
https://store.steampowered.com/app/2536520/Diablo_II_Resurrected__Infernal_Edition/ 菠萝二re + DLC 登陆 steam,暴雪再爱我一次 😭 少年时期和暗黑2的很多美好记忆碎片一直在岁月里闪闪发光,爱在回忆里总是那么明白。
Posted Feb 10
我们考虑一个 c 程序,主线程 epoll_wait 一个空 epfd 超时设置 2ms,循环不干事;另一个线程 nanosleep 17ms 也循环不干事。代码长得像这样 // main thread for (;;) { epoll_wait(epfd, events, 1, 2); } // sub thread struct timespec interval = {0, 17 *1000 * 1000}; for (;;) { nanosleep(&interval, NULL); } 很显然我们应该预期这个进程 cpu 消耗接近 0,毕竟大部分时间都在睡觉,不过睡得好像有点过于频繁,主线程睡 2ms 醒一次,但问题不大,在我笔记本电脑上绑定单 cpu 运行,perf stat 显示只消耗 0.7% 的 cpu,符合预期。 $ perf stat -ddd -p $(pidof a.out) -- sleep 1 6.60 msec task-clock # 0.007 CPUs utilized 正片开始,我们用 go 按照正经生产环境最佳实践移植一版,非常简练。 ticker := time.NewTicker(17 * time.Millisecond) defer ticker.Stop() go func() { for range ticker.C {} }() epfd, _ := unix.EpollCreate1(unix.EPOLL_CLOEXEC) events := make([]unix.EpollEvent, 1) for { unix.EpollWait(epfd, events, 2) } 猜猜看,它跑起来消耗多少 cpu? 2.7% 的 cpu!是 c 版本的四倍消耗! 当然 2.7% 是很少的 cpu,但问题是这程序啥都没干啊,光睡觉睡掉了接近 3% 的 cpu,我看到监控的时候惊呆了,我精心射进的架构预期在大部分时间都是接近静默,零消耗,结果这起手就 3% 了,我还没开始正经干活呢。 执行一遍 perf record + perf report,发现 73.51% cpu 时间消耗在 x64_sys_call,锁定在 syscall 调用上,让我们来看每秒钟 syscall 调用统计。 c 版本 $ timeout 1 strace -c -fTttp $(pidof a.out) % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 56.60 0.008679 152 57 clock_nanosleep 42.51 0.006518 13 476 epoll_wait 0.89 0.000137 137 1 restart_syscall ------ ----------- ----------- --------- --------- ---------------- 100.00 0.015334 28 534 total 大致估算一下 1s / 2ms = 500 次 epoll_wait,1s / 17 ms = 58 次 nanosleep,完全合理。 go 版本 % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 45.19 0.041057 22 1849 54 futex 28.46 0.025861 228 113 epoll_pwait 20.93 0.019016 41 459 epoll_wait 5.33 0.004845 6 734 nanosleep 0.08 0.000073 5 13 sched_yield 0.00 0.000004 4 1 1 restart_syscall ------ ----------- ----------- --------- --------- ---------------- 100.00 0.090856 28 3169 55 total 太鬼畜了,futex 每秒钟调用近两千次,然后是 nanosleep 从预期的 58 次爆炸到 700+,太坏了。 用 strace -e futex,nanosleep -k -fTttp 检查一下是谁在调用这些 syscall [pid 50060] 00:13:03.796991 futex(0x52d220, FUTEX_WAKE_PRIVATE, 1) = 1 <0.000008> > (runtime.futex.abi0+0x23) [0x6c8a3] > (runtime.notewakeup+0x25) [0xe325] > (runtime.startm+0x209) [0x3bb29] > (runtime.handoffp+0x18d) [0x3bd8d] > (runtime.retake+0x255) [0x43b35] > (runtime.sysmon+0x345) [0x437c5] > (runtime.mstart1+0x93) [0x3a013] > (runtime.mstart0+0x75) [0x39f55] > (runtime.mstart.abi0+0x5) [0x68bc5] [pid 50060] 00:23:57.248078 nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0 <0.000377> > (runtime.usleep.abi0+0x37) [0x6c2d7] > (runtime.sysmon+0xa5) [0x43525] > (runtime.mstart1+0x93) [0x3a013] > (runtime.mstart0+0x75) [0x39f55] > (runtime.mstart.abi0+0x5) [0x68bc5] 原来是(以下很可能有事实错误,请 go 语言专家斧正) go runtime 有个 sysmon 线程在监控和调度,但是在 sysmon 线程自身的睡觉节省 cpu 这件事情上 go runtime 试图做得聪明点,有个 adaptive 的 sleep time,如果发现经常被唤醒,就睡短一点,短到 20 us 也就是上面的 nanosleep ({0, 20000});否则可以睡得长一点进入 deep sleep,不过 deep sleep 的时候如果被 syscall exit 打断,会通过 futex 进行通知。 所以我们的进程虽然只有一个 epoll_wait 的 2ms 一次唤醒 + timer 17ms 一次唤醒,但 runtime sysmon 自己在“睡得长一点还是短一点” 这事情上内耗,造成大量的 futex + nanosleep syscall,吃饱了 cpu。 这,就是 go runtime 的魅力 😀每天都在做奇怪的工程实在太好玩了 😀 😀
Posted Feb 6
// gcc -O2 -pthread -std=gnu11 -Wall -Wextra main.c #include <pthread.h> #include <stdint.h> #include <stdio.h> #define u64 uint64_t #define N 1000000000ULL static volatile u64 counter = 0; static volatile int done = 0; static void *reader() { while (!done) { counter; } return NULL; } static void *writer() { for (u64 i = 0; i < N; i++) { counter++; } done = 1; return NULL; } int main(void) { pthread_t rth, wth; if (pthread_create(&rth, NULL, reader, NULL) != 0) return 1; if (pthread_create(&wth, NULL, writer, NULL) != 0) return 1; pthread_join(wth, NULL); pthread_join(rth, NULL); u64 final = counter; printf("final=%lu expected=%llu %s\n", final, N, (final == N ? "OK" : "MISMATCH")); return (final == N) ? 0 : 2; }
Posted Feb 6
// gcc -O2 -pthread -std=gnu11 -Wall -Wextra main.c #include <pthread.h> #include <stdint.h> #include <stdio.h> #define u64 uint64_t #define N 1000000000ULL static volatile u64 counter = 0; static volatile int done = 0; static void *reader() { while (!done) { counter; } return NULL; } static void *writer() { for (u64 i = 0; i < N; i++) { counter++; } done = 1; return NULL; } int main(void) { pthread_t rth, wth; if (pthread_create(&rth, NULL, reader, NULL) != 0) return 1; if (pthread_create(&wth, NULL, writer, NULL) != 0) return 1; pthread_join(wth, NULL); pthread_join(rth, NULL); u64 final = counter; printf("final=%lu expected=%llu %s\n", final, N, (final == N ? "OK" : "MISMATCH")); return (final == N) ? 0 : 2; }
Posted Feb 5
聪哥愤然辞了 TC maintainer,netdev 社区的一大损失
Posted Feb 5
Paul E. McKenney 的 perfbook 实在太好看了,虽然我才肛开始读到第四章,但已经解决了好几个困扰我多年的技术问题了。 比如前两周我才和 codex 讨论了 go 里两个 goroutines 并发(并行)读写一个 u64 全局变量到底有什么危害,我之前以为最多就是并发写导致另一线程读到旧值,这种 bug 我是可以容忍的,于是省掉 atomic.Load 好了,毕竟原子操作性能也不好。 而书上 4.3.4.1 Shared-Variable Shenanigans 这一节列出来一大堆无保护并发读写共享变量的潜在危害,其中 Store tearing 引用的这个 2019 年很新(?)的内核讨论令我大吃一精: https://lore.kernel.org/lkml/20190821103200.kpufwtviqhpbuv2n@willie-the-truck/ void bar(u64 *x) { *x = 0xabcdef10abcdef10; } 上面的 bar 在某个历史版本 arm64 gcc -O2 编译出的结果是 bar: mov w1, 61200 movk w1, 0xabcd, lsl 16 stp w1, w1, [x0] ret 而 arm64 在 v8.4a 之前 stp store pair 指令是非原子的,如果有无保护并发读,另一个线程可能会读到只修改了一半 32bits 的变量。 书上说这类问题用 volatile / WRITE_ONCE / READ_ONCE 就可以比较轻量级地解决,但 go 没有 volatile 只能用原子指令,令人忧愁。
Posted Jan 28
MS teams 没有撤回功能,我麻了,手指肌肉记忆令我职场性骚扰😭