Pull to refresh

Comments 40

UFO just landed and posted this here
Отчасти. Конечно, в силу простоты реализации в электронике, LFSR используют в потоковых шифраторах и для скрэмблинга передаваемых данных. Но, скажем так, алгоритм LFSR нашел свое применение не только в электронике, но и в программном коде. Например, при организации пула для энтропии в рамках псевдоустройства /dev/random в версиях Linux.
p.s. И вот что интересно, а можно ли сделать


Думаю что можно. Если вы имели в виду что-то такое:

#include <inttypes.h>
#include <stdio.h>

#define TAP_MASK_LOW64 0x0
#define TAP_MASK_HIGH64 0x28000005

do_lfsr(uint64_t *p)
{
    uint64_t low  = p[0] & TAP_MASK_LOW64;
    uint64_t high = p[1] & TAP_MASK_HIGH64;

    uint64_t s1 = low ^ high;
    uint32_t s2 = (uint32_t)s1 ^ (uint32_t)(s1 >> 32);
    uint32_t s3 = (s2 & 0xffff) ^ (s2 >> 16);
    uint32_t s4 = (s3 & 0xff) ^ (s3 >> 8);
    uint32_t s5 = (s4 & 0xf) ^ (s4 >> 4);
    uint32_t s6 = (s5 & 0x3) ^ (s5 >> 2);
    uint32_t s7 = (s6 & 0x1) ^ (s6 >> 1);
    p[1] = (p[1] << 1) | (p[0] >> 63);
    p[0] = (p[0] << 1) | s7;
}

int main()
{
    uint64_t lfsr_data[2] = {
        0xffffffffffffffffull,
        0xffffffffffffffffull,
    };
    volatile unsigned i;

    for(i = 0; i < 0xffff * 32; ++i)
        do_lfsr(lfsr_data);
    printf("%016"PRIx64":%016"PRIx64"\n", lfsr_data[1], lfsr_data[0]);
}


то у меня это выполняется вот столько:

$ gcc lfsr.c -o lfsr
$ file lfsr
lfsr: ELF 32-bit LSB executable, Intel 80386 ...
$ time ./lfsr
976a2d15490d6693:844dc6ff26ad7a9a

real    0m0.086s
user    0m0.084s
sys     0m0.000s


А оно _точно_ «выполняется», а не печатает статический результат вычисленный в момент компиляции?
Да. Во-первых это gcc -O0, т.е. никаких оптимизаций, во-вторых, это volatile счётчик цикла, т.е. честный перебор. В третьих даже если внести печать внутрь do_lfsr, то картинка такая:
.......
1b976a2d15490d66:93844dc6ff26ad7a
372ed45a2a921acd:27089b8dfe4d5af5
6e5da8b45524359a:4e11371bfc9ab5ea
dcbb5168aa486b34:9c226e37f9356bd4
b976a2d15490d669:3844dc6ff26ad7a9
72ed45a2a921acd2:7089b8dfe4d5af53
e5da8b45524359a4:e11371bfc9ab5ea6
cbb5168aa486b349:c226e37f9356bd4d
976a2d15490d6693:844dc6ff26ad7a9a
976a2d15490d6693:844dc6ff26ad7a9a

real    0m33.816s
user    0m1.560s
sys     0m6.288s
datacompboy@nuuzerpogodible:~$ time ./lfsr 0
976a2d15490d6693:844dc6ff26ad7a9a

real 0m0.019s
user 0m0.016s
sys 0m0.004s

Точно выполняется…
int main(int argc, char ** argv)
{
    uint64_t lfsr_data[2] = {
        0xffffffffffffffffull,
        0xffffffffffffffffull,
    };
    volatile unsigned i;

    if(argc < 2) {
        printf("Run as \"%s 0\"\n", argv[0]);
        return 1;
    }

    uint64_t arg;
    sscanf(argv[1], "%llu", &arg);
    lfsr_data[0] += arg;
    lfsr_data[1] += arg;
    for(i = 0; i < 0xffff * 32; ++i)
        do_lfsr(lfsr_data);
    printf("%016"PRIx64":%016"PRIx64"\n", lfsr_data[1], lfsr_data[0]);
}
Хах :) Действительно, забыл добавить условие вывода на экран всех вычисленных функцией обратной связи битов.
Так мы скорость вывода будем замерять, а не сдвиговый регистр.
Потому что с полным выводом в консоль я вижу 33 секунды, в файл — 1.2 секунды, а в /dev/null — 0.95
Отлично… Ну что же, мой результат ~0.04 с
Хорошо, «будь по-твоему, старче»:

#include <inttypes.h>
#include <stdio.h>

#define TAP_MASK_LOW64 0x0
#define TAP_MASK_HIGH64 0x28000005

void do_lfsr(uint64_t *p)
{
    uint64_t low  = p[0] & TAP_MASK_LOW64;
    uint64_t high = p[1] & TAP_MASK_HIGH64;

    uint64_t s1 = low ^ high;
    uint32_t s2 = (uint32_t)s1 ^ (uint32_t)(s1 >> 32);
    uint32_t s3 = (s2 & 0xffff) ^ (s2 >> 16);
    uint32_t s4 = (s3 & 0xff) ^ (s3 >> 8);
    uint32_t s5 = (s4 & 0xf) ^ (s4 >> 4);
    uint32_t s6 = (s5 & 0x3) ^ (s5 >> 2);
    uint32_t s7 = (s6 & 0x1) ^ (s6 >> 1);
    p[1] = (p[1] << 1) | (p[0] >> 63);
    p[0] = (p[0] << 1) | s7;
    putchar(s7 ? '1' : '0');
}

int main()
{
    uint64_t lfsr_data[2] = {
        0xffffffffffffffffull,
        0xffffffffffffffffull,
    };
    volatile unsigned i;

    for(i = 0; i < 0xffff * 32; ++i)
        do_lfsr(lfsr_data);
    printf("\n%016"PRIx64":%016"PRIx64"\n", lfsr_data[1], lfsr_data[0]);
    return 0;
}


......
111011011111100001110100101111001110011100001110001111011000001100110100001111101100010101010100100001110000000001000000010011000001000001000011100101000011111101000000011101110110100000110101111001100010110010111101011010010000110100000010010010100001010101001001011110001110101010010000010111010101100001001111011111001110100110111110111000010001101110010111011010100010110100010101010010010000110101100110100100111000010001001101110001101111111100100110101011010111101010011010
976a2d15490d6693:844dc6ff26ad7a9a

real    0m0.577s
user    0m0.104s
sys     0m0.028s

А если занести четность 16-битных чисел в массив — не будет быстрее?
s7=Parity[s3];
Я думаю, что с высокой вероятностью не будет, из-за кэша, но не проверял.
С другой стороны, 4- или 8-битная табличка почти наверняка даст ускорение.
Уже проверил. Для массивов время на 2^21 циклов — 0.0354, для чистого xor — 0.0445 (VS2008, 32-битный режим).
И, кстати, на 32-битных числах получается 0.0192 сек:

unsigned int AR[4];
void step3(){
	unsigned int q=(AR[0]&Mask0)^(AR[1]&Mask1)^(AR[2]&Mask2)^(AR[3]&Mask3);
	unsigned int c=u[(q&0xffff)^(q>>16)];
	AR[3]=(AR[3]<<1)|(AR[2]>>31);
	AR[2]=(AR[2]<<1)|(AR[1]>>31);
	AR[1]=(AR[1]<<1)|(AR[0]>>31);
	AR[0]=(AR[0]<<1)|c;
	*r1++=c;
}


Но я еще не смотрел, что там получилось в ассемблере.
А если реализовать через локальные переменные:

void step3(int m){
	unsigned int a=AR[0],b=AR[1],c=AR[2],d=AR[3];
	do{
		unsigned int q=(a&Mask0)^(b&Mask1)^(c&Mask2)^(d&Mask3);
		q=u[(q&0xffff)^(q>>16)];
		d=(d<<1)|(c>>31);
		c=(d<<1)|(b>>31);
		b=(d<<1)|(a>>31);
		a=(d<<1)|(q);
		*r1++=q;
	}while(--m);
	AR[0]=a; AR[1]=b; AR[2]=c; AR[3]=d;
}

то получается 0.00849 сек на 2^21 циклов.
Для 2^28 циклов 64-битный xor дает 5.630с, с 16-битной табличкой — 4.546с, 32-битный на локальных переменных — 1.072с.
Мои цифры (2 ^ 28 циклов): только xor — 1.889с, 4-битная таблица — 1.677с, 8-битная — 1.517с
только хардкор, только непереносимый код! ;)

почему именно MMX, а не SSE/SSE2?
Чтобы огрести проблем с FPU, не? (:
А если серьёзно, скажите, какой смысл использовать в этой задаче любую из названных технологий?
Если говорить о расширенных наборах инструкций, я вижу (и то сомнительный) выигрыш только от использования popcnt.
в XXI веке живем, считаю, что оптимизацией должен заниматься копилятор :)
Согласен :) Более того, если я правильно помню, то на тот момент, когда мне необходим был этот код, технология SSE2 была очень молода, а команды SSE мне показались не очень «удобными».
xorps, andps выглядят как перспективные для данной задачи
Ну а мне они таковыми не кажутся, потому что узкое место не там.
Если интересно — предложите код, обсудим.
Весело у вас. Я давно с ассемблером не экспериментировал, а тут решил попробовать. Когда замена одного регистра в арифметической команде увеличивает время работы на 16 тактов — это круто!
Быстрее всего получилось с 8-битной таблицей. Главный цикл выглядит так:

	mov	DWORD PTR _m$[esp+20], 65536
	npad	5
$LL3@step3:
	push	ebx
	mov	ebx,1
	npad	10
$LL4@step3:	
	mov	eax, edi
	and	eax, MASK3
	mov	ebp, ecx
	and	ebp, MASK2
	xor	eax, ebp
	mov	ebp, edx
	and	ebp, MASK1
	xor	eax, ebp
	mov	ebp, esi
	and	ebp, MASK0
	xor	eax, ebp

	mov	ebp, eax
	shr	ebp, 16	
	xor	ebp, eax
	mov	eax, ebp
	shr	ebp, 8	
	xor	ebp, eax
	and	ebp, 255
	movzx	eax, BYTE PTR _u[ebp]

	shld	edi,ecx,1
	shld	ecx,edx,1
	shld	edx,esi,1
	add	esi,esi
	or	esi,eax
	ror	eax,1
	rcl	ebx,1

	jnc	short $LL4@step3
	mov	eax,ebx
	pop	ebx
	mov	DWORD PTR [ebx], eax
	add	ebx,4

	sub	DWORD PTR _m$[esp+20], 1	
	jne	$LL3@step3


(программа переделана из листинга C++, поэтому метки странные).

На 2^28 циклов дает 1.46 секунды (предыдущая «быстрая» программа была с ошибкой, даже с тремя).
Вы об ошибках с «d<<1»? :)

Судя по сгенерированному коду, полином задается через константы. Мне кажется исполняемый код не даст такие же результаты, если полином будет меняться в процессе работы, например циклический сдвиг. Фактически, вся логика работы с 256 битами данных (регистр и маска), была сокращена до 128 и аккуратно разбросана по стандартным регистрам.

Я немного оптимизировал код. Он стал похож на Ваш и jcmvbkbc вариант, только вместо таблиц у меня lahf/not:

  mov ecx, RR_
l1:
  ; Apply LFSR mask
  movq mm(inRTmpH),mm(inRSH)
  pand mm(inRTmpH),mm(inRMH)
  movq mm(inRTmpL),mm(inRSL)
  pand mm(inRTmpL),mm(inRML)

  ; Calculate new bit
  pxor mm(inRTmpH),mm(inRTmpL)
  movd ebx, mm(inRTmpH)
  psrlq mm(inRTmpH),020h
  movd eax, mm(inRTmpH)
  xor ebx,eax
  mov ax,bx
  sar ebx,010h
  xor ax,bx
  xor al,ah
  lahf
  not eax
  sar eax,0Ah
  and eax,01h

; Append new bit
  psrlq mm(inRSL),01h
  movq mm(inRTmp),mm(inRSH)
  psllq mm(inRTmp),03Fh
  por mm(inRSL),mm(inRTmp)
  psrlq mm(inRSH),01h
  movd mm(inRTmp), eax
  psllq mm(inRTmp),03Fh
  por mm(inRSH),mm(inRTmp)

  loop l1


На 2^21 циклах скорость работы ~0.02 с. Но, зато, я легко могу добавить конструкцию следующего типа без обращения к памяти:

  movq mm(inRTmpH),mm(inRSH)
  movq mm(inRTmpL),mm(inRSH)
  psllq mm(inRTmpH),03Fh
  psllq mm(inRTmpL),03Fh
  psrlq mm(inRSH),01h  
  psrlq mm(inRSL),01h  
  por mm(inRSH),mm(inRTmpL)
  por mm(inRSL),mm(inRTmpH)


Жаль что в MMX нет SHLD :)
  movq mm(inRTmpH),mm(inRMH)
  movq mm(inRTmpL),mm(inRML)
  psllq mm(inRTmpH),03Fh
  psllq mm(inRTmpL),03Fh
  psrlq mm(inRMH),01h  
  psrlq mm(inRML),01h  
  por mm(inRMH),mm(inRTmpL)
  por mm(inRML),mm(inRTmpH)


конечно же…
Можно и без SHLD:
	add	esi,esi
	adc	edx,edx
	adc	ecx,ecx
	adc	edi,edi
	or	esi,eax

При 8-битной таблице так получилось даже быстрее, чем с shld — 1.17 сек на 2^28 циклов. Пока таблица была 16-битной, adc работал медленнее.
Да, это бесспорно. Но опять же, маска остается константой. Да и в инструкциях MMX нет аналога ADC, к сожалению.
А вообще бит C там есть? Вместо adc, например в 8086, можно обойтись и rcl. Но вообще без C писать длинную арифметику непросто (если нет команд сложения с переполнением).
Там команд то… на пальцах задней ноги пересчитать можно. Что по поводу флагов, то:

«The packed arithmetic instructions operate on a set of bytes, words, or double words within a 64-bit block. For example, the PADDW instruction computes four 16-bit sums of two operand simultaneously. None of these instructions affect the CPU's FLAGs register. Therefore, there is no indication of overflow, underflow, zero result, negative result, etc.»

Отсюда: Art of Assembly: MMX Technology Instructions

Жаль, что не ввели сразу «горизонтальную» арифметку. Сильно упростило бы.
И, кстати, взятие маски из памяти:

and eax, DWORD PTR _AM+12

и т.п. вообще не повлияло на скорость — осталось 1.17 сек
А Вы уверены, что на выходе компилера, эта инструкция не превращается что-то типа в следующий opcode? :)

25 78563412
В листинге написано

00000042 23 05 0000000C E and eax, DWORD PTR _AM+12

Так что не превращается. Да и с чего бы? Массив с маской описан в соседнем файле.
Андрей, а можете поделиться уже скомпилированным кодом?
astr73.narod.ru/Files/cpol.zip — там cpp и asm (исходники), lst, полученный из asm и собранный exe (32-битное консольное приложение для Windows)
Спасибо… Кажется, я был прав (смещение в EXE-файле: 0x05B5):

002811B5  |. 8BDA                            ||MOV EBX,EDX
002811B7  |. 81E3 563412F0                   ||AND EBX,F0123456
002811BD  |. 8BC8                            ||MOV ECX,EAX
002811BF  |. 81E1 F0DEBC9A                   ||AND ECX,9ABCDEF0
002811C5  |. 33D9                            ||XOR EBX,ECX
Да, это похоже на фрагмент откомпилированной C-шной функции step1 (оставленной для контроля). Правда, регистры я сейчас вижу совсем другие:

003E103A  mov         ebx,dword ptr [esp+24h] 
003E103E  and         ebx,12345679h 
003E1044  mov         edi,ebp 
003E1046  mov         ecx,esi 
003E1048  and         ecx,0F0123456h 
003E104E  mov         eax,edx 
003E1050  and         eax,789ABCDEh 
003E1055  xor         eax,ebx 
003E1057  and         edi,9ABCDEF0h 
003E105D  xor         ecx,edi 
Да, так и есть. Запутали меня (:

013E1400   > 8BC7           MOV EAX,EDI
013E1402   . 2305 44303E01  AND EAX,DWORD PTR DS:[13E3044]
013E1408   . 8BE9           MOV EBP,ECX
013E140A   . 232D 40303E01  AND EBP,DWORD PTR DS:[13E3040]
013E1410   . 33C5           XOR EAX,EBP
013E1412   . 8BEA           MOV EBP,EDX
013E1414   . 232D 3C303E01  AND EBP,DWORD PTR DS:[13E303C]
013E141A   . 33C5           XOR EAX,EBP
013E141C   . 8BEE           MOV EBP,ESI
013E141E   . 232D 38303E01  AND EBP,DWORD PTR DS:[13E3038]

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

Кстати, вот тоже работающий вариант не использующий XOR в принципе (:
(правда медленный):

.data
  SH_ dq 0FFFFFFFFFFFFFFFFh ; Sequence[127-64]
  SL_ dq 0FFFFFFFFFFFFFFFFh ;         [63-0]
  MH_ dq 00000000000000000h ; Mask[127-64] taps: 128,126,101,99
  ML_ dq 00000000028000005h ;     [63-0]
  RR_ dd 0001FFFE0h         ; Amount of rounds

  _55 dq 05555555555555555h
  _33 dq 03333333333333333h
  _0F dq 00F0F0F0F0F0F0F0Fh
  _FF dq 000FF00FF00FF00FFh

.code
  ; Initialize MMX registers
  movq mm(inRSH),SH_
  movq mm(inRSL),SL_
  movq mm(inRMH),MH_
  movq mm(inRML),ML_

  mov ecx, RR_
l1:
  ; Apply LFSR mask
  movq mm(inRTmpH),mm(inRSH)
  pand mm(inRTmpH),mm(inRMH)
  movq mm(inRTmpL),mm(inRSL)
  pand mm(inRTmpL),mm(inRML)

  ; Calculate new bit
  movq mm(inRTmp),mm(inRTmpH)
  movq mm(inRTmpH),mm(inRTmpL)
  psrlw mm(inRTmpH),01h
  pand mm(inRTmpH),_55
  pand mm(inRTmpL),_55
  paddw mm(inRTmpL),mm(inRTmpH)
  movq mm(inRTmpH),mm(inRTmpL)
  psrlw mm(inRTmpH),02h
  pand mm(inRTmpH),_33
  pand mm(inRTmpL),_33
  paddw mm(inRTmpL),mm(inRTmpH)
  movq mm(inRTmpH),mm(inRTmpL)
  psrlw mm(inRTmpH),04h
  pand mm(inRTmpH),_0F
  pand mm(inRTmpL),_0F
  paddw mm(inRTmpL),mm(inRTmpH)
  movq mm(inRTmpH),mm(inRTmpL)
  psrlw mm(inRTmpH),08h
  pand mm(inRTmpH),_FF
  pand mm(inRTmpL),_FF
  paddw mm(inRTmpL),mm(inRTmpH)
  movq mm(inRTmpH),mm(inRTmpL)
  psllq mm(inRTmpL),020h
  paddd mm(inRTmpH),mm(inRTmpL)
  movq mm(inRTmpL),mm(inRTmpH)
  psllq mm(inRTmpL),010h
  paddw mm(inRTmpH),mm(inRTmpL)
  psllq mm(inRTmpH),0Fh
  movq mm(inRTmpL),mm(inRTmp)
  movq mm(inRTmp),mm(inRTmpH)
  movq mm(inRTmpH),mm(inRTmpL)
  psrlw mm(inRTmpH),01h
  pand mm(inRTmpH),_55
  pand mm(inRTmpL),_55
  paddw mm(inRTmpL),mm(inRTmpH)
  movq mm(inRTmpH),mm(inRTmpL)
  psrlw mm(inRTmpH),02h
  pand mm(inRTmpH),_33
  pand mm(inRTmpL),_33
  paddw mm(inRTmpL),mm(inRTmpH)
  movq mm(inRTmpH),mm(inRTmpL)
  psrlw mm(inRTmpH),04h
  pand mm(inRTmpH),_0F
  pand mm(inRTmpL),_0F
  paddw mm(inRTmpL),mm(inRTmpH)
  movq mm(inRTmpH),mm(inRTmpL)
  psrlw mm(inRTmpH),08h
  pand mm(inRTmpH),_FF
  pand mm(inRTmpL),_FF
  paddw mm(inRTmpL),mm(inRTmpH)
  movq mm(inRTmpH),mm(inRTmpL)
  psllq mm(inRTmpL),020h
  paddd mm(inRTmpH),mm(inRTmpL)
  movq mm(inRTmpL),mm(inRTmpH)
  psllq mm(inRTmpL),010h
  paddw mm(inRTmpH),mm(inRTmpL)
  psllq mm(inRTmpH),0Fh
  paddw mm(inRTmpH),mm(inRTmp)
  psrlq mm(inRTmpH),03Fh
  psllq mm(inRTmpH),03Fh

  ; Append new bit
  psrlq mm(inRSL),01h
  movq mm(inRTmp),mm(inRSH)
  psllq mm(inRTmp),03Fh
  por mm(inRSL),mm(inRTmp)
  psrlq mm(inRSH),01h
  por mm(inRSH),mm(inRTmpH)
Sign up to leave a comment.

Articles