您看到的最荒谬的悲观是什么?[关闭]


145

我们都知道,过早的优化是万恶之源,因为它会导致代码无法读取/无法维护。更悲观的是悲观主义,因为有人实施“优化”是因为他们认为这样做会更快,但最终会变得更慢,而且有漏洞,无法维护等。您所看到的最荒谬的例子是?


21
“悲观化”是一个伟大的词。
mqp

以防万一您不知道,他们在最新的Podcast上谈论了您的话题。
2009年

Answers:


81

在一个旧项目中,我们继承了一些具有丰富Z-8000经验的(否则为出色)嵌入式系统程序员。

我们的新环境是32位Sparc Solaris。

其中一个人将所有int都更改为短裤以加快我们的代码的速度,因为从RAM抓取16位比抓取32位要快。

我必须编写一个演示程序来显示在32位系统上获取32位值比获取16位值要快,并说明要获取16位值,CPU必须将32位宽进行内存访问,然后屏蔽掉或移位16位值不需要的位。


16
嘿,您在哪里学数学?具有1个高速缓存/ RAM访问的2条指令显然比具有1个高速缓存/ RAM访问的1条指令快!
Razor Storm

2
@RazorStorm在带宽和缓存更为宝贵的更高版本的计算机上,情况恰恰相反。位掩码/移位很便宜,但是您希望尽可能多地容纳在高速缓存中,同时还要使带宽最小化。
2013年

206

我认为“过早的优化是万恶之源”这句话是用过的,用过的。对于许多项目来说,直到项目后期才考虑性能是一个借口。

这个短语通常是人们避免工作的拐杖。当人们真的应该说“ Gee,我们真的没有想到它并且现在没有时间处理它”时,我看到了这个短语。

我看到的愚蠢的性能问题的“荒谬”例子比“悲观”导致的问题的例子还多

  • 在程序启动过程中读取相同的注册表项数千次(或成千上万)。
  • 数百次或数千次加载相同的DLL
  • 通过不必要地保留文件的完整路径浪费兆字节的内存
  • 没有组织数据结构,因此它们占用了过多的内存
  • 调整所有存储文件名或MAX_PATH路径的字符串
  • 对具有事件,回调或其他通知机制的事物进行免费轮询

我认为更好的说法是:“没有测量和理解的优化根本不是优化-它只是随机变化”。

良好性能工作是耗时的-通常更多的是功能或组件本身的开发。


46
“过早”是该报价的关键词。您将其改写为“没有度量和理解的优化”似乎并没有改变一点含义。这正是Knuth的意思。
比尔蜥蜴

13
@Foredecker:对。太多的人忘记了上下文,这使该引用坚决反对微观优化。在实施问题之前分析问题以选择合适的算法并不为时过早,但是常常会引用该报价来证明最懒惰,最无效率的解决方案是正确的。
Shog9

5
这实际上取决于个别情况,过早的优化成为问题的情况要比不充分的优化计划成为问题的情况更多
Mark Rogers 2009年

12
-1:“优化”与适当的设计之间存在差异。对于那些无法分辨的人,一个很好的经验法则是“优化”使代码更难阅读,但更快或更高效。更好的设计将使代码更易于阅读(或至少不会更糟)更高效。
TED

5
如果过度使用,那么在SO方面提出问题的人群将偏重于异常值。:D
dkretz

114

数据库是悲观的乐园。

收藏夹包括:

  • 将表拆分为多个(按日期范围,字母范围等),因为它太大。
  • 为退休记录创建一个存档表,但继续与生产表合并。
  • 通过(部门/客户/产品/等)复制整个数据库
  • 禁止将列添加到索引,因为它会使索引太大。
  • 创建许多汇总表,因为从原始数据进行重新计算太慢。
  • 创建带有子字段的列以节省空间。
  • 归一化为数组形式的字段。

那是我的头上。


拒绝索引的需求令人痛苦。
比尔蜥蜴

2
是的,我认识一个在美国大型石油公司工作的人,该公司的几乎所有表格都具有关联的存档表,并且大多数查询都是从将表对UNION的视图中选择的。性能是您所期望的!
托尼·安德鲁斯

嗯,我认为每个DBA都必须在某个时候与归档表路由合并。它似乎总是那么在当时是合理的。
Cruachan

3
我添加:将数据库划分为几个不同的数据库(客户ac,客户df等)
Gabriele D'Antona,2009年

您能否详细说明“将非正规化为数组”。你这是什么意思
Bart van Heukelom

87

我认为没有绝对的规则:有些事情最好事先进行优化,有些则不能。

例如,我在一家公司工作,在那里我们从卫星接收数据包。每个数据包花费很多钱,因此所有数据都进行了高度优化(即打包)。例如,纬度/经度不是作为绝对值(浮点数)发送的,而是作为相对于“当前”区域的“西北”角的偏移量发送的。我们必须先解压缩所有数据,然后才能使用它们。但是我认为这不是悲观,而是降低通信成本的智能优化。

另一方面,我们的软件架构师决定将解压缩后的数据格式化为可读性强的XML文档,并按原样存储在我们的数据库中(而不是将每个字段存储在相应的列中)。他们的想法是“ XML是未来”,“磁盘空间便宜”和“处理器便宜”,因此无需进行任何优化。结果是我们的16字节数据包变成了存储在一列中的2kB文档,即使是简单的查询,我们也必须在内存中加载兆字节的XML文档!我们每秒收到超过50个数据包,因此您可以想象性能变得多么糟糕(顺便说一句,公司破产了)。

同样,没有绝对的规则。是的,有时候优化得太早是一个错误。但是有时“ cpu /磁盘空间/内存很便宜”的座右铭是万恶之源。


37
我同意“ cpu /磁盘空间/内存便宜”是万恶之源。+1
ksuralta

5
我也听说过XML驱动器。另一辆坦克公司。
n8wrl

19
@ksuralta:“ CPU /磁盘空间/内存便宜”是避免思考的方便借口。避免思想是所有邪恶的假想根源。
Piskvor于

这种XML化也发生在我的工作场所,然后是JSONization。所有这些都避免了一些“费力的”关系数据库设计。
Tanz87

75

哦,天哪,我想我都看过了。通常,这是一种努力,由一个懒惰的人解决性能问题,以至于无法解决导致这些性能问题的原因,甚至研究是否确实存在性能问题。在许多这样的情况下,我想知道是否不仅仅是这个人想要尝试一种特定技术并拼命寻找适合他们闪亮的新锤子的指甲的情况。

这是一个最近的例子:

数据架构师向我提出了一个精心设计的建议,即在相当大且复杂的应用程序中垂直分区键表。他想知道为适应变更而需要进行何种类型的开发工作。对话是这样的:

我:你为什么考虑这个?您要解决的问题是什么?

他:表X太宽,出于性能原因,我们对其进行了分区。

我:是什么让您认为它太宽?

他:顾问说,一个表中没有太多列。

我:这会影响性能吗?

他:是的,用户报告了应用程序XYZ模块中的间歇性速度下降。

我:您怎么知道表格的宽度是问题的根源?

他:那是XYZ模块使用的密钥表,大约有200列。一定是问题所在。

我(解释):但是特别是模块XYZ使用该表中的大多数列,并且它使用的列是不可预测的,因为用户将应用程序配置为显示要从该表显示的数据。无论如何,我们有95%的时间会把所有桌子重新组合在一起,这会影响性能。

他:顾问说,它太宽了,我们需要更改它。

我:这位顾问是谁?我不知道我们聘请了一名顾问,也根本没有与开发团队交谈。

他:嗯,我们还没有雇用他们。这是他们提出的建议的一部分,但他们坚持认为我们需要重新构建该数据库。

我:嗯。因此,销售数据库重新设计服务的顾问认为我们需要数据库重新设计...。

谈话一直这样下去。之后,我再次查看了该表,并确定可以通过一些简单的归一化来缩小它,而无需使用奇异的分区策略。一旦我研究了性能问题(以前没有报告过)并将其归结为两个因素,这当然就成为了争论的焦点:

  1. 一些关键列上缺少索引。
  2. 一些流氓数据分析人员通过直接使用MSAccess查询生产数据库来定期锁定关键表(包括“太宽”的表)。

当然,架构师仍在努力对垂悬在“过宽”元问题上的桌子进行垂直分区。他甚至通过从另一位数据库顾问那里获得建议来支持他的工作,该顾问能够确定我们需要对数据库进行重大设计更改,而无需查看应用程序或进行任何性能分析。


Aaag MSAccess产品。我们编写了一个过程,每隔几分钟就会丢弃所有访问连接。TP最终发现错误消息。
纳特

1
我们曾做过类似的工作,但已被废止。公平地说,访问不是问题,它使新手可以轻松创建/运行非性能查询。
JohnFx

我们公司依赖于与生产数据库的旧式临时Access连接。就像一些随意的SQL'er一样,他们忘记了WHERE子句并锁住了主表!
HardCode

35
“我听说淡紫色的RAM最多”
Piskvor于

它本来会更糟。使用时,Excel查询编辑器将锁定整个数据库。当我不知道它的时候,我会在一天中的大部分时间里打开它的一个实例,而我从事其他工作。最糟糕的部分是MS SQL Server没有报告进行锁定的正确用户名/计算机。几个小时后,我意识到我是锁定的原因,因为被锁定的表是我正在查询的视图的一部分,并且首先检查了其他所有内容。
EstebanKüber2010年

58

我见过人们使用alphadrive-7完全孵化CHX-LT。这是不常见的做法。更常见的做法是初始化ZT转换器,以减少缓冲(由于更大的净过载能力)并创建Java样式字节图形。

完全悲观!


10
也许他们试图embiggen通量电容器
Mikeage

6
因此,基本上唯一涉及的新原理不是由导体和磁通的相对运动产生功率,而是由磁阻和电容性拉力的模态相互作用产生的?
Matt Rogish 09年

17
+1是因为我的显示器仍然需要清洁;-)
RBerteig

1
该死的!!但是ecletromagentic交叉遗传效应呢?我认为也应考虑到这一点。否则对象可能会变成僵尸。
Suraj Chandran'2

1
三个字:“铬制消声器轴承”。
10年

53

我承认,这没什么大不了的,但是我已经吸引了人们使用StringBuffer在Java循环外连接字符串。就像转弯一样简单

String msg = "Count = " + count + " of " + total + ".";

进入

StringBuffer sb = new StringBuffer("Count = ");
sb.append(count);
sb.append(" of ");
sb.append(total);
sb.append(".");
String msg = sb.toString();

循环使用该技术曾经是相当普遍的做法,因为它的速度明显更快。事实是,StringBuffer是同步的,因此,如果仅串联几个String,实际上会产生额外的开销。(更不用说在这种规模上,差异绝对是微不足道的。)关于此实践的另外两点:

  1. StringBuilder是不同步的,因此在无法从多个线程调用代码的情况下,应优先于StringBuffer使用。
  2. 无论如何,现代Java编译器都会将可读的String连接变为适合您的优化字节码。

3
第一:为什么不至少使用Java 5?第二:是的,你可以。在第一个示例中,您可以算到5个,但在第二个示例中,您如何算出5?它使用与第一个相同的String文字。编写可读的代码,让编译器决定何时在后台使用StringBuffer。
比尔蜥蜴

4
@ MetroidFan2002:第二个示例中的String文字也是对象。正如我在回答中所说,在这种规模上,差异微不足道。
比尔蜥蜴

1
这并不意味着它将用自己的StringBuffer替换每个String。编译器执行的优化减少了创建的对象数量。
蜥蜴比尔

3
@Eric:字符串msg =“ Count =” + count +“ of” +总+“。通常用Java编译成String msg = new StringBuffer()。append(“ Count”)。append(count).append(“ of”).append(total).append(“。”)。toString(); ...这正是第二个示例所做的。
格兰特·瓦格纳

3
瓦格纳先生的问题是,您必须查看所有这些方法调用,而不是编译器。您必须编写它们并在以后理解它们。无论如何,编译器都会做同样的事情。因此,在这种情况下,可读性更为重要。
ypnos

47

我曾经看到一个使用“根”表的MSSQL数据库。根表具有四列:GUID(唯一标识符),ID(整数),LastModDate(日期时间)和CreateDate(日期时间)。数据库中的所有表都被“外键”到了根表。每当在任何新行中创建在数据库的表中,您都必须使用几个存储过程在根表中插入一个条目,然后才能到达您关心的实际表(而不是由数据库来完成此工作)您只需几个触发器即可)。

这造成了无用的窃听和头痛,需要在它上面写的任何东西都可以使用存储库(并且消除了我将LINQ引入公司的希望。这是可能的,但不值得为此头痛),但最重要的是没有甚至无法完成原本应该做的事情。

选择此路径的开发人员在假设这样做可以节省大量空间的前提下为其辩护,因为我们没有在表本身上使用Guid(但是...不是我们生成的每一行在Root表中生成的GUID吗?) ,以某种方式提高了性能,并使审核数据库更改变得“容易”。

哦,数据库图看起来像是来自地狱的变异蜘蛛。


42

如何POBI - pessimization明显的意图是什么?

我90年代的Collegue厌倦了CEO的介入,因为CEO花费了每一个ERP软件(定制版)发布的第一天,就在新功能中定位了性能问题。即使新功能压缩了数十亿字节,并使不可能变为可能,他仍然总是发现一些细节,甚至看似重大问题,都在不断思考。他相信自己对编程非常了解,并且还通过踢程序员的屁股获得了成功。

由于批评的无能(他是首席执行官,而不是IT人士),我的同事从未设法解决。如果您没有性能问题,则无法消除它...

在发布一个版本之前,他在新代码中放置了许多Delay(200)函数调用(它是Delphi)。上线仅用了20分钟,他被勒令出现在CEO的办公室,亲自取回他的逾期侮辱。

到目前为止,唯一不寻常的事情是我的同事在他回去,微笑,开玩笑,出去玩一两个巨无霸时会保持沉默,而他通常会踢桌子,向首席执行官和公司开火,并把剩下的一天都花光了。 。

很自然地,我的同事现在在办公桌前休息了一两天,提高了他在Quake中的瞄准技巧-然后在第二天或第三天,他删除了Delay呼叫,重新构建并发布了一个“紧急补丁”,他用这个词来形容他花了2天1夜来修复性能漏洞。

这是邪恶的首席执行官第一次(也是唯一一次)说“伟大的工作!”。给他。这就是全部,对吧?

这是真正的POBI。

但这也是一种社会过程优化,所以100%可以。

我认为。


10
我记得有人写过一篇关于数据处理应用程序的文章,该应用程序以不同级别出售,其中“ Lite”每秒只能处理少量数据集,而“ superduper”版本则每秒处理数千个数据集。唯一的源代码差异是Sleep(N)。
peterchen

1
辉煌!我建议在这种情况下使用该标准。在开发初期,分配大量内存并添加一些睡眠调用,每当您需要提高性能时,只需降低性能即可。这被称为是奇迹工作者;)
RCIX

不幸的是,将Sleeps修补到NOP很容易,因此精简版很容易被破解。此“优化”储备可能需要可执行的打包程序,以使调试和修补更加困难。
TheBlastOne

32

“数据库独立性”。这意味着没有存储的proc,触发器等-甚至没有任何外键。


8
从您到数据库的上方,您已经忘记了什么数据,就意味着这种“独立性”吗?不必要地在数据库上进行抽象以避免“避免迁移麻烦”,这是一个小麻烦。您将不需要它。
罗布

8
差不多了 建筑宇航员在工作。自从有了网络以来,我就一直在构建Web应用程序,并且一直以来,我从未真正从一个数据库平台迁移到另一个数据库平台。
克里斯,2009年

5
我敢肯定它会发生,但是如果您围绕这种可能性设计体系结构,那么您是个白痴就很少了。
克里斯,2009年

6
harpo,那是另一种情况-在这种情况下是必须的。我说的是什么时候不是必需的,但是机管局决定在某个时候“可能是”。
克里斯,2009年

3
@全部:是的,数据库独立性可能会花费您很多钱,但是我们的产品运行在通过出价选择数据库供应商的环境中,因此我们基本上必须坚持不懈。有些开发人员没有垂直集成软件堆栈的奢侈,尽管如此,他们还是不得不做出努力。
克里斯R

31
var stringBuilder = new StringBuilder();
stringBuilder.Append(myObj.a + myObj.b + myObj.c + myObj.d);
string cat = stringBuilder.ToString();

最佳使用我见过的StringBuilder。


9
谈论“概念上的不清楚”!哇!
艾迪(Eddie)2010年

3
凉。“我的负责人说,如果要连接字符串,必须使用StringBuilder类。这就是我的工作。那是怎么回事?” 大声笑...
TheBlastOne

26

当一个简单的string.split足够时,使用正则表达式分割一个字符串


25
但是Java String.Split中使用了正则表达式!
Frank Krueger 2009年

我看不到Regex怎么能像内部字符串拆分一样快。
2009年

2
但是,故意寻找用于字符串拆分的正则表达式,并用“简单”的拆分函数替换它,听起来像是一个悲观的完美例子。正则表达式库足够快。
David Crawshaw,2009年

5
@David Crawshaw:寻找微优化机会会浪费人的时间;芽时编写代码,使用至少复杂足够的解决方案。
Piskvor于

6
-1:如果您习惯使用正则表达式,那么编写它而不是习惯使用1001语言内部的字符串操纵器是很自然的。
KillianDS

26

我知道这个线程很晚,但是最近我看到了:

bool isFinished = GetIsFinished();

switch (isFinished)
{
    case true:
        DoFinish();
        break;

    case false:
        DoNextStep();
        break;

    default:
        DoNextStep();
}

您知道,以防布尔值有一些额外的值...


22
对,对,当然,FileNotFound
Ikke

嘿,您应该始终使用默认值/ case else / etc。当某个聪明人将布尔值更改为一个枚举以反映其他状态,然后下一个人添加到枚举而忘记修改程序时,会发生什么呢?在不需要时使用默认值不会花费执行时间,也不会花费开发时间。跟踪在运行时发生的意外引入的逻辑错误...这会花费时间,金钱和声誉。小洞不补,大洞吃苦。
Oorang

1
@Oorang ...为什么仍然要把它当作开关?这是一个布尔值-仅需if / else。
Damovisa 2010年

@Damovisa facepalm对……非常好:)错过了:)
Oorang 2010年

2
这是Nullable <Boolean> ... :)
George Chakhidze 2011年

25

我能想到的最糟糕的例子是公司内部的数据库,其中包含所有员工的信息。它从HR进行每晚更新,并且在顶部具有ASP.NET Web服务。许多其他应用程序使用Web服务填充诸如搜索/下拉字段之类的内容。

悲观的是,开发人员认为重复调用Web服务太慢而无法重复执行SQL查询。他做了什么?应用程序启动事件将读取整个数据库,并将其全部转换为内存中的对象,这些对象将无限期存储,直到应用程序池被回收为止。这段代码太慢了,加载少于2000名员工需要15分钟。如果您在白天无意中回收了应用程序池,则可能需要30分钟或更长时间,因为每个Web服务请求都会启动多个并发重新加载。因此,新员工在创建帐户的第一天就不会出现在数据库中,因此在最初的几天内将无法访问大多数内部应用程序,这会打乱他们的拇指。

悲观情绪的第二个层次是,开发经理不想因为担心破坏依赖的应用程序而碰它,但是由于如此简单的组件的不良设计,我们仍然在整个公司范围内偶尔出现关键应用程序中断的情况。


28
最好的管理-“不,我们不要花80个程序员小时来修复这个应用程序,这太昂贵了。我们只保留它,这样它的错误就可以每月消耗200多个用户小时,以及每月10个程序员的工作时间。 '保养'。” AAAAAAAAAUGH !!!
Piskvor于

25

似乎没有人提到排序,所以我会说。

在几个不同的时间里,我发现有人手工制作了泡泡糖,因为情况“不需要”调用已经存在的“过于花哨”的快速排序算法。当他们的手工气泡冒泡在用于测试的十行数据上运行良好时,开发人员感到满意。在客户添加了数千行之后,它并没有完全解决。


2
当我确定通常n = 2时,我自己做了一次。后来的产品增强功能使我的前提失效,并且代码被PDQ取代。
Mark Ransom

2
是的,但是现在每次基于algorythm编写代码都很好;)
UpTheCreek 2010年

20

我曾经在一个应用程序中工作,该应用程序的代码如下:

 1 tuple *FindTuple( DataSet *set, int target ) {
 2     tuple *found = null;
 3     tuple *curr = GetFirstTupleOfSet(set);
 4     while (curr) {
 5         if (curr->id == target)
 6             found = curr;
 7         curr = GetNextTuple(curr);
 8     }
 9     return found;
10 }

只需删除found,最后返回null,然后将第六行更改为:

            return curr;

应用性能翻倍。


1
我曾在一家公司的公司工作,该公司的编码准则要求“最后只给一个回报”(为了维护)。而且确实有一些像您一样的吐出代码,因为他们不认为(显而易见的解决方案通常是使用goto到proc出口,或者更改循环的出口条件)
flolo

12
这里的收益率产生明显不同的行为。当您返回curr时,您最终将获得FIRST匹配项,而您粘贴的代码将返回LAST匹配项。
SoapBox

2
@ SoapBox:你是对的。@Dour High Arch:性能的提高与单个返回规则无关,因为flolo表示将循环条件引入(curr &&!found)会产生相同的效果。转到proc出口的操作很糟糕,并且违反了单向返回准则的目的。
Akusete

2
大家好评论。在这种情况下,每个ID应该只有一个元组。
Dour High Arch

7
那不是“悲观化”,对吗?这只是一个等待发生的优化。
蒂姆·朗

20

我曾经不得不尝试修改将这些gem包含在Constants类中的代码

public static String COMMA_DELIMINATOR=",";
public static String COMMA_SPACE_DELIMINATOR=", ";
public static String COLIN_DELIMINATOR=":";

在本应用程序的其余部分中,出于不同的目的,每一个都多次使用。COMMA_DELIMINATOR在8个不同的包中有200多种用法,乱七八糟的代码。


至少类似这样的东西很容易从源头查找/替换-仍然是我的同情。
Erik Forbes,2009年

12
另外-消除者?我以为它被拼成“定界符”。消除声音听起来像是一部90年代中期的烂片,不知何故获得了3分,........
Erik Forbes,2009年

53
事务三:逗号的兴起
罗布

33
另一方面,我很高兴看到正确的Colins分隔符号。每个值得他精打细算的程序员都知道,如果有一件事情您绝对必须正确地分开,那就是该死的科林斯。
罗布

2
进行适当的查找和替换并非易事。由于每个人都有不同的用途。任何优秀的程序员至少都会做这样的事情:COUNTRY_LIST_DELIM = ... CLASSIFICATION_DELIM = ...等等
KitsuneYMG 2009年

19

我在内部软件中一次又一次地遇到的最大的历史第一名:

出于“可移植性”的原因,未使用DBMS的功能,因为“我们稍后可能希望切换到另一家供应商”。

读我的唇语。对于任何内部工作:不会发生!


9
它确实发生了。MySQL-> postgresql,所以我们没有丢失任何东西。
Thomas Thomas

还是postgres / postgis-> sqlite / spatialite ...那真是个难题...
Philip

它确实发生在JUnit测试中
kachanov 2012年

17

我有一位同事试图超越我们的C编译器的优化器,并且例程重写了只有他才能阅读的代码。他最喜欢的技巧之一是更改一种可读的方法,例如(编写一些代码):

int some_method(int input1, int input2) {
    int x;
    if (input1 == -1) {
        return 0;
    }
    if (input1 == input2) {
        return input1;
    }
    ... a long expression here ...
    return x;
}

到这个:

int some_method() {
    return (input == -1) ? 0 : (input1 == input2) ? input 1 :
           ... a long expression ...
           ... a long expression ...
           ... a long expression ...
}

也就是说,曾经可读的方法的第一行将变为“ return”,而所有其他逻辑将被深度嵌套的第三级表达式代替。当您试图争论这是如何难以维持时,他会指出一个事实,即他的方法的汇编输出要短三到四条汇编指令。不一定更快,但总是很小有点短。这是一个嵌入式系统,偶尔使用内存确实很重要,但是可以进行的优化要容易得多,因此可以使代码易于阅读。

然后,在此之后,由于某种原因,他认为这ptr->structElement太难以理解了,于是他开始将所有这些更改为一种(*ptr).structElement更具可读性和更快性的理论。

将可读代码转换为不可读代码最多可提高1%,有时甚至会降低速度。


如果每个循环被调用该模块数百万次,那么我会赞成这种优化,只要他对此发表评论。
Michael Dorgan

2
@Michael:我不会的,除非有测量表明它更快而不是更短
dsimcha

在大多数情况下,三元运算符比更具可读性if。坚持用C语言表达陈述是文化/宗教教条,而不是任何一种客观实践。(更好的准则:如果嵌套的三进制太长而无法阅读,则不应使用if其中任何一个。)
Leushenko 2015年

2
这里的问题是采用整个函数,并用单个语句(返回)替换它,从而用嵌套的三元数替换整个函数的所有逻辑。如果看到它,您将理解。这不是宗教上的“我讨厌三元运算符”。我并不是说要if在函数中使用单值并将其替换为三进制。很好,而且通常更具可读性。我正在谈论用单个return语句和嵌套三元数替换整个30+行方法。没有人认为新代码更具可读性,但一位开发人员则认为它更快。
艾迪(Eddie)

15

在我作为一名成熟的开发人员的第一份工作中,我接手了一个程序的项目,该程序遇到了扩展问题。它在较小的数据集上可以很好地工作,但是在给定大量数据时会完全崩溃。

当我深入研究时,我发现原始程序员试图通过并行化分析来加快速度-为每个其他数据源启动一个新线程。但是,他犯了一个错误,因为所有线程都需要一个共享资源,而这些资源却处于死锁状态。当然,并发的所有好处都消失了。而且,它使大多数系统崩溃,无法启动100个以上的线程,只能锁定其中一个线程。我强大的开发机器是一个例外,因为它在大约6个小时内通过150个源数据集进行了搅拌。

因此,要解决此问题,我删除了多线程组件并清理了I / O。在没有其他更改的情况下,在我的计算机上,具有150个源数据集的执行时间降到了10分钟以下,而在普通公司的计算机上,执行时间从无穷缩短到了不到半小时。


我只是防止这种情况在今天的项目中发生。现在我知道我做出了不错的选择。
deadalnix 2011年

14

我想我可以提供这个宝石:

unsigned long isqrt(unsigned long value)
{
    unsigned long tmp = 1, root = 0;
    #define ISQRT_INNER(shift) \
    { \
        if (value >= (tmp = ((root << 1) + (1 << (shift))) << (shift))) \
        { \
            root += 1 << shift; \
            value -= tmp; \
        } \
    }

    // Find out how many bytes our value uses
    // so we don't do any uneeded work.
    if (value & 0xffff0000)
    {
        if ((value & 0xff000000) == 0)
            tmp = 3;
        else
            tmp = 4;
    }
    else if (value & 0x0000ff00)
        tmp = 2;

    switch (tmp)
    {
        case 4:
            ISQRT_INNER(15);
            ISQRT_INNER(14);
            ISQRT_INNER(13);
            ISQRT_INNER(12);
        case 3:
            ISQRT_INNER(11);
            ISQRT_INNER(10);
            ISQRT_INNER( 9);
            ISQRT_INNER( 8);
        case 2:
            ISQRT_INNER( 7);
            ISQRT_INNER( 6);
            ISQRT_INNER( 5);
            ISQRT_INNER( 4);
        case 1:
            ISQRT_INNER( 3);
            ISQRT_INNER( 2);
            ISQRT_INNER( 1);
            ISQRT_INNER( 0);
    }
#undef ISQRT_INNER
    return root;
}

由于平方根是在非常敏感的位置计算出来的,因此我承担了寻找一种方法来使其更快的任务。这种小的重构将执行时间减少了三分之一(对于使用的硬件和编译器的组合,YMMV):

unsigned long isqrt(unsigned long value)
{
    unsigned long tmp = 1, root = 0;
    #define ISQRT_INNER(shift) \
    { \
        if (value >= (tmp = ((root << 1) + (1 << (shift))) << (shift))) \
        { \
            root += 1 << shift; \
            value -= tmp; \
        } \
    }

    ISQRT_INNER (15);
    ISQRT_INNER (14);
    ISQRT_INNER (13);
    ISQRT_INNER (12);
    ISQRT_INNER (11);
    ISQRT_INNER (10);
    ISQRT_INNER ( 9);
    ISQRT_INNER ( 8);
    ISQRT_INNER ( 7);
    ISQRT_INNER ( 6);
    ISQRT_INNER ( 5);
    ISQRT_INNER ( 4);
    ISQRT_INNER ( 3);
    ISQRT_INNER ( 2);
    ISQRT_INNER ( 1);
    ISQRT_INNER ( 0);

#undef ISQRT_INNER
    return root;
}

当然,有更快,更好的方法可以做到这一点,但是我认为这是悲观主义的一个很好的例子。

编辑:想起来,展开的循环实际上也是一种简洁的悲观化。通过版本控制,我还可以介绍重构的第二阶段,该阶段的性能甚至比上面更好:

unsigned long isqrt(unsigned long value)
{
    unsigned long tmp = 1 << 30, root = 0;

    while (tmp != 0)
    {
        if (value >= root + tmp) {
            value -= root + tmp;
            root += tmp << 1;
        }
        root >>= 1;
        tmp >>= 2;
    }

    return root;
}

尽管实现略有不同,但这是完全相同的算法,因此我认为它是合格的。


我想isqrt()算一下floor(sqrt()),但是为什么这个代码起作用?
Pablo H

11

这可能比您所追求的更高,但是修复它(如果允许的话)也会带来更高的痛苦:

坚持动手滚动对象关系管理器/数据访问层,而不要使用在那里建立的,经过测试的成熟库中的一个(即使在已经指出这些库之后)。


编写自己的代码并不总是一个坏主意。就像一个聪明人曾经说过的,找到依赖并消除它们。如果这是核心业务功能,请自己完成。
Kibbee

我从不推断这总是一个坏主意。除非您说Frans Bouma或类似的人物,否则我会怀疑ORM / DAL内容是否是核心业务功能。通常由于NIH综合症,重新编写(方形)轮子的情况下,编写自己的等价物极其浪费成本。
戈登·哈特利

@Kibbee-我同意。比起使用第三方依赖,不如自己动手理解它。当它断裂(并且会断裂)时,至少您可以修复它。我过去发现过Hibernate和Apache Commons中的错误,这些错误绝对会​​破坏我们应用程序的性能。
CodingWithSpike

4
如果已建立的工具都没有您需要的关键功能,那么手动滚动实际上是您的唯一选择。
staticsan

3
实际上,考虑到上面的一些评论,还有一些其他观点:另一个悲观的想法是尝试使ORM绝对完成所有工作。它通常对95%以上的案例有用。对于最后的5%,出于性能,简单性或两者兼而有之,退出手工制作的持久性代码/直接存储过程调用等要容易得多。
戈登·哈特利

10

所有外键约束都已从数据库中删除,因为否则会出现很多错误。


8

这并不完全适合这个问题,但是无论如何我都会提一个警告性的故事。我当时正在研究一个运行缓慢的分布式应用程序,然后飞到DC参加主要旨在解决问题的会议。项目负责人开始概述旨在解决延迟的重新架构。我自愿在周末进行了一些测量,将瓶颈隔离到一个方法中。原来,本地查询中缺少一条记录,导致该应用程序必须在每次事务中都转到远程服务器。通过将记录添加回本地存储,可以消除延迟-解决了问题。请注意,重新架构不会解决该问题。


8

在每次javascript操作之前检查您要操作的对象是否存在。

if (myObj) { //or its evil cousin, if (myObj != null) {
    label.text = myObj.value; 
    // we know label exists because it has already been 
    // checked in a big if block somewhere at the top
}

我遇到的这类代码问题是,似乎没人在乎它是否不存在?什么都不做 不将反馈提供给用户吗?

我同意这些Object expected错误很烦人,但这并不是解决问题的最佳方法。


那么最好的解决方案是什么?我认为编写代码很草率,即使没有直接后果,偶尔也会发生错误。当然,如果您不希望对象在任何情况下都为null,则不应该这样做-也许这就是您的意思。
西蒙

7

