我们都知道,过早的优化是万恶之源,因为它会导致代码无法读取/无法维护。更悲观的是悲观主义,因为有人实施“优化”是因为他们认为这样做会更快,但最终会变得更慢,而且有漏洞,无法维护等。您所看到的最荒谬的例子是?
我们都知道,过早的优化是万恶之源,因为它会导致代码无法读取/无法维护。更悲观的是悲观主义,因为有人实施“优化”是因为他们认为这样做会更快,但最终会变得更慢,而且有漏洞,无法维护等。您所看到的最荒谬的例子是?
Answers:
在一个旧项目中,我们继承了一些具有丰富Z-8000经验的(否则为出色)嵌入式系统程序员。
我们的新环境是32位Sparc Solaris。
其中一个人将所有int都更改为短裤以加快我们的代码的速度,因为从RAM抓取16位比抓取32位要快。
我必须编写一个演示程序来显示在32位系统上获取32位值比获取16位值要快,并说明要获取16位值,CPU必须将32位宽进行内存访问,然后屏蔽掉或移位16位值不需要的位。
我认为“过早的优化是万恶之源”这句话是用过的,用过的。对于许多项目来说,直到项目后期才考虑性能是一个借口。
这个短语通常是人们避免工作的拐杖。当人们真的应该说“ Gee,我们真的没有想到它并且现在没有时间处理它”时,我看到了这个短语。
我看到的愚蠢的性能问题的“荒谬”例子比“悲观”导致的问题的例子还多
我认为更好的说法是:“没有测量和理解的优化根本不是优化-它只是随机变化”。
良好性能工作是耗时的-通常更多的是功能或组件本身的开发。
数据库是悲观的乐园。
收藏夹包括:
那是我的头上。
我认为没有绝对的规则:有些事情最好事先进行优化,有些则不能。
例如,我在一家公司工作,在那里我们从卫星接收数据包。每个数据包花费很多钱,因此所有数据都进行了高度优化(即打包)。例如,纬度/经度不是作为绝对值(浮点数)发送的,而是作为相对于“当前”区域的“西北”角的偏移量发送的。我们必须先解压缩所有数据,然后才能使用它们。但是我认为这不是悲观,而是降低通信成本的智能优化。
另一方面,我们的软件架构师决定将解压缩后的数据格式化为可读性强的XML文档,并按原样存储在我们的数据库中(而不是将每个字段存储在相应的列中)。他们的想法是“ XML是未来”,“磁盘空间便宜”和“处理器便宜”,因此无需进行任何优化。结果是我们的16字节数据包变成了存储在一列中的2kB文档,即使是简单的查询,我们也必须在内存中加载兆字节的XML文档!我们每秒收到超过50个数据包,因此您可以想象性能变得多么糟糕(顺便说一句,公司破产了)。
同样,没有绝对的规则。是的,有时候优化得太早是一个错误。但是有时“ cpu /磁盘空间/内存很便宜”的座右铭是万恶之源。
哦,天哪,我想我都看过了。通常,这是一种努力,由一个懒惰的人解决性能问题,以至于无法解决导致这些性能问题的原因,甚至研究是否确实存在性能问题。在许多这样的情况下,我想知道是否不仅仅是这个人想要尝试一种特定技术并拼命寻找适合他们闪亮的新锤子的指甲的情况。
这是一个最近的例子:
数据架构师向我提出了一个精心设计的建议,即在相当大且复杂的应用程序中垂直分区键表。他想知道为适应变更而需要进行何种类型的开发工作。对话是这样的:
我:你为什么考虑这个?您要解决的问题是什么?
他:表X太宽,出于性能原因,我们对其进行了分区。
我:是什么让您认为它太宽?
他:顾问说,一个表中没有太多列。
我:这会影响性能吗?
他:是的,用户报告了应用程序XYZ模块中的间歇性速度下降。
我:您怎么知道表格的宽度是问题的根源?
他:那是XYZ模块使用的密钥表,大约有200列。一定是问题所在。
我(解释):但是特别是模块XYZ使用该表中的大多数列,并且它使用的列是不可预测的,因为用户将应用程序配置为显示要从该表显示的数据。无论如何,我们有95%的时间会把所有桌子重新组合在一起,这会影响性能。
他:顾问说,它太宽了,我们需要更改它。
我:这位顾问是谁?我不知道我们聘请了一名顾问,也根本没有与开发团队交谈。
他:嗯,我们还没有雇用他们。这是他们提出的建议的一部分,但他们坚持认为我们需要重新构建该数据库。
我:嗯。因此,销售数据库重新设计服务的顾问认为我们需要数据库重新设计...。
谈话一直这样下去。之后,我再次查看了该表,并确定可以通过一些简单的归一化来缩小它,而无需使用奇异的分区策略。一旦我研究了性能问题(以前没有报告过)并将其归结为两个因素,这当然就成为了争论的焦点:
当然,架构师仍在努力对垂悬在“过宽”元问题上的桌子进行垂直分区。他甚至通过从另一位数据库顾问那里获得建议来支持他的工作,该顾问能够确定我们需要对数据库进行重大设计更改,而无需查看应用程序或进行任何性能分析。
我见过人们使用alphadrive-7完全孵化CHX-LT。这是不常见的做法。更常见的做法是初始化ZT转换器,以减少缓冲(由于更大的净过载能力)并创建Java样式字节图形。
完全悲观!
我承认,这没什么大不了的,但是我已经吸引了人们使用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,实际上会产生额外的开销。(更不用说在这种规模上,差异绝对是微不足道的。)关于此实践的另外两点:
我曾经看到一个使用“根”表的MSSQL数据库。根表具有四列:GUID(唯一标识符),ID(整数),LastModDate(日期时间)和CreateDate(日期时间)。数据库中的所有表都被“外键”到了根表。每当在任何新行中创建在数据库的表中,您都必须使用几个存储过程在根表中插入一个条目,然后才能到达您关心的实际表(而不是由数据库来完成此工作)您只需几个触发器即可)。
这造成了无用的窃听和头痛,需要在它上面写的任何东西都可以使用存储库(并且消除了我将LINQ引入公司的希望。这是可能的,但不值得为此头痛),但最重要的是没有甚至无法完成原本应该做的事情。
选择此路径的开发人员在假设这样做可以节省大量空间的前提下为其辩护,因为我们没有在表本身上使用Guid(但是...不是我们生成的每一行在Root表中生成的GUID吗?) ,以某种方式提高了性能,并使审核数据库更改变得“容易”。
哦,数据库图看起来像是来自地狱的变异蜘蛛。
如何POBI - pessimization明显的意图是什么?
我90年代的Collegue厌倦了CEO的介入,因为CEO花费了每一个ERP软件(定制版)发布的第一天,就在新功能中定位了性能问题。即使新功能压缩了数十亿字节,并使不可能变为可能,他仍然总是发现一些细节,甚至看似重大问题,都在不断思考。他相信自己对编程非常了解,并且还通过踢程序员的屁股获得了成功。
由于批评的无能(他是首席执行官,而不是IT人士),我的同事从未设法解决。如果您没有性能问题,则无法消除它...
在发布一个版本之前,他在新代码中放置了许多Delay(200)函数调用(它是Delphi)。上线仅用了20分钟,他被勒令出现在CEO的办公室,亲自取回他的逾期侮辱。
到目前为止,唯一不寻常的事情是我的同事在他回去,微笑,开玩笑,出去玩一两个巨无霸时会保持沉默,而他通常会踢桌子,向首席执行官和公司开火,并把剩下的一天都花光了。 。
很自然地,我的同事现在在办公桌前休息了一两天,提高了他在Quake中的瞄准技巧-然后在第二天或第三天,他删除了Delay呼叫,重新构建并发布了一个“紧急补丁”,他用这个词来形容他花了2天1夜来修复性能漏洞。
这是邪恶的首席执行官第一次(也是唯一一次)说“伟大的工作!”。给他。这就是全部,对吧?
这是真正的POBI。
但这也是一种社会过程优化,所以100%可以。
我认为。
“数据库独立性”。这意味着没有存储的proc,触发器等-甚至没有任何外键。
var stringBuilder = new StringBuilder();
stringBuilder.Append(myObj.a + myObj.b + myObj.c + myObj.d);
string cat = stringBuilder.ToString();
最佳使用我见过的StringBuilder。
当一个简单的string.split足够时,使用正则表达式分割一个字符串
我知道这个线程很晚,但是最近我看到了:
bool isFinished = GetIsFinished();
switch (isFinished)
{
case true:
DoFinish();
break;
case false:
DoNextStep();
break;
default:
DoNextStep();
}
您知道,以防布尔值有一些额外的值...
我能想到的最糟糕的例子是公司内部的数据库,其中包含所有员工的信息。它从HR进行每晚更新,并且在顶部具有ASP.NET Web服务。许多其他应用程序使用Web服务填充诸如搜索/下拉字段之类的内容。
悲观的是,开发人员认为重复调用Web服务太慢而无法重复执行SQL查询。他做了什么?应用程序启动事件将读取整个数据库,并将其全部转换为内存中的对象,这些对象将无限期存储,直到应用程序池被回收为止。这段代码太慢了,加载少于2000名员工需要15分钟。如果您在白天无意中回收了应用程序池,则可能需要30分钟或更长时间,因为每个Web服务请求都会启动多个并发重新加载。因此,新员工在创建帐户的第一天就不会出现在数据库中,因此在最初的几天内将无法访问大多数内部应用程序,这会打乱他们的拇指。
悲观情绪的第二个层次是,开发经理不想因为担心破坏依赖的应用程序而碰它,但是由于如此简单的组件的不良设计,我们仍然在整个公司范围内偶尔出现关键应用程序中断的情况。
似乎没有人提到排序,所以我会说。
在几个不同的时间里,我发现有人手工制作了泡泡糖,因为情况“不需要”调用已经存在的“过于花哨”的快速排序算法。当他们的手工气泡冒泡在用于测试的十行数据上运行良好时,开发人员感到满意。在客户添加了数千行之后,它并没有完全解决。
我曾经在一个应用程序中工作,该应用程序的代码如下:
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;
应用性能翻倍。
我曾经不得不尝试修改将这些gem包含在Constants类中的代码
public static String COMMA_DELIMINATOR=",";
public static String COMMA_SPACE_DELIMINATOR=", ";
public static String COLIN_DELIMINATOR=":";
在本应用程序的其余部分中,出于不同的目的,每一个都多次使用。COMMA_DELIMINATOR在8个不同的包中有200多种用法,乱七八糟的代码。
我在内部软件中一次又一次地遇到的最大的历史第一名:
出于“可移植性”的原因,未使用DBMS的功能,因为“我们稍后可能希望切换到另一家供应商”。
读我的唇语。对于任何内部工作:不会发生!
我有一位同事试图超越我们的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%,有时甚至会降低速度。
if
。坚持用C语言表达陈述是文化/宗教教条,而不是任何一种客观实践。(更好的准则:如果嵌套的三进制太长而无法阅读,则不应使用if
其中任何一个。)
if
在函数中使用单值并将其替换为三进制。很好,而且通常更具可读性。我正在谈论用单个return语句和嵌套三元数替换整个30+行方法。没有人认为新代码更具可读性,但一位开发人员则认为它更快。
在我作为一名成熟的开发人员的第一份工作中,我接手了一个程序的项目,该程序遇到了扩展问题。它在较小的数据集上可以很好地工作,但是在给定大量数据时会完全崩溃。
当我深入研究时,我发现原始程序员试图通过并行化分析来加快速度-为每个其他数据源启动一个新线程。但是,他犯了一个错误,因为所有线程都需要一个共享资源,而这些资源却处于死锁状态。当然,并发的所有好处都消失了。而且,它使大多数系统崩溃,无法启动100个以上的线程,只能锁定其中一个线程。我强大的开发机器是一个例外,因为它在大约6个小时内通过150个源数据集进行了搅拌。
因此,要解决此问题,我删除了多线程组件并清理了I / O。在没有其他更改的情况下,在我的计算机上,具有150个源数据集的执行时间降到了10分钟以下,而在普通公司的计算机上,执行时间从无穷缩短到了不到半小时。
我想我可以提供这个宝石:
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())
,但是为什么这个代码起作用?
这可能比您所追求的更高,但是修复它(如果允许的话)也会带来更高的痛苦:
坚持动手滚动对象关系管理器/数据访问层,而不要使用在那里建立的,经过测试的成熟库中的一个(即使在已经指出这些库之后)。
在每次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
错误很烦人,但这并不是解决问题的最佳方法。
YAGNI极端主义怎么样。这是过早的悲观化的一种形式。似乎在您每次应用YAGNI时,您最终都需要它,因此添加它的工作量是开始时的10倍。如果您创建一个成功的程序,那么您将需要它。如果您习惯创建寿命很快的程序,那么请继续练习YAGNI,因为那是我认为的YAGNI。
在BBC网站上,一篇讨论Windows 7的文章中并未完全提到过早的优化-但肯定是被误导了。
Curran先生说,Microsoft Windows团队一直在研究操作系统的各个方面以进行改进。“我们能够通过略微调整WAV文件关闭音乐来缩短关闭时间400毫秒。
现在,我还没有尝试过Windows 7,所以我可能错了,但是我敢打赌,那里的其他问题比关机需要多长时间更为重要。毕竟,一旦我看到“正在关闭Windows”消息,显示器就会关闭并且我要走了-400毫秒对我有什么好处?
我部门的某个人曾经写过一个字符串类。像CString
,但没有Windows依赖性。
他们所做的一项“优化”是不分配任何不必要的内存。显然,没有意识到像std::string
这样的原因类确实分配了过多的内存的原因是,一系列+=
操作可以在O(n)时间内运行。
取而代之的是,每个+=
调用都强制进行重新分配,这将重复的追加操作转换为Painter算法的O(n²)Schlemiel。
我的一位前同事(实际上是soab)被指派为我们的Java ERP构建一个新模块,该模块应该已经收集并分析了客户的数据(零售业)。他决定将“每个日历/日期时间”字段拆分为各个部分(秒,分钟,小时,天,月,年,星期几,双月,三个月(!)),因为“我还要如何查询“每个星期一”?”
对任何人都没有冒犯,但我只是给具有此内容的作业(java)评分
import java.lang.*;