Python 性能神话与传说

安东尼奥·库尼(Antonio Cuni),这位资深的 Python 性能工程师兼 PyPy 开发者,在布拉格举行的 EuroPython 2025 大会首日,就“围绕 Python 性能的神话与传说”主题发表了演讲。从标题不难看出,他认为关于 Python 性能的许多传统观点充其量只是误导性的。通过大量实例,他指出了自己所看到的真正问题所在。他得出结论认为,内存管理最终将限制 Python 性能的提升空间,但他有一个名为 SPy 的早期项目,或许能为实现超高速 Python 提供一条路径。

他首先请观众举手,如果他们认为“Python 运行缓慢或不够快”;许多人举手,这与他在 PyCon 意大利大会上做演讲时的情况大不相同,当时几乎没有人举手。“观众差异很大,”他笑着说。他多年来一直致力于 Python 性能研究,与许多 Python 开发者交流过,听到了些许流传已久的误区,他希望尝试澄清这些误区。

元素周期表

误区

第一个误区是“Python并不慢”;但根据举手情况,他认为大多数与会者已经知道这是个误区。如今,他常听到开发者说Python的速度并不重要,因为它是一种粘合语言;“现在只有GPU才重要”,所以Python已经足够快。Python 对于某些任务确实足够快,他说,这就是为什么有这么多人使用它并参加像 EuroPython 这样的会议。

有一类程序中 Python 足够快,但这类程序并不包含所有正在使用的 Python 程序——它只是一个子集。需要更高 Python 性能的程序正是推动各种优化解释器努力的动力,同时也促使开发者不断改进程序性能,常通过使用 CythonNumba 等工具实现。

图0:Python 性能神话与传说

在他的幻灯片中,他将这两个集合表示为圆形,其中“Python性能足够快的程序”完全包含在“Python程序”集合内;随后他添加了“所有可能的程序”集合,该集合完全包含前两个集合。在他理想的世界中,所有可能的程序都能够用 Python 编写;目前,需要充分利用处理器性能的程序无法使用 Python。他希望内圈能够扩大,以便 Python 能够在更多程序中使用。

“它只是一个粘合语言”这一陈述的推论是“只需将热点部分重写为C/C++”,尽管这一观点略显过时;“如今人们说应该用Rust重写”。这“并非完全错误”,确实是一种加速代码的好方法,但很快就会“遇到瓶颈”。帕累托原则——由ChatGPT出于不明原因创建的幻灯片所描述——指出80%的时间将花在20%的代码上。因此优化这20%的代码是有帮助的。

但程序随后将遇到阿姆达尔定律,该定律指出,优化代码某一部分的改进效果受限于该部分代码的执行时间;“原本的热点部分现在变得非常非常快,然后你需要优化其他所有部分”。他展示了一张图表,其中某个inner()函数占用了80%的时间;如果将其减少到原来的10%,那么程序的其余部分现在将主导运行时间。

另一个“误区”是认为Python因解释执行而缓慢;虽然这有一定道理,但解释执行只是导致Python缓慢的因素之一。他以一个简单的Python表达式为例:

    p.x * 2

C/C++/Rust编译器可将此类表达式转换为三步操作:加载x的值、将其乘以2,然后存储结果。而在 Python 中,则需要执行一系列操作,包括确定 p 的类型、调用其 __getattribute__() 方法、对 p.x 和 2 进行 unboxing,最后对结果进行 boxing,这需要分配内存。这些步骤与 Python 是否为解释型语言无关,它们是基于语言语义的必要步骤。

静态类型

现在人们在 Python 中使用静态类型,因此他听到有人说,该语言的编译器现在可以跳过所有这些步骤,直接执行操作。他举了一个例子:

    def add(x: int, y: int) -> int:
        return x + y

    print(add(2, 3))

但静态类型在运行时并不强制执行,因此可以以各种方式调用 add() 方法并传入非整数参数,例如:

    print(add('hello ', 'world')) # type: ignore

这是完全有效的代码,类型检查器因为注释而没有报错,但字符串的加法与整数的加法不同。静态类型“从优化和性能的角度来看完全没有用处”。除此之外,以下也是合法的 Python 代码:

    class MyClass:
        def __add__(self, other):
            ...

    def foo(x: MyClass, y: MyClass) -> MyClass:
        return x + y

    del MyClass.__add__

“Python 的静态编译存在问题,因为一切都可能改变,”他说。

因此,也许“一个即时编译器可以解决所有问题”;它们可以大大提高 Python 或任何动态语言的速度,Cuni 说。但这导致了“一个更微妙的问题”。他展示了一张带有 三难困境 三角形的幻灯片:动态语言、速度或简单实现。你可以拥有其中两个,但不能全部三个。

Python 历史上更倾向于动态、简单实现的语言,但它正朝着动态、快速的语言方向发展,例如 CPython JIT 编译器 项目。这意味着失去了简单实现,但他并不在意,“因为前排有人在为我做这件事”,他笑着说。

然而,在实际应用中,使用JIT预测性能变得困难。基于他在PyPy中的经验,以及作为顾问为客户提升Python性能的经历,他认为必须考虑JIT会如何运作,才能获得最佳性能。这是一个复杂且容易出错的过程;他发现有些情况下“无法触发PyPy编译器的优化,因为代码太过复杂”。

所有这些都导致了他所说的“优化追逐”。它始于一个运行缓慢的程序,通过优化其快速路径使其运行更快,从而让 everyone 都感到满意。随后他们开始依赖这额外的速度,而这种速度可能因程序中某个看似无关的改动突然消失。他最喜欢的例子是一个在PyPy(使用Python 2)上运行的程序突然变慢10倍;原来是因为字符串字典中使用了Unicode键,导致JIT去优化了代码,使得整体运行速度大幅下降。

动态

他展示了一段代码,他说这段代码并没有做任何特别有趣或有用的事情,但确实展示了Python编译器遇到的一些问题:

    import numpy as np

    N = 10

    def calc(v: np.ndarray[float], k: float) -> float:
        return (v * k).sum() + N

编译器无法从该代码中推断出任何信息。表面上看,它以常规方式导入了NumPy,calc()函数对v数组的每个元素乘以k,使用sum()将它们相加,然后再加一个常量N。首先,该导入操作可能根本不会引入NumPy;某个地方可能存在导入钩子,执行完全出人意料的操作。N 不能被假定为十,因为它可能在代码的其他地方被修改;与之前的 add() 函数类似,calc() 函数的类型声明也不是绝对确定的。

但几乎在所有情况下,该代码都会按其表面所示执行。开发者很少会做语言允许的这类事情,但程序员通常编写 Python 的方式与语言定义之间的差距,正是“让解释器头疼”的原因。实际上,Python 允许的很多功能并不会真正发生。

正是语言极强的动态性导致其运行缓慢,但“同时这也是 Python 如此优秀的原因”。Cuni 指出,动态特性在 99% 的情况下并不需要,但“在剩下的 1% 场景中,正是这些特性让 Python 变得卓越”。库文件常常依赖语言的动态特性来设计API,以便“让最终用户能轻松使用”,因此这些特性无法简单移除。

游戏

接下来是“编译器游戏”;他逐步展示了一些代码片段,以说明编译器实际上能“了解”的代码内容非常有限。这段代码看似应该会引发某种错误:

    class Point:
        def __init__(self, x, y):
            self.x = x
            self.y = y

    def foo(p: Point):
        assert isinstance(p, Point)
        print(p.name) # ???

在 foo() 函数内部,编译器知道 p 是 Point 类型,而 Point 没有 name 属性。但当然,Python 是动态语言:

    def bar():
        p = Point(1, 2)
        p.name = 'P0'
        foo(p)

另一方面,这里是一个编译器甚至无法假设方法存在的示例:

    import random

    class Evil:
        if random.random() > 0.5:
            def hello(self):
                print('hello world')

    Evil().hello() # 🤷🏻‍♂️

这是合法的 Python 代码,但他笑着说:“这可不是在生产环境中定义的东西,我希望。” “一半时间它还能正常工作,另一半时间会引发异常。祝你好运,编译它吧。”

在另一个示例中,他展示了一个函数:

    def foo():
        p = Person('Alice', 16)
        print(p.name, p.age)
        assert isinstance(p, Person) # <<<

Person 类尚未展示(目前),但有一个空类(仅“pass”)名为 Student。在此情况下,断言将失败,因为 Person 的定义:

    class Person:
        def __new__(cls, name, age):
            if age < 18:
                p = object.__new__(Student)
            else:
                p = object.__new__(Person)
            p.name = name
            p.age = age
            return p

“你可以有一个类,其双下划线构造函数 [即 __new__()] 返回与类无关且非该类实例的对象。祝你好运优化这个。”

游戏的最后一位参赛者是以下内容:

    N = 10

    @magic
    def foo():
       return N

他“去糖化”了@magic装饰器并添加了一些断言:

    def foo():
       return N

    bar = magic(foo)

    assert foo.__code__ == bar.__code__
    assert bar.__module__ == '__main__'
    assert bar.__closure__ is None

    assert foo() == 10
    assert bar() == 20 # 🤯😱

foo() 和 bar() 的代码对象相同,但它们返回的结果不同。如所料,N 的值已被 magic() 修改;代码如下:

    def rebind_globals(func, newglobals):
        newfunc = types.FunctionType(
            func.__code__,
            newglobals,
            func.__name__,
            func.__defaults__,
            func.__closure__)
        newfunc.__module__ = func.__module__
        return newfunc

    def magic(fn):
        return rebind_globals(fn, {'N': 20})

这返回了一个函数的版本(传递了 foo()),该版本对全局变量的值有不同的视图。这可能看起来像是一个牵强的例子,但他写了类似的代码 用于 pdb++ Python 调试器 多年前。 “我声称我有充分的理由这样做”,他笑着说。

抽象

正如他在游戏中展示的那样,语言中确实有一些部分需要考虑,但更根本的问题是:“在 Python 中,抽象并非免费。”当编写代码时,开发者既希望代码性能良好,又希望代码易于理解和维护。这需要付出代价。他从一个简单的函数开始:

    def algo(points: list[tuple[float, float]]):
        res = 0
        for x, y in points:
            res += x**2 * y + 10
        return

它接受一个点列表,每个点由浮点数元组表示,并使用这些点进行计算。然后他将计算部分提取为独立函数:

    def fn(x, y):
        return x**2 * y + 10

这已经比原版更慢,因为调用函数会产生开销:函数需要被查找、创建帧对象等。即时编译器(JIT)可以帮助优化,但仍会增加额外开销。他进一步将数据结构改为Point数据类:

    @dataclass
    class Point:
        x: float
        y: float

    def fn(p):
        return p.x**2 * p.y + 10

    def algo(items: list[Point]):
        res = 0
        for p in items:
            res += fn(p)
        return

当然,这会让程序运行得更慢。Cuni表示这是一个刻意设计的例子,但核心思想是:每种抽象都会带来开销,“最终你会得到一个运行非常缓慢的程序”。这是他所说的“Python到Python”抽象的例子,即代码仅在语言内部进行重构。

“Python到C”抽象,即将代码中性能关键部分提取到C或其他编译语言中,同样会带来额外开销。可以想象,Python实现会不断优化,使得Point对象的列表以简单的线性浮点数数组形式表示,无需装箱,但如果fn()是为Python的C API编写的,这些数字就需要进行装箱和拆箱(双向操作),这完全是浪费的工作。这是“当前C API无法避免的”。加快在 PyPy 下运行的程序的一种方法是移除 C 代码,直接在 Python 中进行计算,而 PyPy 可以很好地优化这些计算。

一头大象

然而,关于 Python 性能,有一个大家很少提及的“大象”存在:内存管理。在当今硬件中,“计算非常便宜”,但内存是瓶颈。如果数据在任何级别的缓存中,访问它是很便宜的,但 RAM 访问非常慢。“一般来说,如果你想获得非常好的性能,我们应该尽可能避免缓存未命中。”

但 Python 倾向于具有一种对缓存不友好的内存布局。他展示了一个简单的例子:

    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age

    p = [Person('Alice', 16), Person('Bob', 21)]

每个Person对象有两个字段,理想情况下应在内存中相邻放置,列表中的两个对象也应相邻放置,以实现缓存友好的布局。但在实际中,这些对象都分散在内存各处; 他展示了一个来自 Python Tutor 的可视化图. 每个箭头代表一个需要跟随的指针,因此可能导致缓存未命中;对于这个简单的数据结构,几乎有十几条箭头。

“这并不是仅仅通过JIT编译器就能解决的问题;不改变语义就无法解决它。”他说,Python天生就不适合缓存,“我真的不知道如何解决这个问题”。他的“悲伤真相”结论是:“不破坏兼容性,Python无法变得超级快。”他在演讲中提到的某些动态特性(“姑且称之为疯狂”)最终会阻碍性能提升。“如果我们要保留这种疯狂,那么就必须在性能上做出妥协。”

他的下一张幻灯片是“结束”,配有悲伤的表情符号(“😢💔🥹”),这是他在一年前于 PyCon Italy 演讲时结束演讲的地方。不过这次,他想“给一点希望”,于是添加了一个问号,然后重申了在不破坏兼容性的情况下,Python 无法变得超级快速。

他为社区提出了一项建议,如果社区决定Python应努力达到顶级性能水平,他希望社区能做出这一决定,但“说‘不’也无妨”。他建议通过保留动态特性在实际有用的场景中,来调整语言语义,例如通过限制对特定时间点可进行的动态更改类型,以便编译器能够依赖某些行为和结构。“不允许世界在任何时间点像现在这样随意变化。”

与此同时,类型系统应以性能为导向进行重构。目前,类型是可选的且不强制执行,因此无法用于优化。目标是让性能导向的代码能够用Python编写,而非通过Python调用其他语言。但对于仍需调用其他语言的场景,应消除此类操作的额外开销(如装箱操作)。“最重要的是,我们希望保持Python的特性,因为我们喜欢这门语言,否则我们也不会在这里。”

Cuni表示他有一个潜在的解决方案,“不是让Python更快”,因为他声称这是不可能的。SPy(即“静态Python”)是他几年前启动的项目,旨在解决性能问题。SPy的所有标准免责声明均适用,它“处于开发阶段,是研究与开发项目,[且]我们不知道它将走向何方”。有关该项目的最佳信息可查阅上述链接的 GitHub 页面,或他在五月底于 PyCon Italy 发表的 关于 SPy 的演讲

他展示了一个快速演示,展示了如何从摄像头进行实时边缘检测;该演示在浏览器中使用PyScript运行。演示中,左侧显示原始摄像头画面,右侧最初使用NumPy进行边缘检测; NumPy的帧率不足每秒两帧(fps)。切换到基于SPy的边缘检测算法后,右侧图像能跟上摄像头速度,帧率提升至约60fps。该演示的代码已发布在GitHub上。

他特别推荐了SPy仓库及其问题跟踪器给感兴趣的与会者;部分问题已被标记为“新手友好”和“需要帮助”。此外,还有一个Discord服务器用于讨论该项目。不久之后,本次演讲的视频将出现在EuroPython YouTube频道上。

本文文字及图片出自 Python performance myths and fairy tales

