Dlaczego trudno jest zapewnić wydajność podczas korzystania z bibliotek?


10

Każde małe przetwarzanie bazy danych może być łatwo rozwiązane przez skrypty Python / Perl / ..., które używają bibliotek i / lub nawet narzędzi z samego języka. Jednak jeśli chodzi o wydajność, ludzie często sięgają po języki C / C ++ / niskiego poziomu. Wydaje się, że możliwość dostosowania kodu do potrzeb sprawia, że ​​te języki są tak atrakcyjne dla BigData - czy to w zakresie zarządzania pamięcią, równoległości, dostępu do dysku, a nawet optymalizacji niskiego poziomu (za pomocą konstrukcji asemblacyjnych na poziomie C / C ++).

Oczywiście taki zestaw korzyści nie przyszedłby bez kosztów: napisanie kodu, a czasem nawet wynalezienie koła , może być dość kosztowne / męczące. Chociaż istnieje wiele bibliotek dostępnych, ludzie są skłonni do pisania kodu przez siebie ilekroć trzeba przyznać wydajność. Co wyłącza stwierdzenia dotyczące wydajności z korzystania z bibliotek podczas przetwarzania dużych baz danych?

Rozważmy na przykład przedsiębiorstwo, które stale indeksuje strony internetowe i analizuje zebrane dane. Dla każdego okna przesuwnego na wyodrębnionych danych uruchamiane są różne algorytmy eksploracji danych. Dlaczego programiści mieliby rezygnować z korzystania z dostępnych bibliotek / frameworków (do przeszukiwania, przetwarzania tekstu i eksploracji danych)? Wykorzystanie już zaimplementowanych rzeczy nie tylko zmniejszy ciężar kodowania całego procesu, ale także zaoszczędzi dużo czasu.

W jednym ujęciu :

  • co sprawia, że ​​samodzielne napisanie kodu jest gwarancją wydajności?
  • dlaczego opieranie się na frameworkach / bibliotekach jest ryzykowne, skoro musisz zapewnić wysoką wydajność?

1
Czy możesz wyjaśnić dokładne pytanie? Być może niektóre z możliwych odpowiedzi, które masz na myśli, również mogą pomóc.
Amir Ali Akbari

@AmirAliAkbari SeanOwen opublikował odpowiedź i zauważyłem brak szczegółowości w moim pytaniu. Dodałem komentarz do jego postu. Proszę sugerować wszelkie ulepszenia w poście - w przeciwnym razie planuję go usunąć.
Rubens

Odpowiedzi:


4

Po skończeniu gry przepisywania ciągle (i wciąż to robię) moją natychmiastową reakcją była zdolność adaptacji .

Podczas gdy frameworki i biblioteki mają ogromny arsenał (ewentualnie przeplatalnych) procedur standardowych zadań, ich własność frameworka często (zawsze?) Nie zezwala na skróty. W rzeczywistości większość platform ma jakąś rdzeniową infrastrukturę, wokół której implementowana jest podstawowa warstwa podstawowej funkcjonalności. Bardziej szczegółowa funkcjonalność wykorzystuje warstwę podstawową i jest umieszczana w drugiej warstwie wokół rdzenia.

Teraz przez skróty mam na myśli przejście od procedury drugiej warstwy do innej procedury drugiej warstwy bez użycia rdzenia. Typowym przykładem (z mojej domeny) są znaczniki czasu: masz jakieś źródło danych ze znacznikiem czasu. Do tej pory zadaniem było po prostu odczytanie danych z drutu i przekazanie ich do rdzenia, aby twój inny kod mógł się nimi ucztować.

Teraz Twoja branża zmienia domyślny format znacznika czasu z bardzo ważnego powodu (w moim przypadku zmieniły się one z czasu uniksowego na czas GPS). O ile Twój framework nie jest specyficzny dla branży, bardzo mało prawdopodobne jest, że zechcą zmienić podstawową reprezentację czasu, więc w końcu używasz frameworka, który prawie robi to, czego chcesz. Za każdym razem, gdy uzyskujesz dostęp do swoich danych, musisz najpierw przekonwertować je na format czasu branżowego, a za każdym razem, gdy chcesz je zmodyfikować, musisz je przekonwertować z powrotem na to, co rdzeń uzna za właściwe. Nie ma możliwości przekazania danych bezpośrednio ze źródła do zlewu bez podwójnej konwersji.

W tym miejscu zabłysną ręcznie stworzone frameworki, to tylko niewielka zmiana i wrócisz do modelowania świata rzeczywistego, podczas gdy wszystkie inne (nie specyficzne dla branży) framewory będą miały wadę wydajności.

Z czasem rozbieżność między światem rzeczywistym a modelem będzie się sumować. Z ram off-the-shelf chcesz wkrótce obliczu pytania: W jaki sposób można reprezentować thisw thatlub jak robią rutyna Xzaakceptować / produktów Y.

Jak dotąd nie chodziło o C / C ++. Ale jeśli z jakiegoś powodu nie możesz zmienić frameworka, tj. Musisz pogodzić się z podwójną konwersją danych, aby przejść z jednego końca na drugi, wtedy zwykle zastosujesz coś, co minimalizuje dodatkowy narzut. W moim przypadku najlepiej pozostawić konwerter TAI-> UTC lub UTC-> TAI do surowego C (lub FPGA). Nie ma elegancji, nie ma głębokiej inteligentnej struktury danych, która sprawia, że ​​problem jest trywialny. To tylko nudna instrukcja switch i dlaczego nie użyć języka, którego kompilatory są dobre w optymalizacji tego dokładnie?


1
+1 To może być moja wina, że ​​nie jestem bardzo jasny w moim poście, więc inni nie mieli go wcześniej. To jest z pewnością odpowiedź, której szukałem. Dzięki.
Rubens

7

Nie sądzę, że wszyscy sięgają po C / C ++, gdy wydajność stanowi problem.

Zaletą pisania kodu niskiego poziomu jest użycie mniejszej liczby cykli procesora, a czasem mniej pamięci. Ale zauważę, że języki wyższego poziomu mogą wzywać do języków niższego poziomu i zrobić to, aby uzyskać część tej wartości. Można to zrobić w językach Python i JVM.

Naukowiec wykorzystujący na przykład scikit-learn na swoim pulpicie już wywołuje wysoce zoptymalizowane natywne procedury, aby wykonać zgniatanie liczb. Nie ma sensu pisać nowego kodu prędkości.

W rozproszonym kontekście „dużych zbiorów danych” częściej występuje wąskie gardło w zakresie przenoszenia danych: transfer sieci i operacje we / wy. Kod macierzysty nie pomaga. Pomocne jest nie pisanie tego samego kodu, aby działał szybciej, ale pisanie mądrzejszego kodu.

Języki wyższego poziomu pozwolą na implementację bardziej wyrafinowanych algorytmów rozproszonych w danym czasie programisty niż C / C ++. W skali mądrzejszy algorytm z lepszym przepływem danych pobije głupi kod natywny.

Zwykle prawdą jest również to, że czas programisty i błędy kosztują dużo więcej niż nowy sprzęt. Rok pełnego czasu pracy programisty może wynosić 200 000 USD przy pełnym obciążeniu; ponad rok, który również wynajmuje setki serwerów wartych czasu obliczeniowego. W większości przypadków po prostu nie ma sensu zawracać sobie głowy optymalizacją, a nie rzucaniem w nią więcej sprzętem.

Nie rozumiem dalszych działań dotyczących „przyznania” i „wyłączenia” i „potwierdzenia”?


Przepraszam za nieporozumienie. Moim zamiarem było uzyskanie odpowiedzi dotyczących znaczenia kontroli nad aplikacją oraz tego, w jaki sposób kontrola ta jest rozluźniana przez biblioteki. Oczywiście możesz założyć o nich pewne rzeczy (ludzie zwykle nie przepisują pthreads), ale jeśli dane się zmienią (ładowanie, przepustowość, ...), może być konieczne uzyskanie dostępu do źródła lib w celu zapewnienia wydajności. I tak, niekoniecznie jest to C / C ++ - chociaż zwykle są to języki wybrane dla hpc. Czy mogę usunąć moje pytanie, czy chcesz je zmienić na coś bardziej szczegółowego? Akceptuję wszelkie sugestie, aby to poprawić.
Rubens

1
Nie, to dobre pytanie, jeśli chcesz, możesz odzwierciedlić swoje komentarze tutaj w edycjach pytania.
Sean Owen

Sprawdź, czy pytanie ma teraz sens. Dodałem małą skrzynkę, aby była prostsza. Jeśli chcesz dodać uwagę do pytania, możesz ją edytować.
Rubens

4

Jak wiemy, w świecie cyfrowym istnieje wiele sposobów wykonywania tej samej pracy / uzyskiwania oczekiwanych rezultatów.

A obowiązki / ryzyko wynikające z kodu spoczywają na programistach.

To jest małe, ale myślę, że bardzo przydatny przykład ze świata .NET ..

Tak wielu programistów .NET używa wbudowanego BinaryReader - BinaryWriter do serializacji danych w celu uzyskania wydajności / uzyskania kontroli nad procesem.

To jest kod źródłowy CSharp wbudowanej w BinaryWriter klasy FrameWork, jednej z przeciążonych metod zapisu:

// Writes a boolean to this stream. A single byte is written to the stream
// with the value 0 representing false or the value 1 representing true.
// 
public virtual void Write(bool value) 
{
     //_buffer is a byte array which declared in ctor / init codes of the class
    _buffer = ((byte) (value? 1:0));

    //OutStream is the stream instance which BinaryWriter Writes the value(s) into it.
    OutStream.WriteByte(_buffer[0]);
}

Jak widać, ta metoda mogłaby zostać napisana bez dodatkowego przypisywania do zmiennej _buffer:

public virtual void Write(bool value) 
{
    OutStream.WriteByte((byte) (value ? 1 : 0));
}

Bez przypisywania moglibyśmy zyskać kilka milisekund. Te kilka milisekund można zaakceptować jako „prawie nic”, ale co, jeśli istnieje wiele tysięcy zapisów (tj. W procesie serwera)?

Załóżmy, że „kilka” to 2 (milisekundy), a wystąpienie wielotysięczne to tylko 2000. Oznacza to 4 sekundy więcej czasu procesu… 4 sekundy później powraca…

Jeśli nadal będziemy poddawać temat z .NET i jeśli możesz sprawdzić kody źródłowe BCL - .NET Base Class Library - z MSDN, możesz zobaczyć wiele strat wydajności od dewelopera decyduje ..

Dowolny punkt ze źródła BCL To normalne, że deweloper zdecydował się użyć pętli while () lub foreach (), które mogłyby zaimplementować szybszą pętlę for () w swoim kodzie.

Te niewielkie korzyści dają nam całkowitą wydajność.

A jeśli wrócimy do metody BinaryWriter.Write () ..

W rzeczywistości dodatkowe przypisanie do implementacji _buffer nie jest błędem programisty. To właśnie decyduje się „zachować bezpieczeństwo”!

Załóżmy, że zdecydowaliśmy się nie używać _bufora i postanowiliśmy wdrożyć drugą metodę. Jeśli spróbujemy wysłać wiele tysięcy bajtów przewodem (tj. Przesłać / pobrać dane BLOB lub CLOB) za pomocą drugiej metody, może się to często nie udać, ponieważ połączenia utraconego .. Ponieważ próbujemy wysłać wszystkie dane bez żadnych kontroli i mechanizmu kontrolnego. Gdy połączenie zostanie utracone, zarówno serwer, jak i klient nigdy nie wiedzą, że wysłane dane zostały ukończone, czy nie.

Jeśli deweloper zdecyduje „zachować bezpieczeństwo”, zwykle oznacza to, że koszty wydajności zależą od wdrożonego mechanizmu „zachowania bezpieczeństwa”.

Ale jeśli deweloper zdecyduje się „ryzykować, zyskać wydajność”, nie jest to również wina .. Do czasu dyskusji o „ryzykownym” kodowaniu.

I jako mała uwaga: programiści bibliotek komercyjnych zawsze starają się zachować bezpieczeństwo, ponieważ nie wiedzą, gdzie ich kod będzie używany.


4

Z perspektywy programistów ramy rzadko koncentrują się na wydajności jako najwyższym priorytecie. Jeśli twoja biblioteka będzie szeroko wykorzystywana, rzeczy, które ludzie docenią najbardziej, to łatwość użycia, elastyczność i niezawodność.

Wydajność jest ogólnie ceniona w drugorzędnych bibliotekach konkurencyjnych. „Biblioteka X jest lepsza, ponieważ jest szybsza”. Nawet wtedy bardzo często biblioteki te będą wymieniać najbardziej optymalne rozwiązanie dla takiego, które można szeroko wykorzystać.

Korzystając z dowolnego środowiska, z natury ryzykujesz, że istnieje szybsze rozwiązanie. Mogę posunąć się do stwierdzenia, że ​​prawie zawsze istnieje szybsze rozwiązanie.

Samo napisanie czegoś nie jest gwarancją wydajności, ale jeśli wiesz, co robisz i masz dość ograniczony zestaw wymagań, może to pomóc.

Przykładem może być parsowanie JSON. Istnieje sto bibliotek dla różnych języków, które zamieniają JSON w odnośny obiekt i odwrotnie. Znam jedną implementację, która robi to wszystko w rejestrach CPU. Jest mierzalnie szybszy niż wszystkie inne parsery, ale jest również bardzo ograniczony, a to ograniczenie będzie się różnić w zależności od używanego procesora.

Czy zadanie zbudowania wydajnego parsera JSON specyficznego dla środowiska jest dobrym pomysłem? Wykorzystałbym poważaną bibliotekę 99 razy na 100. W tym osobnym przypadku kilka dodatkowych cykli procesora pomnożonych przez milion iteracji sprawiłoby, że warto było poświęcić czas na opracowanie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.