“干净”代码,糟糕的性能(2023)

Hacker News Top 新闻

摘要

Casey Muratori 通过经典的形状面积示例,演示了遵循多态和短小函数等“干净代码”准则如何导致显著的运行时性能开销。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/08/04 16:46

# “整洁”代码,糟糕的性能 来源:https://www.computerenhance.com/p/clean-code-horrible-performance *这是 Performance-Aware Programming 系列的一个免费附加视频。它展示了遵循“整洁”代码准则在真实世界中的性能代价。有关课程的更多信息,请参阅“关于”页面(https://www.computerenhance.com/about)或“目录”页面(https://www.computerenhance.com/p/table-of-contents)。* *以下是视频的轻微编辑版文字记录。* 一些最常被重复的编程建议,尤其是给初学者的建议,是应该编写“整洁”的代码。这个标签伴随着一长串规则,告诉你为了让代码“整洁”应该怎么做。这些规则中有很大一部分实际上并不会影响你编写代码的运行时表现。这类规则无法被客观评估,而且我们也不必去评估,因为它们在那一点上相当武断。然而,有几条“整洁”代码规则——其中一些被格外强调——是我们*可以*客观衡量的,因为它们*确实*影响代码的运行时行为。如果你查看一份“整洁”代码总结,并挑出那些真正影响代码结构的规则,你会得到: - 优先使用多态而不是“if/else”和“switch” - 代码不应了解它操作对象的内部细节 - 函数应该短小 - 函数应该只做一件事 - “DRY”——不要重复自己 这些规则非常具体地描述了为了“整洁”,任何特定代码片段应该如何创建。我想问的是,如果我们创建一段遵循这些规则的代码,它的性能如何?为了构建我认为对“整洁”代码实现最有利的情况,我使用了“整洁”代码文献中已有的示例代码。这样,我不是在凭空捏造,我只是用“整洁”代码倡导者用来阐述这些规则的示例代码来评估他们的规则。如果你看“整洁”代码的示例,你经常会看到这样的例子: `` /* ======================================================================== LISTING 22 ======================================================================== */ class shape_base { public: shape_base() {} virtual f32 Area() = 0; }; class square : public shape_base { public: square(f32 SideInit) : Side(SideInit) {} virtual f32 Area() {return Side*Side;} private: f32 Side; }; class rectangle : public shape_base { public: rectangle(f32 WidthInit, f32 HeightInit) : Width(WidthInit), Height(HeightInit) {} virtual f32 Area() {return Width*Height;} private: f32 Width, Height; }; class triangle : public shape_base { public: triangle(f32 BaseInit, f32 HeightInit) : Base(BaseInit), Height(HeightInit) {} virtual f32 Area() {return 0.5f*Base*Height;} private: f32 Base, Height; }; class circle : public shape_base { public: circle(f32 RadiusInit) : Radius(RadiusInit) {} virtual f32 Area() {return Pi32*Radius*Radius;} private: f32 Radius; }; `` 这是一个形状的基类,从它派生出几个具体形状:圆形、三角形、矩形、正方形。然后我们有一个计算面积的虚函数。正如规则所要求的,我们优先使用多态。我们的函数只做一件事。它们很小。所有这些好东西。所以我们最终得到了一个“整洁”的类层次结构,每个派生类都知道如何计算自己的面积,并存储计算该面积所需的数据。如果我们想象使用这个层次结构来做点什么——比如说,计算传入的一系列形状的总面积——我们期望看到像这样的东西: `` /* ======================================================================== LISTING 23 ======================================================================== */ f32 TotalAreaVTBL(u32 ShapeCount, shape_base **Shapes) { f32 Accum = 0.0f; for(u32 ShapeIndex = 0; ShapeIndex < ShapeCount; ++ShapeIndex) { Accum += Shapes[ShapeIndex]->Area(); } return Accum; } `` 你会注意到我在这里没有使用迭代器,因为规则中没有任何内容表明你必须使用迭代器。因此,我认为我应该给“整洁”代码一个机会,不添加任何可能混淆编译器并导致性能下降的抽象迭代器。你可能还会注意到,这个循环遍历的是一个*指针*数组。这是使用类层次结构的直接后果:我们不知道每个形状在内存中可能有多大。所以,除非我们要添加另一个虚函数调用来获取每个形状的数据大小,并使用某种可变跳过程序来遍历它们,否则我们需要指针来定位每个形状的实际起始位置。因为这是一个累加操作,这里存在一个循环携带依赖(loop-carried dependency),可能会拖慢循环。由于累加可以任意重新排序,为了安全起见,我还写了一个手动展开的版本: `` /* ======================================================================== LISTING 24 ======================================================================== */ f32 TotalAreaVTBL4(u32 ShapeCount, shape_base **Shapes) { f32 Accum0 = 0.0f; f32 Accum1 = 0.0f; f32 Accum2 = 0.0f; f32 Accum3 = 0.0f; u32 Count = ShapeCount/4; while(Count--) { Accum0 += Shapes[0]->Area(); Accum1 += Shapes[1]->Area(); Accum2 += Shapes[2]->Area(); Accum3 += Shapes[3]->Area(); Shapes += 4; } f32 Result = (Accum0 + Accum1 + Accum2 + Accum3); return Result; } `` 如果我在这两个例程上运行一个简单的测试工具,我可以粗略测量完成该操作

相似文章

让 Julia 达到 C++ 的速度(2019)

Hacker News Top

这是 BYU FLOW Lab 于 2019 年发布的一篇博客文章,以真实的空气动力学应用(涡粒子法)作为基准测试,探讨如何优化 Julia 代码以匹配 C++ 的性能。作者分享了在 Julia 中实现高性能计算的经验,涵盖类型声明、JIT 编译以及代码优化技巧。

优化CPU密集型Go热路径的笔记

Hacker News Top

本文讨论了CPU密集型Go代码的性能优化技术,指出了泛型和接口抽象因无法内联而产生的局限性,并主张在热路径中使用代码复制。文章通过一个Brotli移植示例和深入基准测试进行了说明。

ryanmcdermott/clean-code-javascript

GitHub Trending (daily)

一本基于罗伯特·C·马丁《Clean Code》原则的指南,教你编写清洁、可读且易于维护的JavaScript代码,涵盖变量、函数、类、测试等内容。

像人类会维护它一样编写代码

Hacker News Top

文章警告说,依赖LLM编写代码而不保持良好模式,会教会AI不良习惯,导致代码库充满重复逻辑,代码质量不断恶化。