Jó napot, jó kódolást!
Azon tűnődtem, mi történik, ha jószándék ide, cache-elés oda, extrém terhelés van egy metóduson?
Mondjuk több tízezres nagyságrendű hívásszám? Befolyásolja-e mérhetően a lookup eredményének felhasználási módja a futás idejét?
A válasz az alább látható fejtegetés végén található.
Lássuk a kiindulási állapotot.
Adott egy metódus:
public string GetCached(int key)
{
// Hit Count | Avg Time (ticks) | Time (ticks)
string value = null; // 91 615 | 508 | 46 547 159
if (!SomeProperty.TryGetValue(key, out value)) // 91 615 | 13 057 | 1 196 238 202
value = SomeProperty.DefaultValue; // 652 | 6 931 | 4 519 494
return value; // 91 615 | 508 | 46 547 159
}
Láthatjuk, hogy az első sorban adott null értéket rögtön felülírjuk a TryGetValue() metódus eredményével, ezért az az utasítás elhagyható volna.
Érdekes, hogy a C# fordító nem veszi ezt észre, ez kapásból 46 millió (!!!) tickbe kerül nekünk.
A TryGetValue() sor érdekessége továbbá, hogy a negálás művelet minden kiértékelés után le fog futni, márpedig ez csak ritkán (lásd később) szükséges.
Írjuk át úgy a kódunkat, hogy az első sorban az értékadást elhagyjuk, és a negálást elhagyjuk.
Így alakul a kód:
public string GetCached(int key)
{
// Hit Count | Avg Time (ticks) | Time (ticks)
string value; // 91 615 | 0 | 0
if (SomeProperty.TryGetValue(key, out value)) // 91 615 | 11 597 | 1 062 487 321
return value; // 90 963 | 441 | 40 138 767
return SomeProperty.DefaultValue; // 652 | 7 049 | 4 596 520
}
Látható, hogy az értékadás elhagyását illetve a default érték kezelését megváltoztatva újabb súlyos tick milliókat nyertünk.
Az, hogy kb. 91000-szer negáltunk egy boolean-t , amit később nem használtunk, itt durván 133 750 000 ticket jelent. Az sok. Nagyon sok.
Az utolsó két sort összevetve az előző változat kódjával minimális eltérést (kb. 600 000 tick) láthatunk, ez érdekes, de akár mérési hiba is lehet.
Százalékban kifejezve az első és második megoldás lényegi különbségét:
Before | After | |||||
Line | Avg time | Total time | Line | Avg time | Total time | |
1 | 508 | 46 547 159 | 1 | 0 | 0 | |
2 | 13 057 | 1 196 238 202 | 2 | 11 597 | 1 062 487 321 | |
3 | 6 931 | 4 519 494 | 3 | 441 | 40 138 767 | |
4 | 508 | 46 547 159 | 4 | 7 049 | 4 596 520 | |
Total | | 1 293 852 014 | Total | | 1 107 222 608 | |
Before: | 1 293 852 014 | |||||
After: | 1 107 222 608 | |||||
Diff: | 16,856% |
Hát igen. Durván 17%-al gyorsabb lett a kódunk.
Gyorsabb. Lehetne ennél is gyorsabb? – Adódik a kérdés.
Debizony hogy... Ugyanis még 2 metódus hívását megspórolhatjuk. Hogy nincs is hívva csak a TryGetValue()? Dehogynincs.
Ne feledjük, hogy minden property gettelés a háttérben metódushívást jelent, esetünkben get_SomeProperty() nevűt.
Lássuk, mi történik, ha galád módon nem használjuk az oly sokat propagált property mechanizmust!
public string GetCached(int key)
// Hit Count | Avg Time (ticks) | Time (ticks)
string value; // 91 615 | 0 | 0
if (_somePropertyField.TryGetValue(key, out value)) / / 91 615 | 10 621 | 973 096 244
return value; // 90 963 | 290 | 26 381 642
return _somePropertyField.DefaultValue; // 652 | 5 888 | 3 839 036
}
Before | After | |||||
Line | Avg time | Total time | Line | Avg time | Total time | |
1 | 508 | 46 547 159 | 1 | 0 | 0 | |
2 | 13 057 | 1 196 238 202 | 2 | 10 621 | 973 096 244 | |
3 | 6 931 | 4 519 494 | 3 | 290 | 26 381 642 | |
4 | 508 | 46 547 159 | 4 | 5 888 | 3 839 036 | |
Total | | 1 293 852 014 | Total | | 1 003 316 922 | |
Before: | 1 293 852 014 | |||||
After: | 1 003 316 922 | |||||
Diff: | 28,957% |
Bizony, bizony. Durván 29% az eltérés. Az eltérés majdnem felét az adja, hogy 2 property get helyett 2 field getet használunk.
Kriminális. Hogy ezt miért nem csinálja így automatikusan C# compiler? Na, az jó kérdés.
Tanulság
Röviden összefoglalva:
· A C# compiler nem szűri ki a legnyilvánvalóbb pazarlást sem (value = null a TryGetValue() előtt)
· A property get-nek mérhető hátránya van a sima field get-tel szemben (~20%)
· Kellően nagy számban a negálás művelete sem elhanyagolható (~90.000 hívásnál ~46 millió tick)