Co jest szybsze ASP.NET MVC z EF 6 czy JAVA WEB APPLICATION i JPA

0

Mam pytanie o powyższej treści co jest szybsze, dokonałem bowiem testów wydajnościowych dla porównania dwóch identycznych aplikacji, jednej napisanej w ASP.NET MVC z użyciem EF6 struktura tej aplikacji zbudowana jest z czterech modułów

Modułu Danych
Modułu Repozytorium
Modułu Serwisów
Modułu Web MVC

Druga aplikacja została napisana w JEE, jej struktura jest typowa dla JEE czyli kontrolery, ziarna EJB, facade, Java Web Application z użyciem EclipseLink.

Tryb wczytania encji, w obu aplikacjach był ustawiony na LAZY.

Aplikację ASP.NET wdrożyłem na serwer IIS przy pomocy opcji Publish w Visual Studio i testowałem na bazie utworzonej w MS SQL Server.

Do tej samej bazy była podłączona też aplikacja Java Web Application wdrożona na serwer GlassFish.

I tu mnie wynik zaskoczył,

tworzenie 1000 rekordów nagłówków dokumentu i 3000 pozycji w pętli, aplikacja Javy wykonała 40 razy szybciej niż aplikacja w ASP.NET

odczytywanie pojedynczych encji czyli 1000 dostawców, 3000 towarów i 1000 dokumentów, aplikacja Javy wykonała 3,5 razy szybciej niż aplikacja w ASP.NET,

przy wczytaniu list encji różnica była mniejsza 1,3 na korzyść Java

Testy wykonane były na maszynie wirtualnej restartowanej po każdym teście

Różnica w szybkości tworzenia rekordów zszokowała mnie to jakiś błąd ? Jakie są Państwa doświadczenia ?

przykład funkcji dodającej dokumenty w ASP.NET

public void testUtworzDokumntyPz()
        {


            long mIdDostawcy = 1;
            long mIdTowaru = 1;
            long mIdNaglowkaPz = 1;


            decimal ilosc = 1;
            decimal cenaZakupuNetto = 1;
            int stawkaVat = 7;
          

            ObslugaDanych<TOWARY> TowaryFacade = new ObslugaDanych<TOWARY>(Context);
            ObslugaDanych<POZYCJE_PZ> PozycjeDokumentowPZFacade = new ObslugaDanych<POZYCJE_PZ>(Context);
            ObslugaDanych<MAGAZYNY> MagazynyFacade = new ObslugaDanych<MAGAZYNY>(Context);
            ObslugaDanych<DOSTAWCY> DostawcyFacade = new ObslugaDanych<DOSTAWCY>(Context);
            NaglowkiDokumentowPZObslugaDanych NaglowkiDokumentowPZFacade = new NaglowkiDokumentowPZObslugaDanych(Context);
            ObslugaDanych<TESTY> TestyFacade = new ObslugaDanych<TESTY>(Context);

            NAGLOWKI_PZ NaglowkiPz;
            MAGAZYNY magazyn;

            List<POZYCJE_PZ> pozycjePzList;
            POZYCJE_PZ pozycjaPz;
            NAGLOWKI_PZ naglowekPz;


            System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
            watch.Start();

            for (int mIdMagazynu = 1; mIdMagazynu <= 10; mIdMagazynu++)
            {

                magazyn = MagazynyFacade.getWiersz(mIdMagazynu);

                for (int mNrPz = 1; mNrPz <= 100; mNrPz++)
                {
                    NaglowkiPz = new NAGLOWKI_PZ();

                    NaglowkiPz.NR_PZ = mNrPz;

                    NaglowkiPz.ROK_PZ = 2018;

                    NaglowkiPz.ID_MAGAZYNU = mIdMagazynu;

                    NaglowkiPz.ID_DOSTAWCY = mIdDostawcy;

                    NaglowkiPz.DATA_PZ = DateTime.Now;

                    NaglowkiPz.MAGAZYNY = magazyn;

                    NaglowkiPz.DOSTAWCY = DostawcyFacade.getWiersz(mIdDostawcy);

                    NaglowkiDokumentowPZFacade.dodajWiersz(NaglowkiPz);

                    mIdDostawcy++;
                    
                }


            }


         

            for (int mIdMagazynu = 1; mIdMagazynu <= 10; mIdMagazynu++)
            {

                for (int mNrPz = 1; mNrPz <= 100; mNrPz++)
                {

                    pozycjePzList = new List<POZYCJE_PZ>();
                    naglowekPz = NaglowkiDokumentowPZFacade.getWiersz(mIdNaglowkaPz);

                    for (short nrPozycji = 1; nrPozycji <= 3; nrPozycji++)
                    {

                        pozycjaPz = new POZYCJE_PZ();

                        pozycjaPz.ID_NAGLOWKA_PZ = mIdNaglowkaPz;
                        pozycjaPz.NAGLOWKI_PZ = naglowekPz;
                        pozycjaPz.NR_POZYCJI = nrPozycji;
                        pozycjaPz.ID_TOWARU = mIdTowaru;
                        pozycjaPz.TOWARY = TowaryFacade.getWiersz(mIdTowaru);


                        pozycjaPz.ILOSC = ilosc;
                        pozycjaPz.ILOSC_AKTUALNA = ilosc;
                        pozycjaPz.CENA_ZAKUPU_NETTO = cenaZakupuNetto;
                        pozycjaPz.STAWKA_VAT = stawkaVat;

                        

                     //   ilosc += (decimal)0.01;
                     //   cenaZakupuNetto += (decimal)0.01;

                        pozycjaPz = PozycjeDokumentowPZFacade.dodajWierszIZwroc(pozycjaPz);


                        
                        pozycjePzList.Add(pozycjaPz);
                        mIdTowaru++;

                    }

                    naglowekPz.POZYCJE_PZ = pozycjePzList;
                    NaglowkiDokumentowPZFacade.edytujWiersz(naglowekPz);

                    mIdNaglowkaPz++;

                  

                }


            }


            watch.Stop();
            
            TESTY test = new TESTY();
            test.TYP_TESTU = "DODAJ_PZ";
            test.CZAS = watch.ElapsedMilliseconds;
            test.CZAS_TAKTOWANIE = watch.ElapsedTicks;
            TestyFacade.dodajWiersz(test);
     
           

        }
