2013-05-20 8 views
16

Nasza starsza aplikacja jest zablokowana w strasznym środowisku (dobra, będę wymieniać nazwy, to Tapestry 4), która zawiera śmieszną liczbę EventListeners (~ 100 000) dla najprostszych operacje. Zgaduję, że to jest coś więcej niż to, co javax.swing.event.EventListenerList miało kiedykolwiek obsłużyć, i w tym niefortunnym przypadku użycia powoduje to pewne nieprzyjemne problemy z wydajnością.Dlaczego nie wymieniono elementu EventListenerList? (Lub: jakie są pułapki w jego wymianie?)

Spędziłem kilka godzin bicia się w dość naiwny HashMap/ArrayList opartych wymiany poniżej, i to znacznie szybciej, niemal w każdym calu:

Dodaj 50.000 słuchaczy:

  • EventListenerList> 2 sekundy
  • ~ 3,5 milisekundy

Pożarowe wydarzenie dla 50 000 słuchaczy:

  • EventListenerList 0,3-0,5 ms
  • EventListenerMap 0,4-0,5 ms

Usunąć 50000 detektory (po jednym na raz):

  • EventListenerList> 2 sekundy
  • EventListenerMap ~ 280 milisekund

Wypalanie może być po prostu wolniejsze, ale modyfikacja jest niezwykle szybka. Trzeba przyznać, że sytuacja, w jakiej znalazły się te ramy, jest patologiczna, ale nadal wydaje się, że dawno temu można było zastąpić ją. Oczywiście występują problemy z publicznym interfejsem API (np., Ujawnia on swoją surową wewnętrzną macierz stanów), ale musi być w tym coś więcej. Może są przypadki wielowątkowe, gdzie EventListenerList jest o wiele bezpieczniejsze lub bardziej wydajne?

public class EventListenerMap 
{ 

    private final ReadWriteLock lock = new ReentrantReadWriteLock(); 
    private final Lock readLock = lock.readLock(); 
    private final Lock writeLock = lock.writeLock(); 

    private Map<Class, List> llMap = new HashMap<Class, List>(); 

    public <L extends EventListener> void add (Class<L> listenerClass, L listener) 
    { 
     try 
     { 
      writeLock.lock(); 
      List<L> list = getListenerList(listenerClass); 
      if (list == null) 
      { 
       list = new ArrayList<L>(); 
       llMap.put(listenerClass, list); 
      } 
      list.add(listener); 
     } 
     finally 
     { 
      writeLock.unlock(); 
     } 
    } 

    public <L extends EventListener> void remove (Class<L> listenerClass, L listener) 
    { 
     try 
     { 
      writeLock.lock(); 
      List<L> list = getListenerList(listenerClass); 
      if (list != null) 
      { 
       list.remove(listener); 
      } 
     } 
     finally 
     { 
      writeLock.unlock(); 
     } 
    } 

    @SuppressWarnings("unchecked") 
    public <L extends EventListener> L[] getListeners (Class<L> listenerClass) 
    { 
     L[] copy = (L[]) Array.newInstance(listenerClass, 0); 
     try 
     { 
      readLock.lock(); 
      List<L> list = getListenerList(listenerClass); 
      if (list != null) 
      { 
       copy = (L[]) list.toArray(copy); 
      } 
     } 
     finally 
     { 
      readLock.unlock(); 
     } 
     return copy; 
    } 

    @SuppressWarnings("unchecked") 
    private <L extends EventListener> List<L> getListenerList (Class<L> listenerClass) 
    { 
     return (List<L>) llMap.get(listenerClass); 
    } 
} 
+0

+1 szalony (iest) kwestia tego miesiąca, o Q (do najprostszych operacji) to każdy z etapów oddzielnego lub są przykuci, z drzewa lub semafora, jeśli tak, to kolejny szalony (iest) sugestia, użyj eventhandler i aby zbudować ścieżkę w postaci String w czasie wykonywania, to może nie jest wymagane trzymanie w pamięci mnóstwa metod, phaaa Podoba mi się ten pomysł, który pochodzi z twojego pytania, pozwól RPG żyć wiecznie (to ten sam zwariowany niekończący się kod) – mKorbel

+1

Czy masz literówka we względnych czasach ostrzału? Informujesz, że twoja implementacja może być "tylko wolniejsza od włosów" o 0,4 _milliseconds_ vs. 0.3 _seconds_ dla 'EventListenerList'. –

+0

Tak, to był literówka, dzięki! –

Odpowiedz

5

To kwestia optymalizacji. Swing w EventListenerList zakłada:

  • Liczba słuchaczy na liście jest bardzo mały
  • Liczba ListenerLists może być bardzo duża
  • Dodaj/Usuń zdarzenia są bardzo rzadkie

Biorąc pod uwagę te założenia koszt obliczeniowy dodawania i usuwania przedmiotów jest znikomy, ale koszt pamięci posiadania tych list może być znaczny. Dlatego EventListenerList działa poprzez przydzielenie tablicy, która jest wystarczająco duża, aby pomieścić Odbiorniki, dzięki czemu ma najmniejszy możliwy ślad pamięci. (As the docs note, nie przydziela niczego, dopóki nie zostanie dodany pierwszy Listener, aby upewnić się, że nie marnujesz miejsca, jeśli nie ma Słuchaczy.) Wadą tego jest to, że za każdym razem, gdy dodawany jest nowy element, przydziela tablicę i kopiuje wszystkie stare elementy, dając astronomiczne koszty, gdy masz zbyt wielu słuchaczy.

(Właściwie, to nie dość, jak pamięć efektywny, jak to możliwe; lista jest {Type, słuchaczy} pary, więc jeśli to była tablicą tablic to będzie nieco mniejsza w niektórych przypadkach).

jako dla twojego rozwiązania: HashMap s nad alokować pamięć, aby zapewnić wydajne mieszanie. Podobnie domyślny konstruktor ArrayList przydziela miejsce na 10 elementów i rośnie w porcjach. W twojej dziwnej bazie kodu, gdzie na każdej liście znajduje się 100 tysięcy słuchaczy, ta dodatkowa pamięć jest niewielkim dodatkiem do pamięci, której używasz do trzymania wszystkich słuchaczy.

Zgodnie z lżejszym obciążeniem Listener jednak implementacja kosztuje 16 wskazówek dla każdej pustej liście (alokacji domyślna HashMap), 26 wskazówek dla danej EventListenerMap z jednego elementu, a 36 wskazówek dla mapy z dwóch elementów z różnych klas . (Nie liczy się to z pozostałymi rozmiarami struktury.) Dla tych samych przypadków, EventListenerList kosztuje odpowiednio 0, 2 i 4 wskaźniki.

Wydaje się ogromny postęp w kodzie co masz.

+0

Doskonała odpowiedź, dziękuję. –

Powiązane problemy