2013-05-20 34 views
16

我们的传统应用程序被困在一个可怕的框架中(好吧,我会命名,它是Tapestry 4),涉及最简单的EventListeners(〜100,000)操作。我猜这已经超出了javax.swing.event.EventListenerList曾经处理过的范围,而在这个不幸的使用案例中,它给我们带来了一些令人讨厌的性能问题。为什么没有更换EventListenerList? (或者:替换它有什么缺陷?)

我花了几个小时以下掀起了相当幼稚HashMap/ArrayList为基础的替代,它的大规模更快几乎在每一个方式:

新增5名听众:

  • EventListenerList>2秒
  • EventListenerMap〜3.5毫秒

发生火灾事件至50,000个听众:

  • EventListenerList 0.3-0.5毫秒
  • EventListenerMap 0.4-0.5毫秒

删除50000个听众(一次一个):

  • EventListenerList>2秒
  • EventListenerMap〜 280毫秒

发火可能只是一头发较慢,但修改速度更快。无可否认,这个框架使我们处于病态的状况,但它似乎仍然很久以前就可以替代EventListenerList。显然公共API存在问题(例如,它暴露了其原始内部状态数组),但除此之外肯定还有更多。也许有多线程的情况下,EventListenerList更安全或更高性能?

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疯狂(iest)这个月的问题,Q关于(对于最简单的操作)是每一个步骤都是单独的,或者它们被链接在树上或信号量上,如果是,那么另一个疯狂的(iest)建议,使用EventHandler和在运行时以String的形式建立路径,那么可能不需要在内存中保存一堆方法,phaaa我喜欢这个想法,这来自你的问题,让RPG永远活着(然后同样疯狂的无尽的代码) – mKorbel

+1

你有没有在相对射击时间错字?你报告说,你的实现可能会“在0.4_milliseconds_下变慢”,而在EventListenerList中变为0.3_seconds_。 –

+0

是的,这是一个错字,谢谢! –

回答

5

这是一个优化问题。 Swing的EventListenerList对象假定:

  • 听众的名单上的数量非常少
  • ListenerLists的数量可能非常大
  • 添加/删除事件是非常罕见的

鉴于这些假设,添加和删除项目的计算成本可以忽略不计,但让这些列表坐在里面的内存成本可能会很高。这就是为什么EventListenerList通过分配一个足够容纳监听器的数组来工作,因此具有尽可能小的内存占用量。 (As the docs note,直到添加第一个Listener才会分配任何东西,以确保在没有Listener的情况下不会浪费空间)。其缺点是每次添加新元素时,分配数组并复制所有旧元素,当您拥有太多听众时,为您提供天文学费用。

(实际上,它并不像内存效率成为可能;该列表是{类型,监听}对,因此,如果它是一个数组的数组这将是在一些情况下稍微小一些。)

作为为您的解决方案:HashMap s过度分配内存以确保高效哈希。同样,默认的ArrayList构造函数为10个元素分配空间并以块为单位增长。在你奇异的代码库中,每个列表中有10万个监听器,这个额外的内存是你用来容纳所有监听器的内存的一小部分。

下一个打火机监听器负载,不过,你的实施成本,每空表16点的指针(的HashMap默认分配),26个指针的EventListenerMap一个元素,和36个球的地图不同类别的两个元素。 (这不包括HashMapArrayList结构大小的其余部分。)对于那些相同的情况,EventListenerList分别耗费0,2和4个指针。

这看起来像是你的代码的巨大改进。

+0

优秀的答案,谢谢。 –

相关问题