0

Nie wiem czy AD 2018 kogoś taki temat jeszcze rozpala ale jak już coś twierdzisz to wrzuć kod i podaj dokładne wyniki. Może komuś się zechce to powtórzyć. Chociaż nie wiem po co.

0

Poza tym, że kod wygląda naprawdę groźnie, to bez wiedzy o tym, jak masz skonfigurowane ORMy i o co chodzi z tymi "fasadami" ciężko cokolwiek stwierdzić. Np. jeśli u Ciebie getWiersz za każdym razem pobiera coś z bazy, to oczywiste, że będzie wolne. Ważna jest też liczba oddzielnych transakcji. W kodzie, który wkleiłeś nie ma żadnego commita, więc pewnie robisz to w każdej metodzie dodajWiersz. Mam rację? Wersja Javowa też tak robi?

Ale może to faktycznie są identyczne aplikacje, EF z prędkości raczej nie słynie. Ale co konkretnie jest problemem w takiej sytuacji może powiedzieć tylko profiler.

0

konfiguracja ORM jest standardowa ustalona domyślnie odpowiednio w Visual Studio i NetBeans

kod dodajWiersz

namespace HurtowniaData.HurtowniaEntities
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    
    public partial class HURTOWNIAEntities : DbContext
    {
        public HURTOWNIAEntities()
            : base("name=HURTOWNIAEntities")
        {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }
    
        public virtual DbSet<DOSTAWCY> DOSTAWCies { get; set; }
        public virtual DbSet<KLIENCI> KLIENCIs { get; set; }
        public virtual DbSet<MAGAZYNY> MAGAZYNies { get; set; }
        public virtual DbSet<NAGLOWKI_MM> NAGLOWKI_MM { get; set; }
        public virtual DbSet<NAGLOWKI_PZ> NAGLOWKI_PZ { get; set; }
        public virtual DbSet<NAGLOWKI_WZ> NAGLOWKI_WZ { get; set; }
        public virtual DbSet<NUMERY_DOKUMENTOW> NUMERY_DOKUMENTOW { get; set; }
        public virtual DbSet<NUMERY_POZYCJI_DOKUMENTOW> NUMERY_POZYCJI_DOKUMENTOW { get; set; }
        public virtual DbSet<POZYCJE_MM> POZYCJE_MM { get; set; }
        public virtual DbSet<POZYCJE_PZ> POZYCJE_PZ { get; set; }
        public virtual DbSet<POZYCJE_WZ> POZYCJE_WZ { get; set; }
        public virtual DbSet<STANY> STANies { get; set; }
        public virtual DbSet<TESTY> TESTies { get; set; }
        public virtual DbSet<TOWARY> TOWARies { get; set; }
    }
}





namespace HurtowniaRepositories
{
    public class ObslugaDanych<TEncja> : IObslugaDanych<TEncja> where TEncja : class

    {
        protected DbContext Context;

        public ObslugaDanych(DbContext context)
        {
            Context = context;
        }

        public TEncja getWiersz(long id)
        {

            return Context.Set<TEncja>().Find(id);
  
        }


      .......................

        public void dodajWiersz(TEncja entity)
        {
            Context.Set<TEncja>().Add(entity);
            Context.SaveChanges();
            
        }

        public TEncja dodajWierszIZwroc(TEncja entity)
        {
            Context.Set<TEncja>().Add(entity);
            Context.SaveChanges();
            return entity;
        }

        public void dodajWiersze(IEnumerable<TEncja> entities)
        {
            Context.Set<TEncja>().AddRange(entities);
        }


        .................................................

        public void zwolnijContext()
        {
            Context.Dispose();
        }
    }
}

1

Czyli dokładnie jak zostało już wcześniej wspomniane, przy każdym odczycie i zapisie robisz to pojedynczymi operacjami na bazie.
Nie jestem pewny jak faktycznie wygląda obecnie sytuacja z EF, ale kiedyś miał tendencje do przymulania względem odpowiedników, także wcale by mnie nie zdziwiło, gdyby faktycznie wychodził wolniej.
Natomiast kwestia standardowej konfiguracji ORMów też nadal nie jest wyjaśniona, bo trzeba by się zagłębić czy defaultowe ustawienia zwyczajnie się nie różnią, co powoduje na przykład domyślne wrapowanie dużej ilości zapytań w transakcję w przypadku Javy.

Weź do łapy Hibernate i nHibernate, skonfiguruj je identycznie i wtedy się baw, bo jak nie wiesz nawet co się dzieje pod spodem, to porównania nie mają sensu.

I nadal nie mamy dokładnych wyników, więc możemy co najwyżej wierzyć na słowo ;)

I... Kod faktycznie nie zachęca do zapoznawania się. Czemu zdecydowałeś się użyć języka polskiego z próbą zliczbomnożenia z angielskiego?

0

A w wersji Javowej też commitujesz po każdym rekordzie?

0

w nagłówkach używam metody facade create(entity)

a dla pozycji poniżej dzięki temu mogę zwrócić encje i dodać do listy pozycji dołączanej do nagłówka

public PozycjePz dodajNowaPozycjePzIZwroc(PozycjePz obiektPozycjaPz) {

        getEntityManager().persist(obiektPozycjaPz);
        getEntityManager().flush();
        return obiektPozycjaPz;

    }
0

To jak dodawać rekordy w EF by nie dokonywać zapisu po każdej metodzie ?

1

Rozdzielając dodawanie encji do kolekcji kontekstu od zapisywania ich do bazy.
Jeśli masz kontekst wpinany jako zależność klasy, to

context.SaveChanges()

Będzie w osobnej metodzie niż

context.Set<T>.Add(encja)

Normalnie robi się to jeszcze inaczej, ale to nie ten temat.

1

Tak jak się to robi korzystając z ORMa - wykonać wszystkie operacje w ramach unit of work, zatwierdzić na koniec.
Przy czym Ty tu robisz operacje wsadowe, ORMy generalnie do nich nie służą. EF pewnie klęknie od nadmiaru śledzonych obiektów, więc trzeba to podzielić na jakieś pakiety może. Ale najpierw spróbuj normalnego podejścia z jednym UoW.

0

moim celem było porównać wydajność dodawania nowych rekordów przez JPA w moim wypadku EclipseLink i EF

jest lepszy sposób ?

nie mam dużego doświadczenia w ASP.NET, normalne podejście to znaczy w tym wypadku ?

1

To nie chodzi o ASP.NET, tu chodzi o korzystanie z ORMa - najpierw dodajesz/edytujesz/usuwasz ile potrzebujesz encji w sesji/kontekście ORMa, potem zatwierdzasz zmiany, i ORM magicznie wysyła do bazy tyle poleceń ile trzeba. To się nazywa unit of work i jest niezależne od technologii i języka - jedna transakcja ma być dla wszystkich zmian.

A jak chcesz porównać tylko prędkość ORMów, to nie rozumiem po co do tego jakąś warstwę webową dodawać, przecież wystarczy to zrobić w konsoli.

0

