Упаковка и распаковка кардинального числа в четыре байта

#delphi #delphi-2010 #bit-manipulation

#delphi #delphi-2010 #манипулирование битами

Вопрос:

Я должен упаковать и распаковать кардинальное число в четыре однобайтовых поля (в Delphi 2010).

Я делаю это для всех пикселей большого изображения, поэтому мне нужно, чтобы это было быстро!

Кто-нибудь может показать мне, как написать эти две функции? (Ключевые слова const и out приведены просто для наглядности. Если они мешают встроенной сборке, я могу их удалить.)

 procedure FromCardinalToBytes( const aInput: Cardinal;
                               out   aByte1: Byte;
                               out   aByte2: Byte;
                               out   aByte3: Byte;
                               out   aByte4: Byte); inline;

function FromBytesToCardinal( const aByte1: Byte;
                              const aByte2: Byte;
                              const aByte3: Byte;
                              const aByte4: Byte):Cardinal; inline;
  

Комментарии:

1. что именно вы хотите сделать? Вставьте какой-нибудь код, и, возможно, мы сможем предоставить вам какой-нибудь быстрый ассемблер, который исправит вашу потребность в скорости.

2. Я рекомендую вам избегать встроенной сборки, если только вам абсолютно не нужно . Это излишне ограничит вашу переносимость (учтите, что 64-разрядный Delphi уже находится в бета-версии). На самом деле вы можете сделать это без функций. И, как указывает Йохан, вы, вероятно, можете делать все, чего пытаетесь достичь, даже не разлагаясь на отдельные байты.

Ответ №1:

Я бы рекомендовал не использовать функцию, просто использовать запись variant.

 type
  TCardinalRec = packed record
    case Integer of
      0: (Value: Cardinal;);
      1: (Bytes: array[0..3] of Byte;);
    end;
  

Затем вы можете легко использовать это для получения отдельных байтов.

 var
  LPixel: TCardinalRec;
...
  LPixel.Value := 123455;
  //Then read each of the bytes using
  B1 := LPixel.Bytes[0];
  B2 := LPixel.Bytes[1];
  //etc.
  

Если вам абсолютно необходимо, вы можете поместить это в функцию, но это достаточно тривиально, чтобы не беспокоиться о накладных расходах при вызове функции.


РЕДАКТИРОВАТЬ
Чтобы проиллюстрировать эффективность подхода с альтернативной записью, рассмотрим следующее (предполагая, что вы считываете свое изображение из потока).

 var
  LPixelBuffer: array[0..1023] of TCardinalRec;
...

  ImageStream.Read(LPixelBuffer, SizeOf(LPixelBuffer));
  for I := Low(LPixelBuffer) to High(LPixelBuffer) do
  begin
    //Here each byte is accessible by:
    LPixelBuffer[I].Bytes[0]
    LPixelBuffer[I].Bytes[1]
    LPixelBuffer[I].Bytes[2]
    LPixelBuffer[I].Bytes[3]
  end;
  

PS: Вместо произвольно общего массива байтов вы могли бы явно назвать каждый байт в записи варианта как красный, зеленый, синий (и что бы ни означал четвертый байт).

Комментарии:

1. Разве они не называются VARIANT RECORDS? (не переменная, вариант)

Ответ №2:

Существует много способов. Самый простой

 function FromBytesToCardinal(const AByte1, AByte2, AByte3,
  AByte4: byte): cardinal; inline;
begin
  result := AByte1   (AByte2 shl 8)   (AByte3 shl 16)   (AByte4 shl 24);
end;

procedure FromCardinalToBytes(const AInput: cardinal; out AByte1,
  AByte2, AByte3, AByte4: byte); inline;
begin
  AByte1 := byte(AInput);
  AByte2 := byte(AInput shr 8);
  AByte3 := byte(AInput shr 16);
  AByte4 := byte(AInput shr 24);
end;
  

Немного более сложный (но не обязательно более быстрый)

 function FromBytesToCardinal2(const AByte1, AByte2, AByte3,
  AByte4: byte): cardinal; inline;
begin
  PByte(@result)^ := AByte1;
  PByte(NativeUInt(@result)   1)^ := AByte2;
  PByte(NativeUInt(@result)   2)^ := AByte3;
  PByte(NativeUInt(@result)   3)^ := AByte4;
end;

procedure FromCardinalToBytes2(const AInput: cardinal; out AByte1,
  AByte2, AByte3, AByte4: byte); inline;
begin
  AByte1 := PByte(@AInput)^;
  AByte2 := PByte(NativeUInt(@AInput)   1)^;
  AByte3 := PByte(NativeUInt(@AInput)   2)^;
  AByte4 := PByte(NativeUInt(@AInput)   3)^;
end;
  

Если вам не нужно, чтобы байты были байтовыми переменными, вы можете делать еще более сложные вещи, такие как объявление

 type
  PCardinalRec = ^TCardinalRec;
  TCardinalRec = packed record
    Byte1,
    Byte2,
    Byte3,
    Byte4: byte;
  end;
  

а затем просто приведите:

 var
  c: cardinal;
begin
  c := $12345678;
  PCardinalRec(@c)^.Byte3 // get or set byte 3 in c
  

Комментарии:

1. Отличная работа, Андреас! Вы сохранили отличную репутацию, которую заработали. БОЛЬШОЕ спасибо!!

2. функция из bitestocardinal2(const AByte1, AByte2, AByte3, AByte4: byte): кардинальный; встроенный; начинающийся с PByte (@result)^ := AByte1; PByte(NativeUInt(@result) 1)^ := AByte2; PByte(NativeUInt(@result) 2)^ := AByte3; PByte (NativeUInt(@result) 3)^ := AByte4; end; Всего 3 невыровненных обращения к памяти. Это будет действительно очень медленный код.

3. SysUtils уже объявляет некоторые записи для доступа к «вложенным значениям» переменной. Проверьте WordRec, LongRec, Int64Rec, TFloatRec.

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

Ответ №3:

Если вы хотите быстро, вам нужно рассмотреть архитектуру 80×86.

Скорость сильно зависит от того, что вы делаете с байтами. x86 может получить доступ к нижним 2 байтам очень быстро, используя регистры AL и AH
(наименее значимые байты в 32-разрядном регистре EAX)

Если вы хотите получить два байта более высокого порядка, вы не хотите обращаться к ним напрямую. Потому что вы получите не выровненный доступ к памяти, потратите впустую процессорные циклы и испортите кэш.

Ускоряем
Вся эта ерунда, связывающаяся с отдельными байтами, на самом деле не нужна. Если вы хотите работать действительно быстро, работайте с 4 байтами за раз.

 NewPixel:= OldPixel or $0f0f0f0f;
  

Если вы хотите обрабатывать свои пиксели действительно быстро, используйте встроенную сборку MMX и работайте с 8 байтами одновременно.

Ссылки:
Википедия: http://en.wikipedia.org/wiki/MMX_(instruction_set)
Объяснение набора команд MMX: http://webster.cs.ucr.edu/AoA/Windows/HTML/TheMMXInstructionSet.html

Или повторно задайте свой вопрос на SO: как мне выполнить эту манипуляцию с растровым изображением … в MMX.

Действительно, очень быстро
Если вы хотите, чтобы это было действительно очень быстро, например, в 100 или 1000 раз быстрее, чем MMX, ваша видеокарта может это сделать. В Google найдите CUDA или GPGPU.