将多个 int 值存储到一个变量中 - C++

Posted

技术标签:

【中文标题】将多个 int 值存储到一个变量中 - C++【英文标题】:Storing multiple int values into one variable - C++ 【发布时间】:2021-02-28 14:06:56 【问题描述】:

我正在参加算法竞赛,并且正在尝试优化我的代码。也许我想做的是愚蠢和不可能的,但我想知道。

我有这些要求:

可以包含 4 种不同类型的物品的库存。此库存不能包含超过 10 件商品(包括所有类型)。有效库存示例:1 / 1 / 1 / 0。无效库存示例:11 / 0 / 0 / 0 或 5 / 5 / 5 / 0 我有一些收据可以消耗或添加物品到我的库存中。这 recepe 不能添加或消费超过 10 个项目,因为库存 不能超过 10 个项目。有效收据示例:-1 / -2 / 3 / 0. 无效recepe示例:-6 / -6 / +12 / 0

现在,我将库存和收据存储为 4 个整数。然后我可以执行一些操作,例如:

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

我想知道是否可以(如果可以,如何)将库存/收据的 4 个值存储到一个整数中,并对其执行比比较/添加 4 个更快的操作我现在正在做的整数。

我想到了类似这样的库存:

int32: XXXX(第一类项数)-YYYY(第二类项数)-ZZZ(第三类项数)-WWW(第四类项数)

但我有两个问题:

    我不知道如何处理可能的负值 在我看来,这比仅添加 4 个整数要慢得多,因为我必须对库存和收据进行移位以获得我想要的值,然后继续添加。

【问题讨论】:

像这样的位打包很少会产生更快的代码。为什么不只是四个独立的int8_ts? 您正在寻找 矢量化 又名 array programming. 这被称为SIMD,尽管您的用例似乎太简单而无法真正利用它。 bitpacking 主要是为了存储,一旦你要对它进行计算,你大多需要解压它。 仅供参考:我在SO: Atomic operations - C 中提出了类似的想法,但出于不同的原因:使多个计数器的修改成为原子。 【参考方案1】:

特别是如果您正在学习,那么尝试为vectorization 实现您自己的帮助器类并不是一个糟糕的机会,从而加深您对 C++ 中数据的理解,即使您的用例可能不支持该技术。

您想要利用的洞察力是算术运算似乎对位移是不变的,如果考虑到讨厌的进位和标志的影响(例如二进制补码)。但正是由于后面的这些因素,正如@Botje 所建议的那样,使用一些标准化的底层类型(如int8_t[])要好得多。

首先,实现以下功能。 (我的 C++ 生锈了,考虑一下这个伪代码。)

int8_t* add(int8_t[], int8_t[], size_t);
int8_t* multiply(int8_t[], int8_t[], size_t);
int8_t* zeroes(size_t); // additive identity
int8_t* ones(size_t); // multiplicative identity

同时考虑:

您希望如何处理上溢和下溢?让他们成为并要求开发人员谨慎吗?还是抛出异常? 也许您想确定数组的大小并避免处理动态 size_t? 也许您想要重载运算符?

这样的练习的最终结果,但概括和完善,类似于Armadillo。但是,通过首先自己进行练习,您会在完全不同的层面上理解它。此外,如果到目前为止所有这些都有意义,您现在可以查看 How to vectorize my loop with g++? — 在某些情况下,甚至编译器也可以为您进行矢量化。

@Botje 提到的 Bitpacking 是除此之外的另一个步骤。您甚至不会拥有像 int8_t 或 int4_t 这样的整数类型的安全性和便利性。这也意味着您编写的代码可能不再独立于平台。我建议在深入研究之前至少完成矢量化练习。

【讨论】:

【参考方案2】:

将多个 int 值存储到一个变量中

这里有两种选择:

一个数组。这样做的好处是您可以迭代元素:

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

或者一堂课。这样做的好处是可以命名成员:

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

然后我可以执行一些操作,例如:

那些看起来像 SIMD 向量操作(单指令多数据)。在这种情况下,数组是要走的路。由于在您的描述中操作数的数量似乎是恒定的并且很小,因此执行它们的有效方法是在 CPU 1 上进行向量操作。

在 C++ 中没有直接使用 SIMD 操作的标准方法。为了让编译器有最佳机会使用它们,需要遵循以下步骤:

确保您使用的 CPU 支持您需要的操作。 AVX-2 指令集及其扩展广泛支持整数向量运算。 确保告诉编译器程序应该针对该架构进行优化。 确保告诉编译器执行矢量化优化。 确保整数按照操作要求充分对齐。这可以通过alignas 来实现。 确保在编译时知道整数个数。

如果您担心依赖优化器的前景,那么您可能更愿意使用编译器可能提供的向量扩展。语言扩展的使用自然会以移植到其他编译器为代价。以下是 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如果操作数的数量很大,那么专用的矢量处理器(例如 GPU)可能会更快。

【讨论】:

【参考方案3】:

这将是一个非答案,只是为了说明如果你进行 bitpacking 会遇到什么问题。

为简单起见,假设配方只能从库存中移除,并且只包含正值(您可以使用二进制补码表示负数,但它会占用更多位,并且会增加使用位压缩的复杂性数字)。

然后,您有 11 个可能的项目值,因此每个项目需要 4 位。然后可以在一个 uint16 中表示四个项目。

因此,假设您的库存有 10、4、6、9 件物品;这将是uint16_t inv = 0b1010'0100'0110'1001

然后,一个包含 2,2,2,2 项的食谱或uint16_t rec = 0b0010'0010'0010'0010

inv - rec 将为 8,2,4,7 项提供 0b1000'0010'0100'0111

到目前为止,一切都很好。在进行计算之前,无需在此处移动和屏蔽以获取各个值。耶。

现在,一个包含 6,6,6,6 项的配方,即 0b0110'0110'0110'0110,给出 inv - rec = 0b0011'1110'0000'0011 3,14,0,3 项。

糟糕。

算术将起作用,但如果您事先检查单个 4 位结果没有超出范围;在此示例中,这意味着您事先知道库存中有足够的物品来填充配方。

例如,您可以通过以下方式获得库存中的第三项:(inv &gt;&gt; 4) &amp; 0b1111(inv &lt;&lt; 8) &gt;&gt; 12 进行检查。

为了测试,你会得到如下表达式:

if ((inv >> 4) & 0b1111 >= (rec >> 4) & 0b1111)

或者,比较“就地”的 4 位:

if (inv & 0b0000000011110000 >= rec & 0b0000000011110000)

每个 4 位部分。

所有这些事情都是可行的,但你想要吗?在编译器完成其工作后,它可能不会比其他答案中建议的更快,而且它肯定不会更具可读性。

当你在配方中允许负数(二进制补码或其他)时,它变得更加可怕,特别是如果你想对它们进行位移。

因此,位打包非常适合存储,在极少数情况下,您甚至可以在不解包位的情况下进行数学运算,但我不会尝试去那里(除非您的性能和内存非常受限)。

话虽如此,尝试让它工作可能会很有趣;总是这样。

【讨论】:

"0b 1010 0100 0110 1001 (spaces for clarity).",C++14 允许在文字中使用引号:0b1010'0100'0110'1001

以上是关于将多个 int 值存储到一个变量中 - C++的主要内容,如果未能解决你的问题,请参考以下文章

我需要将值存储到int类型的变量,但int是不够的?

将 for 循环中的多个打印输出值存储到列表或变量中

在一个变量 sql 中接收多个值

c++可以用return语句返回两个变量么

C#中的解构

C++变量和常量