正如人们在评论中所说:
if(SQRT[a*a+b*b]>something)
是一个可怕的示例用例。如果这就是您需要 SQRT 的全部,只需 square something
。
只要您可以告诉编译器SQRT
没有任何别名,那么运行时循环将使您的可执行文件更小,并且在启动期间只会增加少量的 CPU 开销。肯定用uint8_t
,不行int
。从 8 位内存位置加载 32 位临时文件并不比从零填充的 32b 内存位置加载慢。(x86 上的额外movsx
指令,而不是使用内存操作数,将在减少缓存污染方面付出更多的代价。RISC 机器通常不允许内存操作数,因此您总是需要一条指令将值加载到寄存器中。 )
此外,sqrt
在 Sandybridge 上是 10-21 周期延迟。如果你不经常需要它,int->double、sqrt、double->int 链并不比 L2 缓存命中差多少。而且比去 L3 或主内存要好。如果你需要很多sqrt
,那么当然,做一个LUT。即使您在桌子上弹跳并导致 L1 未命中,吞吐量也会好得多。
您可以通过平方而不是 sqrting 来优化初始化,例如
uint8_t sqrt_lookup[65536];
void init_sqrt (void)
{
int idx = 0;
for (int i=0 ; i < 256 ; i++) {
// TODO: check that there isn't an off-by-one here
int iplus1_sqr = (i+1)*(i+1);
memset(sqrt_lookup+idx, i, iplus1_sqr-idx);
idx = iplus1_sqr;
}
}
您仍然可以获得存在的好处sqrt_lookup
(const
编译器知道它不能别名)。要么使用restrict
,要么对编译器撒谎,所以表的用户看到一个const
数组,但你实际上是写给它的。
这可能涉及对编译器撒谎,extern const
在大多数地方声明它,而不是在初始化它的文件中。您必须确保这确实有效,并且不会创建引用两个不同符号的代码。如果您只是丢弃了const
初始化它的函数,如果编译器将它放入rodata
(或者如果它未初始化,则可能是只读bss
内存,如果在某些平台上可能的话?)
也许我们可以避免对编译器撒谎,方法是:
uint8_t restrict private_sqrt_table[65536]; // not sure about this use of restrict, maybe that will do it?
const uint8_t *const sqrt_lookup = private_sqrt_table;
实际上,这只是一个const
指向const
数据的指针,并不能保证它所指向的内容不能被另一个引用更改。