16

私たちのレガシーアプリはEventListeners、最も単純な操作でとんでもない数(〜100,000)を含むひどいフレームワーク(タペストリー4です)で立ち往生しています。私は、これはjavax.swing.event.EventListenerListこれまで処理することを意図していたものを超えていると推測しています.

HashMap/ArrayList以下のかなり素朴なベースの置換を作成するのに数時間を費やしましたが、ほぼすべての点で非常に高速です。

50,000 人のリスナーを追加します。

  • EventListenerList> 2 秒
  • EventListenerMap〜 3.5 ミリ秒

50,000 人のリスナーに対してイベントを発生させる:

  • EventListenerList0.3~0.5ミリ秒
  • EventListenerMap0.4~0.5ミリ秒

50,000 人のリスナーを削除します (一度に 1 人ずつ):

  • 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 );
    }
}
4

1 に答える 1

5

最適化の問題です。Swing の EventListenerList は次のことを前提としています。

  • リスト上のリスナーの数が非常に少ない
  • ListenerList の数が非常に多くなる可能性があります
  • 追加/削除イベントは非常にまれです

これらの仮定を考えると、アイテムを追加および削除するための計算コストは​​無視できますが、それらのリストを保持するためのメモリ コストはかなりの量になる可能性があります。そのEventListenerListため、リスナーを保持するのにちょうど十分な大きさの配列を割り当てることで動作し、可能な限り最小のメモリ フットプリントを持ちます。(ドキュメントに記載されているように、最初のリスナーが追加されるまで何も割り当てません。リスナーがない場合にスペースを無駄にしないようにするためです。)これの欠点は、新しい要素が追加されるたびに、配列を再割り当てし、古い​​要素をすべてコピーするため、リスナーが多すぎると天文学的なコストがかかります。

(実際には、可能な限りメモリ効率が良いわけではありません。リストは {Type,Listener} のペアであるため、配列の配列である場合は、場合によってはわずかに小さくなります。)

あなたの解決策については、次のとおりですHashMap。sメモリを過剰に割り当てて、効率的なハッシュを確保します。同様に、デフォルトのArrayListコンストラクターは 10 個の要素にスペースを割り当て、チャンクで大きくなります。各リストに 100k のリスナーがある奇妙なコードベースでは、この余分なメモリは、すべてのリスナーを保持するために使用しているメモリにわずかに追加されます。

ただし、Listener の負荷が軽い場合、空のリストごとに 16 個のポインター ( のデフォルトの割り当てHashMap)、1 つの要素を持つ に 26 個のポインターEventListenerMap、異なるクラスの 2 つの要素を持つマップに 36 個のポインターが必要になります。(これは残りのHashMapandArrayList構造体のサイズをカウントしていません。) これらの同じケースでは、EventListenerListそれぞれ 0、2、および 4 個のポインターのコストがかかります。

あなたが持っているコードの大きな改善のようです。

于 2013-07-05T13:23:33.920 に答える