Search code examples
c++bit-shift

Storing multiple int values into one variable - C++


I am doing an algorithmic contest, and I'm trying to optimize my code. Maybe what I want to do is stupid and impossible but I was wondering.

I have these requirements:

  • An inventory which can contains 4 distinct types of item. This inventory can't contain more than 10 items (all type included). Example of valid inventory: 1 / 1 / 1 / 0. Example of invalid inventories: 11 / 0 / 0 / 0 or 5 / 5 / 5 / 0
  • I have some recipe which consumes or adds items into my inventory. The recipe can't add or consume more than 10 items since the inventory can't have more than 10 items. Example of valid recipe: -1 / -2 / 3 / 0. Example of invalid recipe: -6 / -6 / +12 / 0

For now, I store the inventory and the recipe into 4 integers. Then I am able to perform some operations like:

  • ApplyRecepe: Inventory(1/1/1/0).Apply(Recepe(-1/1/0/0)) = Inventory(0/2/1/0)
  • CanAfford: Iventory(1/1/0/0).CanAfford(Recepe(-2/1/0/0)) = False

I would like to know if it is possible (and if yes, how) to store the 4 values of an inventory/recipe into one single integer and to performs previous operations on it that would be faster than comparing / adding the 4 integers as I'm doing now.

I thought of something like having the inventory like that:

int32: XXXX (number of items of the first type) - YYYY (number of items of the second type) - ZZZ (number of items of the third type) - WWW (number of item of the fourth type)

But I have two problems with that:

  1. I don't know how to handle the possible negative values
  2. It seems to me much slower than just adding the 4 integers since I have to bit shift the inventory and the recipe to get the value I want and then proceed with the addition.

Solution

  • Storing multiple int values into one variable

    Here are two alternatives:

    An array. The advantage of this is that you may iterate over the elements:

    int variable[] {
        1,
        1,
        1,
        0,
    };
    

    Or a class. The advantage of this is the ability to name the members:

    struct {
        int X;
        int Y;
        int Z;
        int W;
    } variable {
        1,
        1,
        1,
        0,
    };
    

    Then I am able to perform some operations like:

    Those look like SIMD vector operations (Single Instruction Multiple Data). The array is the way to go in this case. Since the number of operands appears to be constant and small in your description, an efficient way to perform them are vector operations on the CPU 1.

    There is no standard way to use SIMD operations directly in C++. To give the compiler optimal opportunity to use them, these steps need to be followed:

    • Make sure that the CPU you use supports the operations that you need. AVX-2 instruction set and its expansions have wide support for integer vector operations.
    • Make sure that you tell the compiler that the program should be optimised for that architecture.
    • Make sure to tell the compiler to perform vectorisation optimisations.
    • Make sure that the integers are sufficiently aligned as required by the operations. This can be achieved with alignas.
    • Make sure that the number of integers is known at compile time.

    If the prospect of relying on the optimiser worries you, then you may instead prefer to use vector extensions that may be provided by your compiler. The use of language extensions would come at the cost of portability to other compilers naturally. Here is an example with GCC:

    constexpr int count = 4;
    using v4si = int __attribute__ ((vector_size (sizeof(int) * count)));
    
    #include <iostream>
    
    int main()
    {
        v4si inventory { 1, 1, 1, 0};
        v4si recepe    {-1, 1, 0, 0};
    
        v4si applied = inventory + recepe;
    
        for (int i = 0; i < count; i++) {
            std::cout << applied[i] << ", ";
        }
    }
    

    1 If the number of operands were large, then specialised vector processor such as a GPU could be faster.