Zakładając, że mamy a T myarray[100]
z T = int, unsigned int, long long int lub unsigned long long int, jaki jest najszybszy sposób na zresetowanie całej jego zawartości do zera (nie tylko do inicjalizacji, ale do resetowania zawartości kilka razy w moim programie) ? Może z memsetem?
To samo pytanie dla tablicy dynamicznej, takiej jak T *myarray = new T[100]
.
new
to C ++ ...memset
że C ++ jest w jakiś sposób zaangażowany ... :)for
pętli. Ale, co zaskakujące, możesz zrobić znacznie gorzej, starając się być sprytnym.Odpowiedzi:
memset
(from<string.h>
) jest prawdopodobnie najszybszym standardowym sposobem, ponieważ zwykle jest to procedura napisana bezpośrednio w asemblerze i zoptymalizowana ręcznie.Nawiasem mówiąc, w C ++ idiomatycznym sposobem byłoby użycie
std::fill
(from<algorithm>
):który może zostać automatycznie zoptymalizowany do postaci
memset
; Jestem pewien, że będzie działać tak szybko, jakmemset
dlaint
s, podczas gdy może działać nieco gorzej dla mniejszych typów, jeśli optymalizator nie jest wystarczająco inteligentny. Mimo to, jeśli masz wątpliwości, podaj profil.źródło
memset
ustawienie liczby całkowitej na 0; nie było konkretnego stwierdzenia, którego reprezentacją są wszystkie bity-zero0
. Taką gwarancję dodano w sprostowaniu technicznym zawartym w normie ISO C z 2011 roku. Uważam, że wszystkie bity-zero są poprawną reprezentacją0
wszystkich typów liczb całkowitych we wszystkich istniejących implementacjach C i C ++, dlatego komisja mogła dodać to wymaganie. (Nie ma podobnej gwarancji dla typów zmiennoprzecinkowych lub wskaźnikowych.)0
. (Przy wypełnianiu bitów istnieje możliwość, że wszystkie bity-zero mogą być reprezentacją pułapki). Ale w każdym razie TC powinien potwierdzić i wymienić wadliwy tekst, więc od 2004 roku powinniśmy postępować tak, jakby C99 zawsze zawierał ten tekst.int (*myarray)[N] = malloc(sizeof(*myarray));
.N
jest, ale w zdecydowanej większości przypadków, jeśli używaszmalloc
, wiedziałeś tylko w czasie wykonywania.To pytanie, choć dość stare, wymaga pewnych wzorców, ponieważ wymaga nie najbardziej idiomatycznego sposobu lub sposobu, w jaki można zapisać jak najmniejszą liczbę wierszy, ale w najszybszy sposób. I głupio jest odpowiadać na to pytanie bez rzeczywistych testów. Porównałem więc cztery rozwiązania, memset vs. std :: fill vs. ZERO odpowiedzi AnT z rozwiązaniem, które stworzyłem przy użyciu funkcji AVX intrinsics.
Zauważ, że to rozwiązanie nie jest ogólne, działa tylko na danych 32- lub 64-bitowych. Prosimy o komentarz, jeśli ten kod działa nieprawidłowo.
Nie twierdzę, że jest to najszybsza metoda, ponieważ nie jestem ekspertem od optymalizacji niskiego poziomu. Jest to raczej przykład poprawnej implementacji zależnej od architektury, która jest szybsza niż memset.
Teraz przejdźmy do wyników. Obliczyłem wydajność dla tablic int i long long o rozmiarze 100, zarówno przydzielonych statycznie, jak i dynamicznie, ale z wyjątkiem msvc, który eliminował martwy kod na tablicach statycznych, wyniki były niezwykle porównywalne, więc pokażę tylko wydajność tablicy dynamicznej. Oznaczenia czasu są ms dla 1 miliona iteracji, przy użyciu funkcji zegara o niskiej precyzji time.h.
clang 3.8 (Używając nakładki clang-cl, flagi optymalizacji = / OX / arch: AVX / Oi / Ot)
gcc 5.1.0 (flagi optymalizacji: -O3 -march = natywne -mtune = natywne -mavx):
msvc 2015 (flagi optymalizacji: / OX / arch: AVX / Oi / Ot):
Dzieje się tu wiele interesujących rzeczy: llvm zabijanie gcc, typowe nieregularne optymalizacje MSVC (robi imponującą eliminację martwego kodu na statycznych tablicach, a następnie ma okropną wydajność wypełniania). Chociaż moja implementacja jest znacznie szybsza, może to wynikać tylko z tego, że uznaje, że czyszczenie bitów ma znacznie mniejszy narzut niż jakakolwiek inna operacja ustawiania.
Wdrożenie Clanga zasługuje na dokładniejsze przyjrzenie się, ponieważ jest znacznie szybsze. Niektóre dodatkowe testy pokazują, że jego memset jest w rzeczywistości wyspecjalizowany dla zerowego - niezerowe zestawy memów dla tablicy 400-bajtowej są znacznie wolniejsze (~ 220 ms) i są porównywalne z gcc. Jednak niezerowe memsetting z tablicą 800-bajtową nie robi różnicy w szybkości, i prawdopodobnie dlatego w tym przypadku ich memset ma gorszą wydajność niż moja implementacja - specjalizacja dotyczy tylko małych tablic, a odcięcie wynosi około 800 bajtów. Zauważ również, że gcc 'fill' i 'ZERO' nie optymalizują się do memset (patrząc na wygenerowany kod), gcc po prostu generuje kod o identycznej charakterystyce wydajności.
Wniosek: memset nie jest tak naprawdę zoptymalizowany do tego zadania, jak ludzie by to udawali (w przeciwnym razie gcc i msvc oraz memset llvm miałyby taką samą wydajność). Jeśli wydajność ma znaczenie, to memset nie powinien być ostatecznym rozwiązaniem, szczególnie w przypadku tych niewygodnych tablic średniej wielkości, ponieważ nie jest wyspecjalizowany w czyszczeniu bitów i nie jest zoptymalizowany ręcznie ani lepiej, niż może to zrobić sam kompilator.
źródło
a
pasuje do rejestru. Następnie wykonuje pętlę po wszystkich 32-bajtowych blokach, które powinny zostać całkowicie nadpisane przy użyciu arytmetyki wskaźników ((float *)((a)+x)
). Dwie_mm256
funkcje wewnętrzne (zaczynające się od ) po prostu tworzą zainicjowany zerem rejestr 32-bajtowy i przechowują go w bieżącym wskaźniku. To są pierwsze 3 wiersze. Reszta obsługuje tylko wszystkie specjalne przypadki, w których ostatni 32-bajtowy blok nie powinien zostać całkowicie nadpisany. Jest szybszy dzięki wektoryzacji. - Mam nadzieję że to pomogło.Od
memset()
:Możesz użyć,
sizeof(myarray)
jeśli rozmiarmyarray
jest znany w czasie kompilacji. W przeciwnym razie, jeśli używasz tablicy o rozmiarze dynamicznym, takiej jak uzyskana za pośrednictwemmalloc
lubnew
, będziesz musiał śledzić długość.źródło
sizeof
jest zawsze oceniany w czasie kompilacji (i nie może być używany z VLA). W C99 może to być wyrażenie środowiska wykonawczego w przypadku VLA.c
ic++
. Skomentowałem odpowiedź Alexa, która mówi: „Możesz użyć sizeof (myarray), jeśli rozmiar myarray jest znany w czasie kompilacji”.Możesz użyć
memset
, ale tylko dlatego, że nasz wybór typów jest ograniczony do typów całkowitych.W ogólnym przypadku w C ma sens implementacja makra
To da ci funkcjonalność podobną do C ++, która pozwoli ci "zresetować do zera" tablicę obiektów dowolnego typu bez konieczności uciekania się do takich hacków
memset
. Zasadniczo jest to analog C ++ do szablonu funkcji, z tą różnicą, że musisz jawnie określić argument typu.Oprócz tego możesz zbudować „szablon” dla niezepsutych tablic
W twoim przykładzie zostanie zastosowany jako
Warto również zauważyć, że specjalnie dla obiektów typu skalarnego można zaimplementować makro niezależne od typu
i
zmieniając powyższy przykład w
źródło
;
powhile(0)
, tak można nazwaćZERO(a,n);
, +1 wielką odpowiedźdo{}while(0)
idiomu nie wymaga;
definicji makra. Naprawiony.Myślę, że do deklaracji statycznej można użyć:
W przypadku deklaracji dynamicznej proponuję ten sam sposób:
memset
źródło
zero(myarray);
to wszystko, czego potrzebujesz w C ++.Po prostu dodaj to do nagłówka:
źródło
zero
jest również poprawna np. Tak,T=char[10]
jak mogłoby być w przypadku, gdyarr
argumentem jest tablica wielowymiarowa, npchar arr[5][10]
.ARRAY_SIZE
makro, które podaje niewłaściwy rozmiar, jeśli jest używane w wielowymiarowej tablicy, być może lepsza byłaby nazwaARRAY_DIM<n>_SIZE
.Oto funkcja, której używam:
Możesz to nazwać tak:
Powyżej jest bardziej C ++ 11 niż użycie memset. Otrzymujesz również błąd czasu kompilacji, jeśli używasz tablicy dynamicznej z określeniem rozmiaru.
źródło