【发布时间】:2008-10-22 15:25:55
【问题描述】:
我不是在谈论算法的东西(例如使用快速排序而不是冒泡排序),我不是在谈论循环展开之类的简单事情。
我说的是硬核的东西。喜欢Tiny Teensy ELF,The Story of Mel;演示场景中的几乎所有内容,等等。
【问题讨论】:
-
那些链接很棒 - 我以前没有看到它们。 +1
-
你应该把它变成一个社区维基。
标签: optimization
我不是在谈论算法的东西(例如使用快速排序而不是冒泡排序),我不是在谈论循环展开之类的简单事情。
我说的是硬核的东西。喜欢Tiny Teensy ELF,The Story of Mel;演示场景中的几乎所有内容,等等。
【问题讨论】:
标签: optimization
我曾经写过一个蛮力 RC5 密钥搜索,它一次处理两个密钥,第一个密钥使用整数管道,第二个密钥使用 SSE 管道,两者在指令级交错。然后将其与在系统中每个核心上运行代码实例的主管程序相结合。总的来说,该代码的运行速度比简单的 C 版本快了大约 25 倍。
【讨论】:
在我使用的一个(此处未命名)视频游戏引擎中,他们重写了模型导出工具(将Maya 网格转换为游戏加载的东西的东西),这样它不仅可以发出数据,还可以实际上会发出渲染该特定模型所需的精确微指令流。它使用遗传算法来找到将在最少周期数内运行的算法。也就是说,给定模型的数据格式实际上是一个完美优化的子程序,仅用于渲染该模型。因此,在屏幕上绘制网格意味着将其加载到内存中并分支到其中。
(这不是针对 PC,而是针对具有独立于 CPU 并与 CPU 并行的矢量单元的控制台。)
【讨论】:
在 DOS 的早期,当我们使用软盘进行所有数据传输时,也存在病毒。病毒感染不同计算机的一种常见方法是将病毒引导加载程序复制到插入的软盘的引导扇区中。当用户将软盘插入另一台计算机并在不记得取出软盘的情况下重新启动时,病毒就会运行并感染硬盘引导扇区,从而永久感染主机 PC。我感染的一种特别烦人的病毒叫做“Form”,为了解决这个问题,我编写了一个自定义软盘引导扇区,它具有以下功能:
这是在引导扇区的程序空间中完成的,大约 440 字节 :)
对我的伙伴来说,最大的问题是显示的消息非常神秘,因为我需要所有的空间来写代码。就像“FFVD RM?”,意思是“检测到 FindForm 病毒,删除?”
我对这段代码非常满意。优化是程序大小,而不是速度。组装中的两种完全不同的优化。
【讨论】:
我最喜欢的是通过整数运算的浮点逆平方根。这是一个关于如何存储浮点值的很酷的小技巧,并且可以更快地执行(即使执行 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 - SSE scalar 估计逆平方根运算 - 比 InvSqrt() 快四倍,比 1.0/FSQRT 快六倍。在 PowerPC 上情况有所不同;在那里,由于 load-hit-store 问题,InvSqrt() 完全是一场灾难。
背景简介: 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 开始! (我为这个比赛提议一个名字:你比乙肝聪明吗?)
这是硬核空间优化!
更新:更多信息的链接:
【讨论】:
几年前,我用 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 字节代码序列来支持两个独立的背景层和动画图块:
关键限制是 65816 寄存器(X 和 Y)都用于引用数据并且不能修改。进一步地,直接页寄存器(D)是根据第二背景的来源设置的,不能改变;数据库寄存器设置为保存第二背景像素数据的数据库,不能更改;堆栈指针(S)映射到图形屏幕,因此不可能跳转到子程序并返回。
鉴于这些限制,我需要快速处理即将被压入堆栈的单词混合的情况,即一半来自背景 1,一半来自背景 2。我的解决方案是用内存换取速度。因为 所有 普通寄存器都在使用中,所以我只有程序计数器 (PC) 寄存器可以使用。我的解决方案如下:
这是实际的代码片段
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 帧。
【讨论】:
Michael Abrash 的“Zen of Assembly Language”有一些漂亮的东西,但我承认我不记得具体细节了。
实际上,Abrash 编写的所有内容似乎都包含一些漂亮的优化内容。
【讨论】:
斯大林方案编译器在这方面非常疯狂。
【讨论】:
我曾经看到一个switch 声明有很多空的cases,开关头部的评论说的是:
添加了永远不会命中的 case 语句,因为编译器仅在超过 N 个 case 时才将 switch 转换为跳转表
我忘记了 N 是什么。这是在 leaked in 2004 的 Windows 源代码中。
【讨论】:
我已经查看了 Intel(或 AMD)架构参考资料,看看有哪些指令。 movsx - 带符号扩展的移动非常适合将小有符号值移动到大空间中,例如,在一条指令中。
同样,如果您知道您只使用 16 位值,但您可以访问所有 EAX、EBX、ECX、EDX 等 - 那么您有 8 个非常快的值位置 - 只需将寄存器旋转 16 位即可访问其他值。
【讨论】:
EFF DES cracker,它使用定制硬件来生成候选密钥(他们制造的硬件可以证明密钥不是解决方案,但不能证明密钥是解决方案),然后使用更多常规代码。
【讨论】:
波兰团队制作的 FSG 2.0 打包器,专门用于打包用汇编制作的可执行文件。如果包装组件不够令人印象深刻(应该尽可能低),它附带的加载程序是 158 字节并且功能齐全。如果您尝试使用 UPX 之类的东西打包任何由 .exe 制成的程序集,它会向您抛出 NotCompressableException ;)
【讨论】: