Wątek przeniesiony 2019-06-20 21:57 z przez cerrato.

Jak duże spowolnienie wynika używania języków wysokiego poziomu?

Odpowiedz Nowy wątek
2019-06-17 21:59

Rejestracja: 5 lat temu

Ostatnio: 16 godzin temu

3

Hipotetyczna sytuacja: Buduję sobie webappkę, dajmy na to w C# albo Javie. Nagle zaczyna mi przybywać użytkowników, apka mi siada, szukam zatem możliwości umieszczenia jej na farmie serwerów i jeszcze co prędzej modyfikuję kod, by dało się go w ten sposób zrównoleglić, bo pisząc apkę nie myślałem o skalowalności i głupio napisałem ją tak, że działa wyłącznie na jednym serwerze.

Ile z tego problemu wynika z faktu, że używam C# a nie C++? Istnieje nastawiony na wydajność framework C++-owy do serwisów webowych. Jak bardzo może być on szybszy od takiego np. ASP .Net? Ile razy więcej użytkowników na raz może obsłużyć na tym samym serwerze? Bo jeśli np. 10, to od razu spadają nam koszta utrzymania apki (mniej musimy płacić za chmurę albo nawet i wcale, bo stronę obsługuje "serwer" w postaci laptopa w szafie w naszym domu).

Aj tam, C++. Pójdźmy dalej, C? Gdzie tam. For the sake of argument zastanówmy się nad webappką napisaną w asemblerze. Są szaleńcy w to się bawiący. Ile mogłoby wynosić przyspieszenie w porównaniu z C++? Kolejne 10 razy? To już jest 100-krotne przyspieszenie w porównaniu w .NET! Laptop w szafie dałby radę nawet dla całkiem dużych serwisów, dla których w przypadku .Neta trzeba by już porządnej farmy serwerów (i architektury appki pod taką farmę pisanej)?

Dlaczego pytam. Naszła mnie bowiem myśl, że chociaż przez ostatnie 15-20 lat komputery przyspieszyły o rzędy wielkości (4 rdzenie po 3GHz każdy to norma nawet dla sprzętów ze średnio-dolnej półki vs jeden rdzeń z taktowaniem liczonym w megahercach), to jednak zarówno dawniej Word jak i teraz otwiera mi się nie natychmiast - trzeba przeczekać splash screen. A przecież podstawowe możliwości nie zmieniły się. Czy nie jest zatem tak, że im bardziej komputery przyspieszają, tym bardziej spowalnia software, bo devom nie chce się go optymalizować oraz korzystają z wolnych języków / frameworków / itp (bo są wygodniejsze), i w konsekwencji przyspieszanie komputerów nic nie daje, bo się to wyrównuje ze spowalniającym oprogramowaniem?

Stąd też wytrzasnąłem to oczekiwane 100-krotne przyspieszenie asemblera w porównaniu w C#; stukrotne przyspieszenie komputerów nie przyspiesza ładowania się Worda.

Dobry przykład z Wordem. - Silv 2019-06-17 23:56
A na Rust, Swift, Crystal można przepisać te apkę webową? Wymieniłbym jeszcze Go, ale raczej nie jest już dużo szybszy od Javy. - light 2019-06-18 16:10
co do tego worda, to dość długo miałem równocześnie office 2013 i jakiś tam najnowszy nawet nie wiem. Każdy program offica w najnowszej wersji odpla się szybciej niż tez z 2013 wiec to nie prawda że nie optymalizuja - _flamingAccount 2020-03-07 21:32

Pozostało 580 znaków

2019-06-19 19:18

Rejestracja: 3 lata temu

Ostatnio: 14 sekund temu

Lokalizacja: U krasnoludów - pod górą

3
kmph napisał(a):

Jednak: Ja postawiłem hipotezę, że ten (zrozumiały) trend odbywa się kosztem wydajności; i że przyspieszanie sprzętu pozwala w zasadzie na wejście na wyższy poziom abstrakcji a nie na przyspieszenie działania programów, bo wejcie na wyższy poziom abstrakcji znosi korzyści z przyspieszenia sprzętu. Ktoś zapodał prawo Wrighta, które zdawałoby się to potwierdzać.

Ty postawiłeś tezę, że jest wręcz odwrotnie: że dla nietrywialnych aplikacji to przejście na wyższe poziomy abstrakcji umożliwia wysoką wydajność, podczas gdy pozostawanie na niskim poziomie abstrakcji tę wydajność zabija - niemal zawsze na odcinku ASM - C, niekiedy także na odcinku C - Java.

W sumie to się zgadzam, przyspieszenie sprzętu pozwala robić o wiele więcej dodaktowych rzeczy, których kiedyś nie robiono. Np. tony dodatkowych sprawdzeń związanych z bezpieczeństwem programu. I się z tego korzysta.
A co do wydajności w językach wyższego poziomu:
To ja w sumie formułuję to tak - jeśli mamy problem z wydajnością w czymś typu Java, C#, C++, w jakimś małym fragmencie kodu to na ogól najprosztszym sposobem poprawienia tej wydajności jest przepisanie tego fragmentu lepiej w tym samym jezyku. Schodzenie niżej jest zwykle nieopłacalne, są wyjątki, ale dość specyficzne. Przez 20 lat kilka razy miałem sytuację, że zastanawiałem się, żeby fragment w Javie przerobić na C/Asm. Zawsze wystarczyło poprawić kod w Javie. Sytuację, że wyniesienie na ASM było opłacalne miałem prawie raz (obługa SSE w Javie 1.6 SUN to była kpina).
(Btw. z drugiej strony kilka razy miałem przypadek gdzie sensowne było przejście na GPU, ale to nie były typowe enterprise projekty, co więcej robiłem to GPU w javie :-) (bo można)).

To, że programy robią tony rzeczy bez sensu i po prostu obecnie te tony uchodzą płazem, bo tylko ocieplają planetę, a użytkownikowi zwisa czy word odpala się jedną czy dwie sekundy to inna sprawa. Gdyby nie wymyślono nic innego niż assembler to pewnie byłoby dokładnie tak samo, tylko programiści więcej by pili. (Jeszcze więcej). Dodatkowo wspólczesny assembler to makra, makra i makra i funkcje systemowe. W zasadzie zastanawiasz się czy to w ogóle jeszcze jest jezyk niskiego poziomu. Czy naprawdę wiesz co faktycznie jest produkowane jako kod maszynowy, i czy masz faktycznie jakikolwiek kontakt ze sprzętem (o ile nie jest to assembler na małe Atari, C64 lub Spectrum to możesz przyjąc, że nie masz).


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.
edytowany 7x, ostatnio: jarekr000000, 2019-06-19 19:47

Pozostało 580 znaków

2019-06-19 19:45

Rejestracja: 4 lata temu

Ostatnio: 10 godzin temu

6

Oszczędzaj RAM gdziekolwiek jesteś. Załóżmy sytuacje teoretyczną - mamy webappke.

Przejście zapytania przez sieć zajmuje, załóżmy, sekundę. Wykonanie i zwrócenie odpowiedzi przez Pythona zajmie około 0,1 sekundy. Potem znowu powrót do użytkownika, czyli kolejna sekunda. Łącznie 2,1 sekundy.

Możemy przepisać ten kod w innym języku, załóżmy, C – kod będzie kilka razy dłuższy, pisanie zajmie go więcej czasu, ale za to wykona się, powiedzmy, 100 razy szybciej. Czyli użytkownik, zamiast poczekać 2,1 sekundy, poczeka 2,001 sekundy, gdyż zazwyczaj to nie sam serwer i kod naszej aplikacji, jest wąskim gardłem, a np. baza danych, połączenie sieciowe czy dysk.

Czy ma to sens w większości przypadków? Przeskok z 2,1 do 2,001s? Sami sobie odpowiedzcie.

Obecnie to raczej nie same języki są jakimiś wąskimi gardłami a baza danych, jakiś zapis odczyt, przesył informacji, albo częściej, niż rzadziej, nieumiejętny/niedbały programista itd. Można by wszystko przepisać z Javy/Pythona itd. na nie wiem, C, ale... Po co?
Jak zejdziemy z 80ms do 72 ms to czy ktoś zauważy różnice, poza nami na benchmarkach? W 99% przypadkach nie, pozostałe przypadki to nie są rzeczy dla mózgów naszego pokroju.

Poza tym nie jest przecież też tak, że ten sam program w asmie wykona się 100 razy szybciej niż w C/Pythonie. Absolutnie nie.

Także, zwłaszcza w webapkach, to nie języki są wąskimi gardłami a bazy danych, I/O, network itd.

Czemu programy nie przyśpieszyły, często wręcz zwolniły mimo komputera kilak razy mocniejszego?

Kiedyś twój program robił wodotrysk.

Dziś klient wymaga by twój program robił wodotrysk, gotował obiad w tle zajmując się dzieckiem i dokonując operacji na otwartym mózgu.

To plus pewne ustępstwa na które się idzie, by szybciej dowieźć produkt - coś tam się zrobi mniej wydajniej, ale załóżmy kilka razy szybciej. Później przyśpieszymy. Co prawda często to później nie następuje, ale wciąż, liczy sie to, że uda ci się wypuścić coś szybciej niż twój konkurent i że to zadziała. Zwłascza w obecnych czasach tfu startupów.

W przykładzie, który rzuciłem wyżej - apka w pythonie co to dwóch studentów ją skleiło w kilka tygodni za jakieś grosze 2.1 s vs super appka w C z 2.001 czasu respona, którą kleić musiało wielu doświadczonych programistów, w kilka miesięcy, budżet miliony. Czas mija wreszcie wypuszczacie to cudo techniki. W międzyczasie ta apka sklecona przez dwóch studenciaków zdobyła jakiś klientów, co prawda miała różne błędy, bywała wolna, ale ogółem jakoś działała, generując jakąś wartość dla biznesu i rozwiązując problemy użyszkodników, powodując, że ludzie się do niej przyzwyczaili. Wtedy wchodzicie wasza piękna nowa błyszcząca appka w C, szybsza, lepsza, ale... Nikt jej nie używa. Proste. Biznes upada, jesteś bankrutem.

Zysk niewspółmierny do kosztu. Proste. Monnies się nie zgadzają, no one gives a shit. W gruncie rzeczy klient (znowu - zaznaczam 99% przypadków) ma to w poważaniu czy ta appka jest w C, Pythonie czy czym tam, czy odpowiada w 2.1 s czy 2.001 s. Ma działać, rozwiązywać jakiś jego problem i koniec.

Co nie zmienia faktu, że teraz za bardzo sobie na pewne rzeczy pozwalamy przez co, jak to ktoś już napisał, mamy bloated software. Szlag mnie przez to często trafia, Tak samo jak landing page ważący po 20 mb psia mać. Albo proces wytwarzania oprogramowani, który często jest biedny i popsuty (jakoś-to-będzie-bylejakizm) czy inne scrum-slavy. Ale co zrobić. Pozostaje swoją robotę robić dobrze i koniec.

Ogółem polecam bardzo w tym temacie ten artykuł: https://tonsky.me/blog/disenchantment/
Wyraża więcej niż 1000 słów.

Tldr; to nie języki są zazwyczaj wąskim gardłem a jak coś się da zrobić prościej w języku wyższego poziomu, to zazwyczaj nie warto sie kłopotać dla mizernego zysku, który jest niewspółmierny do włożonego wysiłku.

Okej, koniec mojego rantu.

edytowany 2x, ostatnio: grski, 2019-06-19 19:50
Pokaż pozostałe 2 komentarze
to w końcu co z tym ramem? wyszedłeś od ramu, a rozpisałeś się na temat cpu :D - WeiXiao 2019-06-20 01:53
https://tonsky.me/blog/disenchantment/ ten gość ma słuszną ideę, ale chyba również urojenia We’re getting faster hardware that runs slower software with the same features. bullshit Windows 10 is 4Gb, which is 133 times as big. But is it 133 times as superior? no nie moge But who has time for that? We haven’t seen new OS kernels in what, 25 years? It’s just too complex to simply rewrite by now. Browsers are so full of edge cases and historical precedents by now that nobody dares to write layout engine from scratch. no nie wcale - WeiXiao 2019-06-20 02:04
@WeiXiao: z ramem zupełnie nic, nawiązałem do pewnego ramu/pasty, mysląłem, że bardziej popularna ale widać jednak nie :( - grski 2019-06-20 02:06
Apki proste które kiedyś brały 10-20MB ramu, a teraz ich zamienniki biorą 200 ale różnią się tym, że są cross platformowe, mają pewnie 50 integracji z facebookiem, steamem, githubem, twitterem, instagramem, snapchatem itd itd oraz chociażby takie głupoty jak chat z obrazkami po ctrl+v itd. Czy da się obniżyć ram usage? na pewno. - WeiXiao 2019-06-20 02:10
Jasna rzecz, tak jak mówiłem, ram mam gdzieś tam, a tekst to jedynie nawiązanie do https://www.wykop.pl/cdn/c320[...]A6spAbP6l2a9Nu1BFP8ihzHX3.jpg co miało być nawiązaniem do posta, o ramie celowo nic nie wspominam w poście. - grski 2019-06-20 02:12

Pozostało 580 znaków

2020-03-07 21:23

Rejestracja: 5 lat temu

Ostatnio: 16 godzin temu

0

Zwracam Wam (częściowo) honor. Postanowiłem wreszcie to po prostu sprawdzić. Benchmark C++ vs C#, funkcja Ackermanna:

#include <iostream>
#include <vector>
#include <utility>
#include <stack>
#include <chrono>

using cache_t = std::vector<std::vector<int>>;
using stack_t = std::stack<std::pair<int, int>>;

void ensure_cache_large_enough(int m, int n, cache_t& cache)
{
    if (cache.size() <= m)
    {
        cache.resize(m + 1, std::vector<int>{});
    }

    if (cache[m].size() <= n)
    {
        cache[m].resize(n + 1, 0);
    }
}

int retrieve_or_queue(int m, int n, cache_t& cache, stack_t& stack)
{
    ensure_cache_large_enough(m, n, cache);

    if (cache[m][n] != 0)
    {
        return cache[m][n];
    }
    else
    {
        stack.emplace(m, n);
        return 0;
    }
}

void process_stack(cache_t& cache, stack_t& stack)
{
    while (!stack.empty())
    {
        auto [m, n] = stack.top();

        if (retrieve_or_queue(m, n, cache, stack) != 0)
        {
            stack.pop();
        }
        else if (m == 0)
        {
            cache[m][n] = n + 1;
            stack.pop();
        }
        else if (n == 0)
        {
            auto r = retrieve_or_queue(m - 1, 1, cache, stack);
            if (r != 0)
            {
                cache[m][n] = r;
                stack.pop();
            }
        }
        else
        {
            auto r_in = retrieve_or_queue(m, n - 1, cache, stack);
            if (r_in != 0)
            {
                auto r_out = retrieve_or_queue(m - 1, r_in, cache, stack);
                if (r_out != 0)
                {
                    cache[m][n] = r_out;
                    stack.pop();
                }
            }
        }
    }
}

int ack(int m, int n)
{
    cache_t cache;
    stack_t stack;
    stack.emplace(m, n);

    process_stack(cache, stack);

    return cache[m][n];
}

int main()
{
    int m = 3, n = 20;

    auto start = std::chrono::system_clock::now();
    int res = ack(m, n);
    auto end = std::chrono::system_clock::now();

    std::cout << "A(" << m << ", " << n << ") = " << res << std::endl;
    std::cout << "Took " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
}

Versus:

using System;
using System.Linq;
using System.Collections.Generic;

using Cache = System.Collections.Generic.List<System.Collections.Generic.List<int>>;
using Stack = System.Collections.Generic.Stack<(int, int)>;

namespace csharpackermann
{
    class Program
    {
        static void EnsureCacheLargeEnough(int m, int n, Cache cache)
        {
            if (cache.Count <= m)
            {
                cache.AddRange(Enumerable.Repeat(0, m - cache.Count + 1).Select(_ => new List<int> { }));
            }

            if (cache[m].Count <= n)
            {
                cache[m].AddRange(Enumerable.Repeat(0, n - cache[m].Count + 1));
            }
        }

        static int RetrieveOrQueue(int m, int n, Cache cache, Stack stack)
        {
            EnsureCacheLargeEnough(m, n, cache);

            if (cache[m][n] != 0)
            {
                return cache[m][n];
            }
            else
            {
                stack.Push((m, n));
                return 0;
            }
        }

        static void ProcessStack(Cache cache, Stack stack)
        {
            while (stack.Any())
            {
                var (m, n) = stack.Peek();

                if (RetrieveOrQueue(m, n, cache, stack) != 0)
                {
                    stack.Pop();
                }
                else if (m == 0)
                {
                    cache[m][n] = n + 1;
                    stack.Pop();
                }
                else if (n == 0)
                {
                    var r = RetrieveOrQueue(m - 1, 1, cache, stack);
                    if (r != 0)
                    {
                        cache[m][n] = r;
                        stack.Pop();
                    }
                }
                else
                {
                    var r_in = RetrieveOrQueue(m, n - 1, cache, stack);
                    if (r_in != 0)
                    {
                        var r_out = RetrieveOrQueue(m - 1, r_in, cache, stack);
                        if (r_out != 0)
                        {
                            cache[m][n] = r_out;
                            stack.Pop();
                        }
                    }
                }
            }
        }

        static int Ack(int m, int n)
        {
            var cache = new Cache();
            var stack = new Stack();
            stack.Push((m, n));

            ProcessStack(cache, stack);

            return cache[m][n];
        }

        static void Main(string[] args)
        {
            int m = 3, n = 20;

            var start = DateTime.Now;
            int res = Ack(m, n);
            var end = DateTime.Now;

            Console.WriteLine($"A({m}, {n}) = {res}");
            Console.WriteLine($"Took {Math.Round((end - start).TotalMilliseconds)}ms");
        }
    }
}

Wyniki:

C:\Users\m>source\repos\cppackerman\x64\Release\cppackerman.exe
A(3, 20) = 8388605
Took 2009ms

C:\Users\m>source\repos\csharpackermann\bin\Release\netcoreapp3.1\csharpackermann.exe
A(3, 20) = 8388605
Took 5718ms

Tak więc różnica między C# a C++ ~2.8 krotna. (Przynajmniej na kompilatorach z rodziny Visual Studio na Windowsie). Istotnie, nie jest to wiele.

Włączyłem optymalizacje zarówno na C# jak i na C++ - z tym, że dla C# były to dwie opcje do odptaszkowania w IDE (optymalizacja + wyrzucenie symboli debugowania), dla C++ było to multum opcji które odznaczałem nieco na czuja, może więc by się dało C++ przyspieszyć jeszcze bardziej (np. nie włączyłem optymalizacji na podstawie profilowania).

Przyznaję, że jestem mile zdziwiony: pamiętam, że ileś lat temu robiłem podobny benchmark, tym razem C++ vs Java na Linuxie, i - o ile pamiętam - różnica wyszła dramatyczna na korzyść C++. Niestety, nie mam już tamtego kodu ani dokładnych wyników. Na podstawie tamtych dawniejszych benchmarków wbiłem sobie do głowy, że fakt wykonywania kodu na maszynie wirtualnej musi spowalniać makabrycznie. Może wprowadzenie JITów ulepszyło sytuację pod tym względem?

Dla pełnego obrazu oczywiście przydałoby się dodać jeszcze asm z mikrooptymalizacjami na jednym ekstremum i Haskella na drugim ekstremum. Niestety, to pierwsze wymaga dość ezoterycznej wiedzy, której nie posiadam, więc nie wstawię (AFAIK przyspieszenia między asemblerem wklepanym na pałę a asemblerem napisanym przez kogoś, kto naprawdę wie od czego zależy wydajność mogą być dramatyczne). Co się zaś tyczy Haskella, to obawiam się, że nieco zaburzyłoby to miarodajność porównania: kod C# i C++ tutaj jest dokładnie analogiczny, tymczasem przełożyć takiego kodu na Haskella się nie da tak łatwo (tzn da się, ale byłby to Haskell bardzo nieidiomatyczny), a poza tym znowu - nie do końca wiem, jak żyłować wydajność w Haskellu (pewnie trzeba by użyć mutowalnych tablic do cache, ale czy wtedy w ogóle jest sens używać Haskella?)

edytowany 1x, ostatnio: kmph, 2020-03-07 21:23
Pokaż pozostałe 19 komentarzy
Ideą List<T> jest to, że zmienia rozmiar sama wedle potrzeb, nie ma sensu w niej możliwość ręcznej zmiany rozmiaru. No ale właśnie do cache'owania się to przydaje, zarówno w Ackermannie jak i w 3n+1 na przykład bardzo się przydaje możliwość wstawienia czegoś na indeks 153 BEZ uprzedniego wstawiania wartości na indeksy od 0 do do 152. Pdobnie te indeksy i tak zostaną wypełnione (więc mapa bez sensu) ale za chwilę, nie teraz. Tymczasem jesli napiszę cache[153] to mi poleci wyjątek. Więc muszę rozszerzyć listę, by wyjątek nie leciał. - kmph 2020-03-08 16:39
W przypadku tej najbrzydszej linijki (cache.AddRange(Enumerable.Repeat(0, m - cache.Count + 1).Select(_ => new List<int> { }));) to nie ma znaczenia, gdyż o ile się nie mylę m i tak nie przekracza 3 czy 4, ale linijkę niżej (cache[m].AddRange(Enumerable.Repeat(0, n - cache[m].Count + 1));) to już ma znaczenie - n może być duże. Tu jestem przymuszony albo do pętli, albo do AddRange. - kmph 2020-03-08 16:41
To wszystko prawda, co piszesz, że musiałeś to wszystko zrobić, ale List<T> po prostu nie służy do takiej żonglerki zawartością. - somekind 2020-03-08 16:50
Więc czego powinienem był użyć zamiast List<T>? - kmph 2020-03-08 17:09
Do tego algorytmu, żeby to miało sens, to prawdopodobnie tablic. Albo swojej implementacji, która pozwala na łatwe rozszerzanie z ustawianiem domyślnych wartości. - somekind 2020-03-08 18:55

Pozostało 580 znaków

2020-03-08 00:10

Rejestracja: 5 lat temu

Ostatnio: 1 godzina temu

Lokalizacja: Warszawa

0

Przyznaję, że jestem mile zdziwiony: pamiętam, że ileś lat temu robiłem podobny benchmark, tym razem C++ vs Java na Linuxie, i - o ile pamiętam - różnica wyszła dramatyczna na korzyść C++

A pamiętasz kiedy mniej więcej to bylo? Tzn 3 lata temu czy 10 :P


Nie pomagam przez PM. Pytania zadaje się na forum.
Strzelam w 7? - kmph 2020-03-08 00:15

Pozostało 580 znaków

2020-03-08 11:04

Rejestracja: 8 lat temu

Ostatnio: 8 minut temu

2

Języki typu C++ czy Java mogą być też szybsze od ASM.
1) pod względem czasu implementacji
2) ze względu na szereg optymalizacji które mogą robić w czasie kompilacji, w przypadku C++ to np. eliminacja obliczeń, alokacja rejestrów, zrównoleglanie SIMD
3) ze względu na czytelniejsze struktury łatwiej alokować specyficznie pod procesor (C++)

C w zasadzie nie powinno być szybsze od C++, C++ ma zasadę "zero cost abstractions".
Można specyficzne procedury (CPU-bound) lepiej zoptymalizować pod ASM czy C ale to są bardzo specyficzne przypadki.

Kiedy Java może być szybsza od ASM:


Szacuje się, że w Polsce brakuje 50 tys. programistów

Pozostało 580 znaków

2020-03-08 11:18

Rejestracja: 3 lata temu

Ostatnio: 14 sekund temu

Lokalizacja: U krasnoludów - pod górą

2
kmph napisał(a):

wirtualnej musi spowalniać makabrycznie. Może wprowadzenie JITów ulepszyło sytuację pod tym względem?

Wprowadzenie JITów rzeczywicie dramatycznie polepszyło. I to było w roku 1999 :/ Od tego czasu oczywiście stale JVM (oracle i inne) się poprawia, ale to był najwiekszy przełom (java 1.3.0).

Generalnie JVM i JITy to świnie przy robieniu benchmarków - łatwo dostać wyniki, które nie odpowiadają temu co będzie na produkcji. Jak czasy benchmarku są poniżej 5sekund to na JVM mierzony jest zwykle czas kompilacji i rozgrzewania maszyny, ludzie często robią benchmarki na milisekundy.... Kody benchmarkowe, jesli np. prowadzą do błedów przepełnien itp. dostają kary od JITa (w sensie pzostają nieskompilowane)

Z drugiej strony może się zdarzyć (kilka procent mikrobenchmarków), że czas na JVM wyjdzie nierealistycznie dobry (dead code elimination).

tymczasem przełożyć takiego kodu na Haskella się nie da tak łatwo (tzn da się, ale byłby to Haskell bardzo nieidiomatyczny), a poza tym znowu - nie do końca wiem, jak żyłować wydajność w Haskellu (pewnie trzeba by użyć mutowalnych tablic do cache, ale czy wtedy w ogóle jest sens używać Haskella?)

Nie chiało mi się przekładać tego kodu na haskell - tu masz dyskusje w temacie:
https://stackoverflow.com/que[...]-inefficient-with-haskell-ghc

Dość "naiwne" podejście:

import Data.Function.Memoize

ack :: Int -> Int -> Int
ack 0 n = succ n
ack m 0 = mack (pred m) 1
ack m n = mack (pred m) (mack m (pred n))

mack::Int->Int->Int
mack = memoize2 ack

main = print $ mack 3 20 

dało mi takie czasy (na brudno, na vmce (jeszcze robiłem inne pierdółki)):

8388605

real    2m7.956s
user    7m43.072s
sys 8m7.765s

//ghc 8.6.5 

Czyli - troche to trwało, ale to jednak jest raczej naiwna wersja (nie wiem nawet czy ta memoizacja jest zrobiona dobrze).
(czasy pokazują, że użyte było kilka procków w trakcie obliczeń).


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.
edytowany 4x, ostatnio: jarekr000000, 2020-03-08 11:31

Pozostało 580 znaków

2020-03-09 01:12

Rejestracja: 6 lat temu

Ostatnio: 1 tydzień temu

1
kmph napisał(a):

Tak więc różnica między C# a C++ ~2.8 krotna. (Przynajmniej na kompilatorach z rodziny Visual Studio na Windowsie). Istotnie, nie jest to wiele.

3x szybciej to niewiele? Że różnica 50km/h a 150km/h to niewiele? ;)
Jasne, że jak coś się odpala raz na ruski rok to tego nie zauważysz, ale jak coś hula praktycznie non-stop to jednak czuć różnicę.
Problemem jest raczej wszechobecny źle rozumiany OOP oraz bezmyślne wciskanie wszystkiego(często na siłę) we wszelkiego rodzaju frameworki.
Tak z głowy to mogę polecić dwie prezentacje. Dotyczą stricte C++, ale pokazują ile można zyskać nie tyle na zmianie języka co samym podejściu i zrozumieniu konkretnego problemu.
CppCon 2019: Matt Godbolt “Path Tracing Three Ways: A Study of C++ Style”
Pacific++ 2018: Kirit Sælensminde "Designing APIs for performance"


#duda2020

Pozostało 580 znaków

2020-03-22 17:43
Moderator

Rejestracja: 15 lat temu

Ostatnio: 6 minut temu

10

To ja się podzielę ostatnim doświadczeniem.
Mamy w naszym projekcie pewien task, który czyta pliki z danymi, obrabia je i zapisuje przetworzone dane na dysku jako nowe pliki. Nie jest istotne co dokładnie robi, ale musi wczytać całe pliki od deski do deski, zdekodować pewien dość mocno pokrecony format danych, wykonać dość prosta logikę na tych danych a następnie zserializować do innego formatu.

Kod napisano w Javie i osiąga zawrotną predkość 10 MB/s... na dysku SSD o wydajności 2+ GB/s i 3GHz CPU. Problemem jest CPU.

Firma zatrudniła eksperta od tuningu Javy. Ekspert w ciągu dwóch miesięcy przepisał kluczowy fragment kodu tak, że nie używa alokacji na stercie, nie używa praktycznie dziedziczenia i interfejsow a obiekty są jedynie miejscami na trzymanie danych w pamięci. Kod przyspieszył jakieś 5-8x. Niestety kod jest bardzo niebezpieczny i nieidiomatyczny - wszystkie obiekty są mutowalne i dostępne w publicznym API. Zalecana ostrożność jak przy kodzie w C. Ba, ten kod wygląda jak C ze składnią Javy.

Później ja dla jaj wziąłem ten kod i w kilka dni przeportowałem go na Rust, jednak bez uciekania się do unsafe i starając się zachować oryginalne, wygodne i bezpieczne API. Pokazałem go ludziom nie znającym Rusta i stwierdzili, że rozumieją jak ten kod działa, więc chyba był wystarczająco czytelny.

Efekt - kod jest kolejne 5x szybszy od tego zoptymalizowanego kodu w Javie. Łącznie jakieś 20-30x szybszy od oryginału. Btw nie jestem ekspertem od Rust - właściwie uczyłem się tego języka jak pisałem kod.

I jeszcze wisienka na torcie - zużycie pamięci. Oryginalny kod w Javie potrzebował minimum 0.5 GB, inaczej wywalał się z OOM.
Kod w Rust potezebował 20 MB, z czego większość to pliki wejściowe zmapowane do pamięci oraz sam segment kodu. Wykorzystanie sterty: 0. Oczywiście to trochę nie fair porównanie, bo oryginalny kod miał więcej ficzerów i prawdopodobnie ładował niepotrzebne rzeczy do pamięci (nieużywane w tym teście).

Przejście zapytania przez sieć zajmuje, załóżmy, sekundę. Wykonanie i zwrócenie odpowiedzi przez Pythona zajmie około 0,1 sekundy. Potem znowu powrót do użytkownika, czyli kolejna sekunda. Łącznie 2,1 sekundy.

Wiem o co Ci chodzi, ale w praktyce realne wartości mają znaczenie i mocno zmieniają wnioski z takiej analizy.

Po pierwsze - sieć 2s? Na edge na wsi czy modemie wdzwanianym na 0202122? Bo ja nawet na lichym ADSL mam 20-40 ms.
Natomiast na kablówce można zejść do 2-10 ms roundtrip. Sieć nie jest na ogół problemem.

Zwrócenie odpowiedzi z Pythona 0,1s - jesli to prawda to jest to masakrycznie wolno. Heca polega na tym, że na serwerze nie jesteś sam. Wystarczy że w tej samej sekundzie przyjdzie akurat 20 zapytań i już poczekasz sobie znacznie dłużej. Poza tym obecne apki webowe nie wykonują jednego zapytania. Często leci sekwencja kilkudziesięciu lub kilkuset zapytań. Większość równolegle, ale teraz wystarczy że jedno z nich trafi na większego laga i użytkownik zobaczy to jako mulenie strony. Dlatego nie powinno się patrzeć wyłącznie na średni czas odpowiedzi, a na percentyle - najlepiej co najmniej 99.9%. I pod tym względem takie wynalazki jak Java/Python itp wypadają bardzo blado. Jeżeli co setne zapytanie potrzebuje 2 sekund, a spa wysyła 100 zapytań na akcje użytkownika, to niemal każdy użytkownik będzie widział taki właśnie beznadziejny czas odpowiedzi.

Teoretyzuję? No nie. JIRA cloud właśnie tak działa w godzinach szczytu. Masakra.

edytowany 3x, ostatnio: Krolik, 2020-03-22 18:10
Pokaż pozostałe 14 komentarzy
Słuszna uwaga. Spaczowane. - Krolik 2020-03-24 20:26
Heh, zdaje się że chyba wciąż są ludzie, według których C to najwyższy dopuszczalny poziom abstrakcji: http://harmful.cat-v.org/software/ (o ile dobrze rozumiem, co oni tam piszą) - kmph 2020-03-25 15:16
ta strona chyba nie jest na poważnie. przedstawiać 8cc i tcc jako poważne kompilatory to niezła szajba. Dla nieznających historii alternatywnych kompilatorów. 8cc powstał jako dowód że można napisać kompilator w 40 dni. Dla wygody nie ma tam zwalniania pamięci podczas kompilacji, więc musisz mieć dużo RAMu żeby skompilować duże projekty. TCC za to powstał na konkurs na najbardziej zaciemniony kod jako minimalistyczny kompilator C zdolny skompilować sam siebie :D - KamilAdam 2020-03-25 18:18
8cc powstał jako dowód że można napisać kompilator w 40 dni. Przypuszczam, że dla tych ludzi to zaleta, nie wada. Jeśli się nie mylę, oni dogmatycznie uważają jak najdalej posuniętą prostotę implementacji za najważniejszą jakość oprogramowania. Jeśli kod powstał w 40 dni i jest krótki, to musi być prosty. Dla wygody nie ma tam zwalniania pamięci podczas kompilacji, więc musisz mieć dużo RAMu żeby skompilować duże projekty. Znowu - przypuszczam, że dla nich it's a feature, not a bug. Duże projekty są skomplikowane = nigdy nie powinny powstać. - kmph 2020-03-25 18:43

Pozostało 580 znaków

2020-03-23 11:32

Rejestracja: 1 rok temu

Ostatnio: 22 godziny temu

Lokalizacja: Wrocław

0

Erlang VM. Możesz sobie pisać w Elixirze. Wydajna technologia.

Pozostało 580 znaków

2020-03-23 12:30

Rejestracja: 14 lat temu

Ostatnio: 50 sekund temu

1
donPietro napisał(a):

Erlang VM. Możesz sobie pisać w Elixirze. Wydajna technologia.

W https://benchmarksgame-team.p[...]marksgame/fastest/erlang.html jest wielokrotnie wolniejszy od Javy :]


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit, 2020-03-23 12:31
Odpowiedz

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0