There's no reason for software to be slow anymore

Dan Luu

软件已经没有理由再慢了

原文由 Dan Luu 发布,订阅该博客

前几天,我看到一条疯传的推文,说那些抱怨 LLM 会导致代码臃肿缓慢的人,等到所有代码都被重写成超级优化的汇编后就会被打脸。我们还没到想把所有东西都用汇编写的地步,但 Nolan Lawson 关于测试的那句话——你现在可以选择想要多少 bug,我也曾在这里用没那么漂亮的话提到过——的某种变体,在性能上正变得越来越贴切。

针对我在上一篇文章里提到的观点——那些曾经高度专业化的性能工作的成本已经下降了好几个数量级,过去需要具备稀有技能的个人或团队才能完成的性能工作,现在任何能敲几句话的人都能做1,这意味着许多过去因为成本太高、只有在规模最大或最赚钱的项目里才值得去做的优化,现在都可以做了——Marc Brooker 回应道

完全同意你最后的观点。针对特定负载而非某一类负载量身定制的动态软件,似乎是很可能出现的结果。(这本身也会带来各种有趣的风险和机遇)。这有点让我想起 FFTW,还有一大堆古怪的 demoscene 老技巧,它们都是在某个非常具体的问题上(而且往往是针对非常具体的硬件)追求极致的速度和极致的精简。比如,我记得有一个 demo 把自己的代码当作纹理来复用,以获得极好的缓存局部性。

而 Michael Malis 也指出:

最近流传着一种说法,认为 AI 没什么用,因为“写代码从来都不是难点”。我觉得在某些领域确实如此,但在另一些领域,写代码恰恰就是难点。JIT 编译器就是个很好的例子。对很多软件来说,JIT 编译器能大幅提升速度。JIT 编译器如此稀少,让我相信过去实现一个 JIT 编译器的难度太高,以至于不值得去做。LLM 降低了门槛,让编写 JIT 编译器变得容易得多。这正是 pgrust 背后的核心思路。数据库历来是最难构建的软件之一,也因此受到了限制。现在有了 AI,我们在构建的软件类型上可以更有野心。

针对某一类负载的优化

我们就用 FRE 来试试,这是我们在上一篇文章里构建的正则引擎。回想一下,它是通过让 agent 在能访问 rebar 正则基准测试集的情况下,循环一个月持续优化正则引擎性能而做出来的。这导致 FRE 严重过拟合了 rebar,直到我们提醒 agent 我们还有一个未公开的 holdout 基准,它才把优化泛化得足够好,在 holdout 上表现得还算过得去。本来,一个在 holdout 基准上都打不过久经考验的正则引擎的“软件工厂”式正则引擎就没有什么使用的理由,但 FRE 一个值得注意的地方是,它的原生 AOT 编译版本在较长的搜索上表现相当好。我们当时就指出,让 ripgrep 在运行常规匹配器的同时,在另一个线程里运行原生代码编译器,等编译完成后再切换到原生代码,理应能获得更好的整体性能。当然,这通常会让短查询的性能变差,因为我们少了一个线程去做编译,但比起只跑几秒的查询,我更在意 ripgrep 跑上几十秒甚至几分钟时的耗时,所以我可以接受这个权衡。

就像我们能在几分钟的人力时间内构建一个正则引擎一样,我们也能用几分钟的人力时间来尝试这个实验。我敲了几句话,agent 就去完成了实现这一切所需的工作(如果让人来做,这会是相当大的一块代码改造),并在来自我 codex 历史记录的真实 ripgrep 查询上跑了基准。对于少数非常简单的长查询,我们看到了 2 到 4 倍的性能提升。但大多数查询要复杂得多,在具有代表性的 holdout 查询上,对于那些应该启用 AOT 的查询2,我们获得了大约 7% 的加速。这不算什么惊天动地的结果,但考虑到只是在 codex 上敲了几分钟字就得到这个结果,也算不错了(而且它还在继续做进一步的优化, presumably 还会进一步提速)。

建个索引?

严格来说这么做有点傻,因为如果要在电脑上反复搜索文本,显而易见的提速方法不是为正则匹配写一个原生代码编译器,而是建一个索引。但这里的重点只是想说明,这类过去需要花费大量时间和专业知识的技术工作,现在可以轻而易举地完成。而如果我们真想建一个文本索引,恰好我曾参与过 BitFunnel,也就是为持续、快速的文本摄入而专门优化、并在 SIGIR 上获得最佳论文奖的 Bing 搜索索引,所以如果要为整台机器构建一个快速的本地索引,我能想到几个可以尝试的实验(我见过的那些项目似乎都只想索引你的代码目录,但真正拖垮我机器性能的,是 codex 决定用 ripgrep 去扫那些包含大量生成文件的大型临时目录,然后在没命中时又扩大到搜索整台机器,所以我想要的是对整个磁盘的索引,而不仅仅是某些项目的代码)。

如果我在 AI 实验室工作,能用上跑在 Cerebras 芯片或其他加速器上的 SOTA 模型,大幅提升 tok/s 从而大幅增加对搜索的负载和需求,我可能会真的去调研一下现有的索引器,看看它们是否够快,还是说我得自己定制一个。BitFunnel 的开源版本“只”包含一个字节码解释器和一个 JIT,而 Bing 的版本则包含多个 JIT 编译器。做到那种程度的优化过去是一项大工程,但“我周末就能做出来”这句话,对这类项目中的一部分来说,现在真的成真了。以我每月 200 美元的低配账号来说,一个稍快的 ripgrep 加上任何现成的索引就够用了,所以这个快速摄入的整机索引项目,或许可以作为“留给读者(在 AI 实验室工作的读者)的练习”。

优化变得廉价

优化成本的急剧下降,从 2025 年 11 月起就已经很明显了,甚至用公开模型的话可能还要更早一些(而 AI 实验室内部的人肯定更早就能用上了)。举一个 GPT-5.1 或 5.2 时期的例子,在对游戏 AI 一无所知的情况下,我尝试做了一个 Azul 的 AI。结果它成了世界上最强的 Azul AI,而且领先幅度相当大。从描述第二强 AI 的论文来看,我觉得我的 AI 在“AI”本身上可能略好一些,但它主要赢在优化上——尽管我花的看起来可能少了两个数量级的时间(通过阅读论文、了解对方的流程并与我的流程对比后估算的),而且基本是在笔记本上完成的,而不是用集群(这意味着跑实验、做参数调优等的带宽要小得多)。比如,对方的 AI 是单线程的,而我的 AI 是多线程的。由于我既有原生代码版本,又有一个非常丑陋的共享 wasm 内存 + JavaScript 版本,还有针对两个不同版本的两种不同搜索架构,它们“需要”完全不同的多线程算法(一个用于小而快的网络的 minimax,和一个用于更大网络的 MCTS),如果靠手写,这会是相当大的工程。而且,因为我曾让 LLM 根据它自己(错误的)推理来挑选多线程算法试了几次,之后才自己花了 30 分钟去读了关于游戏 AI 多线程算法的资料,结果我把多线程算法重写(让 codex 重写)了好几次。

对于这类问题,调试和验证多线程算法有一堆标准操作是值得做的,比如基于调试日志实现回放,即使算法本身是非确定性的也能复现 bug。光是这一项,如果让我手写,恐怕就要花上几天到一周的时间,但这恰恰是 agent 可以轻而易举地循环完成的(只要让它尝试回放日志,每次无法完美回放时就为非确定性加上日志)。过去为了让这种棘手的优化真正跑通而需要的那些繁琐工作,很大程度上已经消失了。

这也适用于许多其他棘手的优化。我写过 CPU 微码、做过 CPU 验证、做过搜索引擎索引的优化等等,有很多次我看着某个优化,心想“嗯,这个能提升 2% 的性能,但要验证这个 tricky 的优化真的能用,得花 N 个人天”,然后再根据是否值得投入时间去权衡要不要做。现在这个 N 已经惊人地下降了(具体倍数不定,但就人力时间而言,常常是 1000 倍、10000 倍、1000000 倍,如果按计量计费的 token 成本去对比当年为搜索索引写 JIT 编译器的 Bing 工程师的成本,大概也有 1000 倍左右),值得去做的这类优化的数量就大幅增加了。同样,那些不确定是否能成功的优化也是如此。过去我有时看到一个不确定能否提速的优化,会想“要做到能大致评估性能影响的程度,得花 M 个小时去实现”。现在,有更多这类优化值得去试一试了。

回到游戏 AI 的例子,至少对我尝试的这个 AI 来说,速度每翻一倍,大约能涨 100 分 Elo(比国际象棋要高,我猜是因为平局非常少)。光是加上多线程,就足以在一台大机器上碾压其他水平相当的 AI。如果再叠加 10 到 20 个大多数人觉得手写太麻烦的优化,强度的差距会非常巨大,靠手写的 AI 根本没法追3

游戏 AI 的情况比大多数软件要麻烦一点,因为很多你想做的优化实际上会改变结果,而且没有便宜、简单的方法来判断速度提升加上结果变化后,实际效果究竟是变好还是变差。而且,如我们之前提到的,目前公开可用的 SOTA 模型很不擅长实验设计,所以我不得不为它们搭建了一套判断优化是否有效的框架,但一旦框架就位,这就和其他优化问题没什么两样了。我猜做 LLM 优化的人也得处理这类问题,但大多数优化问题要直截了当得多。

再举一个例子,为了准备性能面试,Jamie Brandon 尝试了 Anthropic 现在已公开的性能作业题。他自己做完后,让 Claude 在他的基础上继续做,结果 Claude 取得了好得多的成绩。当他去看 Claude 做了哪些他没做的事情时,他说很多优化是他也想到了但还没来得及做的,而“4另一些则是我除非花上几周时间,否则根本不会去尝试的疯狂操作”。他是一位靠谱的性能工程师,并且已经拿到了他想要的那份性能岗位的 offer,但在定义明确的优化问题上,他和一个像样的模型比毫无胜算(我自己还没试过那道题,但我怀疑在时间差不多的情况下我也毫无胜算)。

针对特定负载的优化

针对特定负载而非某一类负载量身定制的动态软件,似乎是很可能出现的结果。

这看起来几乎是必然的。在对我文章的另一条回应中,pgrust 的 Michael Malis 也说了类似的话:

[关于 pgrust 优化的讨论]……我觉得创建这些优化已经足够容易,我们完全可以看一下客户的负载,然后按需加上这些优化

在没有任何框架或准备的情况下,就在开始写这篇文章之前,我让一个 agent 针对我的 ripgrep 查询做了面向特定负载的优化(不是上面说的原生代码编译器切换,仅仅是基于一组基准对通用 FRE 引擎的优化),我只花了大约 2 分钟就启动了。优化在一组查询上运行,然后再用另一组 holdout 查询来做验证。这个任务还在跑,但初步结果看起来很有希望。经过一轮优化后,针对特定负载优化的版本在 holdout 上比标准 ripgrep 快了 2%,而且还在继续变快。2% 对我本地的 ripgrep 使用来说不算什么,但考虑到这只花了几分钟时间,而且这些优化是在我开始写这一段时才启动、现在仍在持续改进的,我愿意接受这 2% 的提升(注意,这还没和原生代码编译器结合,如果正确结合,整体提升会更大)。还要记住,这用的是 FRE 正则引擎5,它在 holdout 基准上比 Rust 正则引擎慢得多,而且在 holdout 上的提升一直很缓慢,因为我对正则负载一无所知,而 SOTA LLM 在实验设计上又不够好,无法进行无引导的、开放式的自改进循环,所以我们没有好办法去提升它在 holdout 上的性能。但如果我关心的是它在我自己负载上的性能,我有大量数据,而且还在不断产生新数据。就像上面 Marc Brooker 提到的,我们确实要小心数据中未出现过的范式变化所导致的过拟合等问题,但即便如此,我们的处境也比以前好多了。

更一般地看,如果你是像在 Amazon 的 Marc Brooker 或做 pgrust 的 Michael Malis 那样的人,那么不只是做一次性的尝试,而是与客户合作试点一个利用他们的数据为他们做优化的项目,再想办法将其规模化推广给更广泛的客户,就是顺理成章的事了。我所在的公司,做这个并不是最值得投入时间的事6,但很神奇的是,你能看到这对那些规模更大的公司来说已经近在眼前,而且考虑到为我个人工作流跑这些实验只花我几分钟时间,在个人项目上折腾这类事情也是完全合理的。

感谢 Jamie Brandon、Michael Malis、andrea (@s__video)、Artyom Bologov 和 Max Bittker 的评论、指正和讨论。

P.S. 正如我在上一篇以及上上篇文章中提到的,有了编码 agent,跑一个实验并看到足以满足我好奇心的结果所需的时间已经大幅缩短,而把一个结果做得真正严谨所需的时间却没有变,甚至变长了,所以如果还按我以前的方式来写,就意味着相对于我拥有的带宽,能跑的实验会非常少。因此,我只是跑了这些实验,然后把结果分享给几个朋友。作为一个尝试,我试着用一种非常快速、非严谨的方式把它们写出来,而不是让这些实验的结果在好几年里只有几个朋友知道。就像上一篇文章一样,我给这篇文章定的目标是写作加所有收尾工作在半小时内完成,我没计时,但肯定超时了一点。

即便如此,写这些东西的时间还是太长,以至于我在分享最新结果的进度上已经落后了,但我暂时还不想换成 LLM 写的文章,而且我觉得也不太可能把整理数据和写这样一篇文章的时间进一步压缩到半小时以内。就这篇文章的长度而言,光是打字就得花 20 到 30 分钟,这还没算停下来思考写什么的时间,而且当我查看数据时,有时某些地方看起来不太对劲,我就得更仔细地去排查,看看是不是有什么需要修正的问题(这里就发生了好几次,我估计因为没花更多时间,还有其他我没发现的数据问题)。

总之,如果你对这种快速(也肯定更易出错)的写法有什么看法,请告诉我(X Bsky Mastodon)!

附录:已经没有理由让软件再慢了

长期以来,我一直公开强烈反对那种认为 X 的开发者很烂、应该感到羞愧的普遍看法,他们因为写了慢代码就该被指责。因为编程专长有很多种,不仅大多数程序员本来就不具备性能方面的专长,让他们去培养这种专长恐怕也根本不划算(从业务关心的东西、就业市场的状况等角度来看),所以当然大多数项目在性能上会比性能专家能做到的差得多。我能理解为什么性能专家看到差距在不断拉大——程序能有多快和程序实际有多快之间的差距——会觉得这很荒谬。我也不否认这其中的荒谬性,但如果我想想一个 UI 能做得多好和我(亲手)能做出的 UI 有多好之间的差距,我觉得那看起来也同样荒谬,但我也不觉得我应该花时间去学习如何做出一个出色的 UI,甚至是一个像样的 UI,原因和大多数人不值得花时间去学习如何做出像样的性能工作是一样的。

就上面的例子而言,Jamie Brandon 拿到了 Anthropic 的 offer,除非你是 OpenAI,否则你大概请不起他,但你却请得起一个能在有边界的优化问题上打败他的编码 agent。这个 agent 没有他那样的判断力,在开放性问题上会表现得更差(回想一下,当我们尝试构建一个优化的正则引擎、只是告诉它不要过拟合时,它在我们的 holdout 基准上比最好的正则引擎差了一个多数量级,但也别忘了,在告诉 agent 它在 holdout 上表现很差之后,它把正则引擎的性能提升到了基本能匹敌二线正则引擎的水平,这相比当今大多数代码中普遍的性能优化水平来说,已经是非常好了),但这已经足够好,能在各种各样的问题上实现像样的性能。这篇文章主要讨论的是后端性能问题,但如果你想压低 LCP、CLS 这类指标,agent 在前端性能上似乎也不差。事实上,在把我最近在文章中使用的交互式图表插进去后,我发现客户端性能指标变差了,于是我让 LLM 花了我每周配额的 1% 去做优化,指标很快又恢复到了良好水平。这是一个非常简单的网站,但在那些面向数百万用户发布的相当复杂的应用上,人们也在做这类优化,而且同样有效,只不过要多花一些 token。

我仍然不认为如果某人的软件性能很差,他就很烂、就该感到羞愧,但我确实认为,一个对性能一窍不通、但算是会用 LLM 的人(泛泛地会用,而不是专门在性能问题上会用),一般也应该能做出性能像样的软件。如果你只是让 LLM 去做优化,它常常会做出各种错误且非常糟糕的事情,你必须去发现并纠正,但这本来就是有效使用 LLM 时普遍会遇到的情况,所以获得像样的性能不再是一项专门技能了。

附录:codex 是如何运行 ripgrep 的?

这里有一些关于我机器上 ripgrep 查询分布的信息。我不保证这在任何其他地方都具有代表性。被搜索模式的长度分布中,长模式比我预期的要多得多。p50 是 55 个 Unicode 码点(为简单起见,我就称之为字符),这已经比我手动 grep 时用的要长了,而 p90 是 119!

