🔒 Locked
data alignment problem.
Advertisement
Original Post
when I define float x, y, z, w; and observe their addresses, I see the 4 float are not placed one after another, instead their addresses are 12 bytes apart. however if I define float f[4]; and observe f[0] ~ f[3]'s addresses, their addresses are 4 bytes apart which means they're placed next to each other. is there a way to force the complier to place the 4 seperate floats together? also, with the first definition, x, y, z, w's addresses decreases; with the second definition, f[0] ~ f[3]'s addresses increase. why's that? P.S. I haven't studied complier theory much yet, so forgive me if my questions are very easy to answer after studying that.
When you define variables you have very little power over where in memory they get stuck. This isn't so much the compiler as it is OS. Windows, for example, tells you where things go. It's seldom the other way around...unless you want something very bad to happen. Just trust when you define a number of variables that they're *somewhere* in memory, and be grateful that we have random-access with equal seektimes all around. =)
That said, you can lump them together if you *really* want to. Combine the four of those floats into a record. Or make yourself a four float array...which is really your option, I think, because why would you want to have them all localized together in memory unless you wanted to refer to them relative to each other?
What is your goal?
That said, you can lump them together if you *really* want to. Combine the four of those floats into a record. Or make yourself a four float array...which is really your option, I think, because why would you want to have them all localized together in memory unless you wanted to refer to them relative to each other?
What is your goal?
Hi
I'm trying to use SSE instructions here coz they require the operands to be 16 byte aligned. any suggestion?
I'm trying to use SSE instructions here coz they require the operands to be 16 byte aligned. any suggestion?
Wow, okay. =)
I'm afraid I don't know enough about assembly to answer that question...let alone how you combine inline with c++ code in that way. I am now really curious to see the answer though.
Here's a fair write-up on the SSE instruction set I found via google: link. Might have something for you there. I presume that you're trying to use some SSE instruction(s) to move those four floats into a register (or registers)...something like a block-move is my guess. (I'll read more over this page myself, but I really don't think I'll find a solution for you.)
That said, I hope someone who knows more than I do speaks up. For both our sakes. =) Good luck.
[EDIT] First thing I read, "data movement instructions."
Make them an array or record, then reference the address of the beginning of the array or record. Try that.
I'm afraid I don't know enough about assembly to answer that question...let alone how you combine inline with c++ code in that way. I am now really curious to see the answer though.
Here's a fair write-up on the SSE instruction set I found via google: link. Might have something for you there. I presume that you're trying to use some SSE instruction(s) to move those four floats into a register (or registers)...something like a block-move is my guess. (I'll read more over this page myself, but I really don't think I'll find a solution for you.)
That said, I hope someone who knows more than I do speaks up. For both our sakes. =) Good luck.
[EDIT] First thing I read, "data movement instructions."
Make them an array or record, then reference the address of the beginning of the array or record. Try that.
Here are two possible solutions to your problem: structs and pointers!
#include <stdio.h>#include <stdlib.h>int main() { //Solution 1 typedef struct { float x; float y; float z; float w; } flt; flt f; printf("%u %u %u\n",&f.x, &f.y, &f.z); //How to use them: f.x = 100; f.y = 101; f.z = 102; f.w = 103; printf("x: %f y: %f z: %f w: %f\n", f.x, f.y, f.z, f.w); //Solution 2 float * floats; floats = (float *) malloc(sizeof(float) * 4); printf("%u %u %u\n",floats, floats + 1, floats + 2); //How to use them: * (floats + 0) = 100; * (floats + 1) = 101; * (floats + 2) = 102; * (floats + 3) = 103; printf("x: %f y: %f z: %f w: %f\n", * (floats + 0), * (floats + 1), * (floats + 2), * (floats + 3)); free(floats); return 0;}Quote:
Original post by digitalfreak
when I define
float x, y, z, w;
and observe their addresses, I see the 4 float are not placed one after another, instead their addresses are 12 bytes apart. however if I define
float f[4];
and observe f[0] ~ f[3]'s addresses, their addresses are 4 bytes apart which means they're placed next to each other.
Did you define those variables on the stack ? That may explain some things.
When you declare an array of four floats, the compiler is forced to put them sizeof(float) bytes apart.
Quote:
is there a way to force the complier to place the 4 seperate floats together?
Define a structure containing the four floats. Did it work ?
Quote:
also, with the first definition, x, y, z, w's addresses decreases; with the second definition, f[0] ~ f[3]'s addresses increase. why's that?
If you create local variables on the stack, this will be the case. Since the stack space is freed automatically on function exit, the compiler aligns every float on a 16 byte boundary as an optimization.
Try to create global variables and see what changes.
Quote:
P.S. I haven't studied complier theory much yet, so forgive me if my questions are very easy to answer after studying that.
Yeah, sorry... by "record" I meant "struct." I should've been more specific. Then maybe I would've been helpful. =) Making them a struct will pack them altogether at one mem address, though I admit I'm not sure if they go in ascending or descending order. Wouldn't be that hard to test for though. Just peek at locations around the address of the struct and see where your values are.
If you make them into an array they'll definately be in ascending order.
[EDIT] I just remembered that records within structs go in ascending order. So there's that for you.
If you make them into an array they'll definately be in ascending order.
[EDIT] I just remembered that records within structs go in ascending order. So there's that for you.
Okay, I've had to fight really hard with this, and I have a solution that works for me. Here's probably the simplest:
I don't do that, so I can't promise anything.
My personal SSE vec4 class is declared:
The __m128 implies 16-byte alignment.
This will allow you to use these as member or global or local variables but it will NOT allow you to pass-by-value (gets pushed on the stack, so can't guarantee alignment).
One consequence of no pass-by-value is that you can't make a std::vector of them, because std::vector::resize() passes an argument by value. So to get around this I copied the vector source into a file called aligned_vector and fixed it up to pass by reference in aligned_vector::resize. That solved the compile problem, but it doesn't guarantee anything about alignment!
In fact, there's nothing you can do to guarantee alignment (that I found). Ultimately, vector calls 'new' to allocate, and 'new' calls 'malloc', and at that point malloc knows nothing of the alignment properties of memory it's allocating for. And no, changing default alignment in your project doesn't do it :b
What I ended up doing is dowloading Doug Lea's malloc lib, compiling with the default alignment #define'd to 16 and writing a custom allocator that uses dl_malloc to allocate 16-byte aligned chunks to store my aligned_vector.
So my typedefs look like:
I'm not saying there isn't an easier way... just that I looked very, very hard and couldn't find one. This thread is a summation of how I finally arrived at this solution :)
__declspec(align(16))struct vec4{ float x, y, z, w;}I don't do that, so I can't promise anything.
My personal SSE vec4 class is declared:
#include <xmmintrin.h>struct vec4{ union { struct { float x, y, z, t; }; __m128 v; }; ...};The __m128 implies 16-byte alignment.
This will allow you to use these as member or global or local variables but it will NOT allow you to pass-by-value (gets pushed on the stack, so can't guarantee alignment).
One consequence of no pass-by-value is that you can't make a std::vector of them, because std::vector::resize() passes an argument by value. So to get around this I copied the vector source into a file called aligned_vector and fixed it up to pass by reference in aligned_vector::resize. That solved the compile problem, but it doesn't guarantee anything about alignment!
In fact, there's nothing you can do to guarantee alignment (that I found). Ultimately, vector calls 'new' to allocate, and 'new' calls 'malloc', and at that point malloc knows nothing of the alignment properties of memory it's allocating for. And no, changing default alignment in your project doesn't do it :b
What I ended up doing is dowloading Doug Lea's malloc lib, compiling with the default alignment #define'd to 16 and writing a custom allocator that uses dl_malloc to allocate 16-byte aligned chunks to store my aligned_vector
So my typedefs look like:
typedef std::aligned_vector<vec4, dl_allocator<vec4> > Vert_list;class Triangle_list{ Vert_list m_tris; ...};I'm not saying there isn't an easier way... just that I looked very, very hard and couldn't find one. This thread is a summation of how I finally arrived at this solution :)
Quote:
This isn't so much the compiler as it is OS.
Nope. The OS may have certain alignment restrictions which if the compiler violates (or an over-optimizing programmer violates) can cause the app to crash but otherwise the compiler can do pretty much anything it wants with locals declared on the stack. There are no guarantees that they'll be next to each other in memory. Depending on your function and optimization level the memory may even get reused so that all the variables have the same address.
If you require your variables to have a certain memory layout then you *must* group them in a higher-level structure (as has been discussed) and even then you're venturing into implementation-dependent territory that isn't necessarily guaranteed to be portable.
hi thanX for all your replies.
what about I just use the movups instruction instead of movaps since it doesn't require alignment? would there be a huge performance drop?
what about I just use the movups instruction instead of movaps since it doesn't require alignment? would there be a huge performance drop?
Data alignment is a huge issue. First you'll notice that you can't pass aligned data structures by value to functions. You'll run into huge problems using STL, and when it comes to class inheritance you will almost never get aligned memory by using the __declspec(align 16) or "#pragma pack" stuff. So if you have a child that derives from a base that has aligned memory in it, your SOL. The only other option is to use dynamic memory because you got good odds of getting aligned memory from the heap.
Using movups instead of movaps pretty much defeats the purpose of SSE. Your probably better off just setting your compiler up to optimize, since they optimize the hell out of floating point calculations already.
If you are doing this just to practice with assembly, go for it, I did the same a while ago, otherwise just forget about it and let your compiler do the hard work.
Hope this helps.
Using movups instead of movaps pretty much defeats the purpose of SSE. Your probably better off just setting your compiler up to optimize, since they optimize the hell out of floating point calculations already.
If you are doing this just to practice with assembly, go for it, I did the same a while ago, otherwise just forget about it and let your compiler do the hard work.
Hope this helps.
Quote:
Original post by digitalfreak
what about I just use the movups instruction instead of movaps since it doesn't require alignment? would there be a huge performance drop?
Yes, it would. It will almost negate all the benefits from using SSE.
As for aligning data. Data on the heap will be aligned automaticaly when using declspec(align(16)). For dynamic allocation - you can overload new operator so that it will return aligned memory. Just as DX CMatrixA16 does (you can look at the code in dxd9math.h). So IMHO, using dynamic memory is not too big problem, even in STL and such.
Quote:
Original post by moeron
First you'll notice that you can't pass aligned data structures by value to functions.
AFAIK, it is not true. But even if, why would you want to? Aligned structures are usually so big, that you would want to pass them as references (aka pointers ;))
Quote:
when it comes to class inheritance you will almost never get aligned memory by using the __declspec(align 16) or "#pragma pack" stuff.
With static declaration of variables, or on the heap, you will. With dynamic, use overloaded operators. #pragma pack is a different subject, IMO not belonging to this thread.
Quote:
If you are doing this just to practice with assembly, go for it, I did the same a while ago, otherwise just forget about it and let your compiler do the hard work.
What's the point in practicing with assembly if you don't want to create anything usefull? Compilers will never be as good as human work. I just don't trust them.
Go for optimizations by hand, people!!!
Cheers.
/def
My main point was making SSE optimizations by hand is not going to be worth the time it takes to implement. There are a lot of issues and its really not that much faster than modern FPU operations, unless you are just crunching vectors non-stop. In most applications its not vector operations that are the bottleneck anyway so your better off optimizing something that is.
Quote:
Original post by moeron
My main point was making SSE optimizations by hand is not going to be worth the time it takes to implement. There are a lot of issues and its really not that much faster than modern FPU operations, unless you are just crunching vectors non-stop. In most applications its not vector operations that are the bottleneck anyway so your better off optimizing something that is.
That is insane. I can't find a single correct thought in that post.
I generally write SSE routines in less time than it takes to write the C version. That's because the C version required all the initial thought and understanding and debugging, and SSE is pretty much a transcription... the challenging part being register economization and scheduling.
As to fpu/SSE and movaps/movups. Maybe an example would help better. I recently wrote a time-corrected Verlet integration routine (thanks lonesock for the paper).
//////////////////////////////////////////////////////////////////////////////////////////// use verlet integration to apply accumulated forces to the current position// for each particle and update the last_pos, last_t, and reset force for the vert data.//////////////////////////////////////////////////////////////////////////////////////////void Cloth::advance_time(float dt){ Vert_iter v = m_verts.begin(); Vert_data_iter d = m_vdata.begin(); for(;v != m_verts.end();++v, ++d) { vec4 v0 = d->last_pos; vec4 v1 = *v; float t0 = d->last_t; float grav = d->grav; d->force *= d->inv_mass; d->force.y += grav; d->force *= dt * dt; // verlet with a time adjusted factor and small damping factor. vec4 v_new = v1 + 0.995f*(v1 - v0) * (dt / t0) + d->force; v_new.clamp(m_min_boundary, m_max_boundary); d->last_pos = v1; *v = v_new; d->last_t = dt; d->force.set_zero(); }}And then, spent about 30 minutes writing the SSE version.
void Cloth::advance_time_SSE(float dt){ unsigned int count = static_cast<unsigned int>(m_verts.size()); const float damp = 0.995f; if(count == 0) return; unsigned int vdata_size = sizeof(Vert_data); vec4* vert = &m_verts[0]; Vert_data* data = &m_vdata[0]; __asm { mov eax, vert mov edx, data mov ecx, [this] mov esi, countstart_loop: movaps xmm0, [edx]Vert_data.last_pos movaps xmm1, [eax] movss xmm7, [edx]Vert_data.last_t movss xmm6, [edx]Vert_data.grav movss xmm5, [edx]Vert_data.inv_mass movss xmm3, dt movaps xmm2, [edx]Vert_data.force subps xmm1, xmm0 // xmm1 = (v1 - v0) movss xmm4, damp rcpss xmm7, xmm7 // xmm7 = 1.0f / t0 shufps xmm4, xmm4, 0 // xmm4 : prepare damp for vector multiply shufps xmm5, xmm5, 0 // xmm5 : prepare inv_mass for vector multiply mulss xmm7, xmm3 // xmm7 = dt / t0 shufps xmm6, xmm6, 11110011b // xmm6 : move grav into y component for vector addition mulss xmm3, xmm3 // xmm3 = dt * dt mulps xmm2, xmm5 // xmm2 : force *= inv_mass shufps xmm7, xmm7, 0 // xmm7 : prepare (dt / t0)for vector multiply shufps xmm3, xmm3, 0 // xmm3 : prepare (dt * dt) for vector multiply mulps xmm1, xmm4 // xmm1 = damp * (v1 - v0) addps xmm2, xmm6 // xmm2 : force += grav movaps xmm5, [eax] mulps xmm1, xmm7 // xmm1 = damp * (v1 - v0) * (dt / t0) mulps xmm2, xmm3 // xmm2 : force *= dt * dt addps xmm1, xmm5 // xmm1 = v1 + ... movaps xmm0, [ecx]Cloth.m_min_boundary movaps xmm3, [ecx]Cloth.m_max_boundary addps xmm1, xmm2 // xmm1 = v1 + damp*(v1 - v0) * (dt/t0) + force xorps xmm7, xmm7 movaps [edx]Vert_data.last_pos, xmm5 minps xmm1, xmm3 // xmm1 : clamp against max movss xmm6, dt movaps [edx]Vert_data.force, xmm7 maxps xmm1, xmm0 // xmm1 : clamp against min movss [edx]Vert_data.last_t, xmm6 movaps [eax], xmm1 add eax, 10h add edx, vdata_size sub esi, 1 jnz start_loop }}And see, the C++ version took 140 cycles, while the SSE version took 42 cycles per iteration. This integration was occupying roughly 8% of my execution time, so the 333% speed-up bumped my frame-rate up 5%. IN A HALF HOUR. And that's not even close to the most vector-crunching of the routines going on.
So, for kicks, I replaced all the movaps with movups. The result: 48 cycles per iteration. So please explain to me how
Quote:
Using movups instead of movaps pretty much defeats the purpose of SSE.
I mean... are you just guessing? Have you actually tried it. Ever?
I wrote a simple vector and matrix library a couple months ago for a project. After some profiling I realized that for simple vector operations (dot/cross products, normaliziation, etc) there wasn't much of a difference between moveups and regular FPU operations. If I had to do hundreds of these per frame, or for matrix multiplications, or any more intensive operations there was a huge advantage to movups over regular FPU.
All I'm saying is it depends on the project whether or not SSE will be useful. I don't understand why this is such a troubling concept to people.
All I'm saying is it depends on the project whether or not SSE will be useful. I don't understand why this is such a troubling concept to people.
Quote:
Original post by ajas95
So, for kicks, I replaced all the movaps with movups. The result: 48 cycles per iteration. So please explain to me howQuote:
Using movups instead of movaps pretty much defeats the purpose of SSE.
I mean... are you just guessing? Have you actually tried it. Ever?
You may be right, but for the vector operations, which are, BTW, subject of this thread, there are plenty of other simple operations that are used more frequently than some huge code parts as yours.
When I was looking at the disassembly of, for example:
v = v1 * f1 + v2 * dot(v3, v4);
or:
v = avrg(v1, v2);
or even:
v = v1;
there were plenty of movaps, at least one third. So then, letting them take twice as much cycles, makes the entire operation much slower.
Cheers.
/def
PS: Many people are talking about this 'verlet integration'. I have no idea what this is, but from my experience, if something shows often enough, I'm ending up finding out that I need it too ;\
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement