‘干净’代码,糟糕的性能
这是来自性能意识编程系列的免费视频赠品。它展示了遵循‘干净代码’指南的实际性能成本。有关课程的更多信息,请参见‘关于’页面或‘目录’。下面是视频的轻微编辑版成绩单。一些被反复提及的编程建议,尤其是对初学者编程者的建议,就是他们应该编写‘干净’代码。这个称谓伴随着一长串规则,告诉你如何编写代码才能使其‘干净’。这些规则中有很大一部分实际上并不影响你所编写代码的运行时间。这些类型的规则无法被客观评估,我们也不必这样做,因为在那时它们相当随意。然而,有些‘干净’代码规则——尤其是一些最强调的规则——是我们可以客观测量的,因为它们确实会影响代码的运行时行为。如果你查看一个‘干净’代码摘要,并提取出实际上影响代码结构的规则,你将得到:优先使用多态而不是‘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; } 你会注意到我没有使用迭代器,因为规则中并没有建议你必须使用迭代器。因此,我想我会给予‘干净’代码一些信任,而不添加任何可能混淆编译器并导致更糟性能的抽象迭代器。你可能还会注意到这个循环是遍历一个指针数组。这是使用类层次结构的直接结果:我们不知道每个这些形状在内存中的大小。而且,除非我们要添加另一个虚拟函数调用来获取每个形状的数据大小,并使用某种变量跳过程序来遍历它们,否则我们需要指针来找出每个形状实际上是从哪里开始的。因为这是一个累加,循环中存在循环携带依赖性,这可能会减慢循环的速度。由于累加可以任意重排序,我还写了一个手动展开的版本,以确保安全:/* ======================================================================== LISTING 24 ======================================================================== */ f32 TotalAreaVTBL4(u32 ShapeCount, shape_base **Shapes) { f32 Accum0 = 0.0f;
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