0%

起因

今天同门遇到了一些 draw.io 图像导出的问题,我和他一起分析定位了原因。

draw.io 论文画图三宗罪

截至 2025/4/13:

  1. 导出 pdf 兼容性不好,得用旧版或者 ghostscript 转换。
  2. 把 px 和 pt 混为一谈。
  3. px 的单位支持小数,导出时四舍五入,导致和预览时效果不一致。

我确实也承认 draw.io 方便,但有些地方还是很痛苦的。其他备选项:PowerPoint、Visio。

draw.io 导出 pdf 过大

使用脚本对图像压缩

实际上是一个 draw.io 文件中有大量的图片,每张图片体积过大,一个 draw.io 文件总体积达到 50M 以上。先导出之后再用 pdf 压缩工具图像会糊得厉害,但如果对每张图片分别压缩,再导出即可在保持图像高质量的同时显著降低 draw.io(以及之后导出的 pdf)的 文件体积。

绝对导入需要包在 sys.path 中,可以通过环境变量 PYTHONPATH 来增加一些搜索路径。

相对导入需要当前在一个子包内。Relative imports in Python 3 - Stack Overflow 这个回答就说明如果直接运行一个包含了 import .xx 或者 from .xx import xx 的脚本就会失败,我的直观感受是这样的文件只能出现在比 main 文件(__name____main__ 的那个文件,也就是入口文件)更深的文件夹下。相对导入有助于避免 sys.path 中出现更靠前的搜索路径,且该路径包含同名包,导致真正要导入的包被覆盖。

例子(什么时候相对导入会出错):

main.py
mypackage/
    __init__.py
    mymodule.py
    myothermodule.py  # from .mymodule import xx

直接运行 main.py 和 mymodule.py 都 OK,但是运行 myothermodule.py 则报错说没有 parent module。通过 python -m 来将文件视为一个模块运行,则可以将文件所在的文件夹作为 module 来运行,提供了 module 环境,但是该文件所在的文件夹并不会被加入 sys.path 中。

Elapsed and execution time for commands in ZSH

在 ~/.zshrc 文件中添加:

function preexec() {
  timer=$(($(date +%s%0N)/1000000))
}

function precmd() {
  if [ $timer ]; then
    now=$(($(date +%s%0N)/1000000))
    elapsed=$(($now-$timer))

    export RPROMPT="%F{cyan}${elapsed}ms %{$reset_color%}"
    unset timer
  fi
}

2025/4/20 缺点:计时显示在一行最后,每次复制终端文本会把这个时间一并复制了,而且这一行相当长导致时间显示在第二行(折行),让人摸不着头脑这个多余的文本是哪里来的。

https://godbolt.org/z/n8hns47M8

#include <iostream>
#include <string>

int main() {
  std::string str = "hello";
  std::cout << "String: \"" << str << "\"" << std::endl;
  std::cout << "Length: " << str.length() << std::endl;
  std::cout << "Capacity: " << str.capacity() << std::endl;
  std::cout << "Size: " << sizeof(str) << std::endl;

  str += " world!。。。。";
  std::cout << "\nString: \"" << str << "\"" << std::endl;
  std::cout << "Length: " << str.length() << std::endl;
  std::cout << "Capacity: " << str.capacity() << std::endl;
  std::cout << "Size: " << sizeof(str) << std::endl;

  str += str;
  std::cout << "\nString: \"" << str << "\"" << std::endl;
  std::cout << "Length: " << str.length() << std::endl;
  std::cout << "Capacity: " << str.capacity() << std::endl;
  std::cout << "Size: " << sizeof(str) << std::endl;
  return 0;
}

可以看到 libstdc++ 中的 capacity 的增长方式是 15 → 30 → 60。一开始 SSO 缓冲区只能容纳 16 个字符,最后一个字符是 '\0',因此初始状态下只能容纳 15 个非空字符,capacity 为 15。而后面倍增的时候会直接按照非 '\0' 字符的最大容量来做倍增,而非按照实际缓冲区容量做倍增(即 15 → 31 → 63 这样的路径)。

而 libc++ 中 capacity 的后续倍增是考虑了空字符的:22 → 47 → 95。一开始并不是按照缓冲区容量 23 去倍增,而是用的 24,可能是认为 23 这个数字不够规整。

主题是 Hugo-theme-next,有一些修改。虽然支持夜间模式,但是不太会调色,有一些自定义的样式在夜间模式下效果不好。所有颜色都是保证在日间模式下易读而选择的,如果夜间模式难以阅读可以切换回来。因为动画速率太慢,调快也觉得晃眼,就关掉了。

  • 2025.10.3 验证需要用 Release v0.143.1 · gohugoio/hugo 来渲染。已验证到 v0.146.x 开始不可用,v150.0 可用。参考 New template system in Hugo v0.146.0
  • 2025.10.4 通过将一些模板中的 partials/ 前缀去除,兼容 v0.146.x,但是 achives/ 无法显示列表了。魔改了旧主题还没适配,目前暂时使用 hugo v0.143.1,不做调整。

✨ 一些改动

  1. 改变了左侧 TOC 的行距,使得多行标题能够更容易被区分开。
  2. 增加了 GitHub 风格警告支持。以前使用脚本支持,现在使用钩子重写。
  3. 为 Compiler Explorer 链接启用代码预览,把鼠标悬停在链接上就能看到代码文本。
  4. Markdown 相对路径链接(其他文章或图片)。以前使用 404.html 支持,现在使用钩子重写。新增 Obsidian 风格的 [| width] 宽度标记支持。
  5. 按照顶级元素数量来摘取文章在首页的总结。

💥 Hugo 写作的坑

YAML Front Matter 的类别要使用 categories 而不是 category,而且类型一定要是字符串的列表,而不是单个字符串(不然有些 html 模板不能正确处理)。

