#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.