2016-10-22 7 views
1

Я пытаюсь использовать массив uint8_t для массива uint32_t. Однако, когда я пытаюсь это сделать, я не могу получить доступ к каждому последующему 4 байтам.Чтение из unaligned uint8_t recast как массив uint32_t - не получение всех значений

Скажем, у меня есть массив uint8_t с 8 байтами. Я хотел бы получить доступ к байту 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 последовательным байтам с любого адреса?

Есть ли способ, которым я могу это сделать?

+0

Вы не можете этого сделать. Вы можете получить доступ только к правильно выровненным словам. (Некоторые процессоры могут позволить вам уйти с ним, но это дополнительная работа над работой процессора, а некоторые процессоры не работают, а язык C не требует их.) Если вы хотите сделать неравномерный доступ портативно, у вас есть сделать это «вручную», как в ответе @ DietrichEpp. –

+2

CUDA имеет очень хорошо документированные требования к выравниванию, и все транзакции памяти должны быть упорядочены по размеру транзакции – talonmies

ответ

1

Если вам нужны байты 2..6, вам нужно будет объединить несколько выровненных нагрузок, чтобы получить то, что вы хотите.

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

Технически это также портативный способ сделать вещи в C в целом, но мы все испорчено, потому что вы не должны делать дополнительную работу на x86, ARM, Power, или другие общие архитектуры.

+0

Тип переинтерпретации uint8_t как несовместимый тип - это неопределенное поведение. – 2501

+0

@ 2501: Если это C, это будет так, если вы используете очень узкое чтение стандарта C, но это не C. Это очень похоже на C, поэтому я могу понять, почему вы так думаете. –

+0

Если cuda не использует язык c, какой язык он использует и где я могу найти спецификацию? – 2501

4

Хотя в CUDA недопустимые обращения не допускаются, prmt PTX instruction имеет удобный режим для эмулирования эффекта нестандартных чтений в пределах регистров. Это может быть выставлено бит inline PTX assembly. Если вы можете терпеть чтения за концом массива, код становится довольно просто:

// 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, 0xfffffffc;\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; 
} 

Для обеспечения доступа мимо конца массива остается безвредным, округлить число выделенных байт кратному 4 и добавить еще 4 байта.

Над кодом устройства имеет тот же эффект, что и следующий код на небольшой обратный порядок байт хоста, который терпит невыровненные доступы:

__host__ uint32_t read_unaligned_host(void* ptr) 
{ 
    return *(uint32_t*)ptr; 
} 
+0

Вы не считаете, что адрес меньше 2^32 - 1? – einpoklum

+0

Также, пожалуйста, взгляните на мой ответ. – einpoklum

+0

Почему, по вашему мнению, код не удался для адресов> = 2^32? Обратите внимание, что только самые младшие два бита 'alignment' являются релевантными, поэтому 32-битный регистр более чем достаточен. – tera

0

Как следует @DietrichEpp, вы должны сделать две нагрузки; и, как предлагает @tera, вы можете комбинировать эти две нагрузки в общем случае для дешевых, даже если несоосность заранее неизвестна (то есть, когда начальный адрес uint8Array является произвольным) с использованием инструкции PTX prmt.

я предлагаю решение, основанное на @ тера, который позволит вам сделать:

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); 
} 
+1

Это выглядит неправильно для меня - вам нужно передать (наименьшие два бита) невыровненный указатель на биты управления. Почему бы вам не проверить свой код, когда вы его уже скомпилировали? – tera

+0

@tera: Действительно, мне нужно было передать нижние разряды неглавного указателя. Исправлено и протестировано. – einpoklum