Faster vs Smaller

Hello,

I was wondering. I am a bit curious about this code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

int main (void) {

	/* faster */
	struct big {
		int x;
		int y;
		int z;
	};

	/* smaller */
	struct small {
		short unsigned int x :4;
		short unsigned int y :4;
		short unsigned int z :4;
	};
	
	std::cout << sizeof(big) << std::endl;
	std::cout << sizeof(small) << std::endl;

	std::cin.get();

	return 0;
}


Result:
1
2
12
2


Being the curious person I am, I was thinking about ways that I would optimize information that I would send over the network. I looked into 4-bit integers. I was thinking that 4 bit integers are smaller to send over the network, but integers are faster for the computer to work with.

I guess my question is: Is this worth doing? Would this impact the speed that information travels on my network? (If I am sending a lot of information. My internet up-speed is terrible.)

OR am I just wasting my and everyone's time?

Thanks!
Last edited on
> I guess my question is: Is this worth doing?

Depends. Unless you marshall the bits, it is not portable across C++ implementations.


> Would this impact the speed that information travels on my network?

The network bandwidth will remain the same. But if the payload (amount of data) that is sent is smaller, it will take less time; as for example sending files in compressed form. It will have a measurable impact on network performance only if the total number of bytes transmitted at one time is not very small.


> I am sending a lot of information. My internet up-speed is terrible.

There may be other alternatives depending on your context. For instance, if periodic status updates are being transmitted, it may be possible to reduce the payload by transmitting only the delta (status information that has changed since the last send).
Thanks for the reply, very informative!

I am going to make sure my programs only send the changes.

I was wondering, what do you mean by "marshall the bits"?

I added this code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	small network;
	network.x = 10;
	network.y = 9;
	network.z = 8;

	big uncompressed;
	uncompressed.x = network.x;
	uncompressed.y = network.y;
	uncompressed.z = network.z;

	std::cout << "Network:" << std::endl;
	std::cout << "x: " << network.x << std::endl;
	std::cout << "y: " << network.y << std::endl;
	std::cout << "z: " << network.z << std::endl;
	std::cout << "Total Size:" << sizeof(network) << std::endl;
	std::cout << std::endl;

	std::cout << "Uncompressed:" << std::endl;
	std::cout << "x: " << uncompressed.x << std::endl;
	std::cout << "y: " << uncompressed.y << std::endl;
	std::cout << "z: " << uncompressed.z << std::endl;
	std::cout << "Total Size:" << sizeof(uncompressed) << std::endl;
	std::cout << std::endl;


Ouput:
1
2
3
4
5
6
7
8
9
10
11
Network:
x: 10
y: 9
z: 8
Total Size:2

Uncompressed:
x: 10
y: 9
z: 8
Total Size:12


It works in Visual Studio, I haven't had the chance to test it on Linux with G++ yet.
I was wondering, what do you mean by "marshall the bits"?
It means that you do the bit twiddling yourself rather than letting the compiler do it for you with bit fields, as you're doing above. For example,

1
2
3
4
5
6
7
8
9
10
11
//TODO: Change to a type with a well-defined size, such as boost::uint32_t, instead of just 'unsigned'.

inline unsigned store_triple(unsigned x, unsigned y, unsigned x){
    return ((x&0xF)<<8)|((y&0xF)<<4)|(z&0xF);
}

inline void retrieve_triple(unsigned &x, unsigned &y, unsigned &z, unsigned src){
    x=(src>>8)&0xF;
    y=(src>>4)&0xF;
    z=src&0xF;
}
> I was wondering, what do you mean by "marshall the bits"?

Converting the in-memory representation into a representation that can be transmitted over a network; such that it can be unmarshalled at the other end to create the logically equivalent the in-memory representation on that platform. Size, ordering of fields, padding and alignment, endianness etc. could be different across implementations; marshalling is a way transfer data in a consistent manner across across different implementations.

Some (not all) of these issues are addressed in this thread:
http://www.cplusplus.com/forum/beginner/100547/

Consider using this approach first:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <cstdint>

int main()
{
    struct big
    {
        int x, y, z ;
    };

    struct small
    {
        using byte = unsigned char ;
        byte x, y, z ;
    };

    std::cout << sizeof(big) << '\n' // 12
              << sizeof(small) << '\n' ; // 3
}


Assuming that std::uint8_t is available at both ends (in practice, it is) and the representation of std::uint8_t is the same at both ends (again, in practice, it is), this elides the whole issue of marshalling, at the cost of one extra byte. struct small can be sent across the wire in an unmarshalled form.
Sorry for taking a while to reply. I had to teach myself bitwise. For some reason or other, I was completely unaware of its existence. I don't think many C++ tutorials that I have read included it.

It means that you do the bit twiddling yourself rather than letting the compiler do it for you with bit fields, as you're doing above. For example,
1
2
3
4
5
6
7
8
9
10
11
//TODO: Change to a type with a well-defined size, such as boost::uint32_t, instead of just 'unsigned'.

inline unsigned store_triple(unsigned x, unsigned y, unsigned x){
    return ((x&0xF)<<8)|((y&0xF)<<4)|(z&0xF);
}

inline void retrieve_triple(unsigned &x, unsigned &y, unsigned &z, unsigned src){
    x=(src>>8)&0xF;
    y=(src>>4)&0xF;
    z=src&0xF;
}



Okay, I think that I understand. This code is storing unsigned integers x, y, and z into and unsigned integer.

Does &0xF mean that it is only taking the lower 4 bits from a byte? So would &0xF0 take the upper 4 bits from a byte? And &0xFF take an entire byte? (That is what I could gather from reading the internet and playing around with HEX to Binary convertors.)

Last edited on
Converting the in-memory representation into a representation that can be transmitted over a network; such that it can be unmarshalled at the other end to create the logically equivalent the in-memory representation on that platform. Size, ordering of fields, padding and alignment, endianness etc. could be different across implementations; marshalling is a way transfer data in a consistent manner across across different implementations.


Thanks for telling me about marshalling, there was a huge part of C++ that I didn't know about, because I didn't know about marshalling. Sorry for not responding quickly, it took me a day to learn it.

I am considering your resolution, because I don't think 1 byte is that huge a deal. It is also simple.

Last edited on
Does &0xF mean that it is only taking the lower 4 bits from a byte? So would &0xF0 take the upper 4 bits from a byte? And &0xFF take an entire byte?
More or less. Let xxxxxxxx be a string of random bits:
1
2
3
xxxx xxxx &
0000 1111 =
0000 xxxx

Also:
1
2
3
xxxx xxxx |
0000 1111 =
xxxx 1111
Last edited on
Topic archived. No new replies allowed.