Skip to main content

Pagliacii's Thoughts

  1. Software Design by Example - A tool-based introduction with Python》 by Greg Wilson

    #Book

    The best way to learn design in any field is to study examples, and the most approachable examples are ones that readers are already familiar with. These lessons therefore build small versions of tools that programmers use every day to show how experienced software designers think. Along the way, they introduce some fundamental ideas in computer science that many self-taught programmers haven't encountered. We hope these lessons will help you design better software yourself, and that if you know how programming tools work, you'll be more likely to use them and better able to use them well.
  2. 在看波推荐的《competitive programers handbook》,第一次知道 https://leetcode.com/problems/maximum-subarray/ 的 dp 解法原来是有名字的叫做 Kadane’s algorithm,然后学习了二分查找原来还有一种步进写法
    int k = -1;
    for (int b = n; b >= 1; b /= 2) {
        while (k + b < n && array[k + b] <= x)
            k += b;
    }
    if (k >= 0 && array[k] == x) {
        // found at k
    }

    循环不变式是 array[k] <= x,美

    但更有趣的是 https://leetcode.com/problems/find-common-elements-between-two-arrays/ 这题,书上讨论了两种做法,第一种是对 array1 建立 hashmap,然后再遍历 array2 检查 x in hashmap,时间复杂度 O(n);第二种是两个 array 排序,然后用类似 merge sort 合并时的方法双指针同时遍历,时间复杂度 O(nlogn)。

    但是话锋一转,书上说虽然 hashmap 解法理论时间复杂度最优,但在 len(array) 数量级在1e6、数据是 1..1e9 随机整数这种级别时,实际跑起来却是排序算法更快。

    我不信。虽然我已经见识过不少“理论时间复杂度 vs CPU ILP 输得一败涂地”的例子,但这个 case 我还是不信,所以我用 go 糊了一个测试: https://go.dev/play/p/XmPrC2VWIyX
    $ taskset -c 1 ./bench 
               n         hashset       sortmerge       count      winner
    ------------  --------------  --------------  ----------  ----------
         1000000      123.259 ms      127.794 ms        1070     hashset
         2000000      271.036 ms      267.713 ms        3956   sortmerge
         3000000      452.092 ms      407.585 ms        8983   sortmerge
         4000000      592.858 ms      557.676 ms       16003   sortmerge
         5000000      775.459 ms      706.228 ms       24922   sortmerge
         6000000      996.271 ms      864.900 ms       35715   sortmerge
         7000000     1419.428 ms     1035.839 ms       48523   sortmerge
         8000000     1345.405 ms     1182.133 ms       63042   sortmerge
         9000000     1426.785 ms     1351.538 ms       79642   sortmerge
        10000000     1655.852 ms     1518.615 ms       98806   sortmerge

    O(nlog) 的算法比 O(n) 算法快 37%?计算机真有意思😋Let's perf!

    先看 IPC,发现 hash 算法的 IPC 低达 0.5,sort 算法高达(ガンダム)1.3,micro bench 启动!
    $ taskset -c 1 perf stat -ddd ./bench sortmerge
    6,771,796,030      cpu_core/instructions/           #    1.30  insn per cycle 
    
    $ taskset -c 1 perf stat -ddd ./bench hashset
    3,753,073,963      cpu_core/instructions/           #    0.51  insn per cycle 


    然后做一轮 topdown,发现居然是 tma_mem_bandwidth 造成了巨量 stall
    $ taskset -c 1 perf stat -M topdownl1
    77.2 %  tma_backend_bound
    11.1 %  tma_bad_speculation    
    2.7 %  tma_frontend_bound     
    9.1 %  tma_retiring 
    
    $ taskset -c 1 perf stat -M tma_backend_bound_group
    9.4 %  tma_core_bound         
    68.1 %  tma_memory_bound
    
    $ taskset -c 1 perf stat -M tma_memory_bound_group
    63.0 %  tma_dram_bound
    4.3 %  tma_l3_bound
    0.9 %  tma_l2_bound
    1.5 %  tma_l1_bound
    1.6 %  tma_store_bound
    
    $ taskset -c 1 perf stat -M tma_dram_bound_group
    65.0 %  tma_mem_bandwidth
    24.9 %  tma_mem_latency


    看一下这个 perf metrics 用的什么 pmu: tools/perf/pmu-events/arch/x86/meteorlake/mtl-metrics.json
        {
            "MetricExpr": "min(cpu_core@CPU_CLK_UNHALTED.THREAD@, cpu_core@OFFCORE_REQUESTS_OUTSTANDING.DATA_RD\\,cmask\\=4@) / tma_info_thread_clks",
            "MetricName": "tma_mem_bandwidth",
        },


    好好好,intel 祖传启发式 perf metrics,这个 pmu 是收集 outstanding offcore read >=4,手动采集一下事件看看是哪里的代码造成的:
    $ taskset -c 1 perf record -g -e 'cpu_core/OFFCORE_REQUESTS_OUTSTANDING.DATA_RD,cmask=4/'
    $ perf report  --stdio
                              |--50.38%--runtime.mapaccess2_fast64
                              |--46.22%--runtime.mapassign_fast64
                               --1.41%--runtime.memhash64


    来看看 runtime.mapaccess2_fast64 里具体哪一行导致的
    $ perf annotate --stdio --symbol=runtime.mapassign_fast64
             : 247    match := g.ctrls().matchH2(h2(hash))
        0.00 :   4067f3: mov    (%r12),%r15
       88.76 :   4067f7: movq   %r15,%xmm1


    注意这个采样没有 PEBS (perf evlist -v 输出里没有 precise_ip),annotate 结果有 off-by-one 的问题,所以实际应该是 mov (%r12),%r15 这个指令及之前的几个内存读,代码是 g.ctrls().matchH2(h2(hash)) 不过我不懂 swiss map,大概是 map 过大之后查询全部 L3 miss 落到内存读撞到内存墙了。

    理解了根因(?)之后,修改就有思路了,不必魔改 swiss map,let's do partition 嘛,把原始的 1e6 size 的 array 分成 N 个 buckets,然后把相同的 bucket 的重排到一起,然后对每个 bucket 就可以用独立的小 map 了: https://go.dev/play/p/EJdUiFsN229 。 这样改下来 O(n) 的力量又降临了,在 n=1e6 的规模上比 O(nlogn) 快五倍。

    计算机太有意思了,连力扣都变得好玩起来了🥰
  3. https://sequoiacap.com/article/services-the-new-software/

    「软件即服务」,其实这个概念不新鲜,过去几年里我都在不断地听到有经验的创业者讲到,不要卖工具,而是卖成果;单纯的软件没有竞争力,要在其中包含行业和特定问题的 know-how,软件只是解决方案的载体。而这篇文章可说是这类理念的集大成者,值得深读。

    读完之后,我又产生一些感触。作为开发者,过去几年我都在做将某个垂类行业的 know-how 变成软件的工作,我知道这样做是对的,但仍有很大的不确定性和局限性。因为我自己对于这个新行业的了解程度,哪怕非常努力去学,也只能做到泛泛而谈,这就决定了我自身在进行软件设计时,无法独立做出足够优秀的设计决策,更多是听从行业合作者的建议,但对方不懂产品,容易陷入被客户需求主导、什么都做但核心竞争力不够的漩涡。

    上个月我在了解法律 AI 的时候,调研了 Harvey 和 Legora 这两个公司的背景,其中 Harvey 的创始人是法学院毕业,在大律所工作过;而 Legora 创始人虽然没有法律背景,却是一个经验丰富的连续创业者,有着极强的执行力,他们靠着早期和多个法律行业的客户合作,快速迭代,才走通了一个正确的产品路线。联想到这两个公司的成功,我意识到作为一个开发者最理想的软件创业方向,要么是自身多年深耕的领域,要么是合作者有好的行业洞察 + 对产品的跨界理解。

    等等,开发者深耕的领域,不就是开发这件事本身吗?把自己对开发的 know-how 理解做成产品,这正是一个顺应时代发展的方向不是吗?从 Claude Code 和 Codex 这样的 agentic coding 的发展过程可以看出,他们都在不断加入能优化开发流程中的各个环节和实践的功能,而不只是一个代码生成的工具:Claude Code 自带 feature-dev, ultraplan 等 skills, 期望让用户能直接使用软件工程的最佳实践;Codex 用 computer use 让测试流程也能被可复现地自动化。它们正逐渐变成能够交付开发能力的产品。

    所以我觉得作为资深开发者,去做这样的产品是一个非常值得选择的方向。作为 seniors, 我们要做的不该是以己之短攻彼之长,和 agent 比谁写代码又快又好,而应该把多年锤炼出的对开发流程的深度理解、对软件工程细节的把握能力、对如何产出优秀软件的 know-how, 做成一个可以如自己那样工作,并能最终交付出高质量软件的产品。

    可能有人会问,这不是自己革自己的命,亲手做出替代自己的东西吗?我觉得不然,许多人看到 agentic- coding 工具让 juniors 失业,诚然,代码经验变得不那么重要了,但它只是开发经验中最不重要的一个成分。而且这和之前社交媒体泛滥的所谓「蒸馏」不同,「蒸馏」是死的,而产品是活的,产品只是我的一个映射,它想要继续进化,必须依赖我作为其开发者,不断更新自己的认知,不断改进其工作流程,才能使其可以继续保持先进,继续给使用者提供「一个软件大于10个程序员」的产品价值。