GEO

大语言模型优化代码时,Python和Rust哪个性能更好?(附实测对比)

2026/4/9
大语言模型优化代码时,Python和Rust哪个性能更好?(附实测对比)

AIAI Summary (BLUF)

本文通过寻找特定数位和的实例,对比大语言模型(LLM)在Python与Rust代码优化中的表现,揭示其辅助优化的潜力与局限,包括未能捕捉算法改进等人类洞察。

引言:从一次代码优化实验说起

Max Woolf 最近提出了一个有趣的问题:反复提示大语言模型(LLM)“写出更好的代码”,是否真的能让它写出更好的代码?他的答案可以简要概括为:“某种程度上可以,但存在上限”。然而,在他的文章中,一个明显的优化机会被所有提示都忽略了,这引发了我的思考:LLM 在哪些优化场景下会失效?我们又能走多远?

由于他的示例是新鲜的,不太可能已存在于 LLM 的训练语料库中,我将借用他的问题,探索在有或没有 LLM 辅助的情况下,我们能在优化之路上走多远。

问题定义与初始实现

我们从一个具体的编程问题开始。以下是 Max 使用的原始提示:

Write Python code to solve this problem:

Given a list of 1 million random integers between 1 and 100,000,
find the difference between the smallest and the largest numbers
whose digits sum up to 30.

大语言模型的初始解决方案

使用 GPT-4o 通过 Copilot 生成代码,得到了与 Max 文章中基本一致的 Python 实现:

import random

# Generate a list of 1 million random integers between 1 and 100,000
random_integers = [random.randint(1, 100000) for _ in range(1000000)]

def digit_sum(n):
    return sum(int(digit) for digit in str(n))

# Filter numbers whose digits sum up to 30
filtered_numbers = [num for num in random_integers if digit_sum(num) == 30]

if filtered_numbers:
    min_number = min(filtered_numbers)
    max_number = max(filtered_numbers)
    difference = max_number - min_number
    print(f"The difference between the largest and smallest numbers whose digits sum up to 30 is: {difference}")
else:
    print("No numbers found with digits summing up to 30.")

这段代码在我的 M1 MacBook Pro 上运行耗时约 520 毫秒。这是一个不错的基准。从 Max 的文章中我们已经知道,通过几种技术可以使其快得多:

  • 使用 NumPy / 原生代码 (Using NumPy / Native Code)
  • 并行化 / 向量化 (Parallelization / Vectorization)
  • 减少冗余计算 (Reducing Redundant Work)

切换到 Rust 以获得更清晰的性能视图

由于 Python 在解决此类问题时引入了一些额外的复杂性(如 JIT 或通过 NumPy 使用原生代码的影响),我们切换到 Rust 语言。在 Rust 中,性能选择的影响可以更清晰地展现。以下是 LLM 生成的相同逻辑的 Rust 初始代码:

use rand::Rng;

fn digit_sum(n: i32) -> i32 {
    n.to_string().chars().map(|c| c.to_digit(10).unwrap() as i32).sum()
}

fn main() {
    let mut rng = rand::thread_rng();
    let numbers: Vec<i32> = (0..1_000_000).map(|_| rng.gen_range(1..=100_000)).collect();

    let mut min_number = i32::MAX;
    let mut max_number = i32::MIN;

    for &number in &numbers {
        if digit_sum(number) == 30 {
            if number < min_number {
                min_number = number;
            }
            if number > max_number {
                max_number = number;
            }
        }
    }

    if min_number == i32::MAX || max_number == i32::MIN {
        println!("No numbers with digit sum of 30 found.");
    } else {
        let difference = max_number - min_number;
        println!("Difference between the largest and smallest numbers with digit sum of 30: {}", difference);
    }
}

有趣的是,Rust 代码使用了与 Python 代码相同的“字符串分割”方法来计算数位和。这段代码运行耗时 42 毫秒从 Python 切换到 Rust 带来的速度提升:约 12 倍

第一轮优化:LLM 的贡献与局限

优化 digit_sum 函数

初始实现隐藏了一个问题:digit_sum 函数中“字符串分割”的方法非常慢。我们选中该函数代码并输入提示“make this code faster”,LLM 给出了一个改进版本:

fn digit_sum(n: i32) -> i32 {
    let mut sum = 0;
    let mut num = n;
    while num > 0 {
        sum += num % 10;
        num /= 10;
    }
    sum
}

这个修改是正确的。新版本的运行时间降至 13.2 毫秒通过优化 digit_sum 函数带来的速度提升:约 3.2 倍

值得注意的是,这个优化在 Python 中效果不明显,因为虽然算法更优,但它涉及更多 Python 层面的操作。

LLM 在并行化尝试中的失败

接下来,我们选中主函数并再次要求 LLM “make it faster”。Copilot 尝试并行化检查数字向量,但 Rust 不允许多个线程同时修改同一个变量,因此生成的代码无法编译:

error[E0594]: cannot assign to `min_number`, as it is a captured variable in a `Fn` closure

至此,我们不得不暂时与 LLM 分道扬镳,尽管后续还会少量使用它。

关键洞察:LLM 未能发现的人工优化

这里出现了我们的第一个人工优化,这是一个简单的优化,但我很惊讶 Max 的尝试或我基于 LLM 的优化尝试都没有发现:在计算相对昂贵的数位和之前,先检查数字本身是否有用

所有 LLM 生成的代码都走了相反的路:先按数位和过滤,然后再比较大小。这是一个需要手动完成但很简单的更改:

    for &number in &numbers {
        if number < min_number || number > max_number {
            if digit_sum(number) == 30 {
                if number < min_number {
                    min_number = number;
                }
                if number > max_number {
                    max_number = number;
                }
            }
        }
    }

优化后的运行时间为 10.7 毫秒。速度提升:约 1.2 倍。这个提升比在 Python 代码中做相同改动带来的 5 倍提升小得多,因为 Rust 中优化后的 digit_sum 函数已经非常快了。

深入分析:性能剖析与针对性优化

LLM 的一个主要限制是它无法对代码进行性能剖析(Profiling),但我们可以。通过性能剖析,我们发现 Rust 代码的一个主要开销竟然在于生成随机数向量。Rust 的 rand 包默认使用加密安全的随机数生成器(RNG),速度并不快。

我们通过切换到 fastrand 包来解决这个问题。请注意,我在这里“作弊”了,因为我选择它是为了便于后续引入一些并行化。我们通过 LLM 提示轻松完成了这个更改:

switch to using fastrand instead of rand

主要的代码变更如下:

    let numbers: Vec<i32> = (0..1_000_000).map(|_| fastrand::i32(1..=100_000)).collect();

新的运行时间:2.8 毫秒。速度提升:约 3.8 倍

性能优化路径总结

下表总结了从初始 Python 实现到当前优化 Rust 实现的关键优化步骤及其性能影响:

优化阶段 实现语言/关键改动 运行时间 (ms) 累计加速比 (vs Python) 阶段加速比 优化类型
Optimization Stage Language / Key Change Runtime (ms) Cumulative Speedup (vs Python) Stage Speedup Optimization Type
基线 Python (LLM 初始代码) 520 1x - 基线 / Baseline
语言迁移 Rust (直接移植) 42 ~12.4x ~12.4x 系统语言 / Systems Language
算法优化 Rust (优化 `digit_sum`) 13.2 ~39.4x ~3.2x 算法改进 / Algorithmic
逻辑重排 Rust (先比较后计算) 10.7 ~48.6x ~1.2x 分支预测 / Branch Prediction
依赖库优化 Rust (使用 `fastrand`) 2.8 ~185.7x ~3.8x 第三方库 / Third-party Library

结论与启示

本实验揭示了当前大语言模型在代码优化方面的能力边界:

  1. 局部微观优化表现出色:LLM 擅长识别和重写低效的代码片段,例如将基于字符串的数位和计算改为基于数学运算的方法。
  2. 全局算法与逻辑优化存在盲区:LLM 难以进行跨函数的逻辑流重构,例如未能发现“先进行廉价比较,再进行昂贵计算”这一关键优化模式。
  3. 缺乏系统与环境上下文:LLM 无法感知运行时的性能瓶颈(如随机数生成开销),也无法安全地进行涉及并发数据访问的复杂重构(如并行化)。

因此,虽然 LLM 是强大的辅助工具,可以快速提供初始实现和局部改进,但深入的性能优化仍然需要开发者的洞察力、系统知识以及性能剖析工具的结合。未来的方向可能是将 LLM 与性能剖析工具链更深度地集成,使其能够基于真实的运行时数据做出更明智的优化建议。

常见问题(FAQ)

大语言模型优化代码时有哪些局限性?

LLM在代码优化中存在明显局限,如本文所示,它可能错过关键的算法改进机会,例如优化数字求和的计算方式,而仅关注表面层的性能调整。

为什么在代码优化实验中要切换到Rust语言?

切换到Rust可以更清晰地展示性能选择的影响,避免Python中JIT或NumPy等复杂因素的干扰,从而更准确地评估LLM优化效果和人工优化的差异。

当LLM无法优化代码时,开发者应该怎么做?

开发者应结合人工洞察,深入分析问题本质,如本文中通过算法改进优化数字求和计算,超越LLM的表面提示,实现更根本的性能提升。

晓婷深圳
本文由 晓婷 审核,最后更新于 2026年7月2日
联系编辑 →
← 返回文章列表
分享到:微博

版权与免责声明:本文仅用于信息分享与交流,不构成任何形式的法律、投资、医疗或其他专业建议,也不构成对任何结果的承诺或保证。

文中提及的商标、品牌、Logo、产品名称及相关图片/素材,其权利归各自合法权利人所有。本站内容可能基于公开资料整理,亦可能使用 AI 辅助生成或润色;我们尽力确保准确与合规,但不保证完整性、时效性与适用性,请读者自行甄别并以官方信息为准。

若本文内容或素材涉嫌侵权、隐私不当或存在错误,请相关权利人/当事人联系本站,我们将及时核实并采取删除、修正或下架等处理措施。也请勿在评论或联系信息中提交身份证号、手机号、住址等个人敏感信息。