【问题标题】:What are the most hardcore optimisations you've seen?你见过的最核心的优化是什么?
【发布时间】:2008-10-22 15:25:55
【问题描述】:

我不是在谈论算法的东西(例如使用快速排序而不是冒泡排序),我不是在谈论循环展开之类的简单事情。

我说的是硬核的东西。喜欢Tiny Teensy ELFThe Story of Mel;演示场景中的几乎所有内容,等等。

【问题讨论】:

  • 那些链接很棒 - 我以前没有看到它们。 +1
  • 你应该把它变成一个社区维基。

标签: optimization


【解决方案1】:

我曾经写过一个蛮力 RC5 密钥搜索,它一次处理两个密钥,第一个密钥使用整数管道,第二个密钥使用 SSE 管道,两者在指令级交错。然后将其与在系统中每个核心上运行代码实例的主管程序相结合。总的来说,该代码的运行速度比简单的 C 版本快了大约 25 倍。

【讨论】:

  • 令人印象深刻。但我为必须维护它的人感到抱歉(但这是超级优化代码的本质)。
  • 我是维护者,这是一个宠物项目。注释很好,我将两条管道的代码放在各自的列中,以便于理解。
  • “我将两条管道的代码放在各自的列中,以便更容易理解”——好主意。我想我永远不会想到这一点。
  • 是的,同样的......这实际上非常棒。
  • 如果可以的话,我会给你+2:一个用于代码,另一个用于两列代码。太棒了^2!
【解决方案2】:

在我使用的一个(此处未命名)视频游戏引擎中,他们重写了模型导出工具(将Maya 网格转换为游戏加载的东西的东西),这样它不仅可以发出数据,还可以实际上会发出渲染该特定模型所需的精确微指令流。它使用遗传算法来找到将在最少周期数内运行的算法。也就是说,给定模型的数据格式实际上是一个完美优化的子程序,仅用于渲染该模型。因此,在屏幕上绘制网格意味着将其加载到内存中并分支到其中。

(这不是针对 PC,而是针对具有独立于 CPU 并与 CPU 并行的矢量单元的控制台。)

【讨论】:

  • 这在主机游戏中越来越普遍,因为主机通常有一个用于实际渲染图形的命令缓冲区,因此更容易预先计算所需的命令,然后将它们直接发送到图形卡(除了需要的任何指针修复)。
【解决方案3】:

在 DOS 的早期,当我们使用软盘进行所有数据传输时,也存在病毒。病毒感染不同计算机的一种常见方法是将病毒引导加载程序复制到插入的软盘的引导扇区中。当用户将软盘插入另一台计算机并在不记得取出软盘的情况下重新启动时,病毒就会运行并感染硬盘引导扇区,从而永久感染主机 PC。我感染的一种特别烦人的病毒叫做“Form”,为了解决这个问题,我编写了一个自定义软盘引导扇区,它具有以下功能:

  • 验证主机硬盘的引导扇区并确保它没有被感染。
  • 验证软盘引导扇区并 确保它没有被感染。
  • 删除病毒的代码 硬盘驱动器(如果已被感染)。
  • 复制防病毒的代码 引导扇区到另一张软盘,如果 按下了特殊键。
  • 启动硬盘驱动器的代码(如果一切正常) 还好,没有发现感染。

这是在引导扇区的程序空间中完成的,大约 440 字节 :)

对我的伙伴来说,最大的问题是显示的消息非常神秘,因为我需要所有的空间来写代码。就像“FFVD RM?”,意思是“检测到 FindForm 病毒,删除?”

我对这段代码非常满意。优化是程序大小,而不是速度。组装中的两种完全不同的优化。

【讨论】:

    【解决方案4】:

    我最喜欢的是通过整数运算的浮点逆平方根。这是一个关于如何存储浮点值的很酷的小技巧,并且可以更快地执行(即使执行 1/结果也比标准平方根函数更快)或产生比标准方法更准确的结果。

    在 c/c++ 中的代码是:(来自维基百科)

    float InvSqrt (float x) { float xhalf = 0.5f*x; int i = *(int*)&x; i = 0x5f3759df - (i>>1); // Now this is what you call a real magic number x = *(float*)&i; x = x*(1.5f - xhalf*x*x); return x; }

    【讨论】:

    • 现在我们可以直接调用 __frsqrte。这太容易了;我们都变软了!
    • “__frsqrte”是平方根的汇编指令吗?因为我读过 InvSqrt(n) 比 1/sqrt(n) 快 4 倍(假设 sqrt 使用 FSQRT 汇编指令)。但我还是很软……我不会在乎那 4 倍的性能差异……:/
    • 我在 invsqrts (assemblyrequired.crashworks.org/2009/10/16/timing-square-root) 上运行了很多时间。简短的故事是_frsqrte - SSE scalar 估计逆平方根运算 - 比 InvSqrt() 快四倍,比 1.0/FSQRT 快​​六倍。在 PowerPC 上情况有所不同;在那里,由于 load-hit-store 问题,InvSqrt() 完全是一场灾难。
    【解决方案5】:

    非常生物学的优化

    背景简介: DNA 核苷酸三联体(A、C、G 和 T)编码氨基酸,这些氨基酸被连接成蛋白质,蛋白质构成了大多数生物的组成部分。

    通常,每种不同的蛋白质都需要一个单独的 DNA 三联体序列(其“基因”)来编码其氨基酸 - 例如长度为 30、40 和 50 的 3 种蛋白质总共需要 90 + 120 + 150 = 360 个核苷酸。然而,在病毒中,空间是非常宝贵的——所以一些病毒会重叠不同基因的 DNA 序列,因为有 6 个可能的“阅读框”用于 DNA 到蛋白质。翻译(即从可被 3 整除的位置开始;从 3 除以余数 1 的位置开始;或从以 3 除以余数 2 的位置开始;同样如此,但反向读取序列。)

    为了比较:尝试编写一个 x86 汇编语言程序,其中 300 字节函数 doFoo() 从偏移量 0x1000 开始......而另一个 200 字节函数 doBar() 从偏移量 0x1001 开始! (我为这个比赛提议一个名字:你比乙肝聪明吗?

    这是硬核空间优化!

    更新:更多信息的链接:

    【讨论】:

    • 很有趣,但我只对细菌感兴趣,而且移帧阅读永远不会发生。你能为这种行为提供一些链接吗?你知道是否有任何非病毒显示相同的技术?细菌中的 ORF 通常可以(很少)在不同链上重叠几个密码子,但我不知道在同一链上完全重叠。
    • @Stefano:添加了一些链接,希望对您有所帮助。似乎这甚至发生在 rat 中!不过我没有阅读完整的论文。
    • OP 中列出的“Tiny Teensy ELF”示例可以考虑执行此操作。虽然它不是 2 个独立的函数,但它最终确实使用 ELF 标头来实际包含通常被分离出来的代码(加上一些其他各种信息)。这意味着相同的数据被用于多个(如果我没看错的话是三个)目的(包括执行!)。
    • 那是 Kozak 序列成名的 Marilyn Kozak。还在继续,还在做很酷的事情!
    【解决方案6】:

    几年前,我用 65816 汇编语言为 Apple IIgs 编写了一个基于 tile 的游戏引擎。这是一台相当慢的机器,“在金属上”编程是获得可接受性能的虚拟要求。

    为了快速更新图形屏幕,必须将堆栈映射到屏幕,以便使用一些特殊指令,允许在 5 个机器周期内更新 4 个屏幕像素。这没什么特别奇妙的,在IIgs Tech Note #70 中有详细描述。核心部分是我必须如何组织代码以使其足够灵活以成为通用库,同时仍保持最大速度。

    我将图形屏幕分解为扫描线,并创建了一个 246 字节的代码缓冲区来插入专门的 65816 操作码。之所以需要 246 字节,是因为图形屏幕的每条扫描线是 80 个字宽,并且每端需要 1 个额外的字才能平滑滚动。推送有效地址 (PEA) 指令占用 3 个字节,因此 3 * (80 + 1 + 1) = 246 个字节。

    图形屏幕通过跳转到与屏幕右边缘对应的 246 字节代码缓冲区内的地址并在紧跟最左边的字处的代码中修补始终保持 BRanch (BRA) 指令来呈现图形屏幕单词。 BRA 指令将带符号的 8 位偏移量作为其参数,因此它几乎没有有跳出代码缓冲区的范围。

    即使这不是太难,但真正的核心优化在这里。我的图形引擎实际上通过根据模式使用不同的 3 字节代码序列来支持两个独立的背景层和动画图块:

    1. 背景 1 使用推送有效地址 (PEA) 指令
    2. 背景 2 使用加载间接索引 (LDA ($00),y) 指令,后跟推送 (PHA)
    3. 动画图块使用加载直接页面索引 (LDA $00,x) 指令,然后是推送 (PHA)

    关键限制是 65816 寄存器(X 和 Y)都用于引用数据并且不能修改。进一步地,直接页寄存器(D)是根据第二背景的来源设置的,不能改变;数据库寄存器设置为保存第二背景像素数据的数据库,不能更改;堆栈指针(S)映射到图形屏幕,因此不可能跳转到子程序并返回。

    鉴于这些限制,我需要快速处理即将被压入堆栈的单词混合的情况,即一半来自背景 1,一半来自背景 2。我的解决方案是用内存换取速度。因为 所有 普通寄存器都在使用中,所以我只有程序计数器 (PC) 寄存器可以使用。我的解决方案如下:

    1. 定义一个代码片段以在与代码缓冲区相同的 64K 程序库中进行混合
    2. 为 82 个单词中的每一个创建此代码的副本
    3. 存在1-1对应关系,所以从代码片段返回可以是硬编码地址
    4. 完成!我们有一个不影响 CPU 寄存器的硬编码子程序。

    这是实际的代码片段

    code_buff:   PEA $0000            ; rightmost word (16-bits = 4 pixels)
                 PEA $0000            ; background 1
                 PEA $0000            ; background 1
                 PEA $0000            ; background 1
                 LDA (72),y           ; background 2
                 PHA
                 LDA (70),y           ; background 2
                 PHA
                 JMP word_68          ; mix the data
    word_68_rtn: PEA $0000            ; more background 1
                 ...
                 PEA $0000
                 BRA *+40             ; patched exit code
    
                 ...                  
    word_68:     LDA (68),y           ; load data for background 2
                 AND #$00FF           ; mask
                 ORA #$AB00           ; blend with data from background 1
                 PHA
                 JMP word_68_rtn      ; jump back
    word_66:     LDA (66),y
                 ...
    

    最终结果是近乎最佳的 blitter,其开销最小,在 2.5 MHz CPU 和 1 MB/s 内存总线上以 320x200 每秒输出超过 15 帧。

    【讨论】:

      【解决方案7】:

      Michael Abrash 的“Zen of Assembly Language”有一些漂亮的东西,但我承认我不记得具体细节了。

      实际上,Abrash 编写的所有内容似乎都包含一些漂亮的优化内容。

      【讨论】:

      • 是的,我有黑皮书,非常棒……很遗憾,它已经过时了。
      • 这本书我也有,具体内容不记得了。以前经常组装。
      【解决方案8】:

      斯大林方案编译器在这方面非常疯狂。

      【讨论】:

        【解决方案9】:

        我曾经看到一个switch 声明有很多空的cases,开关头部的评论说的是:

        添加了永远不会命中的 case 语句,因为编译器仅在超过 N 个 case 时才将 switch 转换为跳转表

        我忘记了 N 是什么。这是在 leaked in 2004 的 Windows 源代码中。

        【讨论】:

          【解决方案10】:

          我已经查看了 Intel(或 AMD)架构参考资料,看看有哪些指令。 movsx - 带符号扩展的移动非常适合将小有符号值移动到大空间中,例如,在一条指令中。

          同样,如果您知道您只使用 16 位值,但您可以访问所有 EAX、EBX、ECX、EDX 等 - 那么您有 8 个非常快的值位置 - 只需将寄存器旋转 16 位即可访问其他值。

          【讨论】:

            【解决方案11】:

            EFF DES cracker,它使用定制硬件来生成候选密钥(他们制造的硬件可以证明密钥不是解决方案,但不能证明密钥是解决方案),然后使用更多常规代码。

            【讨论】:

              【解决方案12】:

              波兰团队制作的 FSG 2.0 打包器,专门用于打包用汇编制作的可执行文件。如果包装组件不够令人印象深刻(应该尽可能低),它附带的加载程序是 158 字节并且功能齐全。如果您尝试使用 UPX 之类的东西打包任何由 .exe 制成的程序集,它会向您抛出 NotCompressableException ;)

              【讨论】:

                猜你喜欢
                • 2010-11-22
                • 2010-10-10
                • 1970-01-01
                • 2010-12-11
                • 2012-02-16
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多