Entity Framework jest zbyt wolny. Jakie mam możliwości? [Zamknięte]

93

Postępowałem zgodnie z mantrą „Don't Optimize Prematurely” i zakodowałem moją usługę WCF przy użyciu Entity Framework.

Jednak profilowałem wydajność i Entity Framework jest zbyt wolny. (Moja aplikacja przetwarza 2 wiadomości w około 1,2 sekundy, podczas gdy (starsza) aplikacja, którą piszę ponownie, wykonuje 5-6 wiadomości w tym samym czasie (starsza aplikacja wywołuje sprocy w celu uzyskania dostępu do bazy danych).

Moje profilowanie wskazuje, że Entity Framework zajmuje większość czasu na wiadomość.

Więc jakie mam opcje?

  • Czy są tam lepsze ORMy?
    (Coś, co obsługuje tylko normalne czytanie i pisanie obiektów i robi to szybko ...)

  • Czy istnieje sposób na przyspieszenie Entity Framework?
    ( Uwaga : kiedy mówię szybciej, mam na myśli na dłuższą metę, a nie pierwsze połączenie. (Pierwsze połączenie jest wolne (15 sekund na wiadomość), ale to nie jest problem. Po prostu potrzebuję, aby było szybkie dla reszty wiadomości.)

  • Jakaś tajemnicza trzecia opcja, która pomoże mi przyspieszyć działanie moich usług.

UWAGA: Większość moich interakcji z bazą danych to tworzenie i aktualizacja. Robię bardzo mało zaznaczania i usuwania.

Vaccano
źródło
To brzmi jak powtórzenie „linq is slow”. Skąd wiesz, że to EF? Czy sprofilowałeś wszystkie swoje zmiany?
Maess
6
Niektóre odpowiedzi wskazują na pytania. Z mojego doświadczenia wynika, że ​​powolność w EF ma niewiele wspólnego z zapytaniami, ale zamiast tego z kosztami materializacji, a te koszty są często powiązane ze śledzeniem zmian i ich wpływem na utworzone instancje. Niestety nie mam dla Ciebie srebrnej kuli, więc to tylko komentarz, ale poleciłbym sprawdzić, czy profilowanie ujawnia wysokie koszty materializacji, a jeśli tak, zbadać, co można zrobić z tymi kosztami.
Anthony Pegram
@Maess - myślałem, że wskazałem, że sprofilowałem i stwierdziłem, że to EF / DB był powolny. Tak czy inaczej, tak, zrobiłem. Sprofilowałem to i to interakcje EF / DB są głównym winowajcą.
Vaccano
@Anthony - Czy materializacja nie jest czymś w rodzaju pierwszego uruchomienia? Jeśli tak, masz rację, że jest to bardzo powolne. Pierwszy bieg jest bardzo wolny. Ale jak wskazałem, nie martwię się tym zbytnio. To całkowita przepustowość jest problemem. (Jeśli to nie jest tym, czym jest materializacja, to potrzebuję trochę badań, aby sprawdzić, czy to jest przyczyną mojego problemu)
Vaccano
1
@Vaccano, nie, materializacja to proces pobierania danych z bazy danych oraz tworzenia instancji i wypełniania wykresu obiektów w celu reprezentacji tych danych. Nie mówię o wydajności pierwszego uruchomienia, ponieważ kod jest jitowany (lub nawet, gdy Sql Server może stworzyć plan wykonania zapytania), ale o tym, co dzieje się za każdym razem, gdy otrzymujesz dane w postaci obiektów.
Anthony Pegram

Odpowiedzi:

45

Należy rozpocząć od profilowania poleceń SQL faktycznie wydanych przez Entity Framework. W zależności od konfiguracji (POCO, jednostki Self-Tracking) jest dużo miejsca na optymalizacje. Możesz debugować polecenia SQL (które nie powinny różnić się między trybami debugowania i wydania) przy użyciu ObjectSet<T>.ToTraceString()metody. Jeśli napotkasz zapytanie, które wymaga dalszej optymalizacji, możesz użyć niektórych prognoz, aby przekazać EF więcej informacji o tym, co próbujesz osiągnąć.

Przykład:

Product product = db.Products.SingleOrDefault(p => p.Id == 10);
// executes SELECT * FROM Products WHERE Id = 10

ProductDto dto = new ProductDto();
foreach (Category category in product.Categories)
// executes SELECT * FROM Categories WHERE ProductId = 10
{
    dto.Categories.Add(new CategoryDto { Name = category.Name });
}

Można zastąpić:

var query = from p in db.Products
            where p.Id == 10
            select new
            {
                p.Name,
                Categories = from c in p.Categories select c.Name
            };
ProductDto dto = new ProductDto();
foreach (var categoryName in query.Single().Categories)
// Executes SELECT p.Id, c.Name FROM Products as p, Categories as c WHERE p.Id = 10 AND p.Id = c.ProductId
{
    dto.Categories.Add(new CategoryDto { Name = categoryName });
}

Właśnie wpisałem to z głowy, więc nie jest to dokładnie sposób, w jaki zostałoby to wykonane, ale EF faktycznie wykonuje kilka fajnych optymalizacji, jeśli powiesz mu wszystko, co wiesz o zapytaniu (w tym przypadku będziemy potrzebować kategorii- nazwy). Ale to nie jest jak szybkie ładowanie (db.Products.Include ("Categories")), ponieważ prognozy mogą dodatkowo zmniejszyć ilość danych do załadowania.

J. Tihon
źródło
40
Ta odpowiedź brzmi rozsądnie, dopóki nie zdasz sobie sprawy, że typy anonimowe nie są dostępne poza metodą, w której są zdefiniowane. Jeśli chcesz załadować złożony obiekt i nie pisać megamothy, musisz deserializować swoje nowe anonimowe typy do jakiegoś rodzaju POCO. Ponownie, brzmi to prawie rozsądnie, dopóki nie zdasz sobie sprawy, że robiąc to, zasadniczo ODNOWIŁEŚ SWOJE WŁASNE RAMY PODMIOTU. Co jest bzdurą.
Doug
5
spowodowało to 15x-20x wzrost szybkości dla mnie.
Dave Cousineau
12
Ciekawa i pomocna odpowiedź, wciąż aktualna po pewnym czasie. @Doug: Co nie jest bzdurą, ponieważ optymalizujesz (używając prognoz) tylko te kilka zapytań, w których naprawdę potrzebujesz, skorzystaj z dodatkowej korzyści. EF i POCO zapewniają rozsądną wartość domyślną, co jest bardzo miłe!
Victor,
2
@Doug Większość aplikacji ma modele widoku dla scenariuszy tylko do wyświetlania, prawda? Równie dobrze możesz wykonać taką samą część mapowania, jak wyciągasz dane.
Casey
4
Czułem, że ORM to przyszłość. Po prostu miały sens, dopóki nie zacząłem ich używać. Potem znalazłem Dappera . Teraz, widząc takie rozwiązania, wzdrygam się, widząc, jak szybko rośnie złożoność. Pisanie abstrakcyjnego SQL w C # nie jest sposobem na przetrwanie.
Michael Silver
80

Faktem jest, że produkty takie jak Entity Framework będą ZAWSZE powolne i nieefektywne, ponieważ wykonują dużo więcej kodu.

Uważam również za głupie, że ludzie sugerują, że należy zoptymalizować zapytania LINQ, spojrzeć na wygenerowany SQL, użyć debuggerów, prekompilować, wykonać wiele dodatkowych kroków itp., Tj. Marnować dużo czasu. Nikt nie mówi - Uprość! Każdy chce komplikować sprawy dalej, podejmując jeszcze więcej kroków (marnowanie czasu).

Zdrowym rozsądkiem byłoby w ogóle nie używać EF lub LINQ. Użyj zwykłego języka SQL. Nie ma w tym nic złego. To, że programiści mają mentalność stadną i czują potrzebę używania każdego nowego produktu, nie oznacza, że ​​jest dobry lub zadziała. Większość programistów uważa, że ​​jeśli włączą każdy nowy fragment kodu wydany przez dużą firmę, stanie się to mądrzejszym programistą; wcale nie jest prawdą. Inteligentne programowanie polega głównie na tym, jak zrobić więcej przy mniejszych bólach głowy, niepewności i jak najmniejszym czasie. Pamiętaj czas! To jest najważniejszy element, więc staraj się znaleźć sposób, aby nie marnować go na rozwiązywanie problemów w złym / rozdętym kodzie napisanym po prostu w celu dostosowania się do dziwnych tak zwanych `` wzorców ''

Zrelaksuj się, ciesz się życiem, zrób sobie przerwę od kodowania i przestań używać dodatkowych funkcji, kodu, produktów, „wzorców”. Życie jest krótkie, a czas życia twojego kodu jeszcze krótszy iz pewnością nie jest to fizyka jądrowa. Usuń warstwy, takie jak LINQ, EF i inne, a Twój kod będzie działał wydajnie, będzie skalowany i tak, nadal będzie łatwy w utrzymaniu. Zbyt dużo abstrakcji to zły „wzór”.

I to jest rozwiązanie Twojego problemu.

Sean
źródło
156
To wylewanie dziecka z kąpielą. Optymalizujesz wąskie gardła, głupio jest wyrzucać EF, ponieważ w kilku miejscach jest zbyt wolny, aw większości innych jest wystarczająco szybki. Dlaczego nie użyć obu? EF obsługuje procedury składowane i surowy kod SQL. Właśnie przekonwertowałem zapytanie LINQ-to-SQL, które trwało ponad 10 sekund, na SP, które zajmuje ~ 1 sekundę, ale nie zamierzam wyrzucać wszystkich LINQ-to-SQL. Zaoszczędziło to dużo czasu w innych prostszych przypadkach, z mniejszym kodem i mniejszym miejscem na błędy, a zapytania są weryfikowane przez kompilator i pasują do bazy danych. Mniej kodu to łatwiejsza konserwacja i mniej miejsca na błędy.
JulianR
12
Ogólnie twoja rada jest jednak dobra, ale nie sądzę, aby porzucanie EF lub innych abstrakcji było właściwe, ponieważ nie działają one dobrze w 10% przypadków.
JulianR
50
Zwykły SQL = łatwy w utrzymaniu? Po prostu nie dotyczy bardzo dużych aplikacji z dużą ilością logiki biznesowej. Pisanie złożonego SQL wielokrotnego użytku nie jest łatwą rzeczą. Osobiście miałem pewne problemy z wydajnością z EF, ale te problemy po prostu nie są porównywalne z korzyściami z prawidłowego ORM pod względem RAD i utrzymywania rzeczy w SUCHOŚCI (jeśli wiąże się to z jakimkolwiek poziomem złożoności).
MemeDeveloper
13
+ 10 ^ 100 Zbyt dużo abstrakcji to zły „wzór”
Makach,
58
-1. „EF będzie ZAWSZE powolny i nieefektywny”. Nie rozumiem, dlaczego miałbyś twierdzić, że coś takiego jest absolutną prawdą. Posiadanie większej liczby warstw spowoduje, że coś będzie wolniejsze, ale to, czy ta różnica jest w ogóle ZAUWAŻALNA, zależy całkowicie od sytuacji, takiej jak ilość danych i typ wykonywanego zapytania. Dla mnie jest to to samo, co powiedzenie „C # będzie ZAWSZE powolne i nieefektywne”, ponieważ jest to wyższa abstrakcja niż C ++. Jednak wiele osób decyduje się z niego korzystać, ponieważ wzrost produktywności znacznie przewyższa utratę wydajności (jeśli występuje). To samo dotyczy EF
Despertar,
37

Jedną z sugestii jest użycie LINQ to Entity Framework tylko w przypadku instrukcji CRUD z jednym rekordem.

Aby uzyskać bardziej złożone zapytania, wyszukiwania, raportowanie itp., Napisz procedurę składowaną i dodaj ją do modelu Entity Framework zgodnie z opisem w witrynie MSDN .

To jest podejście, które zastosowałem w przypadku kilku moich witryn i wydaje się, że jest to dobry kompromis między produktywnością a wydajnością. Entity Framework nie zawsze generuje najbardziej wydajny kod SQL dla wykonywanego zadania. Zamiast tracić czas na zastanawianie się, dlaczego, pisanie procedury składowanej dla bardziej złożonych zapytań w rzeczywistości oszczędza mi czas. Po zapoznaniu się z procesem dodawanie przechowywanych procesów do modelu EF nie jest zbyt trudne. I oczywiście zaletą dodania go do modelu jest to, że otrzymujesz całą silnie wpisaną dobroć, która pochodzi z używania ORM.

Steve Wortham
źródło
Czy masz pojęcie o metodach używanych w rusztowaniach, takich jak db.athlete.find (id) itp. Jak działają w porównaniu z ADO.NET lub eleganckim?
To pułapka
15

Jeśli czysto pobierania danych, to duża pomoc do wykonywania kiedy powiesz EF nie śledzić podmiotów to pobierania. Zrób to za pomocą MergeOption.NoTracking. EF po prostu wygeneruje zapytanie, wykona je i deserializuje wyniki do obiektów, ale nie będzie próbować śledzić zmian jednostek ani niczego podobnego. Jeśli zapytanie jest proste (nie spędza dużo czasu na oczekiwaniu na zwrócenie bazy danych), stwierdziłem, że ustawienie go na NoTracking może podwoić wydajność zapytania.

Zobacz ten artykuł MSDN dotyczący wyliczenia MergeOption:

Rozpoznawanie tożsamości, zarządzanie stanem i śledzenie zmian

To wydaje się być dobrym artykułem na temat wydajności EF:

Wydajność i Entity Framework

JulianR
źródło
9
Zanim ktokolwiek to zrobi, dobrym pomysłem może być przeczytanie tutaj. stackoverflow.com/questions/9259480/…
leen3o
6

Mówisz, że sprofilowałeś aplikację. Czy profilowałeś również ORM? Istnieje profiler EF firmy Ayende, który podświetli, gdzie można zoptymalizować kod EF. Znajdziesz go tutaj:

http://efprof.com/

Pamiętaj, że możesz użyć tradycyjnego podejścia SQL razem z ORM, jeśli chcesz zwiększyć wydajność.

Czy istnieje szybszy / lepszy ORM? W zależności od modelu obiektu / danych możesz rozważyć użycie jednego z mikro-ORMów, takich jak Dapper , Massive lub PetaPoco .

Witryna Dapper publikuje porównawcze testy porównawcze, które dadzą ci wyobrażenie, jak wypadają w porównaniu z innymi ORMami. Warto jednak zauważyć, że mikro-ORMy nie obsługują bogatego zestawu funkcji pełnych ORMów, takich jak EF i NH.

Możesz rzucić okiem na RavenDB . Jest to nierelacyjna baza danych (ponownie od Ayende), która umożliwia bezpośrednie przechowywanie POCO bez konieczności mapowania . RavenDB jest zoptymalizowany pod kątem odczytów i znacznie ułatwia życie programistom, eliminując potrzebę manipulowania schematem i mapowania obiektów na ten schemat. Należy jednak pamiętać, że jest to znacznie inne podejście do korzystania z podejścia ORM i są one opisane w witrynie produktu .

Sean Kearon
źródło
4

Uważam, że odpowiedź @Slauma jest tutaj bardzo przydatna do przyspieszenia działania. Użyłem tego samego wzoru zarówno dla wstawek, jak i aktualizacji - i wydajność wzrosła.

MemeDeveloper
źródło
2

Z mojego doświadczenia wynika, że ​​problem nie dotyczy EF, ale samego podejścia ORM.

Ogólnie rzecz biorąc, wszystkie ORM cierpią na problem N + 1 niezoptymalizowanych zapytań itp. Moim najlepszym przypuszczeniem byłoby wyśledzenie zapytań, które powodują spadek wydajności i próba dostrojenia narzędzia ORM lub przepisanie tych części za pomocą SPROC.

Valera Kolupaev
źródło
2
Ludzie mi to powtarzają. Ale skonfiguruję prostą instrukcję select przy użyciu starej szkoły ADO i tę samą prostą instrukcję wyboru przy użyciu kontekstu EF, a EF jest zawsze znacznie wolniejszy. Naprawdę chcę lubić EF, ale to sprawia, że ​​życie jest trudniejsze, a nie łatwiejsze.
Sinaesthetic
1
@Sinaesthetic Oczywiście, że jest wolniejszy. Z tego samego powodu kod napisany za pomocą Linq do Objects jest zwykle wolniejszy niż kod napisany bez niego. Pytanie nie jest naprawdę , czy to szybciej lub tak szybko (jak to może być, gdy pod maską to musi jeszcze wydać zapytanie zostałeś wydawania przez strony?), Ale czy 1) to wciąż wystarczająco szybki dla swoich potrzeb 2) oszczędza czas pisania kodu 3) korzyści kompensują koszty. Na podstawie tych elementów uważam, że EF jest odpowiedni dla wielu projektów.
Casey
@Sinaesthetic Dodałbym również, że jeśli nie używasz ORM, najczęściej zdarza się, że nie każde zapytanie SQL jest dopracowane i zoptymalizowane, ale że aplikacja kończy się opracowywaniem własnego, organicznego, kiepskiego -wspierany, słabo działający ORM, chyba że twój zespół jest wyjątkowo zdyscyplinowany i bardzo zaniepokojony wydajnością.
Casey
1

Natknąłem się również na ten problem. Nienawidzę zrzucać na EF, ponieważ działa tak dobrze, ale jest po prostu powolny. W większości przypadków po prostu chcę znaleźć rekord lub zaktualizować / wstawić. Nawet proste operacje, takie jak ta, są powolne. Wyciągnąłem 1100 rekordów z tabeli do listy i ta operacja trwała 6 sekund z EF. Dla mnie to za długo, nawet oszczędzanie trwa zbyt długo.

Skończyło się na stworzeniu własnego ORM. Wyciągnąłem te same 1100 rekordów z bazy danych i mój ORM zajął 2 sekundy, znacznie szybciej niż EF. Wszystko z moim ORM jest prawie natychmiastowe. Jedynym ograniczeniem w tej chwili jest to, że działa tylko z MS SQL Server, ale można go zmienić, aby działał z innymi, takimi jak Oracle. Obecnie używam MS SQL Server do wszystkiego.

Jeśli chcesz wypróbować mój ORM, tutaj znajduje się link i strona internetowa:

https://github.com/jdemeuse1204/OR-M-Data-Entities

Lub jeśli chcesz użyć samorodka:

PM> Install-Package OR-M_DataEntities

Dokumentacja jest tam również

TheMiddleMan
źródło
0

Optymalizacja ma sens dopiero po profilowaniu. Jeśli okaże się, że dostęp do bazy danych jest powolny, możesz przekonwertować na użycie procedur składowanych i zachować EF. Jeśli okaże się, że to sam EF jest powolny, być może będziesz musiał przełączyć się na inny ORM lub w ogóle nie używać ORM.

Gabe
źródło
0

Mamy podobną aplikację (Wcf -> EF -> database), która z łatwością wykonuje 120 żądań na sekundę, więc jestem więcej niż pewien, że EF nie jest tutaj twoim problemem, to powiedziawszy, widziałem znaczną poprawę wydajności w przypadku skompilowanych zapytań.

np-trudne
źródło
98% mojego kodu to wywołania tworzenia i aktualizowania. Nie wiem, czy to robi różnicę, ale jest znacznie wolniejsze niż 120 na sekundę.
Vaccano
tak, to nie byłaby typowa aplikacja, sugerowałbym profilowanie swojej aplikacji. dla nas to głównie czyta ...
np-ciężko
0

Użyłem EF, LINQ do SQL i eleganckiego. Najszybszy jest elegancki. Przykład: potrzebowałem 1000 głównych rekordów, z których każdy miał 4 podrzędne. Użyłem LINQ do sql, zajęło to około 6 sekund. Następnie przełączyłem się na dapper, odzyskałem 2 zestawy rekordów z pojedynczej procedury składowanej i dla każdego rekordu dodałem rekordy podrzędne. Całkowity czas 1 sekunda.

Również procedura składowana korzystała z funkcji wartości tabelarycznych z zastosowaniem krzyżowym. Okazało się, że funkcje wartości skalarnych są bardzo wolne.

Moją radą byłoby użycie EF lub LINQ to SQL i w pewnych sytuacjach przełączenie się na eleganckie.

tfa
źródło
-1

Entity Framework nie powinien sam powodować głównych wąskich gardeł. Są szanse, że są inne przyczyny. Możesz spróbować przełączyć EF na Linq2SQL, oba mają funkcje porównawcze, a kod powinien być łatwy do konwersji, ale w wielu przypadkach Linq2SQL jest szybszy niż EF.

Wiktor Zychla
źródło