
On Mon, 2010-01-11 at 12:32 -0500, Gottlob Frege wrote:
First question, and this is always the first question: Have you actually *measured* where the performance problems are?
No. To set the stage I build this library with old form of logging done on the same thread that did the processing. With debugging on it naturally took longer than the release. The debug build was done without optimizations therefore processing instructions took longer than their optimized counterpart. The debug build also suffered an IO penalty from having to write its data to disk.
You create the thread in the constructor of Trace, which is called via the first call to Singleton<Trace>::Instance(), which I assume is for the first call to log something.
You are correct in assuming that the function Instance() is used to grab a copy of the singleton for logging data to the file.
Does this potentially happen before main() is called? Typically creating threads before main() isn't recommended. You might need to somehow delay that.
Since this is a library I do not believe it is called before main but I cannot give you a reason why believe that. So since its a conjecture we cannot go on that. Is there a way to do a trace of the code as it runs to see if the constructor is called before main?
template <typename T> class Singleton : boost::noncopyable { public:
static void init () { m_obj.reset ( new T () );
If you ever want to log from the allocator, you will probably want to somehow avoid using 'new' here. ie reserve some memory for T using something like:
template <typename T> struct storage_for { aligned_storage
data; }; storage_for<T> storage;
then in place new:
m_obj.reset( new(&storage) T() );
Where is "MyCriticalSection" declared? Is that my internal class called Trace_State which actually does the logging?
Does this 'work' or do you need to know T?
I am not sure what you are asking here?
template <typename T> boost::scoped_ptr<T> myproject::trace::Singleton<T>::m_obj ( 0 ); template <typename T> boost::once_flag myproject::trace::Singleton<T>::m_flag = BOOST_ONCE_INIT;
void Trace::add_Trace ( boost::shared_ptrinterface::ITraceable trace ) {
is boost::shared_ptrinterface::ITraceable == interface::ITraceable::ptr_t ?
// Add ITraceable object to the queue
add *pointer to* ITraceable object!
Yes. I forgot to make the function definition to have interface::ITraceable::ptr_t instead of the boost::shared_ptrinterface::ITraceable. In the ITraceable declaration is a typedef for: typedef boost::shared_ptr<ITraceable> ptr_t;
void Trace::threadMain() { boost::unique_lockboost::mutex lock ( m_lock );
while ( m_hasWork ) { while ( m_queue.size() == 0 ) { m_condition.wait ( lock ); }
*** note that the mutex is now locked here *** (condition.wait() unlocks while waiting, then relocks before returning)
Ok. I am used to multi-threaded programming in C# but not sure if I had the semantics right here. What I was hoping to happen was: WHILE ( there is work to be done ) { // I have work to do WHILE ( input queue is empty ) { // wait for work } // process the work to be done } Is that what I have done?
this->processTraces(); } }
void Trace::processTraces() { QueueType local_queue;
// Lock queue and copy
*** The queue is ALREADY locked (see above) ***
You are right. It is locked because we are still in the scope of threadMain. Now this will cause a problem. I wanted m_queue to be open for writing after making a copy while the internal thread handled writing the contents of its copied queue to disk. Is there a better way to write threadMain and processTraces?
{ boost::lock_guardboost::mutex lock ( m_lock ); std::copy ( m_queue.begin(), m_queue.end(), local_queue.begin() );
*** don't you need to also empty m_queue? ***
Alas another lapse in thinking. Yes I do need to empty m_queue.
And last but not least - I am concerned that ITraceable is *shared*. Typically when logging something, you capture a 'snapshot' of values in time, and log that out. With a shared snapshot, the caller may actually change values *after* 'logging' them. Maybe in an attempt to reuse the ITraceable. ie
client code:
do_something(); tracer = new Traceable(....); log(tracer); do_something_else(); tracer->foo = 12; log(tracer);
So maybe your interface should *take* the pointer (via auto_ptr or unique_ptr or etc) instead of sharing. Alternatively, you could copy the ITraceable when placing it into the queue (maybe via a 'clone' method - I'm assuming ITraceable is polymorphic with various derivations possible? Otherwise just make it Traceable, no 'I'nterface, and pass by value...)
So change addTrace to something like after adding a clone method: void Trace::addTrace ( interface::ITraceable::ptr_t trace ) { { boost::lock_guardboost::mutex lock ( m_lock ); m_queue.push_back ( trace->clone() ); } }
Oh, and the destructor:
Trace::~Trace() { // stop thread m_hasWork = false; }
m_hasWork = false is not protected by a lock, so other CPUs won't necessarily see it be set in the order you might be expecting. Oh wait, actually, you are not *waiting* for the other thread to end. So the destructor leaves, and the memory for Trace is deleted/reused, etc, and then at some point later the thread wakes up and tries to read it! BOOOM?
And actually, the thread probably will NOT wake up - it will probably still be waiting on the condition.wait() call. So you need to signal that.
Trace::~Trace() { // stop thread { boost::lock_guardboost::mutex lock ( m_lock );
// this needs to be inside the lock (or at least set with an atomic_release memory barrier) // otherwise the thread might wake up from the condition notification, // and yet not see m_hasWork == false. m_hasWork = false; }
m_condition.notify_one();
// use join (whatever the syntax) to wait for thread to finish join(...); }
I appreciate the comments. I will keep appending this email with the
updated code so to further help our discussion. Is there a preferred way
to attach code? I was thinking that just adding it as an attachment
would not allow you to easily make comments.
Stephen
------------------------- Singleton_T.h -------------------------------
#ifndef SINGLETON_H
#define SINGELTON_H
#include