2007-06-02

Jak ustawić tablicę integerów na zadaną wartość?

Mam proste zadanie. Ustawić tablicę kilku milionów intów na podaną wartość. (kompilaor gcc, procek p4/xeon)

Brzmi banalnie. Pierwszy kod:


void fill_standard(int bit_field, int *buf, int nodes_length){
int uid;
for(uid=0; uid < nodes_length; uid++)
*buf++ = bit_field;
}


Problem taki, że to strasznie długo trwa. W przypadku 16 milionów integerów zajmuje to 87ms.
No to może by to zaimplementować dla mmx?

void fill_mmx(int bit_field, int *buf, int nodes_length){
__m64 m = _mm_set1_pi32(bit_field); /*m = */
__m64 *ptr = (__m64*) buf;
int uid;
for(uid=0; uid < nodes_length/2; uid++)
*ptr++ = m;
}
Już lepiej. 47 milisekund. A może sse2?
void fill_sse(int bit_field, int *buf, int nodes_length){
__m128i *ptr = (__m128i*) buf;
__m128i s = _mm_set1_epi32(bit_field);
int uid;
for(uid=0; uid < nodes_length/4; uid++){
*ptr++ = s;
}
}
Hmm. 47 milisekund. Tyle ile w przypadku mmx. To ciekawe że nowoczesny sse2 nie jest szybszy.
A może by tak spróbować zapisywać do pamięci jakoś inaczej? Może na przykład z ominięciem cache?
void fill_sse_nocache(int bit_field, int *buf, int nodes_length){
__m128i *ptr = (__m128i*) buf;
__m128i s = _mm_set1_epi32(bit_field);
int uid;
for(uid=0; uid < nodes_length/4; uid++){
_mm_stream_si128(ptr++, s);
}
}
18 milisekund. Pięciokronie szybciej niż na początku, to już coś. Ciekawe czy da się szybciej. Pełen kod moich testów jest tu.

A gdyby tak mmapować jeden segment z jakiegoś plik który już jest ustawiony na dobrą warość?
No może to już kombinowanie :)

A może jest jakaś szybsza metoda?

UPDATE#1:
Ciekawe, dodanie prefetch do kodu sse3_nocache dwukrotnie wydłuża program :)
void fill_sse2(int bit_field, int *buf, int nodes_length){
__m128i *ptr = (__m128i*) buf;
__m128i s = _mm_set1_epi32(bit_field);
int uid;
for(uid=0; uid < nodes_length/4; uid++){
__builtin_prefetch(ptr+2, 1,1);
_mm_stream_si128(ptr++, s);
}
}
34ms, ale tego można się było spodziewać.

UPDATE#2:
Nie sprawdziłem tego wcześniej. Ale okazuje się że _mm_stream_pi32 dla MMX jest tak samo szybkie jak dla SSE.
void fill_mmx2(int bit_field, int *buf, int nodes_length){
__m64 m = _mm_set1_pi32(bit_field); /*m = */
__m64 *ptr = (__m64*) buf;
int uid;
for(uid=0; uid < nodes_length/2; uid++)
_mm_stream_pi(ptr++, m);
}
Też 16ms.

UPDATE#3:
Ciekawe, już pakowanie danych po 32 bity instrukcją _mm_stream_si32 nie daje dużego przyspieszenia.

UPDATE#4 Ważne:
No dobra. Wszystkie moje wyniki do śmieci... Okazuje się że pierwsze przelecenie przez tablicę jest bardzo wolne. Prawdopodobnie wynika to z tego że system musi obliczyć tablicę TLB.

Podsumowywując wyniki. Pierwszy przebieg jest bardzo wolny. Kolejne przebiegi używające _mm_stream_si32/_mm_stream_si/_mm_stream_si128 są dwukrotnie szybsze niż odpowiedniki 'konwencjonalne'.


No comments: