如何让gcc在像数组访问这样的交换机中处理字段访问?

时间:2014-07-19 20:01:57

标签: c gcc optimization

例如,-O3处的GCC 4.9.0编译此

typedef struct {
  double x, y, z;
} vector;

double
vector_get(const vector *v, size_t i)
{
  switch (i) {
  case 0:
    return v->x;
  case 1:
    return v->y;
  case 2:
    return v->z;

  default:
    __builtin_unreachable();
  }
}

比较和跳跃(更大的例子得到一个跳转表)。但最佳编译只是

return ((double *)v)[i];

(如果已定义)。显而易见的解决方案是用数组替换单独的vector字段,但是编写.x.y等的能力极大地提高了代码其他部分的可读性。

6 个答案:

答案 0 :(得分:2)

这取决于你想要避免未定义行为的严格程度。

是的,语言标准并不保证向量中的双精度数在内存中彼此相邻,但没有理智的编译器会引入填充。由于您也不打算通过int指针或类似的邪恶事物访问double,因此不应该与严格的别名规则冲突。

因此,从语言律师的角度来看,演员((double*)v)[i]是未定义的行为,但在实践中,你很可能会逃脱它。

如果你想避免演员表,你也可以像这样取第一个坐标的地址:(&v->x)[i]

如果你想严格遵守语言规则,那你就不走运了。允许编译器在坐标之间添加任何数量的填充(无论是否以任何方式理解),因此通过指针算法访问yz或通过union是未定义的行为。没有办法解决这个问题,甚至没有memcpy()的技巧。


如果你进行数组访问,尽管它是技术上未定义的行为,你至少可以通过声明结构的大小来防止填充:

_Static_assert(sizeof(vector) == 3*sizeof(double), "Your compiler inserts padding between doubles, but this code assumes that there is no such padding.");

然而,这仍然不是防弹的,因为UB总是意味着如果优化器足够智能以实现它的UB,则允许优化你的代码。

答案 1 :(得分:2)

您的问题是关于gcc,但您似乎希望尽可能与标准C兼容。

标准C提供了有关给定struct变量中struct成员的allignment属性的一些知识。这允许执行一些操作,比如计算结构中成员之间的偏移量。您需要知道此偏移量才能在"指针模式下执行访问"到一个"结构变量"。

但是,似乎你想要完全相反:访问"指针或数组"好像它是一个" struct"。这不能保证工作,因为结构可以在两个连续元素之间具有填充字节。但是在数组中,可以保证所有连续元素都在内存中而没有任何填充。

我的结论是,一般来说,你要做的事情会让你犯错误 这并不是不可能的,但问题是字节的排列取决于编译器:它无法预测。

我们可以肯定地说:

  • 3个元素的数组,具体为doubles,保存在3个double类型的连续对象的存储空间(内存)中的连续字节块中。
  • 类型为double的三个元素x,y,z的结构按照成员声明的顺序保存在一个连续的字节块中。所以.x < .y < .z
  • 结构的字节可以用memcpy()复制,因此,特别是,可以访问这些字节的相对地址(从结构的第一个字节的地址开始)。
  • 它可以是contiguos元素之间的填充字节。因此,.y的地址不一定是成员地址.x&#34;加&#34; sizeof(double)
  • 结构的第一个成员的地址与结构本身的地址一致。这意味着开头没有填充字节。
  • 可能在结构的末尾有填充字节。

填充字节的选择取决于编译器。

因此,使用联合键入punning并不能保证为您提供所期望的可移植结果。

答案 2 :(得分:2)