共有 213 条讨论

  1. 我认为这里一个重要的背景是,计算机在执行推测性最佳路径时非常、非常擅长。

    文章中的例子看起来有些令人沮丧:一个即时编译器(JIT)如何在将参数相加之前,以一种比直接运行解释器更有效的方式,完成所有检查以确保参数没有问题?但在实际中,JIT 确实可以生成包含这些检查的代码,而现代处理器会预测最佳路径并有效地在并行执行检查的同时运行它。

    JavaScript 也有复杂的原型链和常见的盒装对象使用场景——但 V8 已经让常见用例变得极快。我对 Python 的未来充满期待。

    1. 这意味着从绝对意义上讲,Python 并不像你可能天真地认为的那样慢。

      但我们并不以绝对意义来衡量编程语言的性能。我们通常以相对意义来衡量,通常与 C 语言进行比较。而当你的 Python 代码在推测这个 Python 对象将如何被拆箱、其方法的位置、如何拆箱其参数、将对这些参数调用哪些方法等时, 编译后的代码则在推测程序员实际编写的代码,并并行执行该代码,因此当 Python 解释器成功推测出某个方法调用将如何与实际对象解析时,编译后的代码语言已经完成了大约 50 行语法复杂度相似的代码。(这是一个不严谨的术语,因为这本身就是一场不严谨的讨论,但请考虑 Python 中类似 “p.x = y” 级别的语句与 C 语言的对应情况。)

      这是无法避免的。你可以将你那台功能强大的推测性并行 CPU 用于处理 Python 解释,或者用于做真正的工作,但你不能两者兼得。

      毕竟,解释器也是 C 代码。它并没有访问其他程序无法访问的特殊推测性指令集。

      1. 我喜欢这个“实际工作”。实际工作,比如编写链表、数组边界检查、打开文件时的所有错误处理等等?Python和C都有其适用场景,显然Python在执行“1 + 1”这类操作时永远无法达到C的速度。真正的“实际工作”在于完成任务,而不仅仅是确保以最少的 CPU 周期完成一些网页表单生成。

        无论如何,我认为你在大多数情况下是正确的。Python 绝不会在所有场景下都是最快的语言。然而,仍有很大的优化空间,而且考虑到它是一门流行语言,值得付出努力。

        1. 我无法理解你第一段的内容。讨论的主题是Python性能。我们通常不会尝试测量像“实际工作”这样模糊的概念,因为这个术语在性能讨论中并不明确。我的帖子中提到了“代码行数”,尽管这仍然是一个模糊的概念(我在帖子中已经指出了这一点), 但它传达了这样一个想法:虽然 Python 需要为“x.y = z”做大量工作,以处理“x.y”可能代表的所有含义,包括用户可能在上次执行该语句后更改了其含义的可能性,但编译型语言在解决此问题时通常只需做少于一个数量级的“工作”。

          这是我之前提到的Python的一个问题,甚至建议有人可以围绕这个想法设计一种语言:https://jerf.org/iri/post/2025/programming_language_ideas/#s…在 Python 中,你为所有这些动态功能付出了巨大的代价,但实际上,你很少真正动态修改类层次结构,或为任意类型的实例附加任意属性。你为这些功能付费,但从中受益的频率远低于 Python 为这些功能付费的次数。Python 花费大量时间反复检查是否仍可安全执行它认为可执行的操作,即使在 JIT 环境下也难以去除这些检查,因为要证明可消除这些检查极为困难。

          1. 我理解你的意思。某种程度上,我的评论与你的大部分内容并不相关。我在第一段中提到的是,你在描述语言运行时低效性时使用的词汇,同样可以用来解释为何这些低效性存在于更高层次的流程中,比如业务效率。考虑到这些上下文的对比,你选择的词汇让我感到有趣,甚至用了“你付钱,付钱,付钱”这样的表述。

          2. 你声称处理Python解释不是“真正的工作”。你现在正确地提出了这个问题:什么是“真正的工作”?如果解释Python意味着我不用检查数组边界,为什么解释Python不是真正的工作?

            1. >为什么解释 Python 不是真正的工作,如果这意味着我不用检查数组边界?

              因为其他语言也能为你做到这一点,而且快得多……

        2. 换句话说,我选择 Python 正是因为它的语义,包括动态操作符定义、鸭子类型等。

          仅仅因为我没有亲自编写边界检查、类型检查、动态分派和错误处理代码,并不意味着选择 Python 不是一个有意识的决定。这一切都是“真正的工作”。

          1. 类型检查和边界检查并非“真正的工作”,因为当有人在你的网站上查看银行账户余额,或在数字音频工作站中为音频轨道添加音效时,他们不会想:“太好了!现在计算机要为我进行类型检查了!”类型检查和边界检查可能是实现目标的良好手段,但从外部世界的角度来看,它们并非最终目标。

            当然,银行账户只是为了支付牙医安装牙冠等费用,音效只是为了让音乐听起来不像达フト·庞克(Daft Punk)之类的,所以这有点模糊。这取决于人们在想实现什么。作为程序员,因为我们经历过深夜调试数组越界的问题,我们倾向于将边界检查和类型检查视为目的本身。

            但这仅限于一定程度!通常,类型检查和边界检查可以在编译时完成,这更高效。当我们这样做时,只要程序正确运行,我们_绝不会_†对程序未进行运行时类型检查感到失望。我们不会看着正在运行的程序说:“这个程序如果在运行时进行更多的类型检查会更好!”

            不。运行时类型检查纯粹是无谓的开销:浪费部分 CPU 资源在与实现我们编写程序时设定的目标无关的计算上。它可能是一个值得权衡的取舍(例如为了实现的简洁性),但我们必须将其记在账本的借方,而非贷方。

            ______

            † 当然,除非我们在调试 PyPy 的类型特化 bug 之类的问题。那时我们可能会努力构建一个迫使 PyPy 在运行时进行更多类型检查的程序,而类型检查本身就成了目的。

            1. > 而声音效果只是为了让你的音乐听起来不像Daft Punk之类的手段

              你的意思是?Daft Punk不是傻瓜。为什么单挑他们出来 🙂

              1. 嗯,我最初写的是“更像Daft Punk”,但后来担心有人会认为我在刻板印象化音乐人,认为他们缺乏原创性,于是改了说法。

        3. 我认为他们指的是处理器在做实际工作,而不是程序员。

          1. 我明白,但我觉得用词很有趣,因为这些词在更广泛的语境下也适用。这就像说,Python将工作从人力小时转移到CPU小时一样 🙂

        4. “Python和C都有其适用场景[..]”

          如果你指的是历史原因,那么是的,但我认为没有内在原因阻止我们拥有像 Python 这样方便且像 C 这样快速的语言。

          1. 我认为确实存在内在原因。

            为了实现简洁性,你需要接受适用于所有人的默认设置。这些默认设置可能对你来说并不理想,但它们足够好。

            为了获得性能,这些“足够好的默认设置”突然不再足够好,现在你需要对你所做的事情非常具体。

            也许有了足够智能的编译器,高级语言可以编译成性能非常好的东西,但“足够智能的编译器”的承诺尚未实现。

          2. 看起来很多人都在尝试,但似乎没有成功。

              1. 我不知道有谁会同意这一点,包括Python和Rust的爱好者。

                1. 如果我必须选择,我会比较 Go 语言,但绝对不会是 Rust!与 Python 相比,Rust 的编译速度太慢,语法也不够人性化……

      2. > 而当你的 Python 代码在猜测这个 Python 对象将如何被解箱时

        我觉得这不对?GP 讨论的是 JIT 编译的代码。

      3. > 毕竟,解释器也是C代码。

        哪个解释器?我们讨论的是将Python编译为原生代码的JIT。

      4. 好吧,有Mojo,所以很快你可能不需要太在意这些。性能可能比C更好。

        1. 我已经听过关于 Python 性能“优于 C”的承诺超过 25 年了。我记得在 comp.lang.python 讨论组中,那是在大多数人只听说过的 Usenet 时代。

          此时,你真的不该做出这样的承诺。这个承诺很可能比你还要老。就让性能保持现状吧,如果你今天需要更好的性能,请注意,有各种各样的编程语言随时准备为你提供~25-50倍的单线程性能提升,甚至在多核性能方面表现更佳——如果你需要的话,这些性能提升是立竿见影的。如果你需要,等待Python提供这种性能提升并不是一个明智的选择。

          1. 我维护的一个用Python编写的程序比它替换的C语言程序更快。C版本可以执行更多操作,但这相当于枚举2^N种可能性,而你只需要枚举N种可能性。

            当然,如果我用C语言实现,我的版本会更快,但从指数级到线性级的性能提升完全压倒了语言差异。

            1. 所以你说两个不同程序实现两个不同算法的性能不同,就能推断出底层语言/编译器/解释器的行为?

              你听说过控制变量吗?

              1. 我可能表达得不清楚。C语言更快。如果我用C语言重新实现Python程序,它会更快。然而,Python使将问题转换为线性形式变得如此简单,以至于使用Python比继续维护C版本更有优势。

                这就是我的观点: raw 执行速度只有在执行正确的事情时才有帮助。不要低估在 Python 中实现正确事情的便利性。

              1. 在甜度上?在酸度上?在重量上?

          2. 我比Python还要年长一些:)。我认为Clang和LLVM的创建者对如何提升性能有着相当深刻的理解。可以将Mojo视为具有更好使用体验和更先进编译器的Rust,你可以将其与常规Python混合使用。

          3. 你可能说得对,Mojo似乎更像“Python风格”而非与Python源代码兼容。许多特性(尤其是类)都缺失了。

        2. Mojo感觉更像是一种为人工智能设计的语言,而非真正的人类编程语言。该语言的文档直接跳入聊天机器人和人工智能提示词的主题。

          1. 我的意思是,这是他们关注的主要用例,但显然这不是唯一的用例

    2. 主要问题是,当看似无害的更改导致优化悄然失败时,性能突然下降10倍。这其实是任何语言都存在的问题(毕竟CPU缓存未命中是客观存在的,许多非动态语言也有包装对象),但在Python、JS和Ruby等动态语言中,这个问题要严重得多。

      大多数情况下这并不重要,因为大多数高吞吐量的 Python 代码都会调用 C/C++,而这些问题在 C/C++ 中并不那么严重。大多数 JavaScript 代码也会调用 C/C++ 浏览器 DOM 对象。只要热点路径不在这些语言中,你就不会面临“无辜更改导致性能崩溃”的高风险。

      即使在服务器端,大多数 JavaScript/Python/Ruby 代码也只是简单的 HTTP 堆栈处理程序,调用数据库并处理数据。而且,处理请求的大部分过程(编码 JSON/XML 等、解析 HTTP 消息等)可以使用低级语言编写。

    3. 尽管 JavaScript 支持原型修改、with 操作符等会增加优化难度的构造,但典型 JavaScript 代码并不使用这些特性。因此,JIT 编译器只需添加少量检查,识别存在问题的构造并引导其进入慢速路径,同时优化一组常见模式即可。此外,JavaScript JIT 编译器无需过多关注调用任意原生代码,因为浏览器内部实现可调整/重构以适应 JIT 需求。

      对于 Python 而言,情况则不同。存在更多不利于优化的构造,且流行库广泛使用这些构造。此外,Python 会调用具有固定 ABI 的任意 C 库。

      因此,优化 Python 本身就更加困难。

    4. > 但 V8 已将常见用例的性能提升至极致。我对 Python 的未来充满期待。

      V8 不是仍然完全单线程且消息传递受限吗?Python 刚刚经历了大量工作以加快多线程代码的速度,如果它不得不完全放弃线程并退回到基于共享内存的多进程来匹配 V8,那将令人失望。

      1. 多线程代码通常受内存带宽限制,甚至比原始计算更甚。C/C++/Rust 在高效利用内存带宽方面表现出色,而脚本语言相比之下则较为浪费。因此,我不确定多线程能否显著缩小二进制编译语言与 Python 等脚本语言之间的性能差距。

    5. 换个角度说,我们可以认为 Lisp 机器的 CISC 支持语言的全栈设计理念在 JavaScriptCore 中得以延续,体现在 M 系列的大型重新排序缓冲区和 ILP 支持上。

    6. 我好奇分支预测是否还能在分支检查变得复杂或规模较大时隐藏性能损失。分支预测是一种非常底层的优化。即使预测正确,也并非完全免费。CPU 仍需评估条件,这会消耗资源,尽管它不再处于关键路径上。然而,我认为如果 CPU 在条件执行之前走得太远,它会陷入停顿(最终所有代码都必须执行完毕程序才能完成)。或许由于 Python 的特性,检查会变得如此复杂,以至于在紧凑的循环中会对资源造成显著压力?

    7. 有一个显而易见的答案——将一切运行在 GPU 上。每个投机分支在自己的核心上并行运行,当分支遇到问题时,添加一层超快的分支切换。

      鉴于当前的计算状态,我无法明确表示这个建议是否是讽刺。

  2. “用C/C++重写热路径”也是一个地雷,因为边界跨越的效率太低。因此你需要“尽可能一次性分发”而非持续调用原生代码

    1. 这不仅是效率问题。即使使用PyO3或SWIG等高级FFI生成器,添加FFI也会带来大量工作、复杂性,并使调试和分发变得困难等。

      在我看来,在大多数情况下,如果你想用两种语言和FFI来编写一个项目,通常最好不要这样做,而是只使用一种语言,即使这种语言不是最优的。在这种情况下,最好直接用C++(或Rust)编写整个项目。

      虽然有一些例外,但总体来说,FFI是一个巨大的成本,而Python并没有带来足够的优势来证明其使用的合理性,尤其是当你已经使用C++的情况下。

    2. 我见过的一种将Python用作“粘合语言”的场景,实际上避免了这些绑定带来的性能问题,那就是GNU Radio。这是因为其架构基本将Python用作配置语言,在启动时设置计算流程图,而运行时其余部分完全由编译代码(通常是C++)实现。显然这种方法并不适用于所有问题,但它确实塑造了我对何时/如何接受一种较慢的粘合语言的看法。

      1. 没错。仅使用 Python 进行控制流,并将数据流卸载到更适合此任务的库中:用 C 编写、使用紧凑结构体、缓存友好等。

        如果你需要多进程处理,使用多进程库,散射和聚合类型计算等

    3. 如今是“用Rust重写”。

      通常Python只是入口和出口点(带一点数据处理),对吧?

      而绝大多数业务逻辑都是用Rust/C++/Fortran实现的,对吧?

      1. 在计算机视觉领域,你最终会需要读写巨大的缓冲区,这些缓冲区不适合序列化且难以共享。即使以60 FPS的速度分配和释放多兆字节的帧缓冲区,也会对分配器造成一定压力,因此你需要复用它们,这意味着必须考虑内存安全。

        这可能就是为什么他的演示使用了Numpy进行Sobel边缘检测。Sobel在标准分辨率下于CPU上运行足够快,但一旦需要在你的快速语言之外读取或写入那个巨大的缓冲区,事情就会变得棘手。

        这个问题在Tauri中也会出现,因为你需要在Rust和JS之间进行桥接。我不确定Electron应用是否也存在同样的问题。

        1. 不幸的是,这个“numpy”索贝尔代码并不理想——所有迭代都在Python中完成,因此使用numpy并没有带来太多好处。如果使用scipy.convolve2d对numpy.array进行操作,速度会快得多。

        2. 在数据科学/工程领域,Apache Arrow 作为跨语言桥梁,因此无需将数据序列化为特定语言结构,这非常方便。

    4. 这难道不是将循环中重复使用的相同操作提取出来的通用规则的具体例子吗?我不确定在 CPython 中调用 C 是否特别慢(因为许多操作实际上只是在底层调用 C)。

        1. 您无需对数据进行序列化,也无需在CPython和C之间转换数据表示形式。那篇文章是错误的。在他们的示例中,耗时的是以CPython喜欢的方式存储数据(如整数),而非将该形式转换为在C中易于操作的形式(如寄存器中的原生整数)。一旦绕过所有类型检查和引用计数,这只需一条MOV指令。

          您可以通过在C扩展中实现自己的数据容器来一定程度上避免这个问题(文章的解决方案#1);从Python循环中读取该容器仍可能比频繁分配和释放包装整数(伴随动态分派和引用计数)快得多。但确实,要获得合理性能,你需要完全避免在 Python 解释器循环中运行字节码(文章的解决方案 #2)。

          但这并非因为序列化或其他数据格式转换。

        2. 在 Python 中复制和移动数据的开销令人沮丧。当任务受 CPU 性能限制时,由于全局解释器锁(GIL)的存在,无法使用线程(线程具有共享内存),因此只能使用整个进程,结果浪费大量周期在来回通信上。虽然可以在 Python 进程之间创建共享内存缓冲区,但这远不如两个 Java 线程基于带有同步机制的共享数据结构进行协作顺畅。

      1. 关键是将整个循环移至编译型语言,而不仅仅是内部操作。

      2. 它们确实很慢。有一个项目测量了不同语言中 FFI 的开销,而 Python 的表现非常糟糕

    5. >边界跨越的效率有多低

      对于人们编写的 99.99% 的程序来说,现代 M.2 NVMe 硬盘已经足够快,这是将数据加载到 C 扩展或进程中最懒惰的方式。

      然后是 Unix 管道,速度足够快。

      然后是共享内存,基本上不涉及加载。

      与 Python 一样,一切都取决于具体实现。

      1. 问题不在于加载数据,而在于数据转换(即将数据转换为更适合快速语言处理的数据结构,并进行反向转换)。或者如果不进行转换(或数据特殊到无需转换),则可用的优化手段将大大受限。

        1. 存在多种无需序列化的数值数据结构,可实现Python与C/C++/Rust等语言之间的高效互操作。例如标准库中的array.array、numpy.array以及PyArrow。

        2. 这纯粹是设计问题,与具体语言无关。

  3. 这里的主要关注点是合理性,而这是我之前未曾考虑过的:Python 内存的动态特性导致缓存局部性较差。这有道理。我将把这个问题留给他人深入研究。

    除此之外,我原本预期会出现一些吹毛求疵的论点,而这个论点确实没有让我失望:

    “C/C++/Rust 编译器可以将此类表达式转换为三项操作:加载 x 的值、将其乘以二,然后存储结果。然而在 Python 中,需要执行一系列操作,包括确定 p 的类型、调用其 __getattribute__() 方法、对 p.x 和 2 进行解箱,最后对结果进行装箱,这需要内存分配。这些步骤与 Python 是否为解释型语言无关,它们是基于语言语义的必要步骤。"

    这个论点的缺陷在于,用户并非试图执行这些操作,而是试图进行乘法运算。因此,语言最终必须执行所有这些操作的事实确实意味着它很慢。为什么?因为如果不执行这些操作,最终结果仍可实现。它们在这种情况下纯属额外开销,毫无价值。换言之,如果 Python 拥有足够智能的编译器/JIT,这些操作可以被优化掉(在这种使用场景下,但当然不是所有情况)。这种论点类似于:“Python 并不慢,它只是在做很多工作。”这可能是真的,但不能就此止步。必须问这些工作是否有价值,而在此情况下,它们毫无价值。

    同样的道理,有人可能会说,任何经过高度优化的解释型语言都是“快速的”,因为解释器本身就是优化的。但再次强调,这种思考方式是错误的。你必须首先问自己:“用户试图做什么?与被认为是快速的语言相比,它的计算速度是否足够快?” 如果答案是“不”,那么该语言就不算快,即使它达到了预期的目标。用这种方式玩弄概念正是导致用户对“快”与“慢”语言产生混淆的原因。慢并不一定“不好”,但要实事求是。在这种情况下,我认为正确的表述方式是:“它拥有一个快速的解释器”。最后一句足以让有足够经验的开发者明白他们需要知道的内容(因为他们明白静态编译/JIT语言与解释型语言属于不同速度等级,不应直接比较执行速度)。

    1. 一个“足够智能的编译器”无法合法跳过Python的语义。

      在 Python 中,p.x * 2 意味着动态查找、可能的描述符、大整数溢出检查等。编译器只能在证明这些不重要或通过推测并添加保护措施的情况下省略这些操作——但这仍然会增加开销。这就是为什么 Python 在标量热循环中较慢:不是因为它是解释型语言,而是因为其动态契约必须得到遵守。

      1. 在 Smalltalk 中,p x * 2 也有类似的流程,而且更糟糕的是,假设 p x 消息选择器返回的值不理解 * 消息,因此它会进入调试器,然后开发者会通过代码浏览器向对象添加 * 消息,点击保存,并通过重做退出调试器,从而成功结束执行。

        不知何故,Smalltalk 的 JIT 编译器能够在不引发重大问题的情况下处理此类情况。

        1. Smalltalk 的 JIT 通过推测类型并插入守护条件来加速 p x * 2 的执行,而非跳过语义分析。Python 的 JIT 编译器也采用类似方法(如 PyPy),但 Python 的动态特性(如 __getattribute__、无界整数、C-API 钩子)使得优化难度更高且成本更高。

          在 Python 中实现真正速度提升需通过缩窄语义(如借助 NumPy、Numba 或 Cython)而非寄希望于编译器超越语言本身。

          1. Python 的 JIT 也能做到这一点,它可以检查 __getattribute__() 是否为默认实现,并直接用 p x 替换其调用。这仅适用于未在运行时修改且未实现自定义 __getattribute__ 的类。

          2. 人们总是忘记基于图像的语义开发、调试器、元类、_becomes:_等消息。

            所有可以作为 Python 借口的动态特性,Smalltalk 和 Self 都具备,而且是双倍的。

        2. 编辑并继续功能在许多 JIT 运行时语言中可用

      2. 首先,我们需要添加“仅”这个词:“不仅因为它是解释型语言,还因为其动态合同必须得到遵守。”解释型语言天生就慢。这没什么不好,只是一个事实。

        其次,这最多只能解释为什么它慢,而不是它不慢,而这就是我的观点。Python 确实很慢。非常慢(尤其是在计算密集型工作负载下)。但这没关系,因为它做到了它需要做的事情。

    2. > 这些纯粹是开销,在此情境下毫无价值。换言之,如果 Python 拥有足够智能的编译器/JIT,这些问题可以被优化掉(在此用例中,但绝非所有情况)。

      因此,有了 Numba。

    3. 前一段是

      > 另一个“误区”是 Python 因为是解释型语言而慢;虽然这有一定道理,但解释型特性只是导致 Python 慢的原因之一。

      他承认它慢,但他只是说这与解释型特性无关。

      1. 我认为这并不正确。解释型语言的慢速是其性能问题的主要原因之一。最快的解释型语言与C/C++/Rust等语言相比,速度慢了1到2个数量级。如果你的语言进行数学运算的速度比C慢20到100倍,那么从用户角度来看,它就不算快。句号。不过,它可能有一个“快速解释器”。记住,用户并不关心它是否是解释型语言中的快速语言,他们只是试图实现自己的目标(即尽可能快地进行数学运算)。即使他们实现了完美的缓存局部性,Python在数学/计算方面仍然会非常慢。

        1. 200-100倍的慢速有点刻意挑选,但用例确实很重要。

          通常从用户角度来看,对于长期运行的服务,初始启动时间要么可接受,要么几乎察觉不到,尽管还有其他成本。

          如果你看看那些支持上述观点的例子,它们几乎都是微小的玩具程序,其中生成字节码/机器码的成本难以摊销。

          这篇帖子中的这段话也过于简化:

          > 但程序随后会遇到阿姆达尔定律,该定律指出,优化代码某一部分所带来的性能提升受限于在已优化代码中花费的时间

          我是阿姆达尔定律的忠实拥护者,但同时也意识到它过于悲观,且在并行化方面最为现实。

          在多处理器与并行处理之间,由于抢占等因素,它会遇到严重问题。

          是的,你仍然需要承担抽象层等成本……但在当今世界,AMD上的零页、ARM上的16k页以及大量映射寄存器、桶移位器等…… . 使得情况复杂得多,尤其是C语言被迫使用跳转函数等机制。

          如果你实际追踪CPU操作,数学运算的实际操作非常相似。

          不过,现代编译器确实令人惊叹。

          解释型语言往往已经足够。尤其是在互联网、数据库和其他系统组件也限制了速度提升的益处时,由于… 阿姆达尔定律。

          1. 我并不是在挑三拣四,而是专门讨论计算性能(不包括I/O和标准库)。然而,当测量通用任务时,这将涉及计算、I/O、标准库性能等,Python整体上通常不会比其他语言慢20到100倍。其I/O层与许多其他语言一样是用C语言编写的,因此一旦涉及I/O等待,性能差距便会趋于平衡。同样,Python拥有基于C语言实现的高速字典(dict)结构,因此在进行大量映射操作时,也能在计算(速度极慢)与字典(速度极快)之间实现时间摊销。

            总之,这取决于具体情况。我指的是计算性能,而非 I/O 或通用任务的基准测试。是的,如果任务包含计算和 I/O 的混合(这确实是典型用例),它不会慢 20-100 倍,而更可能是“仅”慢 3-20 倍。如果几乎100%受I/O限制,它可能根本不会更慢(甚至在适当缓冲的情况下更快)。如果你在进行数值计算(没有像NumPy这样的C库),你的程序很可能比用C实现慢40-100倍,而其中许多并不是简单的示例程序。

            1. 即使在计算性能方面,实际差距可能比你预期的更小。

              Python 并非按行逐行评估,即使在微型 Python 中也是如此,而微型 Python 是唯一一种不以相同方式工作的常见实现。

              Cython 虚拟机将生成操作码的抽象语法树(AST),二进制操作最终只是从栈中弹出,或者你可以使用像 PyPy 这样的实现。

              如何高效地保持管道的输入比计算成本更关键。

                   int a = 5;
                   int b = 10;
                   int sum = a + b;
              

              编译为:

                   MOV EAX, 5
                   MOV EBX, 10
                   ADD EAX, EBX
                   MOV [sum_variable]
              

              在 PVM 二进制操作中,会从栈顶(TOS)和栈的第二顶部项(TOS1)中移除栈顶元素。它们执行操作,并将结果放回栈中。

              在现代 CPU 上,这些弹出操作的开销并不高,且部分 C 编译器会根据多种因素使用栈。即使在 C 中,你也需要根据具体用例使用数组结构等。由于开销导致的管道阻塞和指令提取才是主要差异。

              正是设置成本、垃圾回收(GC)、全局解释器锁(GIL)等因素导致 Python 在许多情况下运行较慢。

              虽然我并非认为 Java 像 Python 那样慢,但 Java 也是字节码,且其设计决策和假设在一般情况下甚至优于 C,或至少与 C 相当,除非进行高度优化。

              但实际等效计算几乎相同,编译器进行的优化存在差异。

        2. 我将用你引用的首段内容来回应你的论点:

          > C/C++/Rust编译器可以将此类表达式转换为三项操作:加载x的值、将其乘以二,然后存储结果。而在 Python 中,则需要执行一系列操作,包括确定 p 的类型、调用其 __getattribute__() 方法、对 p.x 和 2 进行解箱,最后对结果进行装箱,这需要内存分配。这些步骤与 Python 是否为解释型语言无关,而是基于语言语义的必要步骤。

          1. 通常,动态语言的JIT会通过观察操作实际作用的类型,然后为实际使用的类型(在大多数情况下)或几种不同类型硬编码快速路径来处理此问题。当类型每次都不同时,它必须每次都进行查找——但这种情况非常罕见。

            即:

            if(a->type != int_type || b->type != int_type) abort_to_interpreter();

            result = ((intval*)a)->val + ((intval*)b)->val;

            CPU 确实需要执行这两行代码,但它会并行执行,因此情况并没有你想象的那么糟糕。当然,除非你切换到解释器。

    4. > 这个论点的关键在于,用户并不试图执行这些操作,

      我持不同观点。我认为问题不在于用户在执行这些操作,而在于语言本身无法理解用户试图实现的目标。

      Python 的明确目标一直是易用性,而且易用性始终优先于速度或令人讨厌的编译时错误信息。“只要按原样运行代码就行了”一直是目标。我记得当引入 never 类模型时,不得不引入 __get_attribute__。作为一名 C 程序员,我的第一反应是“天啊,这会影响速度”。后来,我开始利用它将新系统扭曲成其发明者可能从未想过的方式。它是一个LR(1)解析器,允许你用普通的Python语句编写语法规则。

      虽然他们可能没有想到以这种特定方式滥用语言,但我确信其明确目标是创建一个框架,使任何想法都能用最少的代码表达。其他人也利用了他们提供的钩子,融入语言的构建方式,创建了像pydantic和spyne这样的工具。例如,spyne允许你用Python类声明来表达RPC使用的网络序列化格式,然后将它们编译成JSON、XML、SOAP等格式。Sqlalchamey允许你用Python语法表达SQL,尽管方式更为直接。

      这些框架在扭曲语言方面非常巧妙。在这些框架内部,“a = b + c”并不意味着“将b加到c上,并将结果赋值给a”。例如,在LR(1)解析器中,它意味着“存在一个名为'a'的生产规则,该规则由'b' followed by 'c'组成”。在这种表示中,'a'持有对'b'和'c'的引用。随后,LR(1) 解析器会处理该表达式,将其编译为截然不同的形式。最终结果与二进制补码加法相去甚远。

      类似地,也可以利用强大的类型系统实现类似功能。例如,我曾见过用 Scalar 语言表达的 FPGA 设计。然而,由于 Scalar 的类型系统坚持在编译时明确知道程序的运行细节,因此 Scalar 能够大致推断出程序员正在构建的内容。编译结果的速度不会比其他代码慢多少。Python通过几乎完全放弃编译时的类型检查,将所有检查推迟到运行时,实现了相同的灵活性。因此,编译器对最终将要执行的内容一无所知(例如,LR解析器中的加法运算只执行一次),这就是我上面所说的“语言不知道程序员试图做什么”。

      你认为既然它是解释型语言,那么解释器的职责就是在运行时弄清楚程序员试图做什么。当然,解释器可以推断出“a = b + c”实际上是在对两个32位整数进行加法运算,且不会发生溢出。这确实没错,但这会在运行时产生大量额外工作。这与演讲中表达的观点本质上相同:选择在运行时处理意味着语言在灵活性与速度之间做出了取舍。

      你不能总是通过解释器来解决这个问题。JavaScript拥有一些最好的解释器,它们确实能让正常路径运行得很快。但这些解释器有其局限性,通常表现为“如果你在运行时修改类内部结构,比如替换函数定义,我们就放弃所有JIT编译的尝试”。人们通常不会在 JavaScript 中这样做,但巧合的是,Python 的设计,包括元类、使用 “type(…)” 创建的动态类型,以及 “__new__(..)”,几乎可以说是鼓励了这种编码风格。这再次是一个语言设计选择,它优先考虑灵活性而非速度。

  4. 本文突出了 Python 性能优化的重要挑战,特别是由于其高度动态的特性。然而,一个实用的解决方案是将 Python 根本上视为一个领域特定语言(DSL)框架,而非单纯的通用解释型语言。DSL 可以有效地编译为高度高效的机器码。

    例如,Numba JIT用于数值计算、Bodo JIT/dataframes用于数据处理,以及PyTorch用于深度学习,都清楚地展示了这一点。Python灵活的语法使得创建复杂对象及其操作符(如数组和数据框操作)成为可能,而这些编译器能够高效地将它们转换为接近C++级别的性能代码。DSL操作符的实现也可以在必要时利用C++或Rust等低级语言。文章未提及的另一个重要方面是并行性,而 DSL 编译器通常能对此进行非常有效的处理。

    鉴于数据科学和人工智能是Python的主要应用场景,Numba、Bodo和PyTorch等编译器已经证明,许多性能关键型场景可以得到有效解决。进一步投资于领域特定语言(DSL)编译器,为提升Python在多个领域的性能和可扩展性提供了切实可行的路径,同时不会牺牲开发者的易用性和生产力。

    免责声明:我曾参与过Numba和Bodo JIT的开发工作。

    1. 这条评论是由大语言模型(LLM)写的吗?

  5. 我真的希望 PyPy 能够更受欢迎,这样我就不用再第 n 次争辩 Python 速度很快了。

    即使你必须坚持使用 CPython,Numba、Pythran 等也能以最小的代码更改为你带来惊人的性能。

  6. > 他所谓的“悲哀的真相”结论是:“Python无法在不破坏兼容性的情况下变得超级快。”

    这是Python 4.0的一个合理案例吗?

    > 因此,也许“一个JIT编译器可以解决所有问题”;它们可以大大提升Python或其他动态语言的速度,Cuni说道。但这会导致“一个更微妙的问题”。他展示了一张三难选择三角形:动态语言、速度或简单实现。你可以拥有其中两个,但不能全部三个。

    这个三难选择不断让我回到Julia。它比Python更复杂,但速度快得多(受预编译时间影响),且几乎同样动态。我很高兴这种语言没有消亡。

    1. > Python 4.0 的一个不错案例?

      我认为“Python 4.0”实际上将不得不成为由不同团队开发的新语言,只是它与现有语言在语法上有着强烈的相似性。(而至少部分原因在于,每个人都被这项任务的规模吓退了。)

      感谢提醒我从未抽空去了解Julia。

        1. 闭源专有语言永远无法取代Python。

        2. 我没试过,但这符合我的理解,没错。

          个人而言,我更感兴趣的是从头设计。

    2. > Python 4.0 的一个不错案例?

      我最终肯定同意这一点,但目前为什么不让开发者在对象上设置 `dynamic=False` 并使其成为可选项呢?这就是谷歌处理 Angular 升级时断开兼容性的方式,实际上效果很好,因为人们有数年时间来准备任何断开兼容性的更改。

    3. 没错,这正是“因地制宜”的典型案例,正如你所说。

      我热爱Python。它与uv的结合令人惊叹;我今天早上刚用它实现了一个简单的CLI工具,用于分析带有内联依赖的数据,这正是我需要的完美解决方案,且编写、运行和调试都极其简单。

      基于以往经验,我不建议将 Python 用于 API 服务器,尤其是当性能(延迟、吞吐量)和请求可扩展性是关键考量时。这类场景有许多其他优秀工具可选。但若需要编写 API 服务器且对性能要求不高,那么 Python 同样是理想选择。

      但它确实很适合它所擅长的领域。如果 Python 4.0 引入了破坏性变更,我希望它能保留高度解释性的特性,以便像 Pydantic 这样的工具继续正常工作。

    4. 如果 Julia 解决了包管理器的问题(导入文件是否仍然需要较长时间加载?),我认为它可能会变得流行。

      1. 我想你指的是TTFP(首次绘图时间)问题(包管理器本身非常出色)。通过一系列优化,TTFP已大幅提升,且你可以预编译项目以保持运行速度,例如在使用不同参数运行脚本时。

  7. 我觉得Mojo在这个上下文中值得一提https://www.modular.com/mojo 它解决了Python语法超集的问题,其中“fn”函数(而非“def”)被假定为静态类型且可与Numba风格优化一起编译。

    1. Mojo 不是开源的,这完全不可行。

      1. 更多的是一个问题,即 Mojo 是否最终会完全开源,其中一些部分已经开源。Modular 的意图是最终会开源,只是不会一次性全部开源,而且在他们内部为自己的商业实体进行大量开发时也不会开源。这对我来说似乎是合理的。重要的是,他们已经开源了大量标准库,这可能是外部贡献者或希望修改的内容?https://www.modular.com/blog/the-next-big-step-in-mojo-open-

        1. _当它成为开源项目时,我会考虑在此基础上建立专业知识和产品。在它成为开源之前,没有保证它会这样做。

          1. 嗯,所谓的“专业知识”基本上就是Python,这大概就是其核心价值所在。但如果要基于Modular构建实际的AI产品,我更担心的是Modular处于早期阶段的特性,而非其实现是闭源的。

            1. 没错,Numba的核心价值也是如此。但现实情况不同。

        2. 基于 vapor 构建存在很大风险。谁知道他们会不会效仿 Mathworks,结果导致非学术用户每年每许可证需支付 $20k+ 的费用。

      2. 我很好奇;虽然我明白为什么我们希望一种语言是开源的(有很多很好的理由),但你有过开源性帮助你解决问题的例子吗?

        1. 不是原帖作者,但我曾因无法轻松绕过的 bug 而需要修改 Qt。

          在尝试与昂贵的专有软件进行互操作时,我也曾因文档缺失且源代码不可用而感到沮丧。

          在一次案例中,某专有软件的源代码被“公开”,这帮助我绕过其漏洞并正确使用该软件(尽管文档同样糟糕)。

          当然,这种透明性还有其他优势,比如能够独立审计代码中的漏洞或不可接受的“功能”,并进行修复。

          开源往往是我们能够控制软件的先决条件。

        2. 这有助于防止问题发生。我并不担心Python突然添加一条条款,规定我不能发布一个机器学习框架……

        3. 在rustc的早期阶段,能够查看特定编译器错误的上下文非常有用(这是在它现在以错误报告闻名之前)。利用这一点,我能够诊断出我的代码中存在的问题并相应地进行调整。

  8. 我没有全神贯注地阅读,但这篇LWN对演讲的报道似乎证实了这些误区,而非澄清它们。

    1. 需要更仔细地阅读这篇文章。

      第一个误区是“Python并不慢”——它已被证伪,Python确实较慢。

      第二个误区是“它只是粘合语言/只需将热点部分重写为C/C++”——这一说法被推翻,仅将代码重写为C/Rust并不能解决问题。

      第三个误区是“Python因解释执行而慢”——这一说法被推翻,它并非仅因解释执行而慢。

      1. >第一个误区是“Python并不慢”——这一说法已被证伪,Python确实较慢

        这很奇怪。编程社区中大多数人都知道Python较慢。如果它有什么声誉,那就是它相当慢

      2. >仅仅将代码重写为C/Rust并不能解决问题。

        但实际上是有帮助的。关键在于弄清楚你真正需要加快速度的部分,然后用C语言编写。如果你的用例主要受网络延迟影响。

        总体而言,人们似乎忽略了Python的本质。开发软件的最佳方式是“让它运行,让它良好,让它快速”——第一部分让你得到一个端到端的原型,提供可测试的环境;第二部分确立了 robustness 和一致性;第三部分让你专注于在 robust 框架下优化性能,确保你的更改不会破坏任何东西。

        Python 关注的是第一部分。其理念是,您无需花费太多时间让它运行。一旦它能够运行,那么第二部分(添加测试、类型检查等)和第三部分就变得非常容易了。现在,借助大语言模型(LLMs),将一个 Python 文件转换为 .c/.h 文件实际上非常简单,尤其是对于执行额外“思考”循环的代理而言。

        然而,即便如此,实际中你通常无需放弃 Python。例如,我有一个项目用于挖掘 Strava 热力图(即下载整个美国的 PNG 瓦片)。用Python编写并运行该项目(运行时间约为一天)所花费的时间,远短于用C++/Rust编写并通过加速处理运行所需的时间。

        1. 他基本上说,如果不非常小心,仅仅这样做是没有帮助的,因为涉及到装箱/拆箱操作。如果你小心翼翼,当然是可能的。我同意你关于Python有用(但不快)的评论——这是我用过最直观的语言,它就是管用——没有意外,没有奇怪的东西,一旦你让某件事正常工作,你就可以担心性能,而无需从头开始重写一切。

      3. 公平地说,我不会真的称那些为“神话”,只是对Python速度慢的糟糕辩解。我认为说这些话的人并不真的相信——如果涉及生死存亡。他们只是真的很喜欢Python,并试图避免喜欢一种非常慢的语言带来的认知不协调。

        比如,我不会说“Linux易于使用”是个“误区”。

      4. 谢谢!作为一个对Python不熟悉的人,我原本以为Python的内行会试图改变我的观点,而不是证实它们,而我确实误解了。

        1. 我的印象是,GvR很久以前就承认Python运行缓慢,而且并不特别在意(他认为一再提及这一点是挑衅行为)。关键在于,在现实世界中,这很多时候并不重要,至少只要你不犯大O错误——而更易于使用的语言能帮助你更容易地避免这些错误。

          说到这一点,我最近在Python圈子里看到一场演讲,内容是说服人们让计算机在本地做更多工作,因为现在的计算机真的已经快到这个程度了。

    2. 没错,这让我更加确信Python为什么会觉得慢,而且不适合用于任何超出脚本范围的任务。我每天都使用它,并且发现我甚至无法信任像mypy这样的工具,因为它充满了边界案例——事实证明,如果一种语言没有清晰的类型设计,那么外部工具也无法从根本上解决这个问题。测试是唯一能让我信任用这种语言编写的代码的东西

      1. > 是的,这证实了我认为Python运行缓慢的所有原因

        是的,这正是演讲的明确要点。文章中的第一个误区是“Python并不慢”

  9. 我们正在为那1%的编码体验良好的场景付出99%的性能代价。

    为什么人们认为这是个不错的权衡?

    1. 如果代码不正确,性能毫无价值。在简单情况下,用Python编写正确代码相对容易且快速(整数不会像C语言那样溢出,不会像C#那样循环,也没有其他脚本语言中那种荒谬的隐式转换)。

      而且很多时候代码不需要很快。如果只是偶尔由人类运行的数值计算,花一秒钟也无妨。作为shell脚本的替代品也相当不错。

      1. 但许多使 Python 变慢的语言设计决策并未让代码更易于正确编写。例如猴子补丁;它非常强大且有用,但也会引发巨大的可维护性问题,且其作为功能的存在阻碍了代码的优化。

      2. 我的意思是,你可以通过自己的经验看到,人们会在博客文章中贴出 50 行普通的 C 代码片段,看起来就像在阅读一种早已消亡的古老语言,布满了宏,然后说:“这里有很多东西需要理解,这是 Python/Ruby 的等效代码”,而它只有 3 行,完全显而易见。

        HN上的用户对这些语言存在的原因以及人们为何继续使用它们的看法实在奇怪。尽管这些语言存在缺陷、动态特性、垃圾回收和缺乏静态类型等缺点,但在现实世界中,使用高级语言编写的代码往往更准确且编写速度更快。这是Go语言存在的理由。

    2. 因为用它编程很愉快。并非一切都需要可扩展或快速。

      我个人认为,为了那1%的需求而优化99%的时间才是更疯狂的。

      1. 这就是为什么Python是适用于一切的第二好语言。

        在短时间内编写出如此复杂的代码,且几乎所有人都能参与其中,这简直令人惊叹。

      2. 这不是非此即彼的选择。关注性能优化的群体通常与关注语法糖实现的群体不同。确实,扩大整体代码库会增加某些功能集的冲突风险,但这只是在认真扩展语言时需要考虑的因素。

    3. 因为许多人从未使用过Smalltalk、Common Lisp、Self、Dylan等语言,所以他们认为CPython是唯一的选择,再加上他们的计算资源已经被大量Electron应用程序浪费了,因此他们很少质疑CPython的性能,或者说缺乏性能。

      1. 你有没有想过,他们只是喜欢Python?

        1. 我每天 90% 的时间都在使用 Python,但我不能说我喜欢它、讨厌它,或者完全不在乎它。我使用它是因为它拥有我需要的所有库,而且大语言模型(LLMs)似乎也非常了解它。对于那些并不关心编程语言,只是想完成工作的人来说,这是一种很好的语言。

        2. 关于代码运行缓慢的问题,确实也曾让我有所顾虑。

          通常他们调用的Python库中,有95%是C语言代码。

          1. 这种虚伪行为更令人发指:C代码最终会被编译成汇编语言!

            1. 不过C语言开发者会承认这一点,他们不会将用汇编语言编写的库称为C代码。

    4. 这远不止1%,正是它使得像pytest和Pydantic这样的常用库得以实现。

    5. 大多数时候,你是在等待人类或至少是除了CPU之外的其他东西。大多数时候,程序员编写代码所花费的时间比所有用户等待程序运行的时间总和还要多。

      在这两者之间,大多数情况下性能已经足够好,可以进行权衡。

    6. 因为计算机的速度比我开始编程时快了100多倍,而当时它们已经足够快了?(而且与此同时,我的编程能力并没有提高,如果有什么变化的话,反而更差了)

    7. 我认为任何了解这一点的人都不会认为这是一个好的权衡。

      更有趣的问题是,为什么当初要做出这种权衡。

      答案是,我们能够看到并理解这些设计决策的影响,因为我们已经看到了过去20多年Python的发展结果。事后诸葛亮总是容易的。

      请记住,Python于1991年发布,甚至早于Java。当时我们对编程的理解与现在相比有着天壤之别。

      哦,还有一点,这类权衡在一般情况下非常难以做出。你当时可能认为无关紧要的设计决策,实际上可能在后续性能中变得至关重要,但到那时设计已因向后兼容性而固化。

    8. 并非如此。Python在许多方面并不胜任。然而,它已经存在了很长时间,在一些有影响力的垂直领域,如网络安全,Python与原生工具一样有用甚至更实用,并且可以在多个平台上运行。

    9. 因为Matplotlib和Pandas等库可以节省程序员的时间,即使它们会浪费处理器时间。

    10. 我可以肯定地说,我从未为此支付过一分钱。你呢?

  10. 这是一篇关于速度的好文章。

    但说实话,让我的程序变慢的主要原因是网络调用。一个良好的异步设置能起到很大作用。然后是 k8 用于扩展。

    1. 没错。我维护一个用Python编写的电子商务平台。即使Python本身较慢,但不到30%的请求时间用于执行代码,其余时间都花在网络通信上。

    2. 我认为这类文章在谈论“性能”或“<语言>是快/慢”时,范围过于宽泛。

      一群SRE讨论在可比的生产环境中,哪些语言/服务器/运行时是快/慢/高效的,会提供更实用的指导。

      如果你正在构建一个传统的三层应用中的HTTP守护进程(就像HN上大多数人一样),根据我的经验,与同类语言相比,Python在过去8年中已经悄然成为该领域中非常优秀的语言。

    3. 作为SRE,我认为Python的水平扩展会带来影响,因为它会增加与数据库的连接数等,即使你没有直接看到这些影响。

      1. 嗯,即使使用基本的异步处理,我也能够超载 Azure 的 Premium AMPQ 服务内存容量。

        但确实,管理数据库连接是一件痛苦的事。但我认为在 Java 中(这是我在此规模下的唯一参考)情况也不会更好。

  11. 很棒的文章,我认为其中许多问题并非Python特有,因此这是一篇关于如何从这门已有30年历史的语言中学习的良好概述!我认为我们可能会走JS/TS路线,其中另一个编译器(如PyPy或mypyc或其他)将与CPython并行工作,但我看不到Python4会实现。

    1. 我以为我们永远不会看到GIL消失,而现在它真的消失了。永远不要说永远。也许Python4就是使用另一种编译器的Python。

      1. 这需要Facebook和微软改变对它的看法,而现在微软团队已经不复存在。

        那么让我们看看CPython性能优化努力中还剩下什么。

    2. 我不确定我理解对JS/TS的引用:TS只是一个类型检查器,对运行时性能没有任何影响。

  12. > Python 对于某些任务来说已经足够快,他说,这就是为什么有这么多人使用它并参加像 EuroPython 这样的会议。

    这个结论在逻辑上是有缺陷的:它混淆了语言流行度与性能,而会议出席率和广泛使用是社会学指标,而非 Python 性能的证据。混淆两者是智力上的疏忽。

    此外,Python 的速度主要得益于 C 扩展处理性能关键任务,而非解释器本身。然而,Perl 即使在纯代码中也常常更快,尤其在文本处理和正则表达式方面,这得益于其优化的引擎,使其在许多常见场景下天生更快。

  13. 本文最有趣的部分是与SPy的关联。试图找到一个能够实现高性能的Python子集。

    1. 坦白说,这在我看来就像西西弗斯推石上山般徒劳。市场并不需要一个“高性能子集”。市场已经由高性能语言充分满足。市场想要的是Python的表达力。市场想要鸭子类型、运行时可检查的类型层次结构、可变语法和装饰器。它热爱这些特性。这就是Python成功的理由。

      我认为numba在此采取的策略恰到好处。不要试图从上层对Python进行子集化,而是给开发者提供工具[1],让他们自己限制使用高性能子集,用于他们真正需要的代码。让他们自己做出选择。

      (Numba完全失败的一点是,它坚持使用自己150多MB的LLVM构建版本,因此部署起来远不如预期中干净利落。各位,如果你使用系统 libc,就应该准备好使用系统工具链。)

      [1] 甚至简单的工具。我的意思是,大致来说,你只需在需要快速执行的部分加上 “@jit”,并确保它只使用单一数值类型和 numpy 数组,而不是 Python 数据结构,就这样完成了。

      1. 市场中已有大量支持 JIT 编译器的动态语言,包括 Python,只要社区对 PyPy 给予更多支持。

        1. PyPy 并非不受欢迎,只是不兼容。使用 PyPy 的常见经历是:你运行一个简单的基准测试,惊叹于其性能,然后运行整个应用程序时却失败了。通常是因为你依赖于一个没有 PyPy 端口的原生模块,或者该模块的实现与 PyPy 不兼容(我个人就曾遇到过 ctypes 代码在 PyPy 中无法正常工作的麻烦)。

          但确实存在一些非同小可的行为差异。垃圾回收是一个重大差异。CPython采用引用计数和贪婪回收机制,你可以编写一个不仅进行堆内存分配的循环(比如打开文件等),因为对象在不再被使用时会被销毁(关闭),因此运行正常。而在PyPy中,相同的循环会因资源耗尽而失败,因为相关资源(文件描述符)在堆内存耗尽前就已用尽,而垃圾回收器无法运行。

          社区曾试图通过一些晦涩的建议来解决这个问题,比如使用“with”语句,但这些建议没人能完全理解或解释清楚,而且效果不佳。事实是,除非你在PyPy环境下开发,否则PyPy基本上无法处理大型任务。说实话,我没看到太多人关注这个问题,感觉PyPy似乎认为自己不需要100%兼容就能胜出。但它确实需要。

          回到我之前提到的观点:numba 并不具备这些特性。Numba 只是 CPython,额外添加了这样一个功能:当你知道需要一个快速循环时,你可以将 Python 代码写得像 C 代码一样,并添加一个 @jit 装饰器。

      2. > 市场需要鸭子类型、运行时可检查的类型层次结构、可变语法和装饰器。它非常喜欢这些。

        这些特性有一个共同点:它们仅对原型级别的临时代码有用,如果有的话。一旦你的需求转向更注重生产使用和可维护性,它们就会成为严重的缺陷。这不仅仅是性能问题(尽管显然也是一个因素),大多数语言不这样做的原因是有实际依据的。

        1. > 这些特性有一个共同点:它们仅对原型级别的临时代码有用,甚至可能毫无用处。

          从实际情况来看:Python 社区对此有强烈反对意见。而 Python 社区已经席卷了整个世界。

          拥有自己的观点是可以的,但你无法改变 Python。

          1. 多个类型检查器的存在以及Astral在构建工具以帮助Python摆脱困境方面的成功努力似乎表明情况并非如此。

            更好的事情是可能的,我希望Python代码的平均质量提高是其中之一。

          2. 这假设Python是一个单一的整体,而且每个人都同意它是什么。

            确实,你在此表达的观点在社区中乃至指导委员会中都拥有强大支持。

            但关于Python的本质及其成功原因,存在不同观点。

            1. > 这假设Python是一个单一整体,且所有人都对其定义达成一致。

              恰恰相反!我认为Python是“庞大且多样化”的,而像SPy这样的尝试——试图发明一种新的(单一!)子集语言供所有人使用——注定会失败,因为它无法满足那些在做奇怪事情的用户的需求,而这些事情正是SPy的作者认为不重要的。

              对“什么是Python”有不同看法是可以的,但如果这些看法与整个社区的共识不符,而不仅仅是你认为的“好部分”,那这其实就不是在讨论“Python”本身了,对吧?

  14. 关于Python性能的一个常见误区是几年前做出的承诺:未来五年内Python性能将提升5倍。到目前为止,相关改动带来的性能提升甚至不到2倍。

  15. 我对Python的了解不够深入,无法提出有意义的贡献,但在我看来,大多数问题可以通过一种“最终”声明或限定符来缓解,该声明或限定符禁止对底层数据结构进行任何进一步修改,从而使编译器和解释器能够实现所有优化的技巧和捷径,而这些在数据结构允许在运行时改变形状时是无法实现的。

    1. 我猜人们不喜欢这种解决方案,因为极端的动态性在很多日常的Python脚本中很少用到。所以很多“常规”的Python脚本可能需要到处加上“final”来达到最快的速度。

      到那时,你可能需要某种更广泛的方式来标记脚本中哪些部分是动态的。但这样一来,你将拥有一个甚至在动态性本身方面也具备动态性的语言……

  16. 看到网站上最热门的评论讨论Common LISP如何处理这个问题,颇具趣味。而且很难不同意这种观点。

    我不明白几十年前那些超级动态系统为何比人们愿意理解的更容易优化。但愿人们有机会使用Mathematica时能得到上天的帮助。

  17. 在“动态”部分,情况比作者描述的还要糟糕。你甚至不能假设名为“10”的常量会指向一个行为与你预期数字10一致的值。

    1. 我想你指的是“N”。10是一个字面量,不是一个名称。“N不能被假设为10,因为代码的其他地方可能更改了它”这一表述已充分暗示更改可能涉及非整数值。(话说回来,编写`N: int = 10`也无法解决这个问题。)

      1. 不,我指的是字面量。CPython 比它应该有的更灵活,你可以自由地编辑字面量 10 所指向的内存。

        1. 你愿意展示一下你认为如何在 Python 内部实现这一点吗?

          1.   import ctypes
            
              ten = 10
              addr = id(ten)
              
              class PyLongObject(ctypes.Structure):
                  _fields_ = [
                      (“ob_refcnt”, ctypes.c_ssize_t),
                      (“ob_type”, ctypes.c_void_p),
                      (“ob_size”, ctypes.c_ssize_t),
                      (“ob_digit”, ctypes.c_uint32 * 1),
                  ]
              long_obj = PyLongObject.from_address(addr)
              
              long_obj.ob_digit[0] = 3
              assert 10 == 3
              
              # 使用辅助变量以防止内联优化
              # 在实际查询字面量 `10` 的值之前,
              # 解释器级别已完成此操作
              x = 3
              assert 10 * x == 9
              assert 10 + x == 6
            
            1. 好的,但这相当于刻意将运行时本身视为一个 C 程序,并通过 FFI 与之连接。话说回来,认为 id(https://docs.python.org/3/library/functions.html#id)的结果可以合理地传递给 from_address,这是一个实现细节。这是该语言缺乏正式规范的一个原因:不清楚替代实现(如 PyPy)需要验证多少此类“疯狂”行为。但我认为人们会同意,直接操作运行时内存无法保证确定性结果,因此实现应实际上假设这种情况不会发生。(毕竟,我们可以进一步推论;例如,如果让另一个进程来做脏活呢?)

              1. 不过,这种功能在 gevent、pytest 和 numba 等场景中至关重要,而要替换这种功能需要大量额外的语言/标准库工作(如果其他 API 足够用,没有理智的开发者会去使用它)。

                用字面量`10`覆盖内存的荒谬示例“显然”是错误的,但你认为解释器可以假设没有人会覆盖其内存的观点在实践中并不成立。

                1. > 不过,这种情况在gevent、pytest和numba等场景中非常重要

                  什么,修改已文档化为不可变的内置类型的数据表示?出于什么目的?

  18. 随着更强大的语言模型(LLMs)的出现,Python等高级编程语言的流行度可能会下降。如果你不是负责编写代码的人,那么从一开始就使用性能更高的语言来编写代码会更明智。

    1. 在我的工作流程中,我已经倾向于让大语言模型(LLMs)用 Go 而不是 Python 编写脚本。大语言模型(LLMs)并不在乎会让我转向 Python 的繁琐和冗长,而且结果会更快。

    2. 大语言模型(LLMs)确实会使大多数语言变得无关紧要,就像大多数开发人员对他们最喜欢的编程语言的JIT/AOT编译器所生成的汇编/机器代码一无所知一样。

      最终,LLMs甚至可以直接生成可执行文件。

  19. 很好地破除了“编译器=快速”的迷思。希望SPython能随着时间推移将部分理念迁移到CPython。

    1. 你可能会以为Luajit早已说服了人们。但大多数人仍认为需要静态语言和AOT编译器才能实现性能优化。

      1. 还有Smalltalk(Pharo、Squeak、Cincom、Dolphin)、Common Lisp(SBCL、Clozure、Allegro、LispWorks)、Self等。

        不过确实如此。

  20. 他给出的许多示例,如numpy/calc函数,都可以轻松转换为C/C++/Rust。文章在开头就否定了这一点,如果我们想专注于Python本身的性能,这没问题,但似乎对于许多指定的问题,这既是唯一解决方案,也是显而易见的解决方案。

  21. SPy 演示在展示 Python 与其衍生语言之间的性能差异方面非常出色。做得好!

  22. 一再强调,最关键的问题是“为什么”,而非“如何”。Python 本身并非为追求速度而设计。若想打造一款高性能语言,必须从底层设计阶段就融入相关机制:为开发者提供管理内存布局的工具,为开发者提供管理执行流的工具,向编译器提示可优化的场景,限制调度与多态性,将语义解释范围缩小至更少可能。

    Python 没有这些特性。它是一个设计极度糟糕的超臃肿语言。做同一件事有许多种方式,做愚蠢的事情也有许多种方式,无法向编译器传达开发者的意图……那么为什么还要费这个劲?为什么不使用一个由明智的设计师为这个特定目的设计的语言?

    关于 Python 性能提升的新闻听起来就像是在浪费宝贵资源追求无用目标。我们并不是通过让 Python 稍微快一点、稍微臃肿一点来前进,我们只是让这种糟糕的语言变得更难摆脱。

    1. 令人沮丧的是,Python 生态系统中的数学和人工智能支持可能是最好的。这些恰好也是性能至关重要且需要紧凑实现的领域。

      C++也有很好的支持,但它在涉及研究人员和初学者的社区中往往难以使用,因为它对他们来说太难了。启动成本也高得多。

      因此,你往往只能使用Python。

      我们迫切需要在比Python更快但比C++更容易的语言中拥有良好的数学/人工智能支持。C#?Java?

      1. 这有点讽刺,因为现在这已成为时代潮流,而上世纪90年代我的大学曾向大一学生教授C++,我作为高中生使用Turbo C++ 1.0 for MS-DOS学习C++,大约在该软件商业发布一年后,后来又以学生折扣价购买了Turbo C++ 1.5 for Windows 3.1。

  23. 速度快慢最终取决于你使用它的具体场景。或许这些只是对极少数将执行速度视为最高优先级的人而言的传说和童话,但他们选择使用Python进行实现。

  24. 对我来说,在使用Python作为数据分析语言时,困扰我的并非Python的速度,而是并发性问题。我认为Julia内置的并发原语要人性化得多。

  25. “他首先请观众举手,如果他们认为‘Python 运行缓慢或不够快’”;

    问题不对

    或许可以改为:“Python 的启动时间与其他解释器相当”

    相比之下,Python(启动时间)较慢

  26. 安东尼奥是颗明星。他也是位才华横溢的艺术家。

  27. 基本上,将 Python 用于操作系统和应用程序脚本任务,以及作为学习编程者的 BASIC 替代品。

    1. 然而,人们最终所做的大部分工作实际上都是操作系统和应用程序脚本编写。大多数机器学习项目本质上只是搭建一个管道并告诉计算机去运行它。云部署就是“拿这个 YAML 文件并将其转换为另一个 YAML 文件”。正如我不愿用Fortran解析YAML文件一样,我也不想用Python编写操作系统(或数据库)。即使像Django这样的框架,本质上也是将任务委托给更高效的系统,其核心在于作为一种领域特定语言(DSL)进行编程,同时仍能调用其他组件(如机器学习代码)。

      1. 我其实更倾向于使用Fortran,并非所有人都被限制在Fortran 77。

        讽刺的是,Fortran支持正是CUDA战胜OpenCL的原因之一。

        不过,许多具备JIT/AOT工具链的编程语言都拥有优秀的YAML解析器,我看不出来非要用Python来做这件事的必要性。

  28. 只是我一个人觉得,这次讨论实际上证实了所有关于Python的“神话和童话”吗?

    1. 嗯,那个童话是说Python很快,或者“足够快”,或者“如果我们能编译它并去除GIL,它就会很快”。

    2. 它证实了Python确实执行性能不佳。

  29. 写这篇文章的人最好暂时放下编程语言书籍,先提升一下英语写作能力。

    (你们的负面评价证明了我说的没错。)

  30. 我知道这会招来一些“Python粉丝”的批评,但“Python”和“性能”绝不应联系在一起,其他脚本/解释型编程语言也是如此。尤其是当它带有全局解释器锁时。

    虽然性能(无论你如何定义)始终是一个值得追求的目标,但如果你开始遇到性能瓶颈,你可能需要质疑自己选择的语言。

    正如俗语所说——“使用合适的工具完成任务。”用例应决定技术选择,例外情况很少。

    好了,现在我已经说完了,你可以给我点赞了 🙂

    1. > “Python 粉丝”

      我认为“Pythonistas”这个术语更广泛使用

      > 如果你开始遇到性能瓶颈,你可能需要质疑自己选择的编程语言。

      开发者也应质疑是否真的需要像Rust这样的“快速”语言,如果实现一个功能所需的时间比用Python更长。

      我一般不喜欢冗余,但有时为了更快进入市场,启动几个额外实例可能是值得的。如果Python能让你提前一个月实现一个功能,新增的销售额甚至可能覆盖额外的基础设施成本。

      一旦达到一定规模,你可能需要重写系统的一部分,因为你最初的假设往往是错误的。

      1. > 开发者还应质疑是否真的需要像Rust这样的“快速”语言……

        同意。

    2. 有些人以此为借口编写最无效的代码。

      好吧,你不是在与 C++ 竞争,但也不应该因为没有弄清楚数据访问模式而重新计算所有内容。

      1. 我读过,觉得挺好的,还给它点赞了。

    3. “使用合适的工具完成任务。”

      Python + C几乎可以覆盖你需要构建的任何东西,除非你在做需要使用C++/C#的游戏引擎相关工作。Rust则更小众。

  31. 使用多个核心是否还需要额外的库?

    1. 嗯?多进程处理自2.X版本以来就已存在。

  32. 在计算领域,说某件事“慢”其实没什么意义。说某件事“高效”或“低延迟”能提供更多信息。

  33. 作为一门语言,Python 可能永远不会有“快速”的实现方式,同时仍保持其本质。它过于动态,仅凭代码本身或甚至执行流,都无法以一种允许你通过 AOC 或 JIT 简化实际在运行时执行的代码的方式进行预测。就语法和内置功能而言,该语言本身目前也相当庞大,这使得不进行重大取舍的新功能完整实现变得非常具有挑战性。考虑到大语言模型(LLMs)在翻译代码方面的强大能力,现在似乎是构建一种具有相似语法、但范围更广的行为、更严格的类型规则以及能够实现代码和库的自动化移植且相对轻松的工具的完美时机。现有候选方案有哪些,为何无法作为替代方案?

    1. 如前所述,关键在于JIT的复杂性。实际上,这种动态特性在实际应用中使用较少,尤其在优化目标中。JIT会分析代码路径,发现无法对目标进行写入操作,因此将其视为常量。

      Java 具有类似的动态性——尤其是通过 invokedynamic,但即使在动态分派的情况下,实践中 JIT 仍会将代码单一化为单一类,尽管 Java 中类默认是非 final 的,且 JVM 在单一化时可能已知存在多个实现。这就是 JIT 相较于本地编译器的知识优势所在。

      1. 是的,Java 的语法可能看起来像 C++,但其执行语义更接近 Objective-C 和 Smalltalk,这就是为什么采用 StrongTalk JIT 对于 Java Hotspot 来说是一个巨大的胜利。

    2. Pypy 的速度是原生的 10 倍,并且与大多数 cpython 代码兼容。我认为在 2 到 3 的过渡期间没有采用 JIT 是一个巨大的错误。

      1. 这里的“大多数”涵盖范围很广。到一定程度,你可能会觉得自己实际上是在使用 Pypy 的语言而非纯 Python 编程。它本质上是该语言的一种方言,类似于 Turbo Pascal 与 ISO Pascal 或 RPerl 与 Perl 的关系。

        1. “大多数”指的是与 Python 3 兼容的 CPython 代码比 Python 3 本身更多。但将损坏的代码移植到CPython可能比同时迁移到JIT要容易得多。

    3. Self和Smalltalk也加入了进来。

      至于语法相似的语言,你想要Nim、Mojo还是Scala 3?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

你也许感兴趣的: