1

好的,我想创建一个 Point3f 类,它是一个包含在 3D 浮点坐标中的点类。

所以,我有两种简单的方法来定义类的属性:

class Point3f
{
    float x;
    float y;
    float z;
};

或者

class Point3f
{
    float coord[3];
}

我想知道哪个(通常)更有效,尤其是对于图形程序。IE 哪个更适合绘制要点:glVertex3f() 还是 glVertex3v()?
我不知道我是否正确。我认为第一个需要更多的运行时内存,另一个需要更多的 cpu 使用率。

编辑:如果我们谈论更复杂的类型,比如三角形由 3 个点组成,或者四面体由 3 个三角形组成 -> 属性:数组还是一个接一个?

请告诉我哪个更有效,为什么!

4

4 回答 4

2

您可以比较为基本功能生成的程序集(我在 OS X 10.7.4 上使用了 GCC 4.8.1)

struct Point3D {
  float m_data[3];
};

struct Point3Ds {
  float x;
  float y;
  float z;
};

double dot(const Point3D& p1, const Point3D& p2) {
  asm("# Dot - Point3D");
  return p1.m_data[0] * p2.m_data[0] +
         p1.m_data[1] * p2.m_data[1] +
         p1.m_data[2] * p2.m_data[2];
}

double dot(const Point3Ds& p1, const Point3Ds&p2) {
  asm("# Dot - Point3Ds");  
  return p1.x * p2.x +
         p1.y * p2.y +
         p1.z * p2.z;
}

Point3D cross(const Point3D& p1, const Point3D& p2) {
  asm("# Cross - Point3D");  
  return { p1.m_data[1] * p2.m_data[2] - p1.m_data[2] * p2.m_data[1],
           p1.m_data[2] * p2.m_data[0] - p1.m_data[0] * p2.m_data[2],
           p1.m_data[0] * p2.m_data[1] - p1.m_data[1] * p2.m_data[0]};
}

Point3D cross(const Point3Ds& p1, const Point3Ds& p2) {
  asm("# Cross - Point3Ds");  
  return { p1.y * p2.z - p1.z * p2.y,
           p1.z * p2.x - p1.x * p2.z,
           p1.x * p2.y - p1.y * p2.x};
}

g++ -O3 -S我获得以下汇编程序(相关部分)时进行编译:

# 12 "point3f.cpp" 1
    # Dot - Point3D
# 0 "" 2
    movss   (%rdi), %xmm0
    movss   4(%rdi), %xmm1
    mulss   (%rsi), %xmm0
    mulss   4(%rsi), %xmm1
    addss   %xmm1, %xmm0
    movss   8(%rdi), %xmm1
    mulss   8(%rsi), %xmm1
    addss   %xmm1, %xmm0
    unpcklps    %xmm0, %xmm0
    cvtps2pd    %xmm0, %xmm0
    ret
LFE0:
    .align 4,0x90
    .globl __Z3dotRK8Point3DsS1_
__Z3dotRK8Point3DsS1_:
LFB1:
# 19 "point3f.cpp" 1
    # Dot - Point3Ds
# 0 "" 2
    movss   (%rdi), %xmm0
    movss   4(%rdi), %xmm1
    mulss   (%rsi), %xmm0
    mulss   4(%rsi), %xmm1
    addss   %xmm1, %xmm0
    movss   8(%rdi), %xmm1
    mulss   8(%rsi), %xmm1
    addss   %xmm1, %xmm0
    unpcklps    %xmm0, %xmm0
    cvtps2pd    %xmm0, %xmm0
    ret
LFE1:
    .align 4,0x90
    .globl __Z5crossRK7Point3DS1_
__Z5crossRK7Point3DS1_:
LFB2:
# 26 "point3f.cpp" 1
    # Cross - Point3D
# 0 "" 2
    movss   4(%rdi), %xmm3
    movss   8(%rdi), %xmm1
    movss   8(%rsi), %xmm5
    movaps  %xmm3, %xmm2
    movss   4(%rsi), %xmm4
    movaps  %xmm1, %xmm0
    mulss   %xmm5, %xmm2
    mulss   %xmm4, %xmm0
    subss   %xmm0, %xmm2
    movss   (%rdi), %xmm0
    mulss   %xmm0, %xmm5
    movss   %xmm2, -24(%rsp)
    movss   (%rsi), %xmm2
    mulss   %xmm4, %xmm0
    mulss   %xmm2, %xmm1
    mulss   %xmm3, %xmm2
    subss   %xmm5, %xmm1
    subss   %xmm2, %xmm0
    movss   %xmm1, -20(%rsp)
    movss   %xmm0, -16(%rsp)
    movq    -24(%rsp), %xmm0
    movd    -16(%rsp), %xmm1
    ret
LFE2:
    .align 4,0x90
    .globl __Z5crossRK8Point3DsS1_
__Z5crossRK8Point3DsS1_:
LFB3:
# 33 "point3f.cpp" 1
    # Cross - Point3Ds
# 0 "" 2
    movss   4(%rdi), %xmm3
    movss   8(%rdi), %xmm1
    movss   8(%rsi), %xmm5
    movaps  %xmm3, %xmm2
    movss   4(%rsi), %xmm4
    movaps  %xmm1, %xmm0
    mulss   %xmm5, %xmm2
    mulss   %xmm4, %xmm0
    subss   %xmm0, %xmm2
    movss   (%rdi), %xmm0
    mulss   %xmm0, %xmm5
    movss   %xmm2, -24(%rsp)
    movss   (%rsi), %xmm2
    mulss   %xmm4, %xmm0
    mulss   %xmm2, %xmm1
    mulss   %xmm3, %xmm2
    subss   %xmm5, %xmm1
    subss   %xmm2, %xmm0
    movss   %xmm1, -20(%rsp)
    movss   %xmm0, -16(%rsp)
    movq    -24(%rsp), %xmm0
    movd    -16(%rsp), %xmm1
    ret

所以组装是相同的。但我同意将其存储为静态数组(即 a float m_data[3])会更实用,因为我可以两全其美:在需要时传递一个参数,以及一个高级的惯用x, y,z通过吸气剂。从这个意义上说,我相信我会以类似于以下方式实现这样的类:

class MyPoint3S {
 public:
  MyPoint3S(float x, float y, float z)
      : m_data{x, y, z} { }

  // the following getters will be inlined
  float x() const {
    return m_data[0];
  }

  float y() const {
    return m_data[1];
  }

  float z() const {
    return m_data[2];
  }

  // in case you want to use the pointer --- some would advice against
  // offering a hook to a private member.
  float* data() {
    return m_data;
  }

 private:
  float m_data[3];
};

并像这样使用它:

MyPoint3S p(1.0f, 2.0f, 3.0f);
std::cout<<"p = "<<p.x()<<", "<<p.y()<<", "<<p.z()<<std::endl;

获得:

p = 1, 2, 3

或者以您喜欢的任何方式调用 OpenGL 函数:

glVertex3fv(p.data());

或者

glVertex3f(p.x(), p.y(), p.z());
于 2013-11-14T21:31:15.580 回答
2

使用工会怎么样?使用结构,现在您可以通过两种方式访问​​它们。在性能方面,它不应该与一个体面的编译器有任何区别。

struct Point3F {
union {
  float data[3];
  struct {float x, float y, float z};
}
};

Point3F a;
a.x = 21;
a.data[1] == 42;
assert(a.data[0] == 21); // Same data
assert(a.y == 42); // Same data
于 2013-11-14T21:47:57.473 回答
1

我自己找到了答案。摘自openGL的红皮书(可以在这里阅读):

在某些机器上,glVertex*() 的向量形式更有效,因为只需要将一个参数传递给图形子系统,特殊硬件可能能够在一个批次中发送一系列坐标。如果您的机器是这样的,那么安排您的数据以便顶点坐标按顺序打包在内存中对您有利。

但是我仍然想知道哪个更好,例如对于三角形类:一个点数组或每个点一个接一个。同样的问题可以扩展到更复杂的类型。

于 2013-11-14T21:17:10.367 回答
0

我认为第一个需要更多的运行时内存,另一个需要更多的 cpu 使用率。

这两个类具有完全相同的存储大小。如果您的编译器说floats 是 4 个字节,那么整个班级总共有 12 个字节。第二个示例将它们打包为单个内存块,其中第一个示例将它们视为单独的 4 字节块。

就效率而言,这些都是恒定时间访问。不要紧。

我认为你应该使用第一个。point.x比 更具描述性point.coord[0]

于 2013-11-14T20:33:42.957 回答