• Czas czytania ~3 min
  • 19.06.2023

Ostatnim razem analizowałem wydajność kontenera Laravela. Spotkałem się, że aplikacja spędza dużo czasu na budowaniu zależności, szczególnie w przypadku ciężkich punktów końcowych. To było dziwne, ponieważ wolałbym oczekiwać, że odpowiednia logika będzie najcięższą częścią wniosku.

Problem

Okazuje się, że domyślnie każda zależność w Laravel jest niewspółdzielona. Gdy więc aplikacja wymaga określonej zależności, kontener utworzy i wstrzyknie nowe wystąpienie tej zależności. Na przykład, w Symfony domyślnie każda zależność jest współdzielona:

W kontenerze usług wszystkie usługi są domyślnie współużytkowane. Oznacza to, że za każdym razem, gdy pobierzesz usługę, otrzymasz to samo wystąpienie.

https://symfony.com/doc/current/service_container/shared.htmlTak więc implementacja kontenera w Laravel jest dziwna, ponieważ oznacza to, że każda duża aplikacja korzystająca z Laravel będzie miała problem z wstrzyknięciem wielu instancji zależności. Marnuje to dużo zasobów, ponieważ aplikacja musi rozpoznać parametry określonej klasy za pomocą odbicia (magia automatycznego okablowania), a następnie zbudować wszystkie zależności i zrobić te same rzeczy podczas budowania zależności tej konkretnej klasy i tak dalej. 😄 🤯

\Illuminate\Container\Container::resolve

// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
    return $this->instances[$abstract];
}

Jak to rozwiązać?

Użyj zakresu lub pojedynczego tonu zamiast powiązania u dostawców:

$this->app->scoped(ServiceInterface::class, Service::class);
$this->app->singleton(ServiceInterface::class, Service::class);

Istnieje jedna różnica między zakresem a singletonem.

Metoda z zakresem wiąże klasę lub interfejs z kontenerem, który powinien zostać rozwiązany tylko raz w ramach danego żądania Laravel / cyklu życia zadania. Chociaż ta metoda jest podobna do metody singleton, wystąpienia zarejestrowane przy użyciu metody z zakresem będą opróżniane za każdym razem, gdy aplikacja Laravel rozpoczyna nowy "cykl życia", na przykład gdy proces roboczy Laravel Octane przetwarza nowe żądanie lub gdy proces roboczy kolejki Laravel przetwarza nowe zadanie:Można również ręcznie wyczyścić zależności w zakresie:

Można również ręcznie wyczyścić zależności w zakresie:

$this->container->forgetScopedInstances();

W większości przypadków ta zmiana powinna być bezpieczna, ale należy zachować ostrożność i sprawdzić, czy usługi są bezstanowe.

Usługi

okablowane (Auto-wired Services) Niestety, domyślnie używane są niewspółużytkowane zależności. Oznacza to, że gdy mamy usługi automatycznie okablowane bez żadnego interfejsu, nie musimy ich deklarować u żadnego dostawcy, a następnie nie możemy ich udostępnić.

Aby rozwiązać ten problem, wymyśliłem tylko następujące paskudne rozwiązanie:

$this->app->scoped(Service:class, Service:class)

Możemy więc po prostu utworzyć dostawcę i zdefiniować usługę za pomocą metody zakresu, aby udostępnić tę usługę.

Porównanie

przed

Po

Te wyniki są po pewnych zmianach z bind na scoped, ale nadal nie wszystkie, więc nadal jest miejsce na poprawę. Czas w Blackfire nie ma znaczenia, ale nadal widzimy wzrost wydajności o ~60% podczas zależności budowania.

Podsumowanie

Ciekawi mnie, co było pierwotną przyczyną zaimplementowania kontenera w ten sposób. Oznacza to, że Laravel może nie być najlepszą opcją dla dużych monolitycznych zastosowań. Kilka lat temu pracowałem z ogromnym monolitem opartym na frameworku Symfony rozwijanym przez około 50 programistów backendu i nawet nie wyobrażam sobie wykorzystania Laravela w takim projekcie z taką konstrukcją kontenera. To byłby chyba koszmar.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297