文本高亮(==文本== 渲染成 <mark>文本</mark>

这是 2024 年 5 月更新的功能,升级 Hugo-extended 到 0.126.0 以上的版本并修改配置即可。

揭秘linux系统启动流程,面试官问起来再也不怕了-阿里云开发者社区

BIOS -> MBR -> Boot Loader (e.g. GRUB) -> kernel -> mount initramfs as / -> /init
                 /             \                    /                         |
UEFI -> GPT ----+               +----> initramfs --+                          |
                                       (memory)                               V
                                                                1 load drivers
                                         run /sbin/init   <---  2 mount root
                                                                3 switch root
  1. 运行 BIOS(现代系统是 UEFI)。
  2. 进行开机自检(POST,即 Power-On Self-Test)。
  3. 寻找启动设备,依次检查存储设备,如果前 512 字节最后两个字节是 0x55 和 0xaa,那么这个块就是 MBR(主引导记录,Master Boot Record),这个存储设备就含有操作系统。然后根据 MBR 的信息找到 Boot Loader 位置,将其加载并运行。如果是 UEFI,UEFI 固件会读取磁盘上的 GPT (GUID Partition Table)。GPT 中有一个特殊的 EFI 系统分区 (ESP, EFI System Partition),通常格式化为 FAT32。UEFI 固件会直接在该分区中查找并执行引导加载程序文件(通常是 .efi 文件,例如 \EFI\ubuntu\grubx64.efi 或 \EFI\BOOT\BOOTX64.EFI)。它不依赖 MBR 的 0x55AA 签名来启动。
  4. Linux 常见的 Boot Loader 是 GRUB。Boot Loader 能理解文件系统,能显示操作系统选择菜单和加载 Linux 内核。
  5. GRUB 加载 (load) 内核 (vmlinuz-…) 和 initrd/initramfs 文件 到内存。加载完成后,GRUB 将执行控制权转交给已加载到内存中的内核然后,内核会找到内存中的 initrd/initramfs 镜像,并将其解压(如果是压缩格式,如 gzip, xz),然后将其挂载为一个 临时的根文件系统 (rootfs)
  6. 内核执行位于 initramfs 根目录下的 /init 程序 (通常是一个脚本或小程序,如 dracut 或 mkinitcpio 生成的),这个程序会加载必要的驱动模块,找到真正的根设备(可能是 /dev/sda2,或者 /dev/nvme0n1p3 等),将真正的根设备挂到临时路径如 /sysroot 上,然后切换根目录并释放 initramfs 的内存。
  7. 现在根目录已经切换好了,运行 /sbin/init 成为系统的第一个进程。内核引导阶段结束,用户空间初始化开始。

阶段

  • 静态编辑
  • 动态加载
  • 延迟绑定

一、静态编辑

链接器(ld)在生成可执行文件时,会记录可执行文件所依赖的共享库的名称以及需要的符号,并将这些信息存储在特定的段中(例如 .dynamic 段)。同时,它会创建 PLT(Procedure Linkage Table,过程链接表)和 GOT(Global Offset Table,全局偏移表)作为占位符,用于后续的动态链接过程。GOT 表初始时包含的是用于延迟绑定的地址,而非实际的函数地址。

二、动态加载

加载可执行文件时,从其 .interp 字段找到解释器(也就是 loader,一般是 /lib64/ld-linux-x86-64.so.2,可以用 ldd <prog> 或者 readelf -l <prog> | grep "interp" 来检查)。让解释器来加载它。

动态链接器会从 搜索路径(例如 LD_LIBRARY_PATH 环境变量、/etc/ld.so.conf 配置的路径、默认系统路径 /lib, /usr/lib 等)搜索可执行文件所依赖的共享库,并将这些库映射到进程的虚拟地址空间中。在开启了 -fPIC 编译的情况下,动态链接器会解析全局变量和函数的地址,并将解析后的地址填充到 GOT 表中。如果程序编译时使用了 -fno-plt 选项,则所有函数调用都将直接通过 GOT 表进行,而不再依赖 PLT。在这种情况下,所有函数的 GOT 表项在加载时都会被解析并填充实际地址。

如果没有开启 -fPIC 编译,则动态链接器在加载进程时会直接修改 .text 段中的地址,导致 .text 变得私有而不在系统范围内共享。

Triton 源码教程的 softmax 实现中,不做 warmup 的朴素实现快得多?!

我就只是去掉 warmup,其他啥都没做,代码是新下的(今天的日期是 2025/3/7)。感觉是虽然朴素实现没有同时塞下所有任务(任务多了),但是每个任务的计算量变小了。

感觉英伟达推 TensorRT-LLM 是在很努力巩固技术城池了

我不喜欢垄断。

2025/3/19 看到 marlin kernel 中的 bit hack 反量化算法都来自 FasterTransformer,深感震撼。

2025/3/8 个人理解:

  • socket:UNIX 域套接字还是网络套接字,这取决于通信进程是否属于同一个主机。
  • 管道:包括匿名管道(pipe 系统调用)和命名管道(fifo 文件)。
  • System V IPC 三大件(POSIX 中也能找到这几个组件的替代物)
    • 信号量
    • 共享内存
    • 消息队列
    • System V IPC 用 key 来访问,本质是命名的,IPC_PRIVATE 只是生成独一无二的名字而已;除了消息队列必须命名之外,POSIX 中其他两个既可以命名也可以匿名。
    • 其他区别见 51 POSIX IPC 介绍
  • mmap:单独用 mmap 可以实现匿名共享内存,但不能解决命名共享内存的问题。POSIX 共享内存可以解决命名共享内存的问题,但是要配合 mmap 才能附加到进程地址空间。mmap 因为很特殊所以单独放出来,没合并到共享内存里面去。
  • 文件锁
  • futex
  • 信号:信息传输效率低,一般用于操作系统给进程通知信息,或者用户交互式干预进程运行。

LaTeX hyperref CJK 中文短语链接可点击范围只有其高度的一半。但凡其中加入了非 CJK 字符,可点击范围就会恢复正常。用 \vphantom 没有效果,大概是因为不会真正改变 baseline。有一种方法是强制 CJK 字符和其他字符的基线对齐,但是我觉得对齐之后肯定不好看。

我想到真正去渲染一个白色字符(可以改成透明颜色),然后再用 \hspace 减去其长度。最后结果勉强能看,缺点是复制的时候会多出来一个字符(用来打印和阅读的话问题不大)。

% https://tex.stackexchange.com/a/412555/
\newcommand{\nhphantom}[1]{\sbox0{#1}\hspace{-\the\wd0}}

\newcommand{\overlay}[1]{{#1}\nhphantom{#1}}
% \Url 命令是模板原先就有的命令,现在加上了一个 \overlay 来保证对中文的支持
\NewDocumentCommand{\Url}{mm}
{
  \href{#1}{
    {
        \CJKunderline{#2}
        \overlay{\textcolor{white}{.}}
    }
  }
}

2025 年 3 月 5 日:今天投递简历发现很多网站会从 PDF 中抽取文字,这导致抽出来的文字多了一个 .,所以还是尽可能在链接显示文本中包含拉丁字符作为权宜之计 😂。