Byłem zaskoczony, widząc w źródle Java, że System.arraycopy jest metodą natywną.
Oczywiście powodem jest to, że jest szybszy. Ale jakie natywne sztuczki jest w stanie zastosować kod, które czynią go szybszym?
Dlaczego nie po prostu zapętlić oryginalną tablicę i skopiować każdy wskaźnik do nowej tablicy - z pewnością nie jest to takie powolne i uciążliwe?
arraycopy
można zaimplementować przy użyciumemcpy
/memmove
. Inne wymagają sprawdzenia typu w czasie wykonywania dla każdego skopiowanego elementu.Object[]
wypełnionegoString
obiektami do plikuString[]
. Zobacz ostatni akapit java.sun.com/javase/6/docs/api/java/lang/…Nie można go napisać w Javie. Kod natywny może zignorować lub wyeliminować różnicę między tablicami Object i tablicami prymitywów. Java nie może tego zrobić, a przynajmniej nie wydajnie.
I nie można tego napisać pojedynczym
memcpy()
, ze względu na semantykę wymaganą przez nakładające się tablice.źródło
memmove
. Chociaż nie sądzę, że ma to duże znaczenie w kontekście tego pytania.Jest to oczywiście zależne od implementacji.
HotSpot potraktuje to jako „wewnętrzną” i wstawi kod w miejscu wywołania. To jest kod maszynowy, a nie wolny stary kod C. Oznacza to również, że problemy z podpisem metody w dużej mierze znikną.
Prosta pętla kopiowania jest na tyle prosta, że można do niej zastosować oczywiste optymalizacje. Na przykład rozwijanie pętli. Dokładnie to, co się dzieje, jest ponownie zależne od implementacji.
źródło
W moich własnych testach System.arraycopy () do kopiowania tablic wielowymiarowych jest 10 do 20 razy szybszy niż przeplatanie dla pętli:
float[][] foo = mLoadMillionsOfPoints(); // result is a float[1200000][9] float[][] fooCpy = new float[foo.length][foo[0].length]; long lTime = System.currentTimeMillis(); System.arraycopy(foo, 0, fooCpy, 0, foo.length); System.out.println("native duration: " + (System.currentTimeMillis() - lTime) + " ms"); lTime = System.currentTimeMillis(); for (int i = 0; i < foo.length; i++) { for (int j = 0; j < foo[0].length; j++) { fooCpy[i][j] = foo[i][j]; } } System.out.println("System.arraycopy() duration: " + (System.currentTimeMillis() - lTime) + " ms"); for (int i = 0; i < foo.length; i++) { for (int j = 0; j < foo[0].length; j++) { if (fooCpy[i][j] != foo[i][j]) { System.err.println("ERROR at " + i + ", " + j); } } }
To drukuje:
System.arraycopy() duration: 1 ms loop duration: 16 ms
źródło
System.arraycopy
wykonuje płytką kopię (kopiowane są tylko odniesienia do wewnętrznychfloat[]
s), podczas gdy twoje zagnieżdżonefor
-loops wykonuje głęboką kopię (float
przezfloat
). Zmiana nafooCpy[i][j]
zostanie odzwierciedlona wfoo
użyciuSystem.arraycopy
, ale nie będzie używać zagnieżdżonychfor
pętli.Jest kilka powodów:
Jest mało prawdopodobne, aby JIT wygenerował tak wydajny kod niskiego poziomu, jak ręcznie napisany kod C. Użycie niskiego poziomu C może umożliwić wiele optymalizacji, które są prawie niemożliwe do wykonania dla ogólnego kompilatora JIT.
Zobacz ten link po kilka sztuczek i porównań prędkości ręcznie napisanych implementacji C (memcpy, ale zasada jest taka sama): Sprawdź to Optymalizacja Memcpy poprawia szybkość
Wersja C jest prawie niezależna od typu i rozmiaru elementów tablicy. Nie można zrobić tego samego w Javie, ponieważ nie ma możliwości pobrania zawartości tablicy jako surowego bloku pamięci (np. Wskaźnika).
źródło