SIMD

From Wiki.conus.info

Jump to: navigation, search

Single instruction, multiple data.

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

Это, как и FPU, также выглядит отдельным процессором внутри x86.

В x86 все началось с MMX. Появилось 8 64-битных регистров MM0-MM7.

Каждый регистр может содержать 2 32-битных значения, 4 16-битных или же 8 байт. Можно, например, складывая значения в двух MMX-регистрах, складывать одновременно 8 значений, каждое из которых 8-битное.

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

Когда MMX только появилось, эти регистры на самом деле распологались в FPU. Можно было использовать либо FPU или MMX в одно и то же время. Можно подумать что Intel решило немного сэкономить на транзисторах, но на самом деле причина проще - операционная система не знающая о дополнительных регистрах процессора не будет сохранять их во время переключения задач, а вот регистры FPU сохранять будет. Таким образом, процессор с MMX + старая операционная система + задача использующая MMX = все это может работать вместе.

SSE - это расширение регистров до 128 бит, теперь уже отдельно от FPU.

Имеются также планы расширения регистров до 256 бит (AVX).

Немного о практическом применении.

Конечно же, копирование блоков в памяти (memcpy), сравнение (memcmp), и подобное.

Еще пример: имеется алгоритм шифрования DES, который берет 64-битный блок, 56-битный ключ, шифрует блок с ключем и образуется 64-битный результат. Алгоритм DES можно легко представить в виде очень большой электронной цифровой схемы, с проводами, элементами И, ИЛИ, НЕ.

Идея bitslice DES - это обработка сразу группы блоков-ключей одновременно. Скажем, на x86 перменная типа unsigned int вмещает в себе 32 бита, так что там можно хранить промежуточные результаты сразу для 32-х блоков-ключей.

http://www.darkside.com.au/bitslice/

Я написал утилиту для перебора паролей/хешей Oracle RDBMS (которые основаны на алгоритме DES), только переделал алгоритм bitslice DES для SSE2 - и теперь возможно шифровать одновременно 128 блоков-ключей, там и исходники:

http://conus.info/utils/#ops_sse2

Contents

1

Теперь чуть сложнее.

Насчет векторизации.

http://en.wikipedia.org/wiki/Vectorization_(computer_science)

Векторизация это когда у вас есть цикл, который берет на вход несколько массивов и выдает, например, один массив данных. Тело цикла берет некоторые элементы из входных массивов, что-то делает с ними и кладет в выходной. Важно что операция применяемая ко всем элементам одна и та же. Векторизация это обрабатывать одновременно несколько элементов.

Например:

for (i = 0; i < 1024; i++)
{
    C[i] = A[i]*B[i];
}

Этот кусок кода берет элемент из A и B, перемножает и отправляет результат в C.

Если представить что каждый элемент массива - это 32-битный integer, то их можно загружать сразу по 4 из А в 128-битный XMM-регистр, из B в другой XMM-регистр и выполнив инстукцию PMULLD (Multiply Packed Signed Dword Integers and Store Low Result) и PMULHW (Multiply Packed Signed Integers and Store High Result), можно получить 4 64-битных результата умножения.

Таким образом, тело цикла можно будет исполнять не 1024 раза, а 1024/4 раза, что в 4 раза меньше, и, возможно, быстрее.

Некоторые компиляторы немного умеют делать автоматическую векторизацию, например Intel C++.

Я написал очень простую функцию:

int f (int sz, int *ar1, int *ar2, int *ar3)
{
	for (int i=0; i<sz; i++)
		ar3[i]=ar1[i]+ar2[i];
 
	return 0;
};

Intel C++

Компилю при помощи Intel C++ 11.1.051 win32:

icl intel.cpp /QaxSSE2 /Faintel.asm  /Ox

Имеем такое (в IDA):

; int __cdecl f(int, int *, int *, int *)
                public ?f@@YAHHPAH00@Z
?f@@YAHHPAH00@Z proc near
 
var_10          = dword ptr -10h
sz              = dword ptr  4
ar1             = dword ptr  8
ar2             = dword ptr  0Ch
ar3             = dword ptr  10h
 
                push    edi
                push    esi
                push    ebx
                push    esi
                mov     edx, [esp+10h+sz]
                test    edx, edx
                jle     loc_15B
                mov     eax, [esp+10h+ar3]
                cmp     edx, 6
                jle     loc_143
                cmp     eax, [esp+10h+ar2]
                jbe     short loc_36
                mov     esi, [esp+10h+ar2]
                sub     esi, eax
                lea     ecx, ds:0[edx*4]
                neg     esi
                cmp     ecx, esi
                jbe     short loc_55
 
loc_36:                                 ; CODE XREF: f(int,int *,int *,int *)+21�j
                cmp     eax, [esp+10h+ar2]
                jnb     loc_143
                mov     esi, [esp+10h+ar2]
                sub     esi, eax
                lea     ecx, ds:0[edx*4]
                cmp     esi, ecx
                jb      loc_143
 
loc_55:                                 ; CODE XREF: f(int,int *,int *,int *)+34�j
                cmp     eax, [esp+10h+ar1]
                jbe     short loc_67
                mov     esi, [esp+10h+ar1]
                sub     esi, eax
                neg     esi
                cmp     ecx, esi
                jbe     short loc_7F
 
loc_67:                                 ; CODE XREF: f(int,int *,int *,int *)+59�j
                cmp     eax, [esp+10h+ar1]
                jnb     loc_143
                mov     esi, [esp+10h+ar1]
                sub     esi, eax
                cmp     esi, ecx
                jb      loc_143
 
loc_7F:                                 ; CODE XREF: f(int,int *,int *,int *)+65�j
                mov     edi, eax        ; edi = ar1
                and     edi, 0Fh        ; is ar1 16-byte aligned?
                jz      short loc_9A    ; yes
                test    edi, 3
                jnz     loc_162
                neg     edi
                add     edi, 10h
                shr     edi, 2
 
loc_9A:                                 ; CODE XREF: f(int,int *,int *,int *)+84�j
                lea     ecx, [edi+4]
                cmp     edx, ecx
                jl      loc_162
                mov     ecx, edx
                sub     ecx, edi
                and     ecx, 3
                neg     ecx
                add     ecx, edx
                test    edi, edi
                jbe     short loc_D6
                mov     ebx, [esp+10h+ar2]
                mov     [esp+10h+var_10], ecx
                mov     ecx, [esp+10h+ar1]
                xor     esi, esi
 
loc_C1:                                 ; CODE XREF: f(int,int *,int *,int *)+CD�j
                mov     edx, [ecx+esi*4]
                add     edx, [ebx+esi*4]
                mov     [eax+esi*4], edx
                inc     esi
                cmp     esi, edi
                jb      short loc_C1
                mov     ecx, [esp+10h+var_10]
                mov     edx, [esp+10h+sz]
 
loc_D6:                                 ; CODE XREF: f(int,int *,int *,int *)+B2�j
                mov     esi, [esp+10h+ar2]
                lea     esi, [esi+edi*4] ; is ar2+i*4 16-byte aligned?
                test    esi, 0Fh
                jz      short loc_109   ; yes!
                mov     ebx, [esp+10h+ar1]
                mov     esi, [esp+10h+ar2]
 
loc_ED:                                 ; CODE XREF: f(int,int *,int *,int *)+105�j
                movdqu  xmm1, xmmword ptr [ebx+edi*4]
                movdqu  xmm0, xmmword ptr [esi+edi*4] ; ar2+i*4 is not 16-byte aligned, so load it to xmm0
                paddd   xmm1, xmm0
                movdqa  xmmword ptr [eax+edi*4], xmm1
                add     edi, 4
                cmp     edi, ecx
                jb      short loc_ED
                jmp     short loc_127
; ---------------------------------------------------------------------------
 
loc_109:                                ; CODE XREF: f(int,int *,int *,int *)+E3�j
                mov     ebx, [esp+10h+ar1]
                mov     esi, [esp+10h+ar2]
 
loc_111:                                ; CODE XREF: f(int,int *,int *,int *)+125�j
                movdqu  xmm0, xmmword ptr [ebx+edi*4]
                paddd   xmm0, xmmword ptr [esi+edi*4]
                movdqa  xmmword ptr [eax+edi*4], xmm0
                add     edi, 4
                cmp     edi, ecx
                jb      short loc_111
 
loc_127:                                ; CODE XREF: f(int,int *,int *,int *)+107�j
                                        ; f(int,int *,int *,int *)+164�j
                cmp     ecx, edx
                jnb     short loc_15B
                mov     esi, [esp+10h+ar1]
                mov     edi, [esp+10h+ar2]
 
loc_133:                                ; CODE XREF: f(int,int *,int *,int *)+13F�j
                mov     ebx, [esi+ecx*4]
                add     ebx, [edi+ecx*4]
                mov     [eax+ecx*4], ebx
                inc     ecx
                cmp     ecx, edx
                jb      short loc_133
                jmp     short loc_15B
; ---------------------------------------------------------------------------
 
loc_143:                                ; CODE XREF: f(int,int *,int *,int *)+17�j
                                        ; f(int,int *,int *,int *)+3A�j ...
                mov     esi, [esp+10h+ar1]
                mov     edi, [esp+10h+ar2]
                xor     ecx, ecx
 
loc_14D:                                ; CODE XREF: f(int,int *,int *,int *)+159�j
                mov     ebx, [esi+ecx*4]
                add     ebx, [edi+ecx*4]
                mov     [eax+ecx*4], ebx
                inc     ecx
                cmp     ecx, edx
                jb      short loc_14D
 
loc_15B:                                ; CODE XREF: f(int,int *,int *,int *)+A�j
                                        ; f(int,int *,int *,int *)+129�j ...
                xor     eax, eax
                pop     ecx
                pop     ebx
                pop     esi
                pop     edi
                retn
; ---------------------------------------------------------------------------
 
loc_162:                                ; CODE XREF: f(int,int *,int *,int *)+8C�j
                                        ; f(int,int *,int *,int *)+9F�j
                xor     ecx, ecx
                jmp     short loc_127
?f@@YAHHPAH00@Z endp

Инструкции которые имеют отношение к SSE2 это:

MOVDQU (Move Unaligned Double Quadword) - она просто загружает 16 байт из памяти в XMM-регистр.

PADDD (Add Packed Integers) - складывает сразу 4 пары чисел и оставит в первом операнде результат. Кстати, если произойдет переполнение, то исключения не произойдет, запишутся просто младшие 32 бита результата. Эти инструкции не выставляют никаких флагов. Если один из операндов PADDD - адрес в памяти, то требуется чтобы адрес был выровнен по 16-байтной границе. Если он не выровнен, произойдет исключение.

MOVDQA (Move Aligned Double Quadword) - тоже что и MOVDQU, только подразумевает что адрес в памяти выровнен по 16-байтной границе. Если он не выровнен, произойдет исключение. MOVDQA работает быстрее чем MOVDQU, но требует вышеозначенного.

См. также: http://en.wikipedia.org/wiki/Data_structure_alignment

Итак, SSE2-инструкции исполнятся только в том случае если осталось просуммировать 4 пары плюс если указатель ar3 выровнен по 16-байтной границе.

Более того, если еще и ar2 выровнен по 16-байтной границе, то будет выполняться этот кусок:

                movdqu  xmm0, xmmword ptr [ebx+edi*4] ; ar1+i*4
                paddd   xmm0, xmmword ptr [esi+edi*4] ; ar2+i*4
                movdqa  xmmword ptr [eax+edi*4], xmm0 ; ar3+i*4

А иначе, значение из ar2 загрузится в xmm0 используя инструкцию MOVDQU, которая не требует выровненного указателя в памяти, зато может работать чуть медленнее:

                movdqu  xmm1, xmmword ptr [ebx+edi*4] ; ar1+i*4
                movdqu  xmm0, xmmword ptr [esi+edi*4] ; ar2+i*4 is not 16-byte aligned, so load it to xmm0
                paddd   xmm1, xmm0
                movdqa  xmmword ptr [eax+edi*4], xmm1 ; ar3+i*4

А во всех остальных случаях, будет исполняться код, который был бы как если бы не была включена поддержка SSE2.

Еще о том как Intel C++ умеет автоматически векторизировать циклы: http://www.intel.com/intelpress/sum_vmmx.htm (Excerpt: Effective Automatic Vectorization)

GCC

Но и GCC умеет кое-что векторизировать, если компилировать с опциями -O3 и включить поддержку SSE2: -msse2.

Вот что вышло (GCC 4.4.1):

; f(int, int *, int *, int *)
                public _Z1fiPiS_S_
_Z1fiPiS_S_     proc near
 
