什么是你见过的最荒谬的悲观化?

我们都知道,不成熟的优化是万恶之源,因为它会导致不可读/不可维护的代码。 更糟糕的是,当有人实施“优化”,因为他们认为速度会更快,但最终会变得越来越慢,以及越野车,不可维护等等。更糟糕的是,你见过的最荒谬的例子?

在一个旧的项目中,我们inheritance了一些拥有巨大Z-8000体验的embedded式系统程序员。

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

其中一个人去了,并且把所有的整数都换成了空格来加速我们的代码,因为从RAM中获取16位比抓取32位要快。

我不得不写一个演示程序,以显示在32位系统上抓取32位值比抓取16位值更快,并解释为了获取16位值,CPU必须使32位宽内存访问,然后屏蔽掉或移动16位值不需要的位。

我认为“过早优化是万恶之源”这句话就是用过的方式。 对于许多项目来说,它已经成为了一个项目之前不考虑性能的借口。

这句话常常是人们逃避工作的拐杖。 当人们真的应该说“哎呀,我们真的没有想到这一点,现在没有时间去处理这个问题”的时候,我看到了这句话。

我看到很多更愚蠢的表演问题的“荒谬”的例子,比由于“悲观化”

  • 在程序启动期间读取相同的registry密钥数千次(或10万次)。
  • 加载相同的DLL数百或数千次
  • 通过不必要地保留文件的完整path来浪费兆字节的内存
  • 不组织数据结构,以便占用比他们所需更多的内存
  • 调整存储文件名或path到MAX_PATH的所有string
  • 对有事件,callback或其他通知机制的事物进行无理轮询

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

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

数据库是悲观的游乐场。

collections夹包括:

  • 将表格拆分为多个(按date范围,字母范围等),因为它太“大”。
  • 为退休的logging创build一个归档表,但继续将其与生产表联合。
  • 通过(部门/客户/产品等)复制整个数据库
  • 阻止添加列到索引,因为它太大了。
  • 创build大量的汇总表,因为从原始数据重新计算太慢。
  • 创build具有子字段的列以节省空间。
  • 非规范化为字段作为数组。

这是我的头顶。

我认为没有绝对的规则:有些事情是最好的,最好的,有些则不是。

例如,我曾在一家从卫星接收数据包的公司工作。 每个数据包花费很多钱,所有的数据都被高度优化(即打包)。 例如,纬度/经度不是作为绝对值(浮点数)发送的,而是作为相对于“当前”区域的“西北angular”的偏移量发送的。 在可以使用之前,我们必须解开所有的数据。 但我认为这不是悲观,降低沟通成本是智能优化。

另一方面,我们的软件架构师决定,解压后的数据应该格式化成一个非常可读的XML文档,并存储在我们的数据库中(而不是将每个字段存储在相应的列中)。 他们的想法是“XML是未来”,“磁盘空间便宜”,“处理器便宜”,所以不需要优化任何东西。 结果是我们的16字节数据包变成了存储在一列中的2kB文档,而且即使是简单的查询,我们也不得不在内存中加载兆字节的XML文档! 我们每秒收到超过50个数据包,所以你可以想象会有多糟糕(公司破产了)。

所以再一次,没有绝对的规则。 是的,有时候优化太早是一个错误。 但是有时候“cpu /磁盘空间/内存很便宜”的座右铭是一切邪恶的真正根源。

哦,上帝啊,我想我已经看到了他们。 通常情况下,解决性能问题的方法往往是由于某些人太懒,难以解决性能问题的原因,甚至研究是否存在性能问题。 在许多情况下,我想知道这不仅仅是一个人想要尝试一种特定的技术,并拼命寻找一个适合他们shiny的新锤子的钉子的情况。

这是一个最近的例子:

数据架构师向我提出了一个详细的build议,即在一个相当庞大而复杂的应用程序中垂直分配密钥表。 他想知道什么types的发展努力是必要的,以适应变化。 谈话就是这样的:

我:你为什么要考虑这个? 你试图解决什么问题?

他:表X太宽,我们正在分区,因为性能的原因。

我:是什么让你觉得它太宽?

他:顾问说在一张桌子上有太多列。

我:这是影响性能?

他:是的,用户报告在应用程序的XYZ模块中间歇性减速。

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

他:这是XYZ模块使用的关键表格,它就像200列。 这一定是问题。

我(解释):但模块XYZ特别是使用该表中的大多数列,并且它使用的列是不可预知的,因为用户configuration应用程序以显示他们要从该表中显示的数据。 我们可能有95%的时间将所有表格重新组合在一起,这会损害业绩。

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

我:这个顾问是谁? 我不知道我们雇了一个顾问,也没有跟开发团队交stream。

他:呃,我们还没有雇用他们呢。 这是他们提供的build议的一部分,但他们坚持我们需要重新构build这个数据库。

我:呃。 所以出售数据库重新devise服务的顾问认为我们需要重新devise一个数据库….

谈话就这样继续下去。 之后,我又看了一下有关表格,并确定它可能会缩小一些简单的标准化,而不需要特殊的分区策略。 一旦我调查了性能问题(以前未报告),并将其跟踪到两个因素,这当然是一个有争议的问题:

  1. 在几个关键列上缺less索引。
  2. 一些stream氓数据分析师通过直接使用MSAccess查询生产数据库来定期locking关键表(包括“太宽”表)。

当然,架构师仍然在推动桌面的垂直分区,直到“太宽”的元问题。 他甚至通过从另一个数据库顾问那里得到一个提案来支持他的案例,他能够确定我们需要对数据库进行重大devise更改,而不必查看应用程序或运行任何性能分析。

我看到有人用alphadrive-7完全孵化CHX-LT。 这是不常见的做法。 更常见的做法是初始化ZT变压器,以减less缓冲(由于更大的净过载电阻)并创buildJava风格的字节graphics。

完全悲观!

我承认没有什么惊天动地的东西,但是我抓住了使用StringBuffer在Java中循环之外连接string的人们。 这是像转动一样简单

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连接转换为适当的优化字节码。

我曾经看到一个使用“根”表的MSSQL数据库。 根表有四列:GUID(uniqueidentifier),ID(int),LastModDate(datetime)和CreateDate(datetime)。 数据库中的所有表都是外键到根表。 每当在数据库中的任何表中创build一个新行时,必须使用一对存储过程在Root表中插入条目,然后才能到达您关心的实际表(而不是数据库执行你用几个触发器简单的触发器)。

这造成了一堆无用的窃听和令人头痛的事情,要求使用sprocs上的任何东西(并且消除了将LINQ引入公司的希望,这是可能的,但不值得头痛)甚至完成了它应该做的事情。

select这条path的开发者在这样的假设下进行了辩护:这节省了大量的空间,因为我们并没有在表上使用Guid(但是…对于我们生成的每一行,它不是在根表中生成的GUID?)以某种方式改进了性能,并且使得对数据库的更改进行审计变得“容易”。

哦,数据库图看起来像一个来自地狱的突变蜘蛛。

POBI – 悲观化意向明显吗?

上世纪90年代,我的同事厌倦了被CEO踢屁股,因为CEO花了每一个ERP软件(定制版)的第一天,在新function中定位性能问题。 即使新function压缩千兆字节并使不可能成为可能,他总是会发现一些细节,甚至看似重大的问题。 他相信知道很多关于编程的知识,并且通过踢开程序员的屁股来踢他的脚。

由于批评的无能(他是一个首席执行官,而不是一个IT人),我的同事从来没有设法做到这一点。 如果你没有性能问题,你不能消除它…

直到一个版本,他把很多Delay(200)函数调用(这是delphi)到新的代码。 上线后仅用了20分钟,他就被命令出现在首席执行官办公室,亲自接受他的逾期侮辱。

目前为止,唯一不寻常的事情是,我的同事们,当他回来,微笑,开玩笑,出去找BigMac或两个人,而他通常会踢桌子,关于CEO和公司, 。

当然,我的同事现在在办公桌上rest了一两天,提高了他在地震中的瞄准技巧 – 然后在第二天或第三天,他删除了延迟呼叫,重build并发布了一个“紧急补丁”他花了2天1夜的时间修复性能漏洞。

这是邪恶的首席执行官说的第一个(也是唯一的)时间“很棒的工作!” 给他。 这一切都很重要,对吧?

这是真正的POBI。

但它也是一种社会过程优化,所以它是100%的好。

我认为。

“数据库独立”。 这意味着没有存储过程,触发器等 – 甚至没有任何外键。

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

最好的使用我见过的StringBuilder。

当一个简单的string.split足够时,使用正则expression式来分割一个string

似乎没有人提到sorting,所以我会的。

几次不同的时候,我发现有人手工制作了泡泡,因为这种情况“不需要”调用已经存在的“太花哨”的快速sortingalgorithm。 开发人员在他们用来testing的十行数据上手工完成工作时已经足够了。 在客户添加了几千行之后,这个过程并没有完成。

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

 bool isFinished = GetIsFinished(); switch (isFinished) { case true: DoFinish(); break; case false: DoNextStep(); break; default: DoNextStep(); } 

你知道,以防万一布尔值有一些额外的值…

最糟糕的例子,我能想到的是我公司的内部数据库,包含所有员工的信息。 它从HR得到夜间更新,顶部有一个ASP.NET Web服务。 许多其他应用程序使用Web服务来填充search/下拉字段。

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

第二个悲观层面是开发经理不想因为害怕依赖应用程序而触摸它,但是由于这样一个简单组件的devise不佳,我们仍然会在关键应用程序中发生零星的中断。

我曾经在一个充满代码的应用上工作:

  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; 

应用性能翻了一番。

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

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

这些应用程序在应用程序的其余部分多次用于不同的目的。 COMMA_DELIMINATOR在8个不同的包中散布了200多个用户的代码。

我在内部软件中一次又一次地碰到的最大的一次数:

由于“我们可能想在稍后切换到另一个供应商”,所以不会将“DBMS”的function用于“便携性”的原因。

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

我有一个同事试图超越我们的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 ”,所有其他逻辑将被深度嵌套的三元expression式替代。 当你试图争论这是不可能维持的时候,他会指出他的方法的汇编输出是三个或四个汇编指令更短。 它不一定更快,但总是短一点。 这是一个embedded式系统,在这个系统中,内存使用情况偶尔会出现问题,但是本来可以进行的优化要比代码更容易读取。

然后,在这之后,由于某种原因,他决定ptr->structElement是不可读的,所以他开始把所有这些变成(*ptr).structElement ,理论上它更可读,更快。

将可读代码转换成不可读的代码,最多只有1%的改进,有时实际上代码更慢。

在我作为一名成熟的开发人员的第一份工作之一中,我接手了一个正在遭遇扩展问题的项目。 它在小数据集上可以相当好地工作,但是在大量数据的情况下会完全崩溃。

正如我所挖掘的那样,我发现原来的程序员试图通过并行化分析来加快速度 – 为每个额外的数据源启动一个新的线程。 但是,他犯了一个错误,所有的线程都需要一个共享的资源,在这个资源上它们是死锁的。 当然,并发的所有好处都消失了。 此外,它崩溃了大多数系统启动100 +线程只有其中一个locking。 我的强大的开发机器是一个例外,因为它在大约6个小时内通过150个来源的数据集。

所以要修复它,我删除了multithreading组件,并清理了I / O。 没有其他变化,150个源数据集上的执行时间在我的机器上下降到10分钟以下,在平均公司机器上从无穷远到不足半小时。

我想我可以提供这个gem:

 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; } 

由于平方根是在一个非常敏感的地方计算出来的,所以我有了一个寻找使其更快的方法。 这个小的重构将执行时间减less了三分之一(对于使用的硬件和编译器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; } 

当然,这样做有更快和更好的方法,但我认为这是一个很好的例子,pessimization。

编辑:来想一想,展开的循环实际上也是一个整洁的悲观。 通过版本控制挖掘,我可以展示重构的第二阶段,甚至比以上更好:

 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; } 

这是完全相同的algorithm,虽然稍有不同的实现,所以我认为它是合格的。

这可能是在一个更高的水平,你以后,但修复它(如果你被允许)也涉及更高水平的痛苦:

坚持手动滚动一个对象关系pipe理器/数据访问层,而不是使用已经build立的,经过testing的,成熟的库之一(甚至在它们被指出之后)。

所有的外键约束都从数据库中删除,否则会出现如此多的错误。

这并不完全符合这个问题,但无论如何我会提及它是一个警示的故事。 我正在研究一个分布式应用程序,运行缓慢,然后飞到了DC参加一个主要旨在解决问题的会议。 项目领导开始概述旨在解决延误的重新架构。 我自告奋勇地在周末采取了一些措施,将瓶颈隔离到一个单一的方法。 事实certificate,在本地查找时丢失了一条logging,导致应用程序必须在每个事务处都去远程服务器。 通过将logging添加回当地商店,延迟被消除了 – 问题得以解决。 注意重新架构不会解决问题。

在每个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 } 

我用这种types的代码的问题是没有人似乎在乎,如果它不存在? 什么都不做 不要把反馈给用户?

我同意, Object expected错误是烦人的,但这不是最好的解决scheme。

YAGNI极端主义呢? 这是一种过早的悲观化。 看起来,如果你申请YAGNI,那么你最终需要它,结果比添加它开始的时候增加了10倍。 如果你创build一个成功的scheme,那么你就需要它。 如果你已经习惯于创build一个快速生活的程序,那么继续练习YAGNI,因为那时我就想YAGNI。

不完全不成熟的优化 – 但肯定是错误的 – 这是在BBC网站上从一篇讨论Windows 7的文章中读到的。

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

现在,我还没有尝试Windows 7,所以我可能是错的,但是我敢打赌,还有其他问题比closures多长时间更重要。 毕竟,一旦我看到“closuresWindows”消息,显示器closures,我就走开了 – 那400毫秒对我有什么好处?

我部门有人曾经写过一个string类。 像CString的界面,但没有Windows的依赖。

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

相反, 每一个+=调用都会强制重新分配,这个重新分配会附加到O( n2 ) Schlemiel画家的algorithm中 。

我的一名前同事(实际上是一名仆人 )被指派为我们的Java ERPbuild立一个新的模块,用于收集和分析客户数据(零售行业)。 他决定在组件(秒,分,时,日,月,年,一周,双月,三个月(!))中分割每个日历/date时间字段,因为“如果每个星期一我都会询问什么?

No offense to anyone, but I just graded an assignment (java) that had this

 import java.lang.*;