从未对齐的uint8_t读取重新创建为uint32_t数组 - 不获取所有值

时间:2016-10-22 15:46:15

标签: cuda alignment memory-alignment

我正在尝试将uint8_t数组转换为uint32_t数组。但是,当我尝试这样做时,我似乎无法访问每个连续的4个字节。

让我们说我有一个8字节的uint8_t数组。我想访问字节2 - > 6作为一个uint32_t。

这些都获得相同的值*((uint32_t*)&uint8Array[0])*((uint32_t*)&uint8Array[1])*((uint32_t*)&uint8Array[2])*((uint32_t*)&uint8Array[3])

*((uint32_t*)&uint8Array[4])得到字节4 - > 8如预期的那样。

所以看起来我无法从任何地址访问4个连续字节?

我有什么方法可以做到这一点吗?

3 个答案:

答案 0 :(得分:6)

虽然CUDA中不允许未对齐访问,但prmt PTX instruction有一个方便的模式来模拟寄存器中未对齐读取的效果。这可以通过一点inline PTX assembly来暴露。如果你能够容忍读数超过数组的末尾,代码变得非常简单:

{ f -> print f }

为确保超出数组末尾的访问权限仍然无害,请将分配的字节数向上舍入为4的倍数,然后再添加4个字节。

以上设备代码与容忍未对齐访问的little-endian主机上的以下代码具有相同的效果:

// WARNING! Reads past ptr!
__device__ uint32_t read_unaligned(void* ptr)
{
    uint32_t result;
    asm("{\n\t"
        "   .reg .b64    aligned_ptr;\n\t"
        "   .reg .b32    low, high, alignment;\n\t"
        "   and.b64      aligned_ptr, %1, 0xfffffffffffffffc;\n\t"
        "   ld.u32       low, [aligned_ptr];\n\t"
        "   ld.u32       high, [aligned_ptr+4];\n\t"
        "   cvt.u32.u64  alignment, %1;\n\t"
        "   prmt.b32.f4e %0, low, high, alignment;\n\t"
        "}"
        : "=r"(result) : "l"(ptr));
    return result;
}

答案 1 :(得分:1)

如果你想要字节2..6,你将需要组合多个对齐的加载来获得你想要的东西。

uint32_t *ptr = ...;
uint32_t value = (ptr[0] >> 16) | (ptr[1] << 16);

从技术上讲,这也是也是一般用C语言做的便携式方式,但我们都被宠坏了,因为你不需要在x86,ARM,Power上做额外的工作,或其他常见架构。

答案 2 :(得分:0)

正如@DietrichEpp建议的那样,你必须做两次装载;并且正如@tera所建议的那样,即使未使用uint8Array PTX指令预先知道未对齐(即当prmt的初始地址是任意的)时,您也可以将这两个负载合并为便宜。 / p>

我将提供基于@ tera的解决方案,让您可以:

value = read_unaligned(&uint8Array[offset]);

安全且(相对)有效。此外,它只有一个内联PTX汇编指令,如果你需要它还有一个“不安全”变量:

#include <cstdint>
#include <cuda_runtime_api.h>

__device__ __forceinline__ uint32_t prmt_forward_4_extract(
    uint32_t first_word,
    uint32_t second_word, 
    uint32_t control_bits)
{
    uint32_t result;
    asm("prmt.b32.f4e %0, %1, %2, %3;"
        : "=r"(result)
        : "r"(first_word), "r"(second_word), "r"(control_bits) );
    return result;
}

/*
 * This unsafe, faster variant may read past the 32-bit naturally-aligned
 * word containing the last relevant byte
 */
__device__ inline uint32_t read_unaligned_unsafe(const uint32_t* __restrict__ ptr)
{
    /*
     *  Clear the bottom 2 bits of the address, making the result aligned 
     *  for the purposes of reading a 32-bit (= 4-byte) value
     */
    auto aligned_ptr  = (uint32_t*) ((uint64_t) ptr & ~((uint64_t) 0x3));
    auto first_value  = *aligned_ptr;
    auto second_value = *(aligned_ptr + 1);

    auto lower_word_of_ptr = (uint32_t)((uint64_t)(ptr));

    return prmt_forward_4_extract(first_value, second_value, lower_word_of_ptr);
}

__device__ inline uint32_t read_unaligned(const uint32_t* __restrict__ ptr)
{
    auto ptr_is_already_aligned = ((uint64_t)(ptr) & 0x3 == 0);
    if (ptr_is_already_aligned) { return *ptr; }
    return read_unaligned_unsafe(ptr);
}