我们都知道,过早优化是所有邪恶的根源,因为它会导致不可读/不可维护的代码。更糟糕的是悲观化,当有人实现“优化”时,因为他们认为它会更快,但它最终会变慢,以及变得越来越快,不可维护等等。最荒谬的例子是什么?这是你见过的吗?
答案 0 :(得分:205)
我认为“过早优化是所有邪恶的根源”这句话是方式,过度使用。对于许多项目而言,它已成为在项目后期才考虑绩效的借口。
这句话通常是人们避免工作的拐点。当人们真正说“哎呀,我们真的没有想到这一点并且现在没有时间处理它”时,我看到了这句话。
我看到了更多“荒谬”的愚蠢性能问题的例子,而不是由于“悲观化”引入的问题的例子
我认为更好的说法是:“没有测量和理解的优化根本不是优化 - 它只是随机变化”。
良好的性能工作非常耗时 - 通常更多的是开发功能或组件本身。
答案 1 :(得分:114)
数据库是悲观化的游戏场所。
收藏夹包括:
这是我的头脑。
答案 2 :(得分:87)
我认为没有绝对的规则:有些东西最好先预先优化,有些则不是。
例如,我在一家公司工作,我们从卫星接收数据包。每个数据包都需要花费很多钱,因此所有数据都经过了高度优化(即打包)。例如,纬度/经度不是作为绝对值(浮动)发送的,而是作为相对于“当前”区域的“西北”角的偏移。在使用之前我们必须解压缩所有数据。但我认为这不是悲观,而是智能优化以降低通信成本。
另一方面,我们的软件架构师决定将解压缩的数据格式化为非常易读的XML文档,并将其存储在我们的数据库中(而不是将每个字段存储在相应的列中)。他们的想法是“XML是未来”,“磁盘空间便宜”,“处理器便宜”,所以没有必要优化任何东西。结果是我们的16字节数据包变成了存储在一列中的2kB文档,即使是简单的查询,我们也必须在内存中加载兆字节的XML文档!我们每秒收到超过50个数据包,所以你可以想象这个表现有多糟糕(BTW,公司破产了)。
因此,没有绝对的规则。是的,有时过早优化是一个错误。但有时候“cpu /磁盘空间/内存便宜”的座右铭是所有邪恶的真正根源。
答案 3 :(得分:81)
在一个旧项目中,我们继承了一些具有大量Z-8000经验的(优秀的)嵌入式系统程序员。
我们的新环境是32位Sparc Solaris。
其中一个人把所有的内容更改为短路以加速我们的代码,因为从RAM中获取16位比抓取32位更快。
我必须编写一个演示程序,以显示在32位系统上获取32位值比获取16位值更快,并解释为了获取16位值,CPU必须制作32位-bit宽存储器访问,然后屏蔽掉或移位16位值不需要的位。
答案 4 :(得分:75)
以下是最近的一个例子:
数据架构师向我提出了一个详细的建议,即在一个相当大而复杂的应用程序中垂直分区密钥表。他想知道为适应变化需要哪种类型的开发工作。谈话是这样的:
我:你为什么要考虑这个?你试图解决的问题是什么?
他:表格X太广了,出于性能原因我们正在对其进行分区。
我:是什么让你觉得它太宽了?
他:顾问说,在一张桌子上列的内容太多了。
我:这会影响效果吗?
他:是的,用户报告了应用程序XYZ模块的间歇性减速。
我:您如何知道表格的宽度是问题的根源?
他:这是XYZ模块使用的关键表,它就像200列。一定是问题所在。
我(解释):但是模块XYZ特别使用该表中的大多数列,并且它使用的列是不可预测的,因为用户配置应用程序以显示他们想要显示的数据从那张桌子。很可能有95%的时间我们最终会将所有表格重新组合在一起,这会伤害性能。
他:顾问说它太宽了我们需要改变它。
我:这位顾问是谁?我不知道我们聘请了一位顾问,他们也没有和开发团队谈过。
他:好吧,我们还没有雇用他们。这是他们提供的提案的一部分,但他们坚持认为我们需要重新构建这个数据库。
我:嗯。因此,销售数据库重新设计服务的顾问认为我们需要重新设计数据库....
谈话继续这样下去。之后,我又看了一下有问题的表,并确定它可能会通过一些简单的规范化来缩小,而不需要异乎寻常的分区策略。当我调查性能问题(之前未报告过)并将其跟踪到两个因素时,这当然是一个没有实际意义的点:
当然,架构师仍然在推动桌面的垂直分区,这种分区悬挂在“太宽”的元问题上。他甚至通过获得另一位数据库顾问的提议来支持他的案例,该顾问能够确定我们需要对数据库进行重大设计更改,而无需查看应用程序或运行任何性能分析。
答案 5 :(得分:58)
我见过有人使用alphadrive-7来完全孵化CHX-LT。这是一种不常见的做法。更常见的做法是初始化ZT变换器,以减少缓冲(由于更大的净过载阻力)并创建java样式的字节图。
完全悲观!
答案 6 :(得分: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是同步的,所以如果你只连接几个字符串,实际上会有额外的开销。 (更不用说这种差异在这个范围上绝对是微不足道了。)关于这种做法的另外两点:
答案 7 :(得分:47)
我曾经看过一个使用'Root'表的MSSQL数据库。 Root表有四列:GUID(uniqueidentifier),ID(int),LastModDate(datetime)和CreateDate(datetime)。数据库中的所有表都是Root表的外键。每当在db中的任何表中创建新行时,您必须使用几个存储过程在Root表中插入一个条目,然后才能到达您关心的实际表(而不是使用一些触发器简单触发器为您完成工作的数据库。)
这造成了一堆无用的无意中听到和头痛,需要在它上面写任何东西来使用sprocs(并且消除了我将LINQ引入公司的希望。它可能但不值得头疼),并且最重要的是off甚至没有完成它应该做的事情。
选择此路径的开发人员在假设这节省了大量空间的情况下为其辩护,因为我们没有在表本身上使用Guids(但是...不是在每个行的Root表中生成的GUID make?),以某种方式提高了性能,并使审计对数据库的更改变得“容易”。
哦,数据库图看起来像是来自地狱的突变蜘蛛。
答案 8 :(得分:42)
如何 POBI - 显然是故意的悲观化?
90年代我的同事已经厌倦了首席执行官的屁股,因为首席执行官花费了每个ERP软件(一个定制的)发布的第一天,在新功能中找到性能问题。即使新的功能已经达到千兆字节并使不可能变为可能,他总是会发现一些细节,甚至是看似重大的问题。他相信对编程有很多了解并且通过踢程序员驴来获得他们的踢法。由于批评的无能(他是首席执行官,而不是IT人),我的同事从未设法做到正确。如果您没有性能问题,则无法消除它......
直到一个版本,他把很多Delay(200)函数调用(它是Delphi)放入新代码中。 上线后仅用了20分钟,他就被命令出现在首席执行官办公室,以便亲自接受他过期的侮辱。
到目前为止,只有不寻常的事情是我的同事们回来时微笑,开玩笑,出去买BigMac或者两个人,而他通常会踢桌子,对CEO和公司大加赞赏,并且剩下的时间都转过来了直到死亡。
当然,我的同事现在在他的办公桌休息了一两天,提高了他在Quake的瞄准技能 - 然后在第二天或第三天他删除了延迟电话,重建并发布了一个“紧急补丁”,其中他传播他花了2天1夜来修复表演漏洞的消息。
这是邪恶的首席执行官说“干得好”的第一个(也是唯一的)时间。给他。这一切都很重要,对吗?
这是真正的POBI。
但它也是一种社交流程优化,所以100%可以。
我想。
答案 9 :(得分:32)
“数据库独立性”。这意味着没有存储过程,触发器等 - 甚至没有任何外键。
答案 10 :(得分:31)
var stringBuilder = new StringBuilder();
stringBuilder.Append(myObj.a + myObj.b + myObj.c + myObj.d);
string cat = stringBuilder.ToString();
最好地使用我见过的StringBuilder。
答案 11 :(得分:26)
我知道这个帖子已经很晚了,但我最近看到了这个:
bool isFinished = GetIsFinished();
switch (isFinished)
{
case true:
DoFinish();
break;
case false:
DoNextStep();
break;
default:
DoNextStep();
}
你知道吗,以防布尔值有一些额外的值......
答案 12 :(得分:26)
当简单的string.split足够
时,使用正则表达式来分割字符串答案 13 :(得分:25)
最糟糕的例子我能想到的是我公司的一个内部数据库,其中包含所有员工的信息。它从HR获得每晚更新,并在顶部提供ASP.NET Web服务。许多其他应用程序使用Web服务来填充搜索/下拉字段等内容。
悲观的是,开发人员认为重复调用Web服务的速度太慢,无法进行重复的SQL查询。他做了什么?应用程序启动事件读入整个数据库并将其全部转换为内存中的对象,无限期地存储,直到应用程序池被回收。这段代码非常慢,在不到2000名员工中加载需要15分钟。如果您在白天无意中回收了应用程序池,则可能需要30分钟或更长时间,因为每个Web服务请求都会启动多个并发重新加载。出于这个原因,新员工在创建帐户的第一天就不会出现在数据库中,因此在他们的头几天就无法访问大多数内部应用程序,而是大拇指。
第二层悲观情绪是开发经理不想触及它,因为害怕破坏依赖的应用程序,但是由于这样一个简单组件的设计很差,我们仍然在公司范围内零星停止关键应用程序
答案 14 :(得分:25)
似乎没有人提到排序,所以我会。
几个不同的时间,我发现有人手工制作了一个bubbleort,因为情况“不需要”调用已经存在的“过于花哨”的快速排序算法。当他们的手工制作的Bubbleort在他们用于测试的十行数据上运行良好时,开发人员感到满意。在客户添加了几千行之后,它并没有完全过去。
答案 15 :(得分:20)
我曾经不得不尝试在Constants类中修改包含这些gem的代码
public static String COMMA_DELIMINATOR=",";
public static String COMMA_SPACE_DELIMINATOR=", ";
public static String COLIN_DELIMINATOR=":";
这些中的每一个在应用程序的其余部分中多次使用以用于不同目的。 COMMA_DELIMINATOR在8个不同的软件包中使用了200多种用途,从而散布了代码。
答案 16 :(得分:19)
我在内部软件中一次又一次地遇到的最重要的一次:
由于“可移植性”原因未使用DBMS的功能,因为“我们可能希望稍后切换到其他供应商”。
读我的嘴唇。对于任何内部工作:不会发生!
答案 17 :(得分:19)
我曾经在一个充满代码的应用程序上工作过:
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;
将应用效果提升一倍。
答案 18 :(得分: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
”,所有其他逻辑将被深层嵌套的三元表达式替换。当你试图争论这是如何不可维护时,他会指出他的方法的汇编输出是三或四个汇编指令更短的事实。它不一定是更快,但总是 tiny 位更短。这是一个嵌入式系统,其中内存使用偶尔会起作用,但是可以做出比这更容易的优化,这将使代码可读。
然后,在此之后,由于某种原因,他认为ptr->structElement
太难以理解,所以他开始将所有这些改为(*ptr).structElement
,理论上它更具可读性和更快。< / p>
将可读代码转换为不可读代码,最多可提高1%,有时实际上代码更慢。
答案 19 :(得分:15)
在我作为一名成熟的开发人员的第一份工作中,我接手了一个项目,该项目正在遭遇扩展问题。它在小数据集上运行得相当好,但在给定大量数据时会完全崩溃。
当我挖掘时,我发现原始程序员试图通过并行化分析来加快速度 - 为每个额外的数据源启动一个新线程。然而,他犯了一个错误,因为所有线程都需要一个共享资源,在这个资源上他们会陷入僵局。当然,并发的所有好处都消失了。此外,它破坏了大多数系统以启动100多个线程,只有其中一个线程锁定。我的强大的开发机器是一个例外,它在大约6个小时内通过150源数据集进行搅拌。
为了解决这个问题,我删除了多线程组件并清理了I / O.如果没有其他更改,150源数据集的执行时间在我的机器上降至10分钟以下,在普通公司机器上从无穷大降至不到半小时。
答案 20 :(得分: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;
}
这是完全相同的算法,虽然实现略有不同,所以我认为它符合条件。
答案 21 :(得分:11)
这可能是你所追求的更高水平,但修复它(如果你被允许)也会带来更高的痛苦程度:
坚持手动滚动对象关系管理器/数据访问层,而不是使用其中一个已建立的,经过测试的成熟库(即使在他们被指出之后)。
答案 22 :(得分:10)
所有外键约束都从数据库中删除,否则会出现很多错误。
答案 23 :(得分: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
错误很烦人,但这不是最佳解决方案。
答案 24 :(得分:8)
这不完全适合这个问题,但无论如何我都会提到一个警示故事。我正在研究一个运行缓慢的分布式应用程序,然后飞到DC参加主要旨在解决问题的会议。项目负责人开始概述旨在解决延迟的重新架构。我自告奋勇说我周末采取了一些测量方法,将瓶颈分离为单一方法。事实证明,本地查找中缺少记录,导致应用程序必须在每次事务处都转到远程服务器。通过将记录添加回本地商店,延迟被消除 - 问题得到解决。请注意,重新架构不会解决问题。
答案 25 :(得分:7)
YAGNI极端主义怎么样?这是一种过早悲观化的形式。似乎你在任何时候申请YAGNI,然后你最终需要它,导致添加它的努力是你在开始时添加它的10倍。如果你创建了一个成功的程序,那么你可能需要它。如果你习惯于创造生命快速耗尽的程序,那么继续练习YAGNI因为那时我想YAGNI。
答案 26 :(得分:6)
并非完全过早的优化 - 但肯定是错误的 - 这是在BBC网站上从一篇讨论Windows 7的文章中读到的。
Curran先生表示,Microsoft Windows团队一直在研究操作系统的各个方面以进行改进。 “通过略微修剪WAV文件关机音乐,我们能够在关机时间内缩短400毫秒。
现在,我还没有尝试过Windows 7,所以我可能错了,但我愿意打赌,其他问题比关闭需要多长时间更重要。毕竟,一旦我看到“关闭Windows”消息,显示器就会关闭,我正在走开 - 400毫秒的时间对我有什么好处?
答案 27 :(得分:6)
我所在部门的某人曾写过一个字符串类。像CString
这样的界面,但没有Windows依赖。
他们所做的一个“优化”是不分配不必要的内存。显然没有意识到像std::string
这样的类确实分配了多余的内存,这样一系列+=
操作就可以在O(n)时间内运行。
相反,每一次+=
调用都会强行重新分配,这会重复附加到O(n²)Schlemiel the Painter's algorithm。
答案 28 :(得分:5)
我的一位前同事(实际上是s.o.a.b.)被指派为我们的Java ERP构建一个新模块,该模块应该收集并分析客户的数据(零售业)。他决定在其组件中分割每个日历/日期时间字段(秒,分钟,小时,日,月,年,星期几,bimester,三个月(!)),因为“我还会如何查询'每个星期一'?”
答案 29 :(得分:3)
我认为悲观化并不罕见。根据我进行性能调优的经验,很多糟糕的性能是由“良好的编程习惯”引起的,其中“效率”的名称是合理的。例子:
地图集或“字典”
这些通常使用某种哈希编码,因此它们将具有O(1)性能,但只有当填充的项目多于通常使用的项目时才会收支平衡。
迭代
这些被证明是可以优化为有效的内联代码,当很少检查它们是否确实存在时。
通知和事件处理是保持数据一致的一种方法 由于数据结构很少被规范化,因此必须管理不一致,并且通知是通常的方法,因为它应该“立即”处理问题。 但是,即时性和效率之间存在很大差异。 此外,鼓励“获取”或“设置”时,“属性”深入到数据结构中以尝试保持一致。这些“短皮带”方法会导致大量浪费的计算。 “长皮带”方法,例如定期循环通过数据结构来“修复”它,可以稍微“立即”但更有效率。
答案 30 :(得分:3)
while true; do echo 3 > /proc/sys/vm/drop_caches; sleep 3600; done
这导致内核花费时间清理磁盘缓存,一旦成功,一切都运行缓慢,直到缓存重新填充。由于误解而导致磁盘缓存阻止内存可供应用程序使用。
答案 31 :(得分:3)
也许只是在早期快速浏览系统将有助于指出可能的瓶颈。
“这部分不需要快”(存档日志) “这部分必须是快速的hella”(接受新的连接)
然后,非常快速的部件通常不需要额外的优化与肮脏的怪癖,通常不错的硬件和良好编码的部分就足够了。
回答一个简单的问题“我能从这部分代码中快速获得任何东西吗?”将是一个很好的指导方针。我的意思是,使用常识优化项目的其他部分!
答案 32 :(得分:3)
对任何人都没有冒犯,但我只是给了一个有这个
的作业(java)import java.lang.*;
答案 33 :(得分:2)
另一个花哨的表演技巧:)
if (!loadFromDb().isEmpty) {
resultList = loadFromDb();
// do something with results
}
对于额外数据库命中的一小部分价格,您可以节省所有时间,就像10行代码一样,无论如何,这可能对空列表没有太大作用。这样的事情遍布整个代码:)
答案 34 :(得分:2)
一位同事必须检查特定角色的页面访问权限 - 仅限“管理员”。这就是她写的:
if( CurrentUser.CurrentRole == "Role1" || CurrentUser.CurrentRole == "Role2")
{
// Access denied
}
else
{
// Access granted
}
而不是
if( !CurrentUser.CurrentRole.equals("Admin") )
{
// access denied
}
因此,无论何时向系统添加新角色,新角色都可以访问所有机密页面。
同一个同事也加入了所有查询的制作和存档表。
答案 35 :(得分:2)
我的一些同事,正在使用现有服务器端批处理(用C ++编写)的“优化”项目,使用特定于win32的代码和函数“优化”以使日志记录类(!)死亡。
也许瓶颈在于logger.write(...),谁知道......
答案 36 :(得分:2)
多年前我作为顾问访问过的一家公司编写了一个类似于以下内容的排序函数:
procedure sort(string[] values, string direction)
begin
while not sorted do
begin
for every value in values
begin
if direction="Ascending" then
begin
... swap values in ascending order
end
else if direction="Descending" then
begin
... swap values in descending order
end
end;
end;
end;
这是一个bubbleort算法(本身效率很低),内部循环中的方向的字符串比较!我几乎无法相信自己的眼睛,并解释说是的,我可以在这里进行一些速度提升(毕竟他们是我的客户所以我不得不外交这个事实,这是我见过的最不合适的代码:-))
答案 37 :(得分:1)
许多程序员不知道或者不想知道SQL,所以他们发现“技巧”以避免真正使用SQL,这样他们就可以将数据放入数组中。数组使一些人感到高兴。 (我喜欢游标和数组。可口可乐和百事可乐。)我在一些面向对象程序员的代码中发现了这两个代码块,抱怨关系数据库很慢。 (答案不是更多的内存或更多的处理器。)
这种情况下的表是一个巨大的表,uniqueid_col是唯一的id或唯一的行。
将此数据加载到arrayX中(因为数组必须更快)
Select uniqueid_col, col2, col3 from super_big_tbl
(伪代码)
Loop
arrayX.next_record
if uniqueid_col = '829-39-3984'
return col2
end if
end loop
(我的答案在底部。)
下一个是我也看到的一个简单的错误。这个想法是你从来没有这样重复:
Select uniqueid_col, col2, col3 from super_big_tbl group by uniqueid_col, col2, col3 having uniqueid_col = '829-39-3984'
正确的语法应该是
Select uniqueid_col, col2, col3 from super_big_tbl where uniqueid_col = '829-39-3984'
答案 38 :(得分:1)
使用Integer字段分配按位的应用程序我们的客户可以为其添加哪些应用程序访问分组。这意味着当时我们可以创建总共32个组,以便在所有500多个客户中共享。
Aaaah,但是一个按位比较比一个等于快,并且比一个加速对等更快?
不幸的是,当我完全(而且非常声音)对这段代码及其作者感到不满时,我发现作者是我的老板老板。事实证明,这是一个相当专制的家伙。
P.S。
我知道你在想什么,它应该是一个二进制字符串吗? :)
答案 39 :(得分:1)
我要提到StringBuilder用于微小/非循环字符串连接,但它已被提及。
将方法的变量放入私有类成员中,以防止它们在每次运行方法时都会收集“垃圾”。变量是值类型。
答案 40 :(得分:0)
我有一个故意的......我曾经通过回溯实现排序......只是作为概念的证明;))不用说它的表现是可怕的。
答案 41 :(得分:0)
任何不基于分析器工具的分类报告的重要优化工作都会从我那里获得大量的WTF。