Koszt przejazdu przez wspólny PST

Używam STD::tr1:: shared_ptr intensywnie w całej mojej aplikacji. Obejmuje to przekazywanie obiektów jako argumentów funkcji. Rozważmy następujące:

class Dataset {...}

void f( shared_ptr< Dataset const > pds ) {...}
void g( shared_ptr< Dataset const > pds ) {...}
...

Podczas gdy przekazywanie obiektu zbioru danych przez shared_ptr gwarantuje jego istnienie wewnątrz f I g, funkcje mogą być wywoływane miliony razy, co powoduje, że wiele obiektów shared_ptr jest tworzonych i niszczonych. Oto fragment płaskiego profilu gprof z ostatniego biegu:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls   s/call   s/call  name
  9.74    295.39    35.12 2451177304     0.00     0.00  std::tr1::__shared_count::__shared_count(std::tr1::__shared_count const&)
  8.03    324.34    28.95 2451252116     0.00     0.00  std::tr1::__shared_count::~__shared_count()

Więc, ~ 17% z runtime poświęcono na zliczanie referencji z obiektami shared_ptr. Czy to normalne?

Duża część mojej aplikacji jest jednowątkowa i myślałem o przepisaniu niektórych funkcji jako

void f( const Dataset& ds ) {...}

I zastępowanie połączeń

shared_ptr< Dataset > pds( new Dataset(...) );
f( pds );

Z

f( *pds );

W miejscach, gdzie wiem na pewno, że obiekt nie zostanie zniszczony, podczas gdy przepływ programu znajduje się wewnątrz f(). Ale zanim ucieknę, aby zmienić kilka podpisów funkcji / wywołań, chciałem wiedzieć, co typowy hit wydajności przechodzenia przez shared_ptr był. Wygląda na to, że shared_ptr nie powinien być używany do funkcji, które są wywoływane bardzo często.

Każdy wkład będzie mile widziany. Dzięki za przeczytanie.

-Artem

Update: Po zmianie kilku funkcji na accept const Dataset&, nowy profil wygląda tak:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls   s/call   s/call  name
  0.15    241.62     0.37 24981902     0.00     0.00  std::tr1::__shared_count::~__shared_count()
  0.12    241.91     0.30 28342376     0.00     0.00  std::tr1::__shared_count::__shared_count(std::tr1::__shared_count const&)

Jestem trochę zdziwiony tym, że liczba wywołań destruktora jest mniejsza niż liczba wywołań konstruktora kopiującego, ale ogólnie jestem bardzo zadowolony ze zmniejszeniem związanego z tym czasu pracy. Dzięki wszystkim za Rady.

Author: Artem Sokolov, 2010-03-23

5 answers

Zawsze podaj shared_ptr przez const Referencja:

void f(const shared_ptr<Dataset const>& pds) {...} 
void g(const shared_ptr<Dataset const>& pds) {...} 

Edit: odnośnie kwestii bezpieczeństwa wymienionych przez innych:

  • podczas korzystania z shared_ptr mocno w całej aplikacji, przekazywanie przez wartość zajmie ogromną ilość czasu(widziałem go 50+%).
  • Użyj const T& zamiast const shared_ptr<T const>&, gdy argument nie powinien być null.
  • używanie const shared_ptr<T const>& jest bezpieczniejsze niż const T*, gdy wydajność jest problemem.
 57
Author: Sam Harwell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-23 18:37:29

Potrzebujesz shared_ptr tylko po to, aby przekazać go do funkcji / obiektów, które przechowują go do wykorzystania w przyszłości. Na przykład, niektóre klasy mogą zachować shared_ptr do użycia w wątku roboczym. W przypadku prostych połączeń synchronicznych wystarczy użyć zwykłego wskaźnika lub odniesienia. shared_ptr nie powinien całkowicie zastępować zwykłych wskaźników.

 14
Author: Alex F,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-23 18:13:09

Jeśli nie używasz make_shared , możesz spróbować? Po zlokalizowaniu liczby referencji I obiektu w tym samym obszarze pamięci Można zobaczyć wzrost wydajności związany z spójnością pamięci podręcznej. Warto spróbować.

 4
Author: Kylotan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-23 18:14:12

W aplikacjach o krytycznym znaczeniu należy unikać tworzenia i niszczenia obiektów, szczególnie nadmiarowych.

Zastanów się, co robi shared_ptr. Nie tylko tworzy nowy obiekt i wypełnia go, ale także odwołuje się do udostępnionego stanu do inkrementacji informacji referencyjnych, a sam obiekt prawdopodobnie mieszka gdzieś indziej, co będzie koszmarne w pamięci podręcznej.

Prawdopodobnie potrzebujesz shared_ptr (ponieważ gdybyś mógł uciec od lokalnego obiektu, nie przydzieliłbyś jednego ze sterty), ale mógłbyś nawet "buforować" wynik dereferencji shared_ptr:

void fn(shared_ptr< Dataset > pds)
{
   Dataset& ds = *pds;

   for (i = 0; i < 1000; ++i)
   {
      f(ds);
      g(ds);
   }
}

...ponieważ nawet * pds wymaga uderzenia więcej pamięci niż jest to absolutnie konieczne.

 3
Author: dash-tom-bang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-24 23:25:06

Wygląda na to, że naprawdę wiesz, co robisz. Sprofilowałeś swoją aplikację i wiesz dokładnie, gdzie są używane cykle. Rozumiesz, że wywołanie konstruktora do referencyjnego wskaźnika liczenia jest kosztowne tylko wtedy, gdy robisz to stale.

Jedyne informacje, które mogę ci dać to: Załóżmy, że wewnątrz funkcji f (t * ptr), jeśli wywołujesz inną funkcję, która używa współdzielonych wskaźników, i robisz inne(ptr), a inne tworzy współdzielony wskaźnik surowego wskaźnika. Kiedy ta sekunda liczba referencji współdzielonych wskaźników wynosi 0, a następnie skutecznie usuniesz obiekt....nawet jeśli nie chciałeś. mówiłeś, że często używałeś wskaźników, więc musisz uważać na takie przypadki.

edytuj: Możesz uczynić Destruktor prywatnym i tylko przyjacielem klasy shared pointer, więc w ten sposób Destruktor może być wywołany tylko przez shared pointer, wtedy jesteś bezpieczny. nie zapobiega wielokrotnym usunięciom ze współdzielonych wskaźników. Zgodnie z komentarzem od Mat.

 1
Author: Chris H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-24 18:52:34