var_18          = dword ptr -18h
var_14          = dword ptr -14h
var_10          = dword ptr -10h
arg_0           = dword ptr  8
arg_4           = dword ptr  0Ch
arg_8           = dword ptr  10h
arg_C           = dword ptr  14h
 
                push    ebp
                mov     ebp, esp
                push    edi
                push    esi
                push    ebx
                sub     esp, 0Ch
                mov     ecx, [ebp+arg_0]
                mov     esi, [ebp+arg_4]
                mov     edi, [ebp+arg_8]
                mov     ebx, [ebp+arg_C]
                test    ecx, ecx
                jle     short loc_80484D8
                cmp     ecx, 6
                lea     eax, [ebx+10h]
                ja      short loc_80484E8
 
loc_80484C1:                            ; CODE XREF: f(int,int *,int *,int *)+4B�j
                                        ; f(int,int *,int *,int *)+61�j ...
                xor     eax, eax
                nop
                lea     esi, [esi+0]
 
loc_80484C8:                            ; CODE XREF: f(int,int *,int *,int *)+36�j
                mov     edx, [edi+eax*4]
                add     edx, [esi+eax*4]
                mov     [ebx+eax*4], edx
                add     eax, 1
                cmp     eax, ecx
                jnz     short loc_80484C8
 
loc_80484D8:                            ; CODE XREF: f(int,int *,int *,int *)+17�j
                                        ; f(int,int *,int *,int *)+A5�j
                add     esp, 0Ch
                xor     eax, eax
                pop     ebx
                pop     esi
                pop     edi
                pop     ebp
                retn
; ---------------------------------------------------------------------------
                align 8
 
loc_80484E8:                            ; CODE XREF: f(int,int *,int *,int *)+1F�j
                test    bl, 0Fh
                jnz     short loc_80484C1
                lea     edx, [esi+10h]
                cmp     ebx, edx
                jbe     loc_8048578
 
loc_80484F8:                            ; CODE XREF: f(int,int *,int *,int *)+E0�j
                lea     edx, [edi+10h]
                cmp     ebx, edx
                ja      short loc_8048503
                cmp     edi, eax
                jbe     short loc_80484C1
 
loc_8048503:                            ; CODE XREF: f(int,int *,int *,int *)+5D�j
                mov     eax, ecx
                shr     eax, 2
                mov     [ebp+var_14], eax
                shl     eax, 2
                test    eax, eax
                mov     [ebp+var_10], eax
                jz      short loc_8048547
                mov     [ebp+var_18], ecx
                mov     ecx, [ebp+var_14]
                xor     eax, eax
                xor     edx, edx
                nop
 
loc_8048520:                            ; CODE XREF: f(int,int *,int *,int *)+9B�j
                movdqu  xmm1, xmmword ptr [edi+eax]
                movdqu  xmm0, xmmword ptr [esi+eax]
                add     edx, 1
                paddd   xmm0, xmm1
                movdqa  xmmword ptr [ebx+eax], xmm0
                add     eax, 10h
                cmp     edx, ecx
                jb      short loc_8048520
                mov     ecx, [ebp+var_18]
                mov     eax, [ebp+var_10]
                cmp     ecx, eax
                jz      short loc_80484D8
 
loc_8048547:                            ; CODE XREF: f(int,int *,int *,int *)+73�j
                lea     edx, ds:0[eax*4]
                add     esi, edx
                add     edi, edx
                add     ebx, edx
                lea     esi, [esi+0]
 
loc_8048558:                            ; CODE XREF: f(int,int *,int *,int *)+CC�j
                mov     edx, [edi]
                add     eax, 1
                add     edi, 4
                add     edx, [esi]
                add     esi, 4
                mov     [ebx], edx
                add     ebx, 4
                cmp     ecx, eax
                jg      short loc_8048558
                add     esp, 0Ch
                xor     eax, eax
                pop     ebx
                pop     esi
                pop     edi
                pop     ebp
                retn
; ---------------------------------------------------------------------------
 
loc_8048578:                            ; CODE XREF: f(int,int *,int *,int *)+52�j
                cmp     eax, esi
                jnb     loc_80484C1
                jmp     loc_80484F8
_Z1fiPiS_S_     endp

Почти то же самое, хотя и не так дотошно как Intel C++.

Подробнее о GCC: http://gcc.gnu.org/projects/tree-ssa/vectorization.html

2

Прежде всего, следует заметить, что SIMD-инструкции можно вставлять в код при помощи специальных макросов.

http://msdn.microsoft.com/en-us/library/y0dh78ez(VS.80).aspx

В MSVC, часть находится в файле intrin.h.

Имеется возможность сделать strlen() при помощи SIMD-инструкций, работающий в 2-2.5 раза быстрее обычного. Эта функция будет загружать в XMM-регистр сразу 16 байт и проверять каждый на ноль.

size_t strlen_sse2(const char *str)
{
	register size_t len = 0;
	const char *s=str;
	bool str_is_aligned=(((unsigned int)str)&0xFFFFFFF0) == (unsigned int)str;
 
	if (str_is_aligned==false)
		return strlen (str);
 
	__m128i xmm0 = _mm_setzero_si128();
	__m128i xmm1;
	int mask = 0;
 
	for (;;)
	{
		xmm1 = _mm_load_si128((__m128i *)s);
		xmm1 = _mm_cmpeq_epi8(xmm1, xmm0);
		if ((mask = _mm_movemask_epi8(xmm1)) != 0)
		{
			unsigned long pos;
			_BitScanForward(&pos, mask);
			len += (size_t)pos;
 
			break;
		}
		s += sizeof(__m128i);
		len += sizeof(__m128i);
	};
 
	return len;
}

(пример базируется на исходнике отсюда).

Компилируем в MSVC 2010 с опцией /Ox:

_pos$75552 = -4						; size = 4
_str$ = 8						; size = 4
?strlen_sse2@@YAIPBD@Z PROC				; strlen_sse2
 
	push	ebp
	mov	ebp, esp
	and	esp, -16				; fffffff0H
	mov	eax, DWORD PTR _str$[ebp]
	sub	esp, 12					; 0000000cH
	push	esi
	mov	esi, eax
	and	esi, -16				; fffffff0H
	xor	edx, edx
	mov	ecx, eax
	cmp	esi, eax
	je	SHORT $LN4@strlen_sse
	lea	edx, DWORD PTR [eax+1]
	npad	3
$LL11@strlen_sse:
	mov	cl, BYTE PTR [eax]
	inc	eax
	test	cl, cl
	jne	SHORT $LL11@strlen_sse
	sub	eax, edx
	pop	esi
	mov	esp, ebp
	pop	ebp
	ret	0
$LN4@strlen_sse:
	movdqa	xmm1, XMMWORD PTR [eax]
	pxor	xmm0, xmm0
	pcmpeqb	xmm1, xmm0
	pmovmskb eax, xmm1
	test	eax, eax
	jne	SHORT $LN9@strlen_sse
$LL3@strlen_sse:
	movdqa	xmm1, XMMWORD PTR [ecx+16]
	add	ecx, 16					; 00000010H
	pcmpeqb	xmm1, xmm0
	add	edx, 16					; 00000010H
	pmovmskb eax, xmm1
	test	eax, eax
	je	SHORT $LL3@strlen_sse
$LN9@strlen_sse:
	bsf	eax, eax
	mov	ecx, eax
	mov	DWORD PTR _pos$75552[esp+16], eax
	lea	eax, DWORD PTR [ecx+edx]
	pop	esi
	mov	esp, ebp
	pop	ebp
	ret	0
?strlen_sse2@@YAIPBD@Z ENDP				; strlen_sse2

Итак, прежде всего, мы проверяем указатель str, выровнен ли он по 16-байтной границе. Если нет, то мы вызовем обычную strlen().

Далее мы загружаем по 16 байт в регистр XMM при помощи команды MOVDQA.

Наблюдательный читатель может спросить, почему в этом месте мы не можем использовать MOVDQU, которая может загружать откуда угодно не взирая на тот факт, выровнен ли указатель?

Да, можно было бы сделать вот как: если указатель выровнен, загружаем используя MOVDQA, иначе используем работающую чуть медленнее MOVDQU.

Однако здесь имеется не сразу заметная проблема, которая проявляется вот в чем:

В ОС линии Windows NT, и не только, память выделяется страницами по 4 кибибайта (4096). Каждый win32-процесс якобы имеет в наличии 4 гибибайта, но на самом деле, только некоторые части этого адресного пространства присоеденены к реальной физической памяти. Если процесс обратится к блоку памяти, которого не существует, сработает исключение. Так работает виртуальная память.

http://en.wikipedia.org/wiki/Page_(computer_memory)

Так вот, функция, читающая сразу по 16 байт, имеет возможность нечаянно вылезти за границу выделенного блока памяти. Предположим, ОС выделила программе 8192 (0x2000) байт по адресу 0x008c0000. Таким образом, блок занимает байты с адреса 0x008c0000 по 0x008c1fff включительно.

За этим блоком, то есть начиная с адреса 0x008c2000 нет вообще ничего, т.е., ОС не выделила для нас ничего. Обращение к памяти начиная с этого адреса вызовет исключение.

И предположим, что программа хранит некую строку из, скажем, пяти символов почти в самом конце блока, что не является преступлением:

0x008c1ff8 - 'h'
0x008c1ff9 - 'e'
0x008c1ffa - 'l'
0x008c1ffb - 'l'
0x008c1ffc - 'o'
0x008c1ffd - '\x00'
0x008c1ffe - здесь случайный мусор
0x008c1fff - здесь случайный мусор

Затем программа хочет вызвать strlen() передав ей указатель на строку 'hello' по адресу 0x008c1ff8. strlen() будет читать по одному байту до 0x008c1ffd, где ноль, и здесь он закончит.

Теперь, если мы напишем strlen() читающий сразу по 16 байт, с любого адреса, будь он выровнен по 16-байтной границе или нет, MOVDQU попытается загрузить 16 байт с адреса 0x008c1ff8 по 0x008c2008, и произойдет исключение. Это ситуация которой, конечно, хочется избежать.

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

Вернемся к нашей функции.

_mm_setzero_si128 это макрос, генерирующий pxor xmm0, xmm0 - это просто обнуляет регистр XMM0.

_mm_load_si128 - это макрос для MOVDQA, он просто загружает 16 байт по адресу из указателя в XMM1.

_mm_cmpeq_epi8 - это макрос для PCMPEQB, это инструкция которая побайтово сравнивает значения из двух XMM регистров. Иными словами, 16 раз сравниваются все байтовые части регистров. И если какой-то из байт равен другому, то в результирующем значении будет выставлено на месте этого байта 0xff, или 0, если байты не равны.

Например.

XMM1: 11223344556677880000000000000000
XMM0: 11ab3444007877881111111111111111

После исполнения pcmpeqb xmm1, xmm0, регистр XMM1 будет содержать:

XMM1: ff0000ff0000ffff0000000000000000

Эта инструкция в нашем случае, сравнивает каждый 16-байтный блок с блоком состоящим из 16-и нулей, выставленным в XMM0 при помощи pxor xmm0, xmm0.

Следующий макрос _mm_movemask_epi8 - это инструкция PMOVMSKB.

Она очень удобна как раз для использования в паре с PCMPEQB.

pmovmskb eax, xmm1

Эта инструкция выставит самый первый бит eax в еденицу, если старший бит первого байта в регистре XMM1 является еденицей. Иными словами, если первый байт в регистре XMM1 является 0xff, то первый бит в EAX будет также еденицей, иначе нулем. Если второй байт в регистре XMM1 является 0xff, то второй бит в EAX также будет еденицей. Иными словами, инструкция отвечает на вопрос, какие из байт в XMM1 являются 0xff, в результате приготовит 16 бит и запишет в EAX. Остальные биты в EAX обнулятся.

Кстати, не забывайте также вот о какой особенности нашего алгоритма.

На вход может прийти 16 байт вроде hello\x00garbage\x00ab

Это строка 'hello', после нее терминирующий ноль, затем немного мусора в памяти.

Если мы загрузим эти 16 байт в XMM1 сравним нулевым XMM0, то в итоге получим такое (я использую здесь порядок с MSB до LSB):

XMM1: 0000ff00000000000000ff0000000000

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

PMOVMSKB в нашем случае подготовит EAX вот так (в двоичном представлении): 0010000000100000b.

Совершенно очевидно что далее наша функция должна учитывать только первый встретившийся ноль и игнорировать все остальное.

Следующая инструкция - BSF (Bit Scan Forward). Это инструкция находит самый младший бит во втором операнде и записывает его позицию в первый операнд.

EAX=0010000000100000b

bsf eax, eax

После исполнения инструкции, в EAX будет 5, что означает, что еденица найдена в пятой позиции (считая с нуля).

Для использования этой инструкции, в MSVC также имеется макрос _BitScanForward.

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

Почти всё.

Кстати, следует также отметить, что компилятор MSVC закодировал два тела цикла сразу, для оптимизации.

Кстати, в SSE 4.2 (который появился в Intel Core i7) все эти манипуляции со строками могут быть еще проще: http://www.strchr.com/strcmp_and_strlen_using_sse_4.2

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox