1

Ok, I want to create the class Point3f, which is a class of points consist in 3D float coordinates.

So, I have two trivial ways to define the attributes of the class:

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

or

class Point3f
{
    float coord[3];
}

I want to know which is more (generally) efficient, especially for a graphics program. I.E. which is better for drawing the point: glVertex3f() or glVertex3v()?
I don't know if I'm right. I think the first need more run-time memory and the other need more cpu usage.

Edit: And what about if we are talking about more complex types like a triangle consist in 3 points or a tetrahedron consist in 3 triangles -> Attributes: Array or one by one?

Please, tell me which is more efficient and why!

Akronix
  • 1,858
  • 2
  • 19
  • 32
  • 1
    The usual approach for a vector class is to use a union between an array of real (floating-point) scalars and an anonymous structure. Something like: `class Point3f { union { struct { float x, y, z; }; float v[3]; }};` – Andon M. Coleman Nov 14 '13 at 22:37

4 Answers4

2

You can compare the assembly generated for basic functions (I used GCC 4.8.1 on OS X 10.7.4)

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};
}

Compiling as g++ -O3 -S I obtain the following assembler (relevant parts):

# 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

So the assembly is identical. But I agree that it would be more practical to store as an static array (that is, a float m_data[3]) because I can have the best of both worlds: a single parameter to pass when I need to, and a high-level, idiomatic x, y, z via getters. In that sense, I believe I would implement such class in a manner similar to:

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];
};

And use it like:

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

to obtain:

p = 1, 2, 3

Or call OpenGL functions in whatever way you prefer:

glVertex3fv(p.data());

or

glVertex3f(p.x(), p.y(), p.z());
Escualo
  • 40,844
  • 23
  • 87
  • 135
  • Very good answer. Thank you for researching this case translating to assembler, I find it so interesting. I think your proposition is reasonable, also knowing glVertexv* is usually more efficient as I have posted bellow, and it's easier than using unions. – Akronix Nov 15 '13 at 18:24
2

How about using a union? Use a struct and now you can access them both ways. Performance-wise it shouldn't make any difference with a decent compiler.

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
Viktor Sehr
  • 12,825
  • 5
  • 58
  • 90
1

I've found the answer by myself. Extracted from the red-book of openGL (It can be read here):

On some machines, the vector form of glVertex*() is more efficient, since only a single parameter needs to be passed to the graphics subsystem, and special hardware might be able to send a whole series of coordinates in a single batch. If your machine is like this, it's to your advantage to arrange your data so that the vertex coordinates are packed sequentially in memory.

But I'm still wondering which is better, for example, for triangle class: one array of points or each individual point one by one. The same question can be extended to more complex types.

Akronix
  • 1,858
  • 2
  • 19
  • 32
0

I think the first need more run-time memory and the other need more cpu usage.

Both classes have the exact same storage size. If your compiler says floats are 4 bytes, then you have 12 bytes total for your whole class. The second example packs them together as a single block of memory, where the first example treats them as individual 4 byte chunks.

As far as efficiency, these are all constant time access. It does not matter.

I think you should use the first one. point.x is more descriptive than point.coord[0].

yamafontes
  • 5,552
  • 1
  • 18
  • 18