On Fri, Feb 6, 2009 at 5:46 PM, Monty Taylor <[email protected]> wrote:

> Padraig wrote:
> > Blueprint changed by Padraig:
> >
> > Whiteboard changed to:
>
> First of all, great write up. I would love it if you'd take this text
> and make a page on the drizzle.org wiki with it, and then fill in the
> Wiki-page for the specification on the blueprint.


Done. I added a Blueprints section to the Developer/Contributor
documentation page (http://drizzle.org/wiki/Developer_Documentation). The
actual wiki page with the text I wrote up is at:

http://drizzle.org/wiki/Replace_QUEUE


>
>
> > We have started this effort off by replacing the use of QUEUE in
> > mysys/thr_alarm.cc. The code in this file is only executed when a KILL is
> used
> > on a session thread which Jay mentioned does not occur very often on the
> > mailing list.
> >
> > In mysys/thr_alarm.cc, we have a queue which consists of elements of type
> ALARM entitled
> > alarm_queue:
> >
> > static QUEUE alarm_queue;
> >
> > The queue may contain multiple ALARMs with the same thread id.
> alarm_queue
> > sorts via expire_time, but must clean out the queue of other ALARMs with
> the
> > thread_id that has been killed, which means that an iterator must be used
> to
> > traverse the queue.
> >
> > Based on discussions on the mailing list, std::vector was chosen to
> replace
> > QUEUE since it's a standard, simple solution. Thus, we are replacing the
> above with:
> >
> > static std::vector<ALARM*> alarm_stack;
> >
> > An issue which Monty mentioned on the mailing list was that you cannot
> > remove elements from a vector while iterating through an active iterator.
> > There are 2 occasions in mysys/thr_alarm.cc where some logic like this is
> > needed. Thus, we defined 2 function objects which can be used for this
> > purpose. I defined these function objects at the top of the file, since I
> > wasn't sure where else I should put them. Should I put them in a header
> > file? Or should they be in the function that they are called from?
> Anyway,
> > these function objects are:
>
> Since these implementation details, I'd just leave them in thr_alarm.cc.
> If they could be generalized or used in other places, then it would be
> worthwhile putting them in their own header.
>
> It would certainly not _hurt_ to make a header for each one... but I
> think having them in thr_alarm.cc is fine.
>
> > class alarm_if
> > {
> >   public:
> >   inline bool operator()(ALARM *current_alarm)
> >   {
> >     current_alarm->alarmed= 1;           /* Info to thread */
> >     if (pthread_equal(current_alarm->thread,alarm_thread) ||
> >         pthread_kill(current_alarm->thread,thr_client_alarm))
> >     {
> >       return true;
> >     }
> >     return false;
> >   }
> > };
> >
> > class alarm_if_time
> > {
> >   time_t alarm_time;
> >   time_t alarm_time_next;
> >   public:
> >   alarm_if_time(time_t now, time_t next) : alarm_time(now),
> >   alarm_time_next(next) { }
> >   inline bool operator()(ALARM *current_alarm)
> >   {
> >     if (current_alarm->expire_time <= alarm_time)
> >     {
> >       current_alarm->alarmed= 1;          /* Info to thread */
> >       if (pthread_equal(current_alarm->thread,alarm_thread) ||
> >           pthread_kill(current_alarm->thread,thr_client_alarm))
> >       {
> >         return true;
> >       }
> >       else
> >       {
> >         current_alarm->expire_time= alarm_time_next;
> >       }
> >     }
> >     return false;
> >   }
> > };
> >
> > We will discuss where these function objects are used later. Every
> occurence
> > of alarm_queue.elements in the code is replaced with alarm_stack.size().
>
> Good.
>
> > The first modification we made was to the init_thr_alarm() function. In
> this function,
> > instead of:
> >
> > init_queue(&alarm_queue,max_alarms+1,offsetof(ALARM,expire_time),0,
> >              compare_uint32_t,NULL);
> >
> > We now have:
> >
> > alarm_stack.reserve(max_alarms+1);
>
> Great.
>
> > Next, in the function resize_thr_alarm(), we will replace the following:
> >
> > if (alarm_queue.elements < max_alarms)
> >   resize_queue(&alarm_queue,max_alarms+1);
> >
> > With this:
> >
> > if (alarm_stack.capacity() < max_alarms)
> >   alarm_stack.resize(max_alarms+1);
>
> Great.
>
> > Next, in the thr_alarm function, we replace queue insertion, which is:
> >
> > queue_insert(&alarm_queue,(unsigned char*) alarm_data);
> >
> > with the following (not too sure if I should just change this to
> > alarm_stack.push_back(alarm_data)):
> >
> > std::vector<ALARM*>::iterator p= alarm_stack.begin();
> > /* No need to coerce to uchar* since std::vector<> is templatized */
> > alarm_stack.insert(p, alarm_data);
>
> I think push_back would be fine, unless this would keep something in an
> incorrect order. If you do need to keep the above semantics, I think it
> would also be fine to do:
>
> alarm_stack.insert(alarm_stack.begin(), alarm_data);
>
> since that iterator doesn't actually have any purpose in life other than
> that statement.


Good point. I'm going to keep the original semantics for the moment but I
adjusted my code thanks to your feedback.


>
>
> > The next modification needed occurs in the thr_end_alarm() function.
> > Originally, the code was:
> >
> > alarm_data= (ALARM*) ((unsigned char*) *alarmed -
> offsetof(ALARM,alarmed));
> > for (i=0 ; i < alarm_queue.elements ; i++)
> > {
> >   if ((ALARM*) queue_element(&alarm_queue,i) == alarm_data)
> >   {
> >     queue_remove(&alarm_queue,i);
> >     if (alarm_data->malloced)
> >       free((unsigned char*) alarm_data);
> >     found++;
> >     break;
> >   }
> > }
> >
> > This has been modified to the following:
> >
> > alarm_data= (ALARM*) ((unsigned char*) *alarmed -
> offsetof(ALARM,alarmed));
>
> For your next project... figuring out if there's a way to avoid that ^^
> would be wonderful. I get what it's there, but egads. :)
>
> > std::vector<ALARM*>::iterator p= alarm_stack.begin();
> > while (p != alarm_stack.end() && *p != alarm_data)
> >   p++;
> > if (p != alarm_stack.end())
>
> These are fine, although they sort of feel like they could be replaced
> with a find_if:
>
> std::vector<ALARM*>::iterator p=
>      find_if(alarm_stack.begin(),
>              alarm_stack.end(),
>              bind2nd(equal<ALARM *>(), alarm_data));
> if (p !- alarm_stack.end())
>
> > {
> >   /* p now points to the current alarm pointer; remove it */
> >   if (alarm_data->malloced)
> >     free(*p);
> >   alarm_stack.erase(p);
> >   found= 1;
> > }
>
> > Next, come the use of the function objects we defined earlier. These
> > modifications are not as straightforward and would appreciate any
> feedback
> > on these. The 2 modifications are in the process_alarm_part2() function.
> The
> > first was to modify the following code:
> >
> > uint32_t i;
> > for (i=0 ; i < alarm_queue.elements ;)
> > {
> >   alarm_data=(ALARM*) queue_element(&alarm_queue,i);
> >   alarm_data->alarmed=1;                  /* Info to thread */
> >   if (pthread_equal(alarm_data->thread,alarm_thread) ||
> >       pthread_kill(alarm_data->thread, thr_client_alarm))
> >   {
> >     queue_remove(&alarm_queue,i);         /* No thread. Remove alarm */
> >   }
> >   else
> >     i++;                                  /* Signal next thread */
> > }
> >
> > This is modified to use the function object alarm_if and now looks as
> > follows:
> >
> > alarm_stack.erase(remove_if(alarm_stack.begin(),alarm_stack.end(),
> >                   alarm_if()),alarm_stack.end());
> >
> > The above code essentially goes through the entire alarm_stack vector and
> removes an
> > element from the vector if the function object returns true. Monty
> suggested
> > an approach like this on the mailing list as it is safer than removing
> > elements from a vector while iterating through an active iterator.
>
> Yes. the v.erase(remove_if(v.begin(), v.end(), predicate()), v.end) is a
> common patter.
>
> > The second modification in process_alarm_part2() is to modify the
> following
> > code:
>
> Heh. process_alarm_part2(). What a wonderful function name. ;)
>
> > while ((alarm_data= (ALARM*) queue_top(&alarm_queue))->expire_time <=
> now)
> > {
> >   alarm_data->alarmed=1;                  /* Info to thread */
> >   if (pthread_equal(alarm_data->thread,alarm_thread) ||
> >       pthread_kill(alarm_data->thread, thr_client_alarm))
> >   {
> >     queue_remove(&alarm_queue,0);         /* No thread. Remove alarm */
> >     if (!alarm_queue.elements)
> >       break;
> >   }
> >   else
> >   {
> >     alarm_data->expire_time=next;
> >     queue_replaced(&alarm_queue);
> >   }
> > }
> > #ifndef USE_ALARM_THREAD
> > if (alarm_queue.elements)
> > {
> >   alarm((uint32_t) (alarm_data->expire_time-now));
> >   next_alarm_expire_time= alarm_data->expire_time;
> > }
> > #endif
> >
> > The issue I have with replacing the above code is that I can use the
> > alarm_if_time function object to iterator through the elements in the
> > alarm_stack vector and remove any elements as appropriate based on the
> same
> > condition as in the original code. The problem I have is with the
> following
> > code block:
> >
> > #ifndef USE_ALARM_THREAD
> > if (alarm_queue.elements)
> > {
> >   alarm((uint32_t) (alarm_data->expire_time-now));
> >   next_alarm_expire_time= alarm_data->expire_time;
> > }
> > #endif
> >
> > I don't know how I can keep track of what the last ALARM that was
> processed
> > is. Although, I'm thinking it will be the ALARM with the smallest
> > expire_time as that will be the ALARM that is at the top of the
> alarm_queue
> > in the current implementation if I am correct? The reason I think this is
> > because at the start of the while loop in the original code, the ALARM
> from
> > the top of alarm_queue is always used. Thus, at the moment, I am
> replacing
> > the above code with:
> >
> > alarm_stack.erase(remove_if(alarm_stack.begin(),alarm_stack.end(),
> >                   alarm_if_time(now, next)),alarm_stack.end());
>
> Ah ... here we see why there were using a queue before. Your approach
> below of finding the lowest expire_time seems fine.


Cool. Thanks for the confirmation. I really wanted someone with more
familiarity with this code to have a look at it.


>
>
> > #ifndef USE_ALARM_THREAD
> > if (alarm_stack.size())
> > {
> >   std::vector<ALARM*>::iterator p= alarm_stack.begin();
> >   ALARM *smallest_expire_time;
> >   while (p != alarm_stack.end())
> >   {
> >     ALARM *current_alarm= *p;
> >     if (!smallest_expire_time)
> >     {
> >       smallest_expire_time= current_alarm;
> >     }
> >     else
> >     {
> >       if (current_alarm->expire_time < smallest_expire_time->expire_time)
> >         smallest_expire_time= current_alarm;
> >     }
> >     p++;
> >   }
> >   if (smallest_expire_time)
> >   {
> >     alarm((uint32_t) (smallest_expire_time->expire_time-now));
> >     next_alarm_expire_time= smallest_expire_time->expire_time;
> >   }
> > }
> > #endif
> >
> > The next modifications are in the thr_alarm_kill() function. The
> following
> > code is modified:
> >
> > for (i=0 ; i < alarm_queue.elements ; i++)
> > {
> >   if (((ALARM*) queue_element(&alarm_queue,i))->thread_id == thread_id)
> >   {
> >     ALARM *tmp=(ALARM*) queue_remove(&alarm_queue,i);
> >     tmp->expire_time=0;
> >     queue_insert(&alarm_queue,(unsigned char*) tmp);
> >     reschedule_alarms();
> >     break;
> >   }
> > }
> >
> > This is tricky because we are removing an element from alarm_queue,
> > modifying it, and then placing the element back in alarm_queue again. I'm
> > not sure if I'm doing this in the best way but here is what I changed
> this
> > code to:
> >
> > std::vector<ALARM*>::iterator p= alarm_stack.begin();
> > while (p != alarm_stack.end())
> > {
> >   ALARM *current_alarm= *p;
> >   if (current_alarm->thread_id == thread_id)
> >     break;
> >   p++;
> > }
> >
> > if (p != alarm_stack.end())
> > {
> >   ALARM *tmp= *p;
> >   alarm_stack.erase(p);
> >   tmp->expire_time= 0;
> >   alarm_stack.insert(p,tmp);
> >   reschedule_alarms();
> > }
> >
> > Again, maybe it would be better to just use alarm_stack.push_back(tmp)
> above
> > instead of using the insert() method. Any suggestions here?
>
> Well, again it depends on how important the order is. I'm guessing it's
> not since we just about always iterate through the queue. If it's not,
> then I think push_back is likely to be less work than insert - but I
> don't have proof of that.
>
> But why erase it and re-insert it? Couldn't you just do :
> (*p)->expire_time= 0;
> reschedule_alarms();
> ? The more I read the original code, the more I think the remove/insert
> was just an artifact of how QUEUE was written.