正如您所建议的那样,用数组(或矢量,如果需要)替换三个成员。然后添加内联访问函数x()y()z(),它们分别返回v[0]v[1]v[2](您甚至可以返回引用以允许它们作为l值。

答案 3 :(得分:0)

您可以通过多种方式确定您的结构布局是否适合您的优化,但有一种方法是:

#define X_O offsetof(vector, x)
#define Y_O offsetof(vector, y)
#define Z_O offsetof(vector, z)

const int vector_is_packed
    = ((Z_O - Y_O) == (Y_O - X_O)) && ((Y_O - X_O) == sizeof(double));

然后,您可以按如下方式编写代码:

double
vector_get_0 (const vector *v, size_t i)
{
    if (vector_is_packed) {
        return *(double *)((char *)&v->x + i*sizeof(double));
    }

    static const size_t delta[] = {
        offsetof(vector, x), offsetof(vector, y), offsetof(vector, z)
    };
    return *(const double *)((const char *)v + delta[i]);
}

死代码优化将留下一条路径或另一条路径,具体取决于平台是否已打包三个双打。

答案 4 :(得分:0)

理想情况下,您只需使用typedef double vector[3];,这样您的所有代码都可以受益,但如果它是很多代码,您只需用union替换struct(在大多数系统sizeof(double)== 8 so打包和填充不会成为问题, - 如果您正在使用其他平台,请参阅之前对数组的评论)

#include <stddef.h> //for size_t
typedef union  __attribute__ ((packed)){
  double v[3];
  struct { double x, y, z;};
}vector;

double vector_get(vector *v, size_t i) //note the unsigned type - eliminate a conditional branch
{
if (i<3) return v->v[i];
  __builtin_unreachable(); //as long as code is not called with i>2 or i<0
}

这编译为:

vector_get(vector*, size_t int):
    movsd   (%rdi,%rsi,8), %xmm0
    ret

这大致相当于#define vector_get(v,i) ((double*)(v))[i],因为如果i&gt; 2

__builtin_unreachable()将生成会造成严重破坏的程序集

根据Agnar Fog的compilation of assembly latency and throughput,这非常快。

  
指令
---------------
MOVSD   
  
操作数
---------------
R,R   
  
μops
------
1   
  
延迟
-----------
2-3   
   友情
吞吐量
---------------
2   
   指令
设置
---------------
SSE   

这是针对Pentium4但具有单个操作码,2-3个时钟周期延迟(如果下一条指令在同一执行单元中仅为2)并且能够每2个时钟周期处理1个,则新的改进空间不大CPU生成(仅整数运算更快1个操作码,1个周期,每个周期1/3或3个)。

当然,这只能修复这一功能。你总是可以尝试更激烈的东西:

sed -i "s/->x/[X]/g" *.h *.c #and whatever other paths/files
sed -i "s/.x/[X]/g" *.h *.c
#and so on for y and z and change your typedef to an array and an enum {X,Y,Z};
#the enum will solve your code readability concern

但是你可能希望首先从sed中取出-i并为它们grep,这样你就可以确保不会破坏别的东西:grep "\->" | sed "s/->x/[0]/g" ...只是为了安全起见(我想知道是否有一种工具可以做到这一点?)

另一种选择是使用预定义的宏。功能上没有真正的区别;但是,如果您尝试访问范围之外的数据,您将获得编译时断言(因为它未定义)

#define PREFIX_VECTOR_0(v,n) ((v)[0])
#define PREFIX_VECTOR_1(v,n) ((v)[1])
#define PREFIX_VECTOR_2(v,n) ((v)[2])
#define PREFIX_VECTOR_X(v,n) ((v)[0])
#define PREFIX_VECTOR_x(v,n) ((v)[0])
#define PREFIX_VECTOR_Y(v,n) ((v)[1])
#define PREFIX_VECTOR_y(v,n) ((v)[1])
#define PREFIX_VECTOR_Z(v,n) ((v)[2])
#define PREFIX_VECTOR_z(v,n) ((v)[2])
#define PREFIX_VECTOR_i(v,n) ((v)[n]) //for iterations using i (Note n can be > 2 here)
#define PREFIX_VECTOR_GET(v,n) PREFIX_VECTOR_##n(v,n)
#include <stddef.h>
static inline double vector_get(double *v,size_t i){
  return PREFIX_VECTOR_GET(v,i);
}

答案 5 :(得分:-1)

不是C联盟(http://www.tutorialspoint.com/cprogramming/c_unions.htm)你在寻找什么?

typedef union {
   struct { __attribute__ ((aligned (1))) double x, y, z } vector,
   double components[3];
} VectorView;


double
vector_get(const VectorView *v, size_t i)
{
    if (i<0 || i>2) __builtin_unreachable();
    v.components[i];
    // v.vector.x = ...;
}

(编辑:添加了非便携式gcc 属性((aligned(1)))以修复关于对象评论的潜在错误