关于表现:你见过的最荒谬的悲观是什么?

What is the most ridiculous pessimization you've seen?

我们都知道过早的优化是万恶之源,因为它会导致不可读/不可维护的代码。更糟糕的是pessimization,当有人执行"优化"时,因为他们认为它会更快,但最终会变慢,而且会出现问题、无法修复等。您看到的最荒谬的例子是什么?


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

这句话常常是人们逃避工作的拐杖。我看到人们真的应该说"哎呀,我们真的没有事先考虑过,现在也没有时间来处理它"。

我看到过许多"荒谬"的哑巴性能问题的例子,而不是由于"pessimization"而引入的问题的例子。

  • 在程序启动期间读取同一注册表项数千次(或10次)。
  • 加载相同的dll成百上千次
  • 不必要地保持文件的完整路径,从而浪费了兆字节的内存
  • 没有组织数据结构,因此它们占用的内存比需要的多。
  • 将存储文件名或路径的所有字符串调整为最大路径
  • 对具有事件、回调或其他通知机制的对象进行免费轮询

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

好的性能工作是耗时的——通常更多的是为了特性或组件本身的开发。


数据库是一个令人讨厌的游乐场。

最喜爱的包括:

  • 将表拆分为多个(按日期范围、字母范围等),因为它"太大"。
  • 为失效记录创建存档表,但继续将其与生产表合并。
  • 复制整个数据库的方式(部门/客户/产品等)
  • 不要向索引添加列,因为它太大了。
  • 创建大量的汇总表,因为从原始数据重新计算太慢。
  • 使用子字段创建列以节省空间。
  • 将字段作为数组反规范化。

那是我头上的东西。


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

例如,我在一家公司工作,在那里我们接收来自卫星的数据包。每一个包都要花很多钱,所以所有的数据都经过了高度优化(即打包)。例如,纬度/经度不是作为绝对值(浮动)发送的,而是作为相对于"当前"区域的"西北"角的偏移量发送的。我们必须先将所有数据解包,然后才能使用。但我认为这不是一个烦人的过程,而是智能化的优化以降低通信成本。

另一方面,我们的软件架构师决定将解包后的数据格式化为一个可读性很高的XML文档,并存储在我们的数据库中(而不是将每个字段存储在相应的列中)。他们的想法是"XML是未来","磁盘空间是便宜的","处理器是便宜的",所以没有必要优化任何东西。结果是,我们的16字节数据包被转换成存储在一列中的2KB文档,即使是简单的查询,我们也必须在内存中加载兆字节的XML文档!我们每秒接收超过50个数据包,所以您可以想象性能变得多么糟糕(顺便说一句,公司破产了)。

所以,没有绝对规则。是的,有时候优化太早是一个错误。但有时"CPU/磁盘空间/内存很便宜"的座右铭是万恶之源。


在一个老项目中,我们继承了一些(或者优秀的)嵌入式系统程序员,他们拥有大量的z-8000经验。

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

其中一个家伙把所有的整数都改成了短裤来加速我们的代码,因为从RAM中抓取16位比抓取32位要快。

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


哦,天哪,我想我都看过了。通常情况下,解决性能问题的工作是由一个太懒惰的人来完成的,他们根本无法解决这些性能问题的原因,甚至无法研究是否真的存在性能问题。在许多这样的情况下,我想知道,这是否不仅仅是一个案件的人想要尝试一种特殊的技术,并拼命寻找适合他们闪亮的新锤子钉子。

下面是一个最近的例子:

数据架构师向我提出了一个详细的建议,在相当大和复杂的应用程序中对键表进行垂直分区。他想知道需要什么类型的开发工作来适应变化。对话是这样进行的:

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

他:表X太宽了,出于性能考虑,我们正在对它进行分区。

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

他:顾问说一张桌子上的列太多了。

我:这会影响表演吗?

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

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

他:这是XYZ模块使用的键表,它类似于200列。一定是出了问题。

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

他:顾问说太宽了,我们需要换一下。

我:这位顾问是谁?我不知道我们雇了顾问,他们也没有和开发团队谈过。

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

我:嗯。所以销售数据库重新设计服务的顾问认为我们需要一个数据库重新设计……

谈话就这样不停地进行着。之后,我再次查看了所讨论的表,并确定可以通过一些简单的规范化来缩小它的范围,而不需要使用外来的分区策略。当然,当我调查了性能问题(以前未报告)并将其跟踪到两个因素后,发现这是一个没有意义的问题:

  • 缺少一些键上的索引柱。
  • 一些流氓数据分析师锁定键表(包括"太宽"的那个)通过查询生产数据库直接与MSACCESS。
  • 当然,架构师仍然在推动表的垂直分区,而这个分区一直挂在"太宽"的元问题上。他甚至通过从另一个数据库顾问那里得到一个建议来支持他的案例,他能够确定我们需要对数据库进行重大的设计更改,而无需查看应用程序或运行任何性能分析。


    我见过有人使用AlphaDrive-7完全孵化CHX-LT。这是一种罕见的做法。更常见的做法是初始化ZT变压器,从而减少缓冲(由于更大的净过载电阻),并创建Java风格的字节图。

    完全悲观!


    我承认,没有什么惊天动地的东西,但我发现人们使用StringBuffer来连接Java中的循环之外的字符串。这就像转动

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

    进入之内

    1
    2
    3
    4
    5
    6
    StringBuffer sb = new StringBuffer("Count =");
    sb.append(count);
    sb.append(" of");
    sb.append(total);
    sb.append(".");
    String msg = sb.toString();

    在一个循环中使用这种技术曾经是很常见的实践,因为它可以测量得更快。问题是,StringBuffer是同步的,所以如果只连接几个字符串,实际上会有额外的开销。(更不用说这个尺度上的差别是微不足道的。)关于这个实践的另外两点:

  • StringBuilder是不同步的,因此在不能从多个线程调用代码的情况下,应该优先使用StringBuffer。
  • 不管怎样,现代Java编译器都会将可读的字符串连接转换成优化的字节码。

  • 我曾经看到一个使用"根"表的MSSQL数据库。根表有四列:guid(uniqueidentifier)、id(int)、lastmoddate(datetime)和createdate(datetime)。数据库中的所有表都是根表的外键。每当在数据库中的任何表中创建了一个新行时,必须使用两个存储过程在根表中插入一个条目,然后才能到达您关心的实际表(而不是数据库用几个触发器简单触发器为您执行该任务)。

    这造成了一堆无用的窃听和头疼,需要在上面写上任何东西来使用存储过程(并且消除了我向公司介绍Linq的希望)。这是可能的,但不值得头疼),而且最重要的是,它甚至没有完成它应该做的。

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

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


    波比怎么样——显然是故意的迷惑?

    90年代,我的同事厌倦了被首席执行官踢屁股,因为首席执行官在每一个ERP软件(定制软件)发布的第一天都在寻找新功能中的性能问题。即使新的功能压缩了千兆字节并使之成为可能,他总是在抱怨中找到一些细节,甚至是看似重要的问题。他相信自己对编程有很多了解,因而对编程一窍不通。

    由于批评的不称职(他是首席执行官,而不是IT人员),我的同事从来没有设法纠正。如果没有性能问题,就无法消除它…

    直到有一个版本发布,他在新代码中加入了大量延迟(200)函数调用(它是delphi)。上线只花了20分钟,他就被命令亲自到CEO办公室接受他逾期的侮辱。

    到目前为止,唯一不寻常的是我的同事们的沉默,当他回来的时候,他微笑着,开玩笑,出去玩一两个巨无霸,而他通常会踢桌子,对CEO和公司大发雷霆,一天中的其余时间都被拒绝了。

    当然,我的同事现在在他的办公桌上休息了一两天,提高了他在地震中的瞄准技能,然后在第二天或第三天,他删除了延迟的电话,重建并发布了一个"紧急补丁",他传播了他花了2天1个晚上来修复性能漏洞的信息。

    这是邪恶的首席执行官第一次(也是唯一一次)说"干得好!"对他来说。就这些,对吧?

    这是真正的波比。

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

    我想。


    "数据库独立性"。这意味着没有存储的进程、触发器等,甚至没有任何外键。


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

    最好使用我见过的一个架线工。


    当简单的string.split足够时,使用regex来拆分字符串。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    bool isFinished = GetIsFinished();

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

        case false:
            DoNextStep();
            break;

        default:
            DoNextStep();
    }

    你知道,万一一个布尔值有多余的值…


    我能想到的最糟糕的例子是我公司的内部数据库,其中包含所有员工的信息。它每晚都会从人力资源部得到更新,并在顶部安装了一个ASP.NET Web服务。许多其他应用程序使用Web服务来填充搜索/下拉字段等内容。

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

    第二个悲观的层面是,开发经理不想接触它,因为担心破坏依赖的应用程序,但是由于这样一个简单的组件的设计不好,我们仍然在公司范围内偶尔出现关键应用程序的中断。


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

    几次不同的时候,我发现有人手工制作了一个冒泡排序,因为这种情况"不需要"调用已经存在的"太花哨"的快速排序算法。当他们手工制作的BubbleSort在用于测试的十行数据上运行得足够好时,开发人员感到满意。客户加了几千行后,情况就不太好了。


    我曾经试图修改常量类中包含这些gem的代码

    1
    2
    3
    public static String COMMA_DELIMINATOR=",";
    public static String COMMA_SPACE_DELIMINATOR=",";
    public static String COLIN_DELIMINATOR=":";

    在应用程序的其余部分中,每种方法都被多次用于不同的目的。逗号消除器在8个不同的包装中有200多个用途。


    我曾经在一个充满了这样代码的应用程序上工作过:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     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,并将第六行更改为:

    1
                return curr;

    应用程序性能提高了一倍。


    我在内部软件中一次又一次遇到的最重要的时刻:

    由于"可移植性"的原因,没有使用DBMS的特性,因为"我们以后可能想切换到另一个供应商"。

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


    我有一个同事,他试图利用我们C编译器的优化器和例程重写只有他才能阅读的代码。他最喜欢的技巧之一是改变一种可读的方法,比如(编一些代码):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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;
    }

    进入这个:

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

    也就是说,一个曾经可读的方法的第一行将成为"return",所有其他逻辑将被深深嵌套的terniary表达式替换。当您试图讨论这是如何不可维护的时,他会指出这样一个事实,即他的方法的汇编输出更短了三到四条汇编指令。它不一定快,但总是短一点。这是一个嵌入式系统,内存使用有时的确很重要,但是可以进行的优化要比让代码可读的优化容易得多。

    然后,在这之后,出于某种原因,他认为ptr->structElement太不可读了,所以他开始把所有这些都改成(*ptr).structElement,其理论是它更可读、更快。

    将可读代码转换为不可读代码最多可提高1%,有时甚至更慢。


    在我作为一个成熟的开发人员的第一份工作中,我接手了一个项目,这个项目正面临着扩展问题。它在小数据集上可以很好地工作,但在给定大量数据的情况下会完全崩溃。

    在我深入研究的过程中,我发现最初的程序员试图通过并行分析来加快速度——为每个额外的数据源启动一个新的线程。然而,他犯了一个错误,所有线程都需要一个共享资源,在这个资源上它们是死锁的。当然,并发的所有好处都消失了。此外,它使大多数系统崩溃,只为了启动100多个线程而锁定了其中的一个线程。我的BeefyDev机器是一个例外,它在大约6小时内搅动了150个源数据集。

    为了解决这个问题,我移除了多线程组件并清理了I/O。在没有其他更改的情况下,150源数据集的执行时间在我的机器上下降到了10分钟以下,在公司机器上从无穷大下降到半小时以下。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    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):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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;
    }

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

    编辑:想一想,展开的循环实际上也是一个整洁的比喻。通过对版本控制的深入研究,我还可以展示重构的第二个阶段,这一阶段的性能甚至优于上述阶段:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;
    }

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


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

    坚持手工滚动一个对象关系管理器/数据访问层,而不是使用现有的、经过测试的、成熟的库(即使在已经向您指出它们之后)。


    所有的外键约束都从数据库中删除了,否则会有很多错误。


    这不完全符合这个问题,但我还是要提一个警告性的故事。我正在开发一个运行缓慢的分布式应用程序,我飞到华盛顿参加了一个主要旨在解决问题的会议。项目负责人开始概述旨在解决延迟的重新架构。我自告奋勇地说,我在周末做了一些测量,把瓶颈隔离到了一种方法上。结果发现在本地查找中有一个丢失的记录,导致应用程序必须在每个事务上转到远程服务器。通过将记录添加回本地存储,可以消除延迟-问题已解决。注意,重新构建的体系结构不会解决这个问题。


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

    1
    2
    3
    4
    5
    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,最后都会用到它,这使得添加它的工作量是最初添加时的10倍。如果你创建了一个成功的程序,那么你很可能需要它。如果你习惯于创建生命很快耗尽的程序,那么继续练习雅格尼,因为我想是雅格尼。


    不完全是过早的优化——但肯定是被误导了——这是从BBC网站上一篇讨论Windows7的文章中读到的。

    Mr Curran said that the Microsoft Windows team had been poring over every aspect of the operating system to make improvements.
    "We were able to shave 400 milliseconds off the shutdown time by slightly trimming the WAV file shutdown music.

    现在,我还没有尝试过Windows7,所以我可能是错的,但我敢打赌还有其他问题比关闭需要多长时间更重要。毕竟,一旦我看到"关闭Windows"的消息,监视器就会关闭,我就走开了——400毫秒对我有什么好处?


    我部门有人曾经写过一个字符串类。类似于CString的接口,但不依赖于窗口。

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

    相反,每一个单独的+=调用都会强制重新分配,从而将重复的附件转换为painter算法的o(n2)schlemiel。


    我的一个前同事(实际上是一个S.O.A.B.)被分配来为我们的Java ERP建立一个新的模块,它应该收集和分析客户的数据(零售业)。他决定将每个日历/日期时间字段拆分为其组成部分(秒、分钟、小时、日、月、年、周、双月、四个月!!)因为"我还能怎么查询‘每周一’?"


    没有冒犯任何人,但我只是分级了一个任务(Java)

    1
    import java.lang.*;


    1
    while true; do echo 3 > /proc/sys/vm/drop_caches; sleep 3600; done

    这导致内核花费时间清除磁盘缓存,一旦成功,一切都将缓慢运行,直到缓存重新填充。由于误用磁盘缓存导致内存无法供应用程序使用。


    也许在早期快速浏览一下系统就可以找到可能的瓶颈。

    "这部分不需要太快"(归档日志)"这部分必须很快"(接受新连接)

    然后,非常快的部分通常不需要额外优化与肮脏的怪癖,通常体面的硬件和良好编码的部分就足够了。

    只需回答一个简单的问题:"我是否从快速获得这部分代码中得到了什么?"将是一个很好的指导方针。我的意思是,用常识优化项目的其他部分!


    我认为瘟疫并不罕见。从我进行性能调优的经验来看,很多性能不佳是由于"良好的编程实践"以"效率"的名义证明的。实例:

    • 地图收藏或"字典"它们通常使用某种散列编码,因此它们将具有O(1)性能,但仅当填充的项比通常使用的项多得多时才会中断。

    • 遍历器当很少检查它们是否真的被优化为高效的内联代码时,这些都是合理的。

    • 通知和事件处理是保持数据一致的一种方法由于数据结构很少被规范化,所以必须管理不一致性,通知是通常的方法,因为它应该"立即"处理问题。然而,直接性和效率之间有很大的区别。此外,当获取或设置"属性"时,鼓励深入数据结构以保持其一致性。这些"短约束"方法会导致大量的计算浪费。长时间的"束缚"方法,例如定期在数据结构中循环以"修复"它,可以稍微"立即"一点,但效率更高。

    实例


    一名同事必须检查访问页面的特定角色的权限-"仅限管理员"。这就是她写的:

    .

    1
    2
    3
    4
    5
    6
    7
    8
    if( CurrentUser.CurrentRole =="Role1" || CurrentUser.CurrentRole =="Role2")  
    {
    // Access denied
    }
    else
    {
    // Access granted
    }

    而不是

    1
    2
    3
    4
    if( !CurrentUser.CurrentRole.equals("Admin") )
    {
     // access denied
    }

    因此,每当向系统添加新角色时,新角色就可以访问所有机密页面。

    同一个同事也参与了所有查询的生产和存档表。


    另一个花哨的表演技巧:)

    1
    2
    3
    4
    if (!loadFromDb().isEmpty) {
        resultList = loadFromDb();
        // do something with results
    }

    对于一个额外的db命中的小代价,您可以节省所有的时间,就像10行代码一样,这在一个空列表上可能不会做太多。像这样的东西散布在密码上。)


    我多年前作为顾问访问过的一家公司编写了一个类似这样的排序函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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;

    这是一个冒泡算法(本身效率很低),在内部循环中对方向进行字符串比较!我简直不敢相信自己的眼睛,并解释说,是的,我可能会在这里进行一些速度改进(毕竟他们是我的客户,所以我必须对这是我见过的最非最佳代码这一事实保持外交态度:—)


    我的一些同事,在一个"优化"项目的现有服务器端批处理(写在C++),"优化"死亡日志类(!),使用Win32特定的代码和函数。

    可能瓶颈在logger.write(…)中,谁知道呢…


    我本来打算提到用于微小/非循环字符串连接的StringBuilder,但它已经被提到了。

    将方法的变量放入私有类成员中,以防止它们"每次方法运行时都被垃圾收集"。变量是值类型。


    一个应用程序,它使用一个整型字段来按位分配我们的客户机可以向哪些应用程序访问分组添加更多用户。这意味着在那时,我们可以创建总共32个组,供所有500多个客户共享。

    aaaah,但是按位比较比等号快,waaay比联接快,对吗?

    不幸的是,当我完全(更确切地说)害怕这段代码和它的作者时,我发现作者是我的老板。结果是一个相当独裁的家伙。

    附笔。

    我知道你在想什么,它应该是一个二进制字符串,对吧?:)


    很多程序员不知道或不想知道SQL,所以他们找到了避免真正使用SQL的"窍门",这样他们就可以将数据放入数组中。数组使一些人高兴。(我喜欢光标和数组。可口可乐和百事可乐)我在一些面向对象的程序员的代码中发现了这两块代码,他们抱怨关系数据库运行缓慢。(答案不是更多的内存或处理器。)

    本例中的表是一个巨大的表,其中unique id_col是唯一的ID或唯一的行。

    将此数据加载到arrayx中(因为数组必须更快)

    1
    2
       Select uniqueid_col, col2, col3
         from super_big_tbl

    (伪代码)

    1
    2
    3
    4
    5
    6
    Loop
       arrayX.next_record
        if uniqueid_col = '829-39-3984'
          return col2
        end if
    end loop

    (我的答案在下面。)

    下一个是一个简单的错误,我也看到过。我的想法是你永远不会得到这样的复制品:

    1
    2
    3
    4
       Select uniqueid_col, col2, col3
         from super_big_tbl
     group by uniqueid_col, col2, col3
       having uniqueid_col = '829-39-3984'

    正确的语法应该是

    1
    2
    3
       Select uniqueid_col, col2, col3
         from super_big_tbl
        where uniqueid_col = '829-39-3984'


    我有一个故意的…我曾经通过回溯实现排序…正如一个概念的证明;)不用说它的表现是可怕的。


    任何重要的优化工作,如果不是基于来自探查器工具的分检报告,都可以从我这里获得一个很大的WTF。