Loading...
stambi4a avatar stambi4a 126 Точки

[Useful Info]Databases Advanced - Entity Framework - Performance Measurements или наистина ли N + 1 проблема е проблем?

   На фона на твърденията от много места в нета , че  Lazy трябва да се избягва(заради N + 1 проблема), че ToList преди края е скрит  N + 1 проблем, че Native заявките са най-бързи, че е по-добре да се работи с IQueryable отколкото с IEnumerable и т.н., направих няколко теста, за да видя горе долу колко по-бърз или по-бавен е даден подход.

   Използвах базата BookShopSystem с 1000 редa във всяка от използваните таблици, за версията на EF - най-последната стабилна Entity Framework 6.1.3, както и последният SQL Server - 2016.  В началото преди всяко измерване правих по една малка заявка за да може да е конектната към базата.

    Резултатите ме изненадаха доста, в смисъл, че поне за мен опровергават всички горни твърдения.

    Това са Моделите, като не съм включил тези които не съм използвал при замерванията.

    Това е Measurements  класа, като с коментари съм описал накратко целта на даден метод-тест. Някои от тестовете впоследствие установих, че са излишни, тъй като се отнасят до оптимизациите на Linq, но все пак ги оставих, за да се видят и тези оптимизации.

    Това е Таблицата с резултатите, които получих.

    Ето няколко извода които извлякох от тестовете:

  1. Нещо, което се подразбира, но все пак съм го виждал и на лекции - използването на Console.WriteLine при замервания не само, че добавя време, но и обърква реалните пропорции м/у времената на заявките.
  2. В някои случаи използването на Include сякаш се пренебрегва, например когато след това се търси Count на резултатите, в други материализацията с ToList след Include показва много (10 пъти в конкретният тест 5) по-голяма разлика спрямо случаят без Include, при слаба материализация с FirstOrDefault - 2 пъти повече време на съответните заявки.
  3. Include не помага при foreach, нито при ToList(което е същото, тоест N + 1 проблема). Даже времето с Include  e няколко пъти повече(над 4 от тестове 7, 8, 9, 10). Има ли смисъл да се използва въобще Eager loading с Include, за да си спестите заявки. SQL Server използва сторнати процедури за проблема N+1 и тяхното комбинирано изпълнение в тестовете е много по-бързо от едната заявка с Include. Предполагам, че това все пак зависи от конкретната база или колко записи има в джойнатият обект. 
  4. Използването на ToList в края не е по-бързо от използването му в началото или по средата, при условие, че предходните linq изрази в единият случай са същите като следващите в другият случай.
  5. Select [Column] като предпочитан вариант нa Include(Select *) не е по-бърз от съответният Include, за предпочитане е заради паметта. Ако анонимните обекти са проблем, то може да се селектне в Data Transfer Object или т.н. DTO.
  6. Native заявка в Entity Framework не е по-бърза от съответните заявки (тестове 17, 18), затова и може би се радва на ползване само при заявки без връщанe на резултат - add, update, delete.

   

    На мен ли така ми се струва или оптимизациите в EF и SQL са на много високо ниво? Да, използваната база е проста, малка, представлява само 1 случай от хилядите възможни, но така или иначе досега не съм видял замервания които да опровергават горните изводи. Затова ми е интересно дали някой колега се е занимавал с performance и какво може да сподели по темата. 

    Благодаря.

1
Databases Advanced - Entity Framework 27/11/2016 10:25:22
val4o89 avatar val4o89 240 Точки

По точка 1 - естествено, че печатането и визуализирането е бавно, никой не мери времето на изпълнение на заявка, заедно с печатането на конзолата.

По точка 4 - измери ли използваната памет? Ако ToList-неш преди да си селектнал необходимите ти данни в анонимен обект ще направиш излишно по-голям трафик, от колкото ти трябва. Като в магазина, първо реши какво ти трябва, и после го кажи на продавачката, а не да кажеш "дай ми шоколадите", тя да ти ги даде и ти чак тогава да избираш кои точно шоколади искаш да купиш.

В крайна сметка се гони максимален performance, което не значи само бързина на действие, а и минимално използване на ресурсите на машината. Ако някой ми изхарчи мобилния интернет, щото съм цъкнал на една картинка, a приложението ми дръпне гигабайт данни, че да ги пресее локално... няма да ми хареса и ще звъня на *88 да питам кво става :D

1
28/11/2016 16:24:42
stambi4a avatar stambi4a 126 Точки

Точка 1 я споменах, защото съм виждал такова измерване от  лекция.

