我正在阅读这篇文章,并认为一切都很清楚,直到我偶然发现:
同样,大多数真实的 Scheme 系统使用稍微不同的实现。例如,如果
GET_PAIR
减去 的低位x
,而不是将它们屏蔽掉,优化器通常能够将该减法与添加我们正在引用的结构成员的偏移量相结合,从而使修改后的指针与使用一样快未修改的指针。
究竟如何才能实现这种减法以及优化器将如何发挥它的魔力来像未修改的指针一样快地修改指针?
我正在阅读这篇文章,并认为一切都很清楚,直到我偶然发现:
同样,大多数真实的 Scheme 系统使用稍微不同的实现。例如,如果
GET_PAIR
减去 的低位x
,而不是将它们屏蔽掉,优化器通常能够将该减法与添加我们正在引用的结构成员的偏移量相结合,从而使修改后的指针与使用一样快未修改的指针。
究竟如何才能实现这种减法以及优化器将如何发挥它的魔力来像未修改的指针一样快地修改指针?
文章中介绍的技巧是将类型信息编码为 8 字节对齐指针的未使用的三个最低位。使用此信息找出类型后,
#define PAIR_P(x) (((int) (x) & 7) == 2)
在再次将指针用作地址之前,必须清除这些额外的位。
#define GET_PAIR(x) ((struct pair *) ((int) (x) & ~7))
请注意,此时,我们已经知道类型,因此我们知道三个最低有效位的值。它们将永远是0b010
(十进制 2)。因此,((int) (x) & ~7)
作者建议不要写作,而是写作((int) (x) - 2)
。这个想法是,如果你写这样的代码,
if (PAIR_P(x))
{
SCM * thing = GET_PAIR(x)->cdr;
/* Use the thing… */
}
因为我们访问的是指向cdr
内部的成员(在清除低位之后),编译器将生成代码来适当地调整指针。像这样的东西。struct pair
x
SCM * thing = (SCM *) ((char *)((int) (x) - 2)) + offsetof(struct pair, cdr));
由于整数加减法的关联性,我们可以省略一级括号并得到(不显示无论如何不产生机器代码的外部指针转换)
(int) (x) - 2 + offsetof(struct pair, cdr)
其中,the2
和 theoffsetof(struct pair, cdr)
都是编译时常量,可以折叠成一个常量。如果我们要求car
成员(偏移量为 0),这个技巧将无济于事,但每隔一段时间提供帮助也不错。
现代优化器可能能够自己弄清楚,在我们刚刚测试之后,(x & 7) == 2
,x & ~7
相当于x - 2
所以这些天可能不再需要这个技巧了。不过,您想在依赖它之前对其进行测量。