9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Observerパターンの実装方法比較

Posted at

目的

実装方法によってObserverパターンの速度がどれぐらい変わってくるかを知りたい

調査内容

環境

VS 2013 Express
Win32 Releaseビルド

基本形(インターフェース)

Subjectに対してObserverをattachしておけば、
Subjectの値に変化があったときOvserverに対してnotifyが実行される。
変化検知方法も変えたい。

Observer.hpp
# pragma once
# include <cstdint>

class Observer
{
public:
	virtual void notify(const int&, const int&) = 0;
	virtual uint64_t count() = 0;
};

class Subject
{
public:
	Subject() :cur_(), observer_(){}
	void attach(Observer* observer)
	{
		observer_ = observer;
	}
	void set(const int& next)
	{
		if (observer_ != 0){ observer_->notify(cur_, next); }
		cur_ = next;
	}
private:
	int cur_;
	Observer* observer_;

};

測定プログラム

上記インターフェースを複数の方法で実現し、下記プログラムで測定した。実現方法は派生形セクションに記載。
全部一度に実行すると実行順などによって遅くなるモノが出てきたので測定対象以外はコメントアウトして1つずつ実行した。
(測定プログラムの書き方が悪いんだろうけど・・・)
結果は「名前:かかった時間, notifyされた回数」という書式にしてある。

main.cpp
# include <iostream>
# include <iomanip>
# include "Timer.hpp"
# include "Observer.hpp"
# include "ObserverBySwitch.hpp"
# include "ObserverInternalStragegy.hpp"
# include "ObserverFP.hpp"
# include "ChangeDetectionObserver.hpp"
# include "TemplatedObserver.hpp"
# include "ObserverT.hpp"
# include "TemplatedSubject.hpp"
# include "Judge.hpp"

template<typename SubType, typename ObType>
class Measure
{
	static const uint64_t rep = 100000000;
public:
	Measure(){}
	double execute(SubType* s)
	{
		Timer<TimerType::HighAccuracy> timer;
		for (uint64_t i = 0; i < rep; ++i)
		{
			s->set(i);
		}
		return timer.elapsed();
	}

};

# define measure(SubType, ObType) \
{ \
	Measure<SubType, ObType> a; \
	ObType o; \
	SubType s; \
	s.attach(&o); \
	volatile double r = a.execute(&s); \
	auto c = o.count(); \
	cout << setiosflags(ios::left) << setw(35) << #ObType":" << fixed << setprecision(6) << r << ", " << c << endl; \
}
int main(int argv, char* argc[])
{
	//measure(Subject, ObserverInnerStrategy);
	//measure(SubjectFP, ObserverFP);
	//measure(Subject, ChangeDetectionObserver);
	//measure(Subject, TemplatedObserver<Judge::IsChanged>);
	//measure(Subject, TemplatedObserver2<Judge::IsChanged>);
	//measure(SubjectT<ObserverT<Judge::IsChanged> >, ObserverT<Judge::IsChanged>);
	//measure(SubjectBySwitch, ObserverBySwitch);

	/*おまけ。Subjectで扱う型もtemplateにした場合の検証*/
	//typedef  TemplatedObserverImpl<int, Judge::IsChanged> TemplatedNotifier;
	//measure(TemplatedSubject<int>, TemplatedNotifier);
	/*判定に対して常にfalseを返す方式と、notify処理自体を空っぽにする方式の比較*/
	//typedef  NullObserverTImpl<int, Judge::IsChanged> NullNotifier1;
	//measure(TemplatedSubject<int>, NullNotifier1);
	//typedef  TemplatedObserverImpl<int, Judge::Never> NullNotifier2;
	//measure(TemplatedSubject<int>, NullNotifier2);
	return 0;
}

派生形

ストラテジ

ObserverInternalStragegy.hpp
# pragma once
# include <cstdint>
# include "Observer.hpp"

// Observerを継承して内部でストラテジを持っている。
// notifyの継承、judgeの継承で合計2回の仮想関数呼び出しが発生。
// また、ストラテジ部で親クラスのメンバを操作するためにcount_を引き渡すというコストも発生。
class ObserverInnerStrategy: public Observer
{
	class NotificationStrategy;
public:
	ObserverInnerStrategy() :count_(), notifier_(&isChanged_){}
	void setFelld()  { notifier_ = &isFell_;    }
	void setRised()  { notifier_ = &isRised_; }
	void setChanged(){ notifier_ = &isChanged_; }
	void notify(const int& cur, const int& next)override
	{
		notifier_->notify(cur, next, count_);
	}
	uint64_t count()override{ return count_; }
private:
	uint64_t count_;
	class NotificationStrategy
	{
	public:
		void notify(const int& cur, const int& next, uint64_t& c)
		{
			if (judge(cur, next))
			{
				++c;
			}
		}
		virtual bool judge(const int& cur, const int& next) = 0;

	}* notifier_;
	class IsFell : public NotificationStrategy
	{
	public:
		bool judge(const int& cur, const int& next)override
		{
			return cur && !next;
		}
	}isFell_;
	class IsRised : public NotificationStrategy
	{
	public:
		bool judge(const int& cur, const int& next)override
		{
			return !cur && next;
		}
	}isRised_;
	class IsChanged : public NotificationStrategy
	{
	public:
		bool judge(const int& cur, const int& next)override
		{
			return cur != next;
		}
	}isChanged_;
};

実行結果

ObserverInnerStrategy: 0.347974, 99999999
続行するには何かキーを押してください . . .

関数ポインタで処理をポリモる

ObserverFP.hpp
# pragma once
# include <cstdint>

//関数ポインタで判定部を実装。
//count_を引き渡さなくてよくなっている。
class ObserverFP
{
	class NotificationStrategy;
public:
	ObserverFP() :count_(), judge(isChanged){}
	void setFelld()  { judge = isFell; }
	void setRised()  { judge = isRised; }
	void setChanged(){ judge = isChanged; }
	void notify(const int& cur, const int& next)
	{
		if ((*judge)(cur, next))
		{
			++count_;
		}
	}
	uint64_t count(){ return count_; }
private:
	uint64_t count_;
	bool (*judge)(const int& cur, const int& next);
	static bool isFell(const int& cur, const int& next)
	{
		return cur && !next;
	}
	static bool isRised(const int& cur, const int& next)
	{
		return !cur && next;
	}
	static bool isChanged(const int& cur, const int& next)
	{
		return cur != next;
	}
};
class SubjectFP
{
public:
	SubjectFP() :cur_(), observer_(){}
	void attach(ObserverFP* observer)
	{
		observer_ = observer;
	}
	void set(const int& next)
	{
		if (observer_ != 0){ observer_->notify(cur_, next); }
		cur_ = next;
	}
private:
	int cur_;
	ObserverFP* observer_;

};

実行結果

ObserverFP: 0.376251, 99999999
続行するには何かキーを押してください . . .

継承して直接処理を実装しちゃう

ChangeDetectionObserver.hpp
# pragma once
# include <cstdint>
# include "Observer.hpp"

// 継承でStrategy部を自分で実装する。
// judgeを自分で持っているのでポインタ経由での関数呼び出しが一回減っている。
class ChangeDetectionObserver : public Observer
{
public:
	ChangeDetectionObserver() :count_(){}
	void notify(const int& cur, const int& next)override
	{
		if (judge(cur, next))
		{
			++count_;
		}
	}
	uint64_t count()override{ return count_; }
private:
	uint64_t count_;
	bool judge(const int& cur, const int& next)
	{
		return cur != next;
	}
};

実行結果

ChangeDetectionObserver: 0.202233, 99999999
続行するには何かキーを押してください . . .

継承して処理はtemplate引数で変更する

TemplatedObserver.hpp
# pragma once
# include <cstdint>
# include "Observer.hpp"

//テンプレートで実装するパターン。
template<typename Judge>
class TemplatedObserver : public Observer
{
public:
	TemplatedObserver() :count_(){}
	void notify(const int& cur, const int& next)override
	{
		if (Judge::judge(cur, next))
		{
			++count_;
		}
	}
	uint64_t count()override{ return count_; }
private:
	uint64_t count_;
};

//テンプレートで実装するパターンその2。ファンクタのコンストラクタにコストがかかるのか確認。
template<typename Judge>
class TemplatedObserver2 : public Observer
{
public:
	TemplatedObserver2() :count_(){}
	void notify(const int& cur, const int& next)override
	{
		if (Judge()(cur, next))
		{
			++count_;
		}
	}
	uint64_t count()override{ return count_; }
private:
	uint64_t count_;
};

実行結果

TemplatedObserver<Judge::IsChanged>:0.206786, 99999999
続行するには何かキーを押してください . . .
TemplatedObserver2<Judge::IsChanged>:0.204743, 99999999
続行するには何かキーを押してください . . .

Subjectごとtemplate化する

ObserverT.hpp
# pragma once
# include <cstdint>

template<typename Judge>
class ObserverT
{
public:
	ObserverT() :count_(){}
	void notify(const int& cur, const int& next)
	{
		if (Judge()(cur, next))
		{
			++count_;
		}
	}
	uint64_t count(){ return count_; }
private:
	uint64_t count_;
};

template<typename Observer>
class SubjectT
{
public:
	SubjectT() :cur_(), observer_(){}
	void attach(Observer* observer)
	{
		observer_ = observer;
	}
	void set(const int& next)
	{
		if (observer_ != 0){ observer_->notify(cur_, next); }
		cur_ = next;
	}
private:
	int cur_;
	Observer* observer_;

};

実行結果

ObserverT<Judge::IsChanged>: 0.202937, 99999999
続行するには何かキーを押してください . . .

switch文で実現

ObserverBySwitch.hpp
# pragma once
# include <cstdint>

//継承なんてしない方式
class ObserverBySwitch
{
public:
	enum DetectionType
	{
		eRised,
		eFell,
		eChanged,
	};
	ObserverBySwitch() :count_(), detectionType_(eChanged){}
	void setDetectionType(DetectionType detectionType){ detectionType_ = detectionType; }
	void notify(const int& cur, const int& next)
	{
		if (judge(cur, next))
		{
			++count_;
		}
	}
	uint64_t count(){ return count_; }
private:
	bool judge(const int& cur, const int& next)
	{
		switch (detectionType_)
		{
		case eRised:   return !cur && next;
		case eFell:    return cur && !next;
		case eChanged: return cur != next;
		}
		return false;
	}
	uint64_t count_;
	DetectionType detectionType_;
};

class SubjectBySwitch
{
public:
	SubjectBySwitch() :cur_(), observer_(){}
	void attach(ObserverBySwitch* observer)
	{
		observer_ = observer;
	}
	void set(const int& next)
	{
		if (observer_ != 0){ observer_->notify(cur_, next); }
		cur_ = next;
	}
private:
	int cur_;
	ObserverBySwitch* observer_;

};

実行結果

ObserverBySwitch: 0.221719, 99999999
続行するには何かキーを押してください . . .

結果まとめ

種類 時間[sec]
ChangeDetectionObserver 0.202233
ObserverT<Judge::IsChanged> 0.202937
TemplatedObserver2<Judge::IsChanged> 0.204743
TemplatedObserver<Judge::IsChanged> 0.206786
ObserverBySwitch 0.221719
ObserverInnerStrategy 0.347974
ObserverFP 0.376251

結果からわかること

  1. 関数ポインタが一番遅い(ObserverFP)
  2. 仮想関数をなくすのが一番速い。(ObserverT)
  3. 関数ポインタと継承では、継承の方が速い(ObserverFP > ChangeDetectionObserver)
  4. メンバ変数や仮想関数を持たないファンクタには生成コストは存在しない(TemplatedObserver ≒ TemplatedObserver2 )

どれが良いのか?

ObserverTは速いがいろんなSubject型ができてしまう。(boost::fusion::vector的なものを使えば同じコンテナに入れて管理できるんだろうけど・・・。)
ChangeDetectionObserverは種類が増えてくると面倒。
TemplatedObserverにしておけば、生成する側が自由に処理を変えられる上に速くて嬉しい。

おまけ

Subjectで扱う型もtemplateにしてみたパターン

TemplatedSubject.hpp
# pragma once
# include <cstdint>
# include "Judge.hpp"

template<typename T>
class TemplatedObserverIF
{
public:
	virtual void notify(const T& cur, const T& next) = 0;
};
template<typename T, typename Judge>
class TemplatedObserverImpl : public TemplatedObserverIF<T>
{
public:
	TemplatedObserverImpl() :count_(){}
	void notify(const T& cur, const T& next)
	{
		if (Judge()(cur, next))
		{
			++count_;
		}
	}
	uint64_t count(){ return count_; }
private:
	uint64_t count_;
};

template<typename T, typename Judge>
class NullObserverTImpl : public TemplatedObserverIF<T>
{
public:
	NullObserverTImpl(){}
	void notify(const T& , const T& ){}
	uint64_t count(){ return 0; }
};

template<typename T>
class TemplatedSubject
{
public:
	TemplatedSubject():cur_(), observer_(){}
	void attach(TemplatedObserverIF<T>* observer)
	{
		observer_ = observer;
	}
	void set(const T& next)
	{
		if(observer_ != 0)observer_->notify(cur_, next);
		cur_ = next;
	}
private:
	T cur_;
	TemplatedObserverIF<T>* observer_;
};

実行結果

TemplatedNotifier: 0.205507, 99999999
続行するには何かキーを押してください . . .

後、なにもしたくないという処理をポリモる場合に、notify実装を空っぽにする方法と、judgeを常にfalseにする方法の比較

実行結果

NullNotifier1: 0.046586, 0
続行するには何かキーを押してください . . .
NullNotifier2: 0.048761, 0
続行するには何かキーを押してください . . .

最適化でif(false)を消してくれてどっちでも一緒になるっぽい。

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?