YAGNI极端主义怎么样。这是过早的悲观化的一种形式。似乎在您每次应用YAGNI时,您最终都需要它,因此添加它的工作量是开始时的10倍。如果您创建一个成功的程序,那么您将需要它。如果您习惯创建寿命很快的程序,那么请继续练习YAGNI,因为那是我认为的YAGNI。


3
谢谢,我讨厌这些la脚的“极限编程”缩写,以及人们如何使用它们来支持懒惰的,适得其反的做法。
日航

对实际项目的研究表明,一次性代码和重用代码之间的实际因素平均约为3。因此10只是“感觉”值,但您的意图是正确的。
peterchen

@peterchen-您是说研究表明编写一次性代码到一次性代码所花费的时间是一次性代码的三倍,或者他们表明一次性代码转换为可重复使用的代码所花费的时间是一次性的三倍。首先可重用的代码?
杰夫·斯特恩

@jeff:IIRC,他们比较了一些复杂的方法(无论您如何考虑),这些方法已被转移到单独的方法中。由于支持其他情况,参数检查等,复杂度增加了(这使我认为方法相当小)。让我尝试找出参考。
peterchen

6

在BBC网站上,一篇讨论Windows 7的文章中并未完全提到过早的优化-但肯定是被误导了。

Curran先生说,Microsoft Windows团队一直在研究操作系统的各个方面以进行改进。“我们能够通过略微调整WAV文件关闭音乐来缩短关闭时间400毫秒。

现在,我还没有尝试过Windows 7,所以我可能错了,但是我敢打赌,那里的其他问题比关机需要多长时间更为重要。毕竟,一旦我看到“正在关闭Windows”消息,显示器就会关闭并且我要走了-400毫秒对我有什么好处?


您可能会发现,其他问题并不容易在BBC网站上向非程序员解释。
汤姆·莱伊斯

现在我没有考虑这个角度-也许我开始失去玩世不恭的态度了:-)
belugabob

这400毫秒就是400毫秒的功耗。可能微不足道,但随着时间的流逝,它可能会累加。不过,我不用担心。
ZachS

1
我已经花了很多时间等待XP VM关闭,这样我才能继续下一步。我非常感谢您更快地关机。
詹姆斯

1
有趣的是,WAV文件是异步播放的,因此,只要关机时间短于关机所需的时间,修剪WAV文件就不会起作用。甚至更有趣的是,如果他们对关机进行了很大的优化,那么我关机的每个Windows盒怎么会需要aeons直到它真正停机?(当然,除了使用红色的大按钮。)
TheBlastOne

6

我部门的某个人曾经写过一个字符串类。像CString,但没有Windows依赖性。

他们所做的一项“优化”是分配任何不必要的内存。显然,没有意识到像std::string这样的原因类确实分配了过多的内存的原因是,一系列+=操作可以在O(n)时间内运行。

取而代之的是,每个+=调用都强制进行重新分配,这将重复的追加操作转换为Painter算法的O(n²)Schlemiel


5

我的一位前同事(实际上是soab)被指派为我们的Java ERP构建一个新模块,该模块应该已经收集并分析了客户的数据(零售业)。他决定将“每个日历/日期时间”字段拆分为各个部分(秒,分钟,小时,天,月,年,星期几,双月,三个月(!)),因为“我还要如何查询“每个星期一”?”


3
那不是过早的优化,他认为他需要这样做以确保正确性
Pyrolistical

当然,他认为他需要这样做,但是由于大多数DBMS都具有某种DAYOFWEEK(timestamp)函数,因此在我看来,提前进行这种混乱还为时过早:)
Joril

1
我不会将其用于OLTP,但是,如果您要“分析客户的数据”,那实际上是设计数据仓库的一种非常灵活的方法(只要将日期和时间分成不同的维度)。您是否真的想针对数百万行的数据调用DAYOFWEEK()或只是针对整数字段进行索引查找?
蒂姆·梅多拉

好吧,我不知道是否有太多的行,但是可以肯定的是,这不是给出的解释:)
Joril

3

对任何人都没有冒犯,但我只是给具有此内容的作业(java)评分

import java.lang.*;

1
除非这是高年级的课程,否则我认为您需要给这个学生一点儿懈怠,除非您教给她足够的知识来知道为什么这不是一个好主意。
Bryan Oakley,2009年

24
我是否将成为唯一注意到一位老师对学生代码负责的WTF的讽刺者,他/她负责正确编程?
JohnFx

3
是的,我看不到这会疼。最糟糕的是,它是多余的。在学习过程中,学生倾向于诉诸于严格的一致性,并且导入java.lang与学生所学的关于导入的知识严格一致。
cygil

1
谢谢大家告诉我显而易见的事情。这是计算生物学的任务,我没有数,甚至没有提及。
溢出

2
@JohnFX:年级生和老师并不总是同一个人。
艾迪(Eddie)2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.