mixed integer/float ops slower?

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

camsr wrote:If I were to instead declare a bitfield on byte boundaries, would this be faster? Assuming the processing of the values are the same, just having the CPU address a contiguous byte results in less overhead?
How it fetches your bitfield whether on a byte boundary or not depends on the efficiency of your compiler. Placing an 8 bit bitfield on a byte boundary does not guarantee it will be accessed that way. Also, which bit in a bitfield is bit 0 is up to the compiler. Most will allow you to set it. This is not related to the endian of the CPU. And if you ever move your app from the Intel family, those semi-free accesses to unaligned data are going away.

In x64 mode, you shouldn't be that tight on memory. I'd burn memory for speed and take the fixed point conversion out of bitfields.

Cheers

Post

MadBrain wrote:
ChocoBilly wrote: Since you seem to be comparing on Intel architecture, code runs differently on AMD vs. Intel. Important when building for speed. You may need 2 versions to run as fast as possible. You would need to do this even if you wrote entirely in assembly.
This makes sense if you're writing for in-order CPUs such as the Arm A8, where if you read the result of a calculation too soon, the entire pipeline will stall (so you want to have the cpu cycle latency manual with you as you code). But on crazy out-of-order CPUs like all the P2 and Athlon derivatives, I'm not sure this really makes sense - one of the CPU resources will max out, but it can be hard to tell which one, and it varies from version to version of the same chip as they add or remove calculation units and cache levels and ports on reorder buffers and whatnot. Maybe you'll see a difference if you use bleeding edge stuff like AVX, but I think the gains you'd see for other specific optimizations will be small.
Not every program has a need for insane levels of speed optimization. And you are quite correct, with all the chip flavors, the number of cases for CPU specific code can be significant but it is done. Most commonly on premier video games for the PC that are graphic intensive. And they would print on the box that the game was optimized for AMD and Intel (often receiving money for doing so). I don't know if it's still printed on the box but it is still done because there is a drive to show they are better than the competition or the previous versions as well as a selling point to end users.

You are also correct about the 80 bit internal floating point but I think I recall reading that in reality using 64 bit floats was still faster and yes, both must go through similar conversion. I can think of operations that would be faster in 32 bit but I don't know if that applies to Intel such as 32 bit Vector4 operations. Usually, the only way to find out for certain is try it and see because too often the results don't match what you read in the specs.

Cheers

Post

ChocoBilly wrote:
camsr wrote:If I were to instead declare a bitfield on byte boundaries, would this be faster? Assuming the processing of the values are the same, just having the CPU address a contiguous byte results in less overhead?
How it fetches your bitfield whether on a byte boundary or not depends on the efficiency of your compiler. Placing an 8 bit bitfield on a byte boundary does not guarantee it will be accessed that way. Also, which bit in a bitfield is bit 0 is up to the compiler. Most will allow you to set it. This is not related to the endian of the CPU. And if you ever move your app from the Intel family, those semi-free accesses to unaligned data are going away.

In x64 mode, you shouldn't be that tight on memory. I'd burn memory for speed and take the fixed point conversion out of bitfields.

Cheers
It's not tight on memory at all. It's distorting the float by setting bits that represent the internal math of the FPU.

About the endian issue, it's been a long time since I read something along the lines of what you are saying. It is either the compiler or the CPU that determined the LSB or MSB first. I can't remember which, but my plugin hasn't had problems on Intel or AMD cpus.

Post

camsr wrote: About the endian issue, it's been a long time since I read something along the lines of what you are saying. It is either the compiler or the CPU that determined the LSB or MSB first. I can't remember which, but my plugin hasn't had problems on Intel or AMD cpus.
Because Intel and AMD are currently all use the Intel x86 architecture which happens to be little endian. However if endianess changes also the in memory layout of the IEEE floats may change and you are screwed anyway.

But I still don't think you should ever want to do dum() because:

1. It makes a lot of assumptions about undefined and implementation defined behavior of C, namely the following two:
1: ISO/IEC 9899:201x 5.2.4.2.1 Sizes of integer types <limits.h>
sizeof(unsigned) >= 8, so it could happen that sizeof(unsigned) == 16. Use uint32_t instead.
2: ISO/IEC 9899:201x 6.7.2.1 Structure and union specifiers "An implementation may allocate any addressable storage unit large enough to hold a bit-field. [...] The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined."

2. What if "f.e--" underflows? That floating point number gets huge, no? But maybe that is desired ... but then dummer() does not exhibit that "feature".

Anyway don't know whether it is fast but you could try:

Code: Select all

#include <assert.h>
#include <stdint.h>

void evendummer ()
{
    #error Please check this code! It makes many assumption! Once you checked it you may remove this error and compile it.

    // or make this static by cpp assertion
    assert(sizeof(float)==sizeof(uint32_t));

    unsigned i;
    union {
        float x;
        uint32_t i;
    } f;

    f.x = 1.f;

    for (i=0; i<80000000; i++)
    {
    array[i] = f.x;
    }

    for (i=0; i<80000000; i++)
    {
    f.x = array[i];
    #if 1
    f.i -= (1<<23); // on under flow we are f**ked anyway so we don't restore the sign
    #else
    f.i = ((f.i-(1<<23))&0x7fffffff) | (f.i & 0x80000000); // we preserve the sign ... though fmul will likely be faster than this
    #endif
    array[i] = f.x;
    }
} 

EDIT: Also loading to a float then doing a union cast likely confuses the compiler and you could just load directly to the uint32_t via "f.i = *(uint32_t*)&(array);" ... but again that all depends on the compiler and is in my opinion irrelevant because one should use dummer() anyway as it conveys the essence of the code better is faster and is more portable ... unless scaling by 0.5 is not the essence of the code, but the essence really is bit manipulation inside a float.

Post

Okay, doing that looks faster, but I will need to test it. The difference as I see it is using the bitfield allows simple float/int math to determine the distortion algos, whereas using the int will require some preprocessing and possibly more cache usage as complexity increases. Masking is also required to separate the exponent and mantissa. Thanks a ton ro.cking!

Underflows aren't a concern as using if statements is fast enough on modern CPUs.

Post

The dumer() function is just a benchmark. Using the ALU should allow more ops overall.

Post

So far, using the int method has shown about 7% increase in speed. When combining a float op into the reassignment to the array, the int method showed the same increase, maybe faster. This was unoptimized in GCC. When I set -O3, dumer() did not profile but was executed, dum() ran 2.1x faster. At -O1, dumer() was slightly faster, with total program time the same as -O3.

EDIT: dum() was used with the unsigned/float union as ro.cking showed.

Post Reply

Return to “DSP and Plugin Development”