Niedawno miałem za zadanie wykonanie kilku kontroli prędkości, dzięki czemu mogę stwierdzić, czy szybciej użyć php/php-cli lub C++ do wstawienia pewnej liczby wierszy do bazy danych.Wstawianie MySQL, szybsze w PHP niż C++, jest to oczekiwane?
Zanim zaczniemy, powiem wam kilka szczegółów, więc wszystko jest jasne:
- Część php jest prowadzony przez Apache, wymagane bezpośrednio w przeglądarce.
- Testy dysku twardego są uruchamiane na dysku SSD. Sądzę, że w zwykłych napędach rzeczy będą wolniejsze. Maszyna sama w sobie nie jest niczym specjalnym, mniej więcej sześcioletnim.
- Wszystkie wkładki są wykonywane za pomocą przygotowanych instrukcji. Używamy mysqli na php i mysqlcppconn (mysql C++, dostarczone przez Oracle).
- Wszystkie wkładki są gotowe do wprowadzenia po wprowadzeniu. Wiem, że możemy je układać, ale dobrze, testujemy tutaj.
- Czasy są wyświetlane za pomocą mikrotomów w php i poprzez nagłówek w C++.
- Sam kod nie jest równoważny, oczywiście. Więcej o tym później.
- Cały tekst jest w formacie UTF-8. Tam jest rosyjski, chiński, arabski, hiszpański, angielski i wszelkiego rodzaju szalone rzeczy. Tabela mysql znajduje się w utf8_4mb.
- Liczby dla kodu C++ są wynikiem użycia poziomów std :: vector i -O2 kompilujących się z g ++ (wektory lepsze od map, unordered_maps i std :: arrays).
Tak, jest to proces:
- Połącz się z bazą danych.
- Otwórz plik tekstowy za pomocą N wierszy.
- Przeczytaj wiersz pliku.
- Podziel linię na znak separatora.
- Użyj pewnych części podzielonej linii, aby uzyskać wartości wstawiania (np. Indeksy 0, 1 i 3).
- Wyślij te części do przygotowanej instrukcji, aby je wstawić.
- Powtarzaj, aż plik zostanie całkowicie przeczytany.
Oba kody działają dokładnie zgodnie z oczekiwaniami. Oto wynikające numery:
php:
- 5000 wpisów: 1,42 - 1,27 sek.
- 20000 wpisów: 5,53 - 6,18 sek.
- 50000 wpisów: 14,43 - 15,69 sek.
C++:
- 5000 wpisów: 1,78 - 1,81 sek.
- 20000 wpisów: 7,19 - 7,22 sek.
- 50000 wpisów: 18,52 - 18,84 sek.
php wyprzedza C++, gdy linie w pliku rosną ... Początkowo podejrzewałem o funkcję dzielenia linii: dzielenie w php odbywa się za pomocą "explode". Algorytm jest tak naiwny jak dla C++ ... Kontener jest przekazywany przez referencję, a jego zawartość zmienia się w locie. Kontener jest przesuwany tylko raz. Upewniłem się, że pojemnik "rezerwuje()" wszystkie niezbędne miejsca (pamiętaj, ostatecznie wybrałem wektory), które zostały naprawione. Kontener jest tworzony na głównej funkcji, a następnie przekazywany przez odniesienie za pomocą kodu. Nigdy nie jest opróżniany ani zmieniany: zmienia się tylko jego zawartość.
template<typename container> void explode(const std::string& p_string, const char p_delimiter, container& p_result)
{
auto it=p_result.begin();
std::string::const_iterator beg=p_string.begin(), end=p_string.end();
std::string temp;
while(beg < end)
{
if((*beg)==p_delimiter)
{
*(it)=temp;
++it;
temp="";
}
else
{
temp+=*beg;
}
++beg;
}
*(it)=temp;
}
Jak powiedziano wcześniej, wykonywane zadanie jest równoważne, ale kod generujący go nie jest. Kod C++ ma standardowe bloki try-catch do kontrolowania interakcji mysql. Co do reszty, główna pętla działa aż do osiągnięcia EOF, a każda iteracja sprawdza, czy wstawienie nie powiodło się (zarówno w języku C++, jak i php).
Widziałem C++ znacznie przewyższające php w pracy z plikami i ich zawartością, więc spodziewałem się, że to samo będzie miało zastosowanie tutaj. Jakoś podejrzewam o algorytm podziału, ale może po prostu jest to, że złącze do bazy danych jest wolniejsze (nadal, gdy wyłączam interakcję z bazą danych, php jest nadal przetwarzane szybciej) lub mój kod jest podrzędny ...
Jeśli chodzi o profilowanie, gprof wypluł to się o kod C++:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ns/call ns/call name
60.00 0.03 0.03 50000 600.00 600.00 void anc_str::explotar_cadena<std::vector<std::string, std::allocator<std::string> > >(std::string const&, char, std::vector<std::string, std::allocator<std::string> >&)
40.00 0.05 0.02 insertar(sql::PreparedStatement*, std::string const&, std::vector<std::string, std::allocator<std::string> >&)
0.00 0.05 0.00 1 0.00 0.00 _GLOBAL__sub_I__ZN7anc_str21obtener_linea_archivoERSt14basic_ifstreamIcSt11char_traitsIcEE
Gdzie „explotar_cadena” jest „eksploduje” i „insertar” jest „podzielony tę linię i ustaw przygotowane oświadczenie w górę”. Jak widać 60% czasu tam spędzamy (nic dziwnego ... działa 50000 razy i robi to szalone dzielenie rzeczy). "obtener_linea_archivo" to po prostu "proszę, zrzuć następną linię do ciągu".
Bez mysql interakcji (wystarczy wczytać plik, czytać wiersze i podzielić je) uzyskać te pomiary:
php
- 5000 wpisów: 0,019 - 0,036 sek.
- 20000 wpisów: 0,09 - 0,10 sek.
- 50000 wpisów: 0,14 - 0,17 sek.
C++
- 5000 wpisów: 0,07 - 0,10 sek.
- 20000 wpisów: 0,25 - 0,26 sek.
- 50000 wpisów: 0,49 - 0,55 sek.
Dobra, oba razy są dobre i prawie niezauważalne dla prawdziwych warunków życia, jestem zaskoczony ... Więc pytanie brzmi: czy mam się tego spodziewać? Ktoś, kto ma wcześniejsze doświadczenie, gotów pomóc?
Z góry dziękuję.
Edytuj: Oto krótki link do obcinanej wersji zawierającej pliki wejściowe, kod C++ i kod php [http://www.datafilehost.com/d/d31034d6]. Zauważ, że nie ma interakcji sql: tylko otwieranie pliku, dzielenie łańcucha i mierzenie czasu. Proszę, wybaczcie zmasakrowany kod i pół hiszpańskie komentarze i nazwy zmiennych, ponieważ zrobiono to w pośpiechu. Zwróć też uwagę na wyniki gprof powyżej: nie jestem ekspertem, ale myślę, że staramy się znaleźć lepszy sposób podziału łańcucha.
Czy możesz poprosić o przetestowanie programu C++ za pomocą [Very Sleepy] (http://www.codersnotes.com/sleepy) i tutaj dodaj wyniki. –
Nie osoba z Windows tutaj, przepraszam ... Próbowałem gprof. Zmodyfikuje post, aby to odzwierciedlić. –
Usuń liczniki z kodu, użyj polecenia czasu systemu na konsoli, aby uzyskać pomiar. Nie wliczasz czasu uruchamiania i zamykania PHP, pochopne wyniki. –