ORM jest jednym z kryteriów ich oceny u mnie.

1

Jeśli ma to być jakkolwiek miarodajny test, to wypadałoby to zrobić po ludzku i korzystając z aktualnej wiedzy.
Jak już wspominałem, nie wiem jak to wygląda przy javowym odpowiedniku, który wybrałeś, konfiguracja EF w domyśle zakłada, że wiesz co robisz. To oznacza, że jeśli masz 3000 rekordów zapisywanych w sposób taki, jaki przedstawiłeś, to jest to dosłownie 3000 operacji zapisu do bazy.

A czy tak się robi w realnych sytuacjach? No ni dioboła, nie w cywilizowanych systemach, bo wywołując te operacje w ten sposób zabijasz bazę.

Co do UoW, jest on niezależny od technologii, jak wcześniej wspomniał somekind.
Jeśli nie znasz wzorca, to poczytaj o jego idei i wykorzystaniu.
W mocnym uogólnieniu, możesz założyć, że odpowiada za końcowy stan bazy po wszelkich operacjach wykonanych w trakcie jego życia - wstrzykujesz mu ten sam context, który dostaje repozytorium czy co tam sobie wymyślisz u siebie, ale to właśnie przy jego pomocy wywołujesz zapisanie zmian do bazy lub wycofanie ich, po wykonaniu WSZYSTKICH operacji na danych.
Dzięki temu unikasz sytuacji, w której wykonujesz 3000 zapytań zamiast wykonać jedno większe.
Używanie narzędzi niezgodnie z ich przeznaczeniem nijak nie potwierdza ich ułomności przecież.

A jako, że pewnie trochę nabredziłem, bom młody zupełnie jak godzina, o której to piszę, to nie obrażę się na wszelkie korekty :)

1

@Klojtex: Ale do testów nie ma znaczenia czy się tak robi czy nie robi czy nie robi. Znaczenie ma czy JPA i EF działają w raki sam sposób. Czy JPA też commituje po każdym zapytanie. A kod testu jest jaki jest.

Badanie ORM-a jako kryterium oceny ASP.NET MVC vs. Java Web App??? Przecież to ma jeszcze mniej sensu niż początkowo myślałem.
Nie podoba Ci się EF to użyj czegoś innego ORM-a. Chcesz sprawdzić ORM-a to sprawdzaj ORM-a. A, jak @somekind wspomniał, takich batch insertów nie trzeba robić ORMem. Zrób sobie test mapowanie tabel na obiekty, jeśli już musisz. Wyłącz śledzenie zmian i zrób ponowny test.

2

Moim zdaniem nie masz pojęcia co robisz, a twoja wiedza jest za kiepska, aby zrobić miarodajny benchmark.

Ten co zrobiłeś ma wartość ujemną. Wprowadza tylko chaos (jakby go mało było).
Miarodajny test powienien mieć :

  • podane źródła, żeby można było sobie samemu zweryfikować,
  • podane wyniki i konfiguracje na której zostały osiągnięte,
  • powinien być zoptymalizowany dla każdej z platform.
    (i pewnie jeszcze czegoś zapomniałem)

Wynikiem napradę wiarygodnego benchmarku jest nie tylko liczba, ale też odnalezienie przyczyny, dla której coś jest szybsze lub wolniejsze.

Raczej nie masz wystarczającej wiedzy ani o ASP.NET, ani o Java EE ( jak ktoś pisze JEE to już raczej wskazuje na co najmniej stare źródła).

Pocieszę Cię. Mało kto ma wiedzę i środki, żeby zrobić sensowny benchmark różnych platform. Mam prawie 20 lat doświadczenia w Javie, zrobiłem kilka (tylko) niby bardziej profesjonalnych benchmarków. I tak, po czasie, zwykle wychodzi, że coś było zrypane :/
Większośc benchmarków, które przygotowywałem, zarzuciłem w połowie, po uswiadomieniu sobie, że brak mi odpowiedniej wiedzy. Nadal to się zdarza, nawet jeśli się kręcę koło Javy, którą niby znam całkiem dobrze :-(

Dodatkowy smaczek, w Javie nie używam JavaEE, bo jest dla mnie zbyt ociężałe, ale nie mam na to benchmarków dobrych, to tylko subiektywne odczucie,
Konkretniejszy fakt, to taki że moje serwery javowe startuje w czasie sekundy- dwóch versus 20-30 sekund Glassfischowych.
Mało kto też już używa JavaEE, i Glassfischa. W ogóle, jak chcesz coś robić sensownego w javie to nie rób tego na Application serwerze. Application Serwery to ślepa odnoga ewolucji.

0

Większość benchmarków jest semantycznie bezsensowna i od razu pojawia się 100 pomysłów jak sprawić by były mniej oderwane od rzeczywistości. To dotyczy tego benchmarka jak i np benchmarks game czy techempower web framework benchmarks. Wszystkim naraz się nie dogodzi, więc każdy sam sobie stwierdzi czy benchmark ma dla niego jakąś wartość. Ale by benchmark był miarodajny to poszczególne programy którym mierzy się wydajność powinny robić mniej więcej to samo, tzn spełniać wymagania benchmarka. Inaczej nie wiadomo co się w ogóle porównuje, więc benchmark traci sens zupełnie.

2

Technicznie sensowne jest sprawdzenie wymagań dotyczących systemu ( ile np. transakcji ma przetwarzać na sekundę) i sprawdzenie benchmarkiem, czy platforma którą wybraliśmy mieści się w realiach.

W większości przypadków ważniejsze jest wybranie platformy wygodniejszej w użyciu niż szybszej.

W praktyce często ludzie robią zepsute benchmarki , a potem jeszcze wybierają jakiś durny i niewygodny język lub framework , który w benchmarku był zupełnie przypadkowo pięc razy szybszy. Tylko po to żeby obsłużyć piętnastu użytkowników na dzień, bo takie są zaplanowane realia biznesowe :-)

0

W podanym przykładzie wstrzykuję ten sam kontekst do wszystkich encji czyli ten warunek chyba jest częściowo choćby spełniony, co do metody sprawdzenia to przyznam nie oddaje on realnej sytuacji z życia

pytanie jak sprawdzić miarodajnie wydajność dodawania encji w EclipseLink i EF, jakim rozwiązaniem zbadanie jednego zapisu jest nierealne choć tak naprawdę szukam odpowiedzi który z nich
szybciej zapiszę tę samą encje, jak to sprawdzić jakim rozwiązaniem

środowisko jest przykładem i zapewne tylko w nim realne będą wyniki testu może w jakimś stopniu tylko będą się te wyniki potwierdzać w innych środowiskach testowych i to może

w takim razie miarodajne będą testy odczytu danych (pozostałe testy), czasy odczytu danych są kluczowe przy dodawaniu bo czytam różne encje nim utrwalam nową z ich wartościami

2

Ja to bym raczej wystawił endpoint, który przyjmuje jakieś dane i przetwarza je zgodnie z dobrymi praktykami, a potem w ten endpoint wysłał 1000 requestów jakimś JMeterem czy tego typu narzędziem. To będzie lepsze sprawdzenie prędkości całej aplikacji niż insertowanie w pętli, które zabija ułomnego ORMa.

1

@Pawel412: To może tak Ci się tylko wydaje bo to zależy od rodzaju aplikacji. Jeśli to app dla ludzi (sądząc po kodzie jakaś sprzedażówka) to i tak pewnie przez większość czasu będzie robić NIC. Bo to raczej nie nowy Amazon czy Allegro.

0

cel edukacyjny, uczę się

0

Ja to bym raczej wystawił endpoint, który przyjmuje jakieś dane i przetwarza je zgodnie z dobrymi praktykami, a potem w ten endpoint wysłał 1000 requestów jakimś JMeterem czy tego typu narzędziem

I co to niby ma sprawdzić? W której aplikacji jest szybszy Cache ?

Czyli rozumiem, że chcecie przetestować zapis i odczyt bazy danych i debatujecie nad tym którego języka i frameworka użyć w tym celu ? No super, bardzo interesujące... Może na wykop dajcie ten temat...?

To jest naprawdę Inteligentna dyskusja. ;)

0

@Pawel412: No to ucz się czegoś pożytecznego.

0

przy zapisie pewnie tak, a przy odczycie to już chyba trochę inna sytuacja np. przy odczycie wielu danych bo aplikacja czeka nim może ruszyć dalej, w życiu prędkość chyba zależy od najwolniejszego elementu

0

Rozwiązań jest tyle samo co problemów. Piszą Ci tu doswiadczeni ludzie zeby dac sobie z tym spokoj ale jak xhcesz ro mozesz przepalac kolejne godziny na sprawdzanie co jest szybsze zamiast pisania działającego kodu. Twój wybór.
Problemy wydajnosciowe czasem sie zdarzaja i wtedy sie je rozwiązuje YAGNI.

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