【问题标题】:Given an array of ints, what is the most efficient way to split the array down the middle into two arrays of shorts?给定一个整数数组,将数组从中间分成两个短裤数组的最有效方法是什么?
【发布时间】:2011-09-26 02:21:16
【问题描述】:

这让我震惊,一定有一个聪明的方法来做到这一点。这不适用于家庭作业、工作或任何事情。我只是在研究一种数据交错的文件格式。

所以,在通用 C/C++ 中,(或其他)给定一些数组

int x[] = ...

有没有聪明的方法把它分成两个短数组

short sa1[], sa2[]

使得 int 数组从中间拆分

x[i] = 1111111111111111 1111111111111111
             sa1[i]         sa2[i]

编辑:如果措辞不好,请见谅。对于 int 数组的每个第 i 个元素,最左边的 16 位成为一个数组的第 i 个元素,最右边的 16 位成为第二个数组的第 i 个元素。

给定

x[i] = 0001111111111111 1111111100011111

然后

sa1[i] = 0001111111111111
sa2[i] = 1111111100011111

我正在寻找不遍历每个元素并移动和掩盖每个元素的非显而易见的答案。这很容易:)

【问题讨论】:

  • 在通用 C/C++ 中?还是在某个特定平台上?
  • @David Schwartz - 通用 C/C++。没关系。
  • 您想避免复制吗?让short数组和int数组占用同样的内存?
  • 显而易见的答案(线性迭代)是缓存友好的,并且只触及每个元素一次。它可以调整或展开,但如果不并行,则没有太大的改进空间。

标签: c++ c arrays algorithm


【解决方案1】:

有很多方法可以做到这一点:

假设:

  1. short 是 16 位。
  2. int 是 32 位。

方法一:(一个简单的循环)

for (int i = 0; i < size; i++){
    int tmp = x[i];
    sa1[i] = (tmp      ) & 0xffff;
    sa2[i] = (tmp >> 16) & 0xffff;
}

方法二:SSE2

for (int i = 0; i < size / 8; i++){
    __m128i a0 = ((__m128i*)x)[2*i + 0];
    __m128i a1 = ((__m128i*)x)[2*i + 1];

    a0 = _mm_shufflelo_epi16(a0,216);
    a1 = _mm_shufflelo_epi16(a1,216);
    a0 = _mm_shufflehi_epi16(a0,216);
    a1 = _mm_shufflehi_epi16(a1,216);
    a0 = _mm_shuffle_epi32(a0,216);
    a1 = _mm_shuffle_epi32(a1,216);

    ((__m128i*)sa1)[i] = _mm_unpacklo_epi64(a0,a1);
    ((__m128i*)sa2)[i] = _mm_unpackhi_epi64(a0,a1);
}

如果循环进一步展开,最后一个示例会非常快。如果这可以击败所有字节操作库,我不会感到惊讶。

但是,它有以下限制:

  1. 数据必须对齐到 16 个字节。
  2. 迭代次数必须能被 8 整除。
  3. 它需要 SSE2。

前两个可以通过清理代码来解决。这很乱,但如果你真的渴望表现,那可能是值得的。

编辑:

是的,这违反了严格别名,但如果不这样做,几乎不可能使用 SSE 内在函数。

【讨论】:

    【解决方案2】:

    如果 int 恰好是您平台上的两个shorts,您可以只将reinterpret_cast int 数组转换为short 数组,然后取偶数/奇数元素。

    但请注意,intshort 的大小不能保证(short 除外,不能大于 int)。例如int 可能等于short 或者可能超过2 个shorts。即使int绝对大小也无法保证(典型大小为 4 和 8 字节)。

    对于真正可移植的解决方案,最好将您尝试解释的文件的确切格式映射到bit fields

    【讨论】:

    • 这违反了严格的别名规则,所以这样做会调用未定义的行为。除了鼻恶魔,这可能是一个问题,具体取决于它的使用方式。
    • @DennisZickefoose 你能澄清一下吗?比如说,如果int 是 4 个字节,short 是 2,那会“违反严格的别名规则”吗?
    • 简而言之,一个对象不能通过指向除实际类型之外的任何东西的指针来访问。有一些例外,例如char* 和基类,但这不是其中之一。它可能会起作用,但作为未定义的行为,编译器仍然会产生意想不到的结果,尤其是在优化时。
    【解决方案3】:

    您需要知道 x 的长度,但您可以执行以下操作:

    #include <stdio.h>
    
    int main(int argc, char * argv[])
    {
        int x[] = {1, 2, 3, 4, 5, 6};
        int xlen = 6;
    
        short * a = &x[0];
        short * b = &x[xlen/2];
    
        printf("%d\n%d\n", a[0], b[0]);
    }
    

    a 指向原始int 数组的开头,并允许您以short 为增量进行索引。与b 处理相同,只是它从原始int 数组的中间开始。

    我认为这很聪明,但你会从 gcc 收到关于指针类型不兼容的警告。这是我在嵌入式环境中可能会做的事情,但我会确保它不会在控制较少的环境中导致任何安全问题。

    【讨论】:

    • 警告是因为你省略了演员表。在 C++ 中,您应该使用 reinterpret_cast。这也不是问题要问的。
    • 原来的问题似乎不清楚。 “交错”与“将其拆分为两个短数组的巧妙方法,以便将 int 数组从中间拆分”。我觉得我回答的是后者,但你是对的,我的解决方案与交错数据无关。
    • 对不起,如果我没有很好地表达我的问题,但这不是我的意思。我的意思是每个元素最左边的 16 位成为一个数组的元素,最右边的 16 位成为第二个数组的元素。请参阅神秘主义者的回答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-19
    • 1970-01-01
    • 2019-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多