Ah ok. That code was really confusing to me alright. I pretty much just
tried to preserve the original semantics. I've modified it to what you
suggested though. Again, thanks for the feedback there.


>
>
> > Those are all the major modifications that were needed to replace the use
> of
> > QUEUE in mysys/thr_alarm.cc
> >
> > I have compiled this, and just run make test and all tests succeed after
> > making modifications. I was wondering if anyone knew of a way I could
> test
> > this piece of code specifically? Also, do we want to try and benchmark
> > performance before and after the removal of QUEUE?
>
> Yeah. That's the tricky part. Anybody else got a good testing
> suggestion? Jay? I'm working right now on a continuous benchmarking
> thing so we can check for performance regressions, but it's not quite
> done yet.
>
> > I pushed this code to the branch which is linked to this blueprint:
> >
> > lp:~posulliv/drizzle/code-cleanup-c++-replace-queue
>
> Great. Submit a merge proposal when you're happy with testing of it and
> it's ready for a merge and I'll grab it.


Done. Ive just done standard testing of it and I have not run in to any
problems with it. I'm open to any suggestions for testing from anyone
though?

-Padraig


>
>
> Monty
>
_______________________________________________
Mailing list: https://launchpad.net/~drizzle-discuss
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~drizzle-discuss
More help   : https://help.launchpad.net/ListHelp

Reply via email to