我们还可以看看正则中分支数量的分布,同样比我手动写的要复杂得多。

另一个视角是看看这两者之间的相关性:随着正则变长,分支数会不会也变多?会的。

那么,这些非常长的正则到底是什么呢?如果我们看一下,其中最长的那些大多数是对函数名或测试名的长分支,比如下面这个正则,它似乎与 FRE 的开发有关。

  fn (hot_byte_compiler_is_generic_only_and_anonymous_count_uses_auto_count|
  one_pattern_count_spans_uses_the_retained_complete_span_session|
  formal_compact_state_byte_visitors_coexist_with_native_count|
  fixed_boundary_record_visit_matches_line_relative_reference_and_is_atomic|
  unbounded_languages_refuse_finite_extraction_before_allocation|
  formal_single_raw_span_sweep_preflight|
  assert_exact_fixture_uses_formal_large_continuation_sweep|
  url_only_compile_identity_binds_language_and_owner_mode|
  url_only_compile_exact_limits_and_runtime_refusals_close|
  url_only_compile_post_plan_allocation_faults_close|
  url_only_owner_discriminator_is_stable_and_precharged|
  url_only_compile_owner_is_strategy_and_operation_scoped|
  formal_rebar_url_owner_is_compile_only_and_matches_oracle|
  formal_rebar_url_exact_fixture_uses_certified_execution|
  formal_fixed_schema_materialization_matches_both_record_oracles_and_controls|
  formal_single_count_selects_compact_state_byte_complete_bound_visitors|
  authenticated_bound_line_total_lf_free_domain_opportunity_exceeds_five_percent|
  prepared_absolute_onepass_fuses_slots_and_preserves_pre_source_fallback|
  authenticated_word_boundary_russian_compact_lowering_public_canary|
  ordered_nfa_x86_epsilon_edges_bypass_the_assertion_call|
  ordered_nfa_aarch64_epsilon_edges_bypass_the_assertion_call|
  ordered_edge_dispatch_v2_is_target_neutral_deterministic_and_relocation_free|
  ordered_edge_dispatch_v2_copies_canonical_tables_and_cap_falls_back_to_v1|
  ordered_nfa_v3_composes_terminal_range_and_dispatch_without_data_relocations|
  ordered_nfa_x86_terminal_range_emits_authenticated_reverse_scan|
  ordered_nfa_aarch64_terminal_range_emits_authenticated_reverse_scan|
  ordered_nfa_x86_boundary_assertion_cache_is_lazy_and_boundary_scoped|
  ordered_nfa_aarch64_caches_repeated_assertions_once_per_boundary|
  boundary_assertion_cache_requires_dense_exact_kind_reuse|
  boundary_assertion_cache_selection_is_compiler_only_and_deterministic)

但也有一些是很有趣的数值构造,比如

:(13[0-9]|14[0-9]|15[0-9]|16[0-9]|17[0-9]|18[0-9]|19[0-9]|20[0-9]|21[0-9]|22[0-9]|23[0-9]|24[0-9]|25[0-9]|26[0-9]|27[0-9]|28[0-9]|29[0-9]|30[0-9]|31[0-9]|32[0-9]|33[0-9]|34[0-9]|35[0-9]|36[0-9]|37[0-9]|38[0-9]|39[0-9]|40[0-9]|41[0-9]|42[0-9]|43[0-9]|44[0-9]|45[0-9]|46[0-9]|47[0-9]|48[0-9]|49[0-9]|50[0-9]|51[0-9]|52[0-9]|53[0-9]|54[0-9]|55[0-9]|56[0-9]|57[0-9]|58[0-9]|59[0-9]|60[0-9]|61[0-9]|62[0-9]|63[0-9]|64[0-9]|65[0-9]|66[0-9]|67[0-9]|68[0-9]|69[0-9]|70[0-9]|71[0-9]|72[0-9]|73[0-9]|74[0-9]|75[0-9]|76[0-9]|77[0-9]|78[0-9]|79[0-9]|80[0-9]|81[0-9]|82[0-9]|83[0-9]|84[0-9]|85[0-9]|86[0-9]|87[0-9]|88[0-9]|89[0-9]|90[0-9]|91[0-9]|92[0-9]|93[0-9]|94[0-9]|95[0-9]|96[0-9]|97[0-9]|98[0-9]|99[0-9])[0-9]:

