Jump to content
  • Advertisement
Sign in to follow this  

My profile results and multithreading

This topic is 4801 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey all. I decided it was about time I profiled my MUD server code. I know it's not going to be doing much a lot of the time, but I thought I should profile it anyway, and I was rather surprised at the results. Here they are: Profile Results As you can see, CSocket::GetBufferLen() is right up at the top, followed by CSocketServer::GetNextConnection(). Here's the code for both of them:
size_t CSocket::GetBufferLen()
{
size_t dwRet;

   EnterCriticalSection(&m_cs);
   dwRet = m_vBuffer.size();
   LeaveCriticalSection(&m_cs);
   return dwRet;
}

CSocket* CSocketServer::GetNextConnection()
{
CSocket* pSocket;

   EnterCriticalSection(&m_csAcceptedSockets);
   if(m_vAcceptedSockets.empty())
      pSocket = NULL;
   else
   {
      pSocket = m_vAcceptedSockets[0];
      m_vAcceptedSockets.erase(m_vAcceptedSockets.begin());
   }
   LeaveCriticalSection(&m_csAcceptedSockets);
   return pSocket;
}

As you can see, there's not a lot to it. DevPartner shows me that each function is spending ages in EnterCriticalSection(). The CS m_csAcceptedSockets is only entered in another thread if accept() returns that there's a pending connection. Which happens extremely rarely in comparison to how many times GetNextConnection() is called (once per tick). And m_cs in CSocket is entered every 100ms, or when data is pending. Which is also not very often. So, it seems that EnterCriticalSection() is pretty damn expensive to call. I guess it blocks the current thread no matter what, and then comes back to it later or something. So, can anyone suggest any alternatives to what I have at the moment? I suppose for GetBufferLen(), I could just read a size_t value instead of entering the critical section, and only write to it from the worker thread. However, I'm sure that sounds ugly and will cause "Out of Sync" errors that'll be murder to fix/find. Also, is there perhaps a way to read a value without entering a critical section? Sort of like a read-only critical section. I know about TryEnterCriticalSection, but its Win2k+ only, which isn't really a problem, but I'd like to know if there's an alternative. Or am I going about this all wrong? Should I be using something other than Critical Sections? Thanks in advance, Steve

Share this post


Link to post
Share on other sites
Advertisement
Actually you're over synchronizing. You could safle omit enterint the critical section when reading the buffer size and it would be just as correct as what you have now. If that sounds odd just look at what you're currently doing you enter the cs read the value leave the cs that means after you've left the cs you could end up in a thread that modifies the buffer and then context switch back and return the old value.

Simply put the synchronization there simply doesnt help you since if you base your decisions on that value they're just as likely to be wrong in this case. If you use the buffer length to check if you should do any work you should be using a test test/modify approach anyhow.

Share this post


Link to post
Share on other sites
Yeah, I just realised that the CS in GetBufferLen() is pointless. At the moment, I do GetBufferLen(), then, if that returns non-zero, I copy that number of bytes out into my own buffer. So I'm already using the test, test/modify approach you described. Just with a needless lock [smile].

[Edit]
Actually, I'm not so sure. Since I call size() (on a std::vector). Isn't it possible that the vector could be in the middle of a reallocation, which could mess things up? I can get around that by adding a m_dwSize member though.
[/Edit]

Any idea about the GetNextConnction() code? I think this is a perfect candidate for TryEnterCriticalSection() actually, since if I can't get in, it means that a socket is being added to the list, and I can check it at the next update.

Share this post


Link to post
Share on other sites
Well Im guessing that most of the time it's empty working under that assumption and just using test test/modify the code would become.


CSocket* CSocketServer::GetNextConnection()
{
//can we early out?
if(m_vAcceptedSockets.empty())
return NULL;
CSocket* pSocket = NULL;
//lock and retest to make sure
EnterCriticalSection(&m_csAcceptedSockets);
if(!m_vAcceptedSockets.empty())
{
pSocket = m_vAcceptedSockets[0];
m_vAcceptedSockets.erase(m_vAcceptedSockets.begin());
}
LeaveCriticalSection(&m_csAcceptedSockets);
return pSocket;
}



hope that helps.

Share this post


Link to post
Share on other sites
Quote:
Original post by Evil Steve
[Edit]
Actually, I'm not so sure. Since I call size() (on a std::vector). Isn't it possible that the vector could be in the middle of a reallocation, which could mess things up? I can get around that by adding a m_dwSize member though.
[/Edit]


as long as the actual vector doesn't get relocated that won't matter since you'll always recheck the value with proper synchronization later.

Share this post


Link to post
Share on other sites
Quote:
Original post by DigitalDelusion
as long as the actual vector doesn't get relocated that won't matter since you'll always recheck the value with proper synchronization later.
Oh, good point.

Thanks, that source snippet looks good. However, should I be declaring anything as volatile? Would the compiler not optimize the second .empty() call away otherwise?

Share this post


Link to post
Share on other sites
Quote:
Original post by Evil Steve
Thanks, that source snippet looks good. However, should I be declaring anything as volatile? Would the compiler not optimize the second .empty() call away otherwise?


any shared variable should probably be declared volatile more often than not you get away without doing it but can never be to sure.

Share this post


Link to post
Share on other sites
Quote:
Original post by DigitalDelusion
Quote:
Original post by Evil Steve
Thanks, that source snippet looks good. However, should I be declaring anything as volatile? Would the compiler not optimize the second .empty() call away otherwise?


any shared variable should probably be declared volatile more often than not you get away without doing it but can never be to sure.
Ok, great.

Thanks for your help [smile]

Share this post


Link to post
Share on other sites
Quote:
Original post by Evil Steve
Ok, great.
Thanks for your help [smile]


np glad I could help, hope it gives you a nice little performance boost :)
always feels nice todo something usefull right before heading to bed.

Share this post


Link to post
Share on other sites
EnterCriticalSection, in the case of not contending, will do an atomic bus operation (i e, typically LOCK some-opcode). This is significantly slower than just dirtying a cache line, because you may need to synchronize with the bus/memory controller, which can take a full microsecond or so. Leaving the critical section does the same.

IF there is contention, then EnterCriticalSection will call into the kernel to block on a kernel primitive, which has all of the associated overhead of a kernel call. It doesn't sound as if you're suffering from contention, though.

One thing you might want to consider is whether your program is using 100% of the CPU. If it's not, and the profiler is just showing %-age of what your program is doing, then your program could be basically doing nothing, and most of the time will go to synchronization. Make sure your CPU is actually running at 90-100% load before you start profiling, if you want results that are actually useful.

That being said, synchronization overhead is why I suggest putting all networking into a single thread, and either making that the main thread, or using non-blocking primitives to shuffle the data between threads.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!