Допустимо ли добавлять целый массив байтов одновременно, преобразовывая их в больший целочисленный тип данных? - PullRequest
0 голосов
/ 18 сентября 2019

Если у меня есть два массива, которые содержат u8 s, могу ли я преобразовать их в больший целочисленный тип, чтобы уменьшить количество необходимых дополнений?Например, если каждый из двух байтовых массивов содержит 4 байта, могу ли я превратить их в u32, выполнить сложение и затем преобразовать их обратно?

Например:

let a = u32::from_ne_bytes([1, 2, 3, 4]);
let b = u32::from_ne_bytes([5, 6, 7, 8]);

let c = a + b;
let c_bytes = u32::to_ne_bytes(c);

assert_eq!(c_bytes, [6, 8, 10, 12]);

Этот пример приводит к правильному выводу.

  1. Всегда ли это приводит к правильному выводу (при условии, что переполнения нет)?
  2. Это быстрее, чем просто делать индивидуальные добавления?
  3. Действительно ли это для других целочисленных типов?Например, 2 u16 с в u32 добавлено с 2 другими u16 с в u32?

Если это существует и является общим, как это называется?

1 Ответ

1 голос
/ 19 сентября 2019
  1. Всегда ли это приводит к правильному выводу (при условии, что переполнения нет)?

Да.При условии, что каждая сумма меньше 256, это добавит байты, как вы хотите.Вы указали "ne" в каждом случае для нативного порядка байтов.Это будет работать независимо от собственного порядка байтов, поскольку операции выполняются побайтово.

Если бы вы написали код для проверки того, что все суммы находятся в диапазоне, то вы почти наверняка отменили бы любое дополнительное ускорение, которое у вас было (если оно было для начала).

Это быстрее, чем просто делать дополнения по отдельности?

Может быть.Единственный способ узнать наверняка - это проверить.

Верно ли это для других целочисленных типов?Например, 2 u16 s в u32 добавлено с 2 другими u16 s в u32?

Да, но вы должны обратить внимание на порядок байтов.

Если это существует и является общим, как оно называется?

Это не распространено, потому что обычно это не нужно.Этот тип оптимизации усложняет чтение кода и создает значительные сложности и возможности для ошибок.Компилятор Rust и LLVM между ними способны находить чрезвычайно сложные оптимизации, о которых вы никогда не думаете, в то время как ваш код остается читабельным и обслуживаемым.

Если у него есть имя, это SIMD и поддержка большинства современных процессоров.форма его изначально (SSE, MMX, AVX).Вы можете сделать это вручную, используя встроенные функции, например, core::arch::x86_64::_mm_add_epi8, но LLVM может сделать это автоматически.Возможно, что попытка сделать это вручную может помешать оптимизации, которую в противном случае сделала бы LLVM, и в то же время сделать ваш код более подверженным ошибкам.


Я не эксперт по ассемблерному коду.любым способом, но я взглянул на сборку, сгенерированную для следующих двух функций:

#[no_mangle]
#[inline(never)]
pub fn f1(a1: u8, b1: u8, c1: u8, d1: u8, a2: u8, b2: u8, c2: u8, d2: u8) -> [u8; 4]{
    let a = u32::from_le_bytes([a1, b1, c1, d1]);
    let b = u32::from_le_bytes([a2, b2, c2, d2]);
    u32::to_le_bytes(a + b)
}

#[no_mangle]
#[inline(never)]
pub fn f2(a1: u8, b1: u8, c1: u8, d1: u8, a2: u8, b2: u8, c2: u8, d2: u8) -> [u8; 4]{
    [a1 + a2, b1 + b2, c1 + c2, d1 + d2]
}

сборка для f1:

movzx r10d, byte ptr [rsp + 8]
shl ecx, 24
movzx eax, dl
shl eax, 16
movzx edx, sil
shl edx, 8
movzx esi, dil
or esi, edx
or esi, eax
or esi, ecx
mov ecx, dword ptr [rsp + 16]
shl ecx, 24
shl r10d, 16
movzx edx, r9b
shl edx, 8
movzx eax, r8b
or eax, edx
or eax, r10d
or eax, ecx
add eax, esi
ret

А для f2:

add r8b, dil
add r9b, sil
add dl, byte ptr [rsp + 8]
add cl, byte ptr [rsp + 16]
movzx ecx, cl
shl ecx, 24
movzx edx, dl
shl edx, 16
movzx esi, r9b
shl esi, 8
movzx eax, r8b
or eax, esi
or eax, edx
or eax, ecx
ret

Меньшее количество инструкций не обязательно делает это быстрее, но это неплохой ориентир.


Рассматривайте этот вид оптимизации в качестве последнего средства, после тщательных измерений и испытаний.

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