这等价于 :(?:1[3-9]|[2-9][0-9])[0-9]{2}:(如果在原始输入上用 ripgrep 跑,两者性能大致相同;更短的那个正则在真实查询数据上技术上要快一点,但只快了非常少)。产生它的完整流水线是

cargo clippy … | rg 'crates/fre-aot-regex/src/module.rs:' | rg NUMBER_REGEX | head -250

这对人类来说可能是件奇怪的事,但 agent 似乎经常干这种事。

换个话题,如果我们看看 ripgrep 查询花了多长时间,会发现有相当多慢查询,比如 p99 接近 1 分钟!而 p999 接近 10 分钟!而在这段时间里(在一台笔记本上大约一个月的时间;在我运行 agent 的 AWS 主机上,查询和分布很可能有所不同等等,但我还没去看)最长的那次查询接近 2 小时!

在命令行选项方面,我们看到如下情况。毫不意外,codex 经常需要行号,而且出于某种原因,它偶尔会使用 PCRE2 正则。

这些我就不放图或表格了,但另一点值得注意的是,被搜索模式的局部性相当低(约 94% 的模式只出现过一次),考虑到很多查询都很长,这倒也说得通。然而,被搜索文件的局部性却相当高,一个被搜索过的文件在不久后很可能被再次搜索,这表明(对于足够小的文件来说)它们很可能是在内存中被搜索的。

另外,99% 的查询是正则查询(1% 是非正则的字符串搜索),99.9% 的搜索查询仅包含 ASCII,但在被搜索的文件方面,大约 45% 仅包含 ASCII,55% 包含 Unicode,包含 Unicode 的比例比我预想的要高。

在上一篇文章的草稿上,Peter Geoghegan 指出:

正则实现也可能通过支持更少的功能来变得更快。有些实现不支持反向引用等等。

这在这里也同样适用。这里所做的面向特定负载的优化相当浅显,因为我只是给了 codex 一些简短的指令,然后让它想做什么就做什么(这通常不是最有效地使用 codex 的方式),但如果有一个更详细的计划,针对我查询中的常见用例做更有针对性的优化,预计能带来更大的提升。


  1. 不过,正如我们在那篇文章以及之前讨论过的,SOTA 模型在基准测试和实验设计方面的能力,还不足以在没有人类(或某个 skill)为 agent 搭建好基准测试环境的情况下,在一般情况下做到这一点。[返回]
  2. 从我们之前的基准测试中可以看出,即使有时间运行编译器,原生代码编译版本在很多情况下仍然比 Rust 正则库慢。如果去看原因,这些往往是更复杂的查询,Rust 正则库做了某些算法层面的优化,而 FRE 的原生代码编译器则回退到了某种朴素的实现(创建 FRE 的 agent 在原生代码编译器上花的时间远少于在“常规”正则引擎上花的时间)。[返回]
  3. 我毫不怀疑,一个由真正具备 AI 专业知识的人手写的 AI,比如写过世界顶尖围棋和象棋引擎的人,完全可以在“AI”本身的水平上凭借更强的实力打败我的 AI,但如果专业水平大致相当,那么在给定的任何时间内, LLM 写的版本都会占据主导。[返回]
  4. 可以说,比较一个 agent 在他留下的基础上继续工作的结果是不公平的,因为他的工作是一个起点,可能会让 agent 比单独从零开始做得好得多,所以我尝试把全新的任务直接交给 agent,它取得了和他在复用他人成果时让 agent 取得的分数非常相似的成绩(另一个 agent 的快速检查也没有发现作弊的证据)。[返回]
  5. 如果我让 agent 直接去改 ripgrep 的一个分支,性能可能会更好,但我好奇这是否也能解决 FRE 在我的查询上过拟合的问题。[返回]
  6. 前段时间,我把我们注册流程中某个页面的大小从 50 MB 减到了 5 MB,一次收入 A/B 测试似乎表明这让收入提升了约 0.5%。一般来说,我非常推崇先做那些简单易行的胜利,比如这个,而且相比去构建定制编译器或做其他高度专业化的技术工作,这里很可能还有很多投资回报率更高的胜利可做。[返回]

本文章由 muse-spark-1.2-contributor 进行翻译

评论