比 rapidfuzz 更快,比 parasail 更快,一行 import 即可替换。
stride-align 是一个 SIMD 加速的 Python 库,专注于模糊字符串匹配、
序列比对、语音编码与时间序列距离——把 Unicode/CJK 作为一等公民,
并由运行时 CPU dispatch 自动选用当前机器支持的最宽 SIMD 后端
(x86、ARM、龙架构、POWER),并带标量回退。
它同时是四个常用库的直接替代品:
import stride_align.rapidfuzz as rapidfuzz 替代 rapidfuzz,
from stride_align.thefuzz import fuzz, process 替代 TheFuzz,
import stride_align.parasail as parasail 替代 parasail-python,
import stride_align.jellyfish as jellyfish 替代 jellyfish——
现有代码只需改一行 import,即可跑在原生 SIMD 内核上。
(新代码应优先直接使用原生 stride_align API。)
完整的功能清单、所支持的全部算法以及各后端的细节,请见
docs/api/ 下的 API 参考,以及面向 LLM 的
llms.txt 与 llms-full.txt。
这里不讲理论,直接动手——边做边学。
快速开始
pip install stride-align
预编译 wheel 覆盖 Linux x86_64(glibc 与 musl)、macOS arm64、 Linux aarch64、Linux ppc64le 在 CPython 3.12 / 3.13 / 3.14 上的 组合。其他(平台,Python)组合会回退到 PyPI 上的源码发行版并在 本地编译,这需要支持 C++20 的编译器和 CMake ≥ 3.26。
龙芯 / 龙架构(LoongArch64)用户: wheel 不在 PyPI 上,发布 渠道是 GitHub Releases,安装时还需要在老世界与新世界两套二进制 世界之间二选一——详见后面的 龙架构 LoongArch 安装。
先声明一下:这里用宗教文本不是要带任何立场——这个 demo 需要几份 比较大的、含义相同但表达不同的公共领域文档,圣经恰好极其符合这个 要求。
假设我们有两个句子——用《创世记》的开篇:
美国标准译本(American Standard Version,ASV)是这样的:“In the beginning God created the heavens and the earth.”
英王钦定版(King James Version,KJV)则是:“In the beginning God created the heaven and the earth.”
肉眼就能看到区别——heavens 还是 heaven。但怎么把这种差异量化?用 下面这一小段代码:
import stride_align as sa
print(sa.smith_waterman_normalized_score(
"In the beginning God created the heavens and the earth.",
"In the beginning God created the heaven and the earth."))
运行后输出:
0.9907407407407407
归一化得分在 0 和 1 之间。得分为 1 表示在默认评分模型下两个
输入完全匹配。得分接近 0 表示两份输入几乎没有共同之处,不过
Smith-Waterman 仍可能在原本不相关的字符串内部找到小的局部匹配。
现在换一下文本,看看得分怎么变。
import stride_align as sa
print(sa.smith_waterman_normalized_score(
"In the beginning God created the heavens and the earth.",
"The quick brown fox jumped over the lazy dog."))
Python 输出:
0.12222222222222222
明白了吗?字符串越相似,得分越高。
我们来做个稍大点的例子,感受一下这个库的性能。你可能会注意到我们 在 Smith-Waterman 和 Needleman-Wunsch 之间切换,大概会想什么时候 用哪一个。当你想拿整段输入与整段输入比较时,用 Needleman-Wunsch。 当你想在较大的输入内部找出匹配最好的区域时,用 Smith-Waterman。
好,进入 demo 代码。这一部分需要 requests:
pip install requests
import os, time, requests
import stride_align as sa
if not os.path.exists("kjv.txt"):
response = requests.get("https://openbible.com/textfiles/kjv.txt")
response.raise_for_status()
response.encoding = "utf-8-sig"
open("kjv.txt", "w", encoding="utf-8").write(response.text)
lines = [line.strip().lower() for line in open("kjv.txt")][2:]
while True:
if not (query := input("Enter a snippet to match. Press enter to end.\n")):
break
t = time.perf_counter()
scores = sa.needleman_wunsch_normalized_scores(query.lower(), lines)
best = int(scores.argmax())
print()
print("Score:", float(scores[best]))
print(lines[best])
print("Search time: %0.2fms" % ((time.perf_counter() - t) * 1000))
print()
print()
这能拿来干什么?假设我们手上有一段圣经经文,想知道它出自哪一章
哪一节。你说用 grep?哎,不行,这里有个问题:我们手上的经文是
另一个译本,比方说 Catholic Public Domain 译本,而电脑上的是
King James 版本。grep 的精确匹配在这里行不通。怎么找到对应的
章节?当然是用 stride-align 搜索“最接近”或“最相似”的字符串。
demo 的第一部分处理下载和缓存。Open Bible 把这份文本放在了 HTTP 可访问的位置,我们要尊重他们的带宽,所以 下载下来就缓存住,做个体面的网络公民。
接下来把所有行加载到一个列表里。我们去掉换行符,把所有内容转成 小写——免得在 Shift 键问题上较劲。
最后那段 while True: 循环读取一行文本——大概就是来自 Catholic
版本、我们想查章节的那句经文——用 Needleman-Wunsch 的批处理形式
把它和 King James 版本里所有行做匹配。它返回一个得分数组。我们
用 argmax() 找到最高分对应的行,再把对应索引的那行打印出来。
试一下。
我准备用 Catholic 圣经里的 Jeremiah 4:28——它和 King James 圣经 里的同一节其实差别挺大。来看看效果……
$ python3 demo2.py
Enter a snippet to match. Press enter to end.
The earth will mourn, and the heavens will lament from above. For I have spoken, I have decided, and I have not regretted. Neither will I be turned away from it.
Score: 0.3598901098901099
jeremiah 4:28 for this shall the earth mourn, and the heavens above be black: because i have spoken [it], i have purposed [it], and will not repent, neither will i turn back from it.
Search time: 206.51ms
……找到了!而且相当快。
再来一个 demo:拼写检查。
这是一个玩具拼写检查器,不是生产级的。它忽略标点、大小写、词频、 专有名词和上下文。重点是用一个熟悉的任务展示同样的“一对多查询” 模式。
import os, sys
import stride_align as sa
paths = ['/usr/share/dict/words',
'/usr/dict/words',
'/var/lib/dict/words',
'/etc/dictionaries-common/words']
for path in paths:
if os.path.exists(path):
break
else:
print("Sorry, I can't find your dictionary", file=sys.stderr)
exit(1)
words = [line.strip().lower() for line in open(path)]
for line in sys.stdin:
new_line = []
for word in line.split():
scores = sa.needleman_wunsch_normalized_scores(word.lower(), words)
word = words[int(scores.argmax())]
new_line.append(word)
print(' '.join(new_line), flush=True)
脚本做的第一件事是尝试找到操作系统里的拼写正确词表。它的位置因 发行版而异。找到之后,加载、去掉换行符,然后开始拼写检查。
拼写检查本身跟前面的匹配很像。对每个输入词,把它和正确词表里的
所有词做匹配,用 argmax() 找到分最高的那个,然后用它替换。
这里还有不少可以优化的地方,比如对已经拼对的词直接跳过查找,
不过这是个 demo,这点优化就留作读者练习了。
来看看效果!
$ cat - | python3 demo3.py
this is a demonstrtion of a spel checker
it doesn't matter that I can't spell corectly
this is a demonstration of a spell checker
it doesn't matter that i can't spell correctly
细节
当前实现提供:
- Needleman-Wunsch 仅得分对齐
- Needleman-Wunsch 带回溯的对齐
- Smith-Waterman 仅得分对齐
- Smith-Waterman 带回溯的对齐
- 与
massive-speedup中专门化模式一致的 backend 布局 - CPU/backend 检测,以及 Python 层的 backend dispatch
C++/Python 边界接受:
bytes对bytesstr对str- 由不可变可哈希 Python 对象组成的序列
- 混合的“序列/对象”输入——某一侧的
str或bytes会被当作 序列处理
直接拿 bytes 对 str 配对会抛出 TypeError。
当前实现是通用的动态规划内核,配上把 Python 输入序列化成 8、16、 32 或 64 位 token 流的预处理。SIMD 专门化的 backend 之后可以替换 对应的 backend 编译单元,而不动 Python API。
仅得分函数返回数值得分。归一化版本返回 0 到 1 之间的分数。
路径函数返回对齐结果对象,里面包含得分、对齐后的值、操作序列,
以及可用时的 CIGAR 摘要。
API
import stride_align
score = stride_align.needleman_wunsch_score("ACGT", "ACCT")
scores = stride_align.Scores("ACGT", variant="needleman_wunsch").compare(["ACCT", "AGGT"])
result = stride_align.smith_waterman_path("ACCGT", "CCG")
wide_result = stride_align.smith_waterman_path("ACCGT", "CCG", width=64)
object_result = stride_align.needleman_wunsch_path(
[frozenset({1}), frozenset({2})],
[frozenset({1}), frozenset({3})],
)
print(score)
print(scores)
print(result.score, result.aligned_query, result.aligned_target, result.operations)
print(wide_result.score)
print(object_result.aligned_query, object_result.aligned_target)
“一个 query 对多个 target”的得分工作流,请使用
Scores(...).compare([...]) 或者 *_scores() 函数。这条路径
会一次性准备好 query / profile,是反复做中英文文本比对时推荐的
高性能 API。
对于直接配对的快速路径,回溯输出会保留输入类型:
str输入返回对齐后的strbytes输入返回对齐后的bytes- 序列/对象输入返回对齐后的
tuple值,空位用None
传 width=8、16、32 或 64 可以强制内部 token / 评分宽度,
而不是用自动选择。
部分函数会暴露 CIGAR 字符串——CIGAR 是 “Concise Idiosyncratic Gapped Alignment Report” 的缩写,是 SAM/BAM 工具链使用的紧凑 对齐操作记法。如果想看完整规范,见 SAM specification。
替换矩阵(BLOSUM、PAM)
针对蛋白质比对,stride_align.matrices 提供了规范的 BLOSUM
与 PAM 替换矩阵。可通过 smith_waterman_score、
needleman_wunsch_score 及其 _scores 批量版本上的 matrix=
关键字传入:
import stride_align
from stride_align.matrices import blosum62, pam250
# 局部比对,NCBI 标准 BLOSUM62 + 仿射空位(open=-11, extend=-1)。
# matrix= 与 match_score / mismatch_score 互斥。
stride_align.smith_waterman_score(
"HEAGAWGHEE", "PAWHEAE",
matrix=blosum62,
gap_open_score=-11, gap_extend_score=-1,
)
# 批量(1 条查询 × N 条目标),共用一次构建好的剖面 —— 推荐用法。
stride_align.smith_waterman_scores(
"HEAGAWGHEE",
["PAWHEAE", "HEAGAWGHEE", "MEEPS"],
matrix=pam250, gap_open_score=-14, gap_extend_score=-2,
)
# 自定义矩阵:直接解析 NCBI 文本格式
custom = stride_align.matrices.SubstitutionMatrix.from_ncbi_text(
open("/path/to/BLOSUM62").read(),
name="BLOSUM62",
gap_open=-11, gap_extend=-1,
)
每个内置 SubstitutionMatrix 都暴露字母表、矩阵数据
(int8 ndarray)以及推荐的空位默认值 .gap_score(线性)、
.gap_open、.gap_extend。线性空位(gap_score=)与仿射空位
(gap_open_score= + gap_extend_score=)在 AVX-512 后端都受支持;
其它 SIMD 后端目前对矩阵模式会回退到标量通用内核。
矩阵数值来自 NCBI BLAST 发行版
ftp.ncbi.nih.gov/blast/matrices/,
即规范的参考分值。原始文献为:
- BLOSUM45 / 50 / 62 / 80 / 90 —— Henikoff S.、Henikoff J.G.(1992)。 Amino acid substitution matrices from protein blocks。PNAS 89(22):10915–10919。 doi:10.1073/pnas.89.22.10915 · PDF(开放获取)
- PAM30 / 70 / 250 —— Dayhoff M.O.、Schwartz R.M.、Orcutt B.C. (1978)。A model of evolutionary change in proteins。收于 Atlas of Protein Sequence and Structure,第 5 卷,补遗 3, 第 345–352 页。美国国家生物医学研究基金会(NBRF),华盛顿特区。 (书籍章节,无开放 PDF。常引用的后续推导见 Schwartz R.M.、Dayhoff M.O.(1978),Matrices for detecting distant relationships,同卷第 353–358 页。)
编辑距离评分器
除了 Smith-Waterman 和 Needleman-Wunsch,stride-align 还提供六种
单位代价的编辑距离/相似度度量。其中大多数都有 SIMD 批处理代码
路径;true-DL 目前是例外,仍然走标量 DP:
import stride_align
# Levenshtein(Myers 1999 位并行)——插入、删除、替换
stride_align.levenshtein_score("kitten", "sitting") # -> 3
stride_align.levenshtein_normalized_score("kitten", "sitting") # -> 0.571...
stride_align.levenshtein_scores("kitten", ["kit", "sitting"]) # -> ndarray[int64]
# 可选的 `score_cutoff`(rapidfuzz 的约定):按 target 提前退出,
# 超过 cutoff 的结果以 `cutoff + 1` 形式返回。
stride_align.levenshtein_scores(query, targets, score_cutoff=3)
# Damerau-Levenshtein(OSA 受限版,Hyyrö 2002)——在单位代价
# 下增加相邻字符的转置。这是 rapidfuzz 用 OSA.distance 暴露的
# 那个,也是大多数人嘴里说的“Damerau-Levenshtein”实际上想要的。
stride_align.damerau_levenshtein_score("ab", "ba") # -> 1
# True Damerau-Levenshtein ——不受限版本,一个字符可以参与多次
# 编辑。较慢(目前没有位并行内核),但与
# rapidfuzz.distance.DamerauLevenshtein 完全一致。和 OSA 在重叠
# 转置上会分叉,例如 "ca" -> "abc":OSA=3,true-DL=2。
stride_align.true_damerau_levenshtein_score("ca", "abc") # -> 2
# Indel ——仅限插入和删除的 Levenshtein,不允许替换。
# 等价于 |a| + |b| - 2 * LCS(a, b)。Allison-Dix(1986)位并行
# 内层循环。
stride_align.indel_score("kitten", "sitting") # -> 5
# Hamming ——两个等长字符串中位置不同的个数。
# 带 cutoff 的变体在累计差异超出上限时直接退出。
stride_align.hamming_score("100", "110") # -> 1
# Jaro / Jaro-Winkler ——[0, 1] 区间的相似度;Winkler 在前缀
# 上加一个有上限的加成。
stride_align.jaro_similarity("martha", "marhta") # -> 0.944...
stride_align.jaro_winkler_similarity("martha", "marhta") # -> 0.961...
批处理变体(*_scores、*_similarities)在每一个支持的 backend
上都按“一个 target 一个 SIMD lane”的方式打包起来:
- x86:SSE4.1 / AVX2 / AVX-512 / AVX10-256 / AVX10-512
- ARM:NEON(Linux + macOS)、SVE / SVE2
- 龙架构(LoongArch):LSX / LASX
- PowerPC:VSX
对 Lev / OSA 而言,长度 ≤ 64 的模式走单字 Myers;65–256 走多字 内核(W=2/3/4)。Indel 在模式长度 > 64 时回退到标量位并行 (多字推广暂未实现);true-DL 目前只有标量 DP。
语音编码器(Phonetic encoders)
对于姓名匹配、去重和即时搜索这类场景,stride-align 提供完整的
标准语音编码器家族。每个编码器把字符串映射成一个短代码,发音
相近的名字共享同一个代码,与拼写差异无关:
import stride_align as sa
# American Soundex(Russell & Odell, 1918)。4 字符代码。
sa.soundex("Robert") # -> "R163"
sa.soundex("Rupert") # -> "R163"
sa.soundex_equal("Robert", "Rupert") # -> True
# Metaphone(Lawrence Philips, 1990)——两个版本:1990 规范本身
# 和广为使用的 jellyfish 库在少数边界情况上有分歧,variant 参数
# 选择对应的规则家族。
sa.metaphone("Schmidt") # -> "SKMTT" (PHILIPS, 规范)
sa.metaphone("Schmidt", variant=sa.MetaphoneVariant.JELLYFISH) # -> "SXMTT"
sa.metaphone_equal("Schmidt", "Smith") # -> False
# Double Metaphone(Lawrence Philips, 2000)——返回 primary 与
# alternate 两个代码;alternate 捕捉合理的非英语发音变体。
# COMMONS 忠实复刻 Apache Commons Codec;PYTHON 与 PyPI 的
# metaphone 包按位兼容。
sa.double_metaphone("Schwartz") # -> ("XRTS", "XFRTS")
sa.double_metaphone("Hugh") # -> ("H", "")
sa.double_metaphone("Hugh",
variant=sa.DoubleMetaphoneVariant.PYTHON) # -> ("HH", "")
# NYSIIS(Taft, 1970)。对英文人名比 Soundex 更具区分度——
# "Watkins" / "Wilkins" / "Wilkinson" 不会冲突。
sa.nysiis("Watkins"), sa.nysiis("Wilkins") # -> ("WATCAN", "WALCAN")
# Match Rating Approach(Moore, Western Airlines, 1977)。
# 包含一个 codex 和一个配对比较器;后者用长度差和评分阈值
# 两条规则判定是否匹配。
sa.match_rating_codex("Christopher") # -> "CHRPHR"
sa.match_rating_compare("Robert", "Rupert") # -> True
# Caverphone 2.0(Hood, 2004)。固定长度 10 字符代码,
# 不足时用 '1' 右填充。最初是为 19 世纪末新西兰选民名册设计的,
# 现广泛用于一般英语姓名匹配。
sa.caverphone("Stevenson") # -> "STFNSN1111"
这七个编码器共享同一套字节提取助手,str 与 bytes 输入可以
互换,编码前会跳过非字母 / 非 ASCII 码点——如果需要去掉重音
符号,请先用 unicodedata.normalize("NFKD", s) 预处理。所有结果
都与 Apache Commons Codec 的规范参考数据,以及 jellyfish、
metaphone、doublemetaphone 这几个 PyPI 包做了交叉验证。
动态时间规整(Dynamic Time Warping)
对于时序速度不一致的数值序列比对——音频信号、手势 / 传感器轨迹、
金融时间序列——stride-align 提供带 Sakoe-Chiba 窗口的动态时间
规整:
import numpy as np
import stride_align as sa
q = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
t = np.array([1.0, 2.0, 2.5, 4.0, 5.0])
# 默认距离按 dtype 选择:
# float32 / float64 -> L2 平方,(x - y)^2
# int16 -> L1,|x - y|(音频约定)
sa.dtw(q, t) # -> 0.25
# Sakoe-Chiba 窗口:整数半径,或 max(|q|, |t|) 的小数比例。
sa.dtw(q, t, window=2)
sa.dtw(q, t, window=0.2)
# 显式指定距离。
sa.dtw(q.astype(np.int16), t.astype(np.int16), distance="l1")
# 批处理。
sa.dtw_distances(q, [t, t * 2, t + 0.5], window=2)
输入必须是 dtype 相同的 NumPy ndarray(float32、float64 或
int16——int16 是天然的音频 dtype)。其它 dtype 与非 ndarray
输入会抛出 TypeError。
cdist、cdist_above_threshold、cdist_top_k、cdist_top_k_per_query
针对两份字符串列表的全配对评分场景,stride-align 提供三个
矩阵风格的入口:
qs = ["kitten", "sitting", "kit"]
ts = ["kitten", "kit", "sitting", "biting"]
# 完整 N×M 相似度矩阵 —— ndarray[float64](相似度评分器)
# 或 ndarray[int64](距离评分器)。
sa.cdist(qs, ts, scorer=sa.Scorer.JARO)
# 流式过滤 ——只产出相似度超过阈值的对。worker 向一个有界队列喂数据,
# 调用方负责消费。长度剪枝 + 把每对的 cutoff 下推到 kernel 里,
# 在高阈值下能跳过绝大部分工作。
for score, q, t in sa.cdist_above_threshold(
qs, ts, scorer=sa.Scorer.LEVENSHTEIN_NORMALIZED, threshold=0.7,
):
...
# 按得分取 top-k ——返回最多 k 个最高得分(对距离评分器则是
# 最低)的 (score, query, target) 三元组。每个线程一个堆;
# 一个共享的原子全局最小值边界,让每对的 cutoff 下推
# 在工作推进时不断抬高剪枝阈值。
sa.cdist_top_k(qs, ts, scorer=sa.Scorer.JARO, k=10)
# 每个 query 取 top-k ——以生成器形式 yield。与 cdist_top_k
# 不同(后者返回 query × target 矩阵中全局得分最高的 k 对),
# 本函数为每个 query 各自维护一个 top-k 堆。设置 pruning=True
# 后,会跟踪当前堆中最差的得分;当一个 target 的长度差闭式
# 相似度上界证明无法越过该得分时,跳过评分内核——在长度差
# 较大的工作负载上能带来显著加速。
for query, top in sa.cdist_top_k_per_query(
qs, ts, scorer=sa.Scorer.LEVENSHTEIN_NORMALIZED, k=5, pruning=True,
):
# top 是 [(score, target), ...],按得分降序排好
...
在高阈值下剪枝的效果非常夸张——见 BENCHMARK
中的跨架构表(cdist pruning 那几行)。特别是龙芯(Loongson)的
LASX 后端在 T=0.99 下,让通常预期会领先的 Tiger Lake AVX-512
反而落后;对比报告见
docs/loongson-vs-tiger-lake-cdist-2026-05-24。
完整的跨架构数据见 BENCHMARK。
优化和基准测试
stride-align 的性能一直被认真对待,而且仍在持续优化。这个库为
x86、Arm、龙架构(LoongArch)等多个常见目标做了 SIMD 优化。
Intel AVX-512 对 Parasail(2026-07-18)。 当前固定核基准运行在
Granite Rapids Xeon 6975P-C 上,使用 GCC 16.1 与 Parasail 1.3.4。
80 个可直接比较的组合覆盖英文和中文输入、线性和仿射空位、16/32
位得分宽度、1:1 和 1:many 两种形状,以及七种评分、路径和
CIGAR 变体。比值为 Parasail 中位运行时间除以 stride-align 中位
运行时间,因此大于 1 表示 stride-align 更快。
| AVX-512 分组 | 胜出项 | 几何平均值 | 中位数 |
|---|---|---|---|
| 全部 | 79 / 80 | 1.682x | 1.656x |
| 仅评分 | 47 / 48 | 1.559x | 1.558x |
| 路径 / CIGAR | 32 / 32 | 1.883x | 1.737x |
表现最强的变体是 Needleman-Wunsch 路径(2.356x)、
Needleman-Wunsch CIGAR(2.341x)和 Smith-Waterman 评分(1.989x)。
AVX-512 唯一落后的项目是中文、线性空位、1:1、16 位宽度的
Needleman-Wunsch 评分(0.874x)。完整方法、AVX2 对照、逐变体表格
和最慢五项见 BENCHMARK。规范原始数据为
benchmark.csv,并保留了不可变的
日期快照。
龙架构(LoongArch)/ 龙芯(Loongson)。 龙芯的优化故事尤其 值得一说:在基准用例下(英文文本、16 位得分宽度、仅得分的 Smith-Waterman),LASX backend 比 generic backend 快 16 倍, 比 Parasail 快 22.4 倍。
如果你是在龙芯服务器上做研究,并且从这份提速里得到好处的研究者, 欢迎引用、提交 bug、贡献基准用例;如果你愿意表达一点心意,我也很喜欢收到一些不贵的中国小纪念品—— 茶叶、书法书签、剪纸、中国结、熊猫钥匙扣、小龙摆件之类都很好。 请不要寄贵重物品或需要报关的东西。
完整基准测试见 BENCHMARK。
原生微基准
如果想在不经 Python 帧、不走基准编排的情况下做性能分析,可以配置 一份原生 x86 microbench 构建:
nanobind_dir="$(.venv/bin/python -m nanobind --cmake_dir)"
cmake -S . -B build/perf \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DSTRIDE_ALIGN_BUILD_MICROBENCH=ON \
-DSTRIDE_ALIGN_PERF_SYMBOLS=ON \
-DPython_EXECUTABLE=.venv/bin/python \
-Dnanobind_DIR="$nanobind_dir"
cmake --build build/perf --target stride_align_x86_microbench
build/perf/stride_align_x86_microbench --backend avx2 --shape 1:many --pass english --width 16
python tools/x86_microbench_regression.py \
--binary build/perf/stride_align_x86_microbench \
--cpu 2 \
--backends avx2,avx512bwvl \
--shapes 1:1,1:many \
--passes english,chinese \
--widths 16,32 \
--write-json /tmp/stride-align-x86-microbench.json
.venv/bin/python tools/pinned_benchmark_sweep.py \
--output-dir /tmp/stride-align-pinned \
--cpu 2 \
--iterations 15 \
--warmups 3
STRIDE_ALIGN_PERF_SYMBOLS=ON 会保留 nanobind 模块不被 strip,
并加入调试符号和 frame pointer,同时保持 -O3。
签入仓库的原生 microbench 基线在
benchmarks/x86_microbench_baseline.json。把它当作一个宽松阈值的
本地兜底,不要当成跨机器的 SLA。
龙架构 LoongArch 安装
龙架构的 wheel 不在 PyPI 上(PyPI 暂不索引
linux_loongarch64 平台标签),所以走的是另一条发布通道:
| 渠道 | URL 前缀 |
|---|---|
| GitHub Releases(主渠道) | https://github.com/adamdeprince/stride-align/releases/download/v0.5.0/ |
stride-align.com 镜像 |
https://stride-align.com/wheels/v0.5.0/ |
两个渠道提供完全相同的 wheel,挑访问更快的那个用。在国内访问 GitHub 较慢时,镜像比较方便;GitHub Releases 是规范主页。
先用发行版安装 NumPy(PyPI 上的 loongarch64 NumPy wheel 还很 稀疏,发行版的版本通常和系统其他组件 ABI 兼容):
sudo apt install python3-numpy
PY=$(python3 -c 'import sys; print(f"cp{sys.version_info.major}{sys.version_info.minor}")')
老世界 vs 新世界:怎么选
龙架构硬件运行在两套互不兼容的二进制世界之一。两者在两个地方 不一样:
- 可执行文件引用的动态加载器(这个文件名是链接时固化进 ELF 头里的)。
- 二进制依赖的 glibc ABI 版本。
一个世界编译出的 wheel 无法在另一个世界加载——加载器文件名在 对面那边根本不存在,就算存在符号也会对不上。我们为每个世界提供 一份独立的 wheel:
| 世界 | 加载器 | glibc | 典型机器 | wheel build 标签 |
|---|---|---|---|---|
| 老世界 | /lib64/ld.so.1 |
2.28 时代 | 出厂 Kylin、原版龙芯发行版 | 1.oldworld |
| 新世界 | /lib64/ld-linux-loongarch-lp64d.so.1 |
≥ 2.36 | 较新的龙架构发行版,或者任何安装了新加载器的机器 | 1.newworld |
两份 wheel 都静态链接了 libstdc++ / libgcc,唯一的差别就是 加载器和 glibc ABI 世界。
用下面这一行命令判断:
test -e /lib64/ld-linux-loongarch-lp64d.so.1 && echo new-world || echo old-world
如果输出 new-world,说明加载器已就位——直接用新世界的 wheel。
如果输出 old-world,要么装老世界的 wheel,要么跑下面那条
一次性的 sudo 软链接命令进入新世界(安全——它是新增一个文件名而
不是替换,老世界的二进制照样正常)。
老世界 wheel
pip install \
https://github.com/adamdeprince/stride-align/releases/download/v0.5.0/stride_align-0.5.0-1.oldworld-${PY}-${PY}-linux_loongarch64.whl
镜像:
pip install \
https://stride-align.com/wheels/v0.5.0/stride_align-0.5.0-1.oldworld-${PY}-${PY}-linux_loongarch64.whl
新世界 wheel
新世界 wheel 需要新的加载器出现在 ELF 引用的那个路径上。每台 机器一次性 sudo 即可,不影响已有的老世界二进制:
sudo ln -sf /opt/loongson-gcc-16.1.0/sysroot/lib64/ld-linux-loongarch-lp64d.so.1 \
/lib64/ld-linux-loongarch-lp64d.so.1
(发行版打包者通常会把同等的软链接随新世界一起放进来,那种情况下 可以跳过这一步。)
然后:
pip install \
https://github.com/adamdeprince/stride-align/releases/download/v0.5.0/stride_align-0.5.0-1.newworld-${PY}-${PY}-linux_loongarch64.whl
镜像:
pip install \
https://stride-align.com/wheels/v0.5.0/stride_align-0.5.0-1.newworld-${PY}-${PY}-linux_loongarch64.whl
其他说明
预编译的龙架构(LoongArch64)wheel 在两个镜像上都覆盖 Python
3.12、3.13 和 3.14——两个世界都有。如果你用的是其他 Python
版本,或者想从源码构建,pip install stride-align 会回退到
PyPI 上的源码发行版,在本地编译 LSX/LASX 内核。构建细节
(工具链、RPATH wrapper、静态 C++ 运行时)见
docs/loongson-build。
引用
如果你在研究中用到了我的软件,请引用我。
@software{deprince_stride_align,
author = {DePrince, Adam},
title = {stride-align: Fast Smith-Waterman and Needleman-Wunsch alignment for Python},
year = {2026},
publisher = {GitHub},
url = {https://github.com/adamdeprince/stride-align},
note = {Python/C++ library for sequence and string alignment}
}