Performance и  памет са различни и често противоречащи си неща, например кеширането. Идеята на Include e точно performance в определени заявки, защото е ясно, че паметта и мрежовият трафик са доста.  До каква степен и дали да се използва Include можем да прочетем тук

Направих обаче замервания на паметта за случите с ToList.

По точка 12, паметта при използване на Where.Select.ToList беше почти една и съща(около 7.2 MB) независимо от условието в where(тоест кои шоколади искам да си купя). Разликата в паметта при нула върнати и 1000 върнати резултати е горе долу същата,+/-5% Това показва, че изпълнението на самата заявка е много по скъпо като памет от данните, които връща. Използването на ToList.Where.Select даде около 3.3 МБ. Само ToList() дава същият резултат. Само Where е 5.1 MB, Select също 5.1 МБ. Очевидно в тези случаи, че ToList() в началото е повече от 2 пъти по-бърза от ToList() в края. ToList() e по-евтина от Where и Select(). Това е при стратегия MigrateDatabaseToLatestVersion.

При DropCreateDatabaseIfModelChanges стратегия само ToList() e 5.5 MB, Select 3.3 MB, Where също 3.3 МБ. Слагането на ToList() преди или след Where() и Select() няма никакво почти никакво значение, има 50 KB повече ако е в началото.

Тези измервания са правени с GC, не с memory profiler.

Та от разгледаните случаи изглежда, че ToList() дърпа повече памет в някои стратегии, докато в други намалява паметта, а разликата в скоростта с него не е много голяма. За съжаление тука никакви метафори не вървят, вземат се предвид много повече неща от това, дали първо да избираме шоколадите и след това да ни ги дават. Може понякога клиента да е по-оправен от продавача и алгоритъмът му на събиране от купчината да е по-добър. Освен всичко друго, може и продавачът да е зает, да му е по-лесно да ти даде цялата купчина и да ти каже "аз съм зает, ако искаш оправяй се, нали можеш и сам".

0
28/11/2016 19:43:44
val4o89 avatar val4o89 240 Точки

Имах предвид, че ако съм потребител и искам да видя една картинка от базата, моята машина няма нужда да тегли всички картинки, че да ми селектне тази, която аз искам да видя(смисъла на примера със шоколадите). Ако направиш ToList() преди да си избрал конкретно това, което искаш да видиш, ще събереш сташно много информация, която не ти трябва.

1
stambi4a avatar stambi4a 126 Точки

И аз си мислех така преди. Въпросът е дали sql прави по-бързо това, което прави само linq. От това, което мерих, излиза, че паметта използвана с ToList() първо при някои стратегии е по-малко, а скоростта е кажи-речи същата. Излиза, че linq на локално ниво се справя доста добре, дори и цената в памет да се използва локално може да е примамлива. А иначе, споменатият от тебе конкретен пример звучи като случай 3, където се използва ToList().FirstOrDefault(), което наистина е безсмислено, но го направих доколкото си спомням, за да видя дали има някакви оптимизации. Е явно няма, а такава заявка надали някой ще направи и без това.

0
RFilipov avatar RFilipov 136 Точки

С базите и тестовата среда, с която разполагаш няма да видиш никаква разлика.

При работа с голям обем от данни в конкурентна среда (повече потребители работят с една база по едно и също време и записват голям обем данни) и високи нива на паралелизъм тогава ще видиш разлика.

1
stambi4a avatar stambi4a 126 Точки

За кое от всичките неща говориш, Lazy vs Eager, Native скоростта или N + 1  проблема? Lazy vs Eager се моделира спрямо ситуацията, Native е за предпочитане при Delete и извикване на сторнати процедури , за Bulk заявките си има Extended библиотеката. За N + 1 може би е така, ако приемем, че sql сървърите се натоварват повече, макар да използват оптимизации.

Има и един момент, че зависи колко на брой подобни заявки се изпълняват една след друга. Например 1 заявка Include е в пъти по-бавна от 1 lazy, при 1000 и при 10000 те са почти същите като време. Като правих измерванията използвах минимално 100 итерации заради усредненото време, ама това се оказва, че също може да влияе на резултатите. Причинaта, доколкото разбирам е в модела на използване на т. н. execution plans и свързаното plan cache(не зареждането на поръчките при eager loading в ObjectStateManager-a).

Това, което трябва да се прави когато се препоръчва определен модел на заявките е не да се търси универсално решение, защото такова няма, а да се аргументира при кои условия кое решение най-добро. Затова и "не използвайте ToList()" само по себе си не върви.

 

0
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.