Fog Creek Software
Discussion Board




Which would be more efficient?

The Objective:  Logging serial data.
[under Windows CE.  (Win32)]


Method #1:
========
2 worker threads (Read, Write).  Logging to "the same single file" occurs within each thread.

Read_Thread:
Rx_Data()
_CriticalSectionBegin:
WriteRx_to_Logfile()
_CriticalSectionEnd:


Write_Thread:
Tx_Data()
_CriticalSectionBegin:
WriteTx_to_Logfile()
_CriticalSectionEnd:



Method #2:
========
3 worker threads (Read, Write, Log).  Logging to a single file occurs within its own seperate thread.

Create FIFO buffer structure  (_FIFO_Q)

Read_Thread:
Rx_Data()
WriteRx_to_FIFO_Q()
ResumeThread( Log_Thread );


Write_Thread:
Tx_Data()
WriteTx_to_FIFO_Q()
ResumeThread( Log_Thread );


Log_Thread:
SuspendThread();
If( something in _FIFO_Q[x] )
    Write__FIFO_Q[x] to file;




Method #1 is simpler, but I fear it might sacrifice efficiency, and don't want to lose any data while the thread is busy writing to the file.  (if even possible.  haven't measured latency)  Method #2 is more complex, but is it overkill?

I ask because some of you might already have crossed this design dilemma and would save me time.

sedwo
Monday, April 05, 2004

Threads. Bah. Complication for the sake of complication.

Windows CE doesn't offer async I/O?

Brad Wilson (dotnetguy.techieswithcats.com)
Monday, April 05, 2004

As in most things, it depends.  But the way I do things like that is to have a seperate, lower priority thread for logging.  This lets the real task do it's think without interference, the logger jumps in when it can and does as much as it can.

Asynchronous I/O won't help as it takes time to format the log string, send the data to disk, etc.  You want the low priority thread to block while the high priority thread does the real work, not vice versa.

Of course, there are times where the logged data is most ciritical.  In those cases just adjust your priorities so the logger is higher than the program. 

Snotnose
Monday, April 05, 2004

Currently method #1 has been 'mostly' implemented.  But will not further it until I resolve this (because its bugging me).  I view all three threads as high priority and cannot afford to lose any logged data.  Still though, the WriteFile routines already buffer and cache (not using writethrough) so its latency might not be that problematic.

I fear of overburdening the two Read/Write threads with growing features but really do not have any performance data to backup my concerns.

I guess in the worst case scenario, I just raise their priority to kingdom high and hope they don't starve the system.

sedwo
Monday, April 05, 2004

On Windows CE you only have one CPU right? So remember that only one thread will actually be executing at once. Async I/O might be useful because you can explicitly control the scheduling of operations instead of leaving it to the kernel.

Dan Maas
Tuesday, April 06, 2004

I would definitely use async i/o. 

I used to use thread pools and i/o completion ports, but have found just single threaded async i/o to offer almost the same performance at a significant reduction in code complexity.


Tuesday, April 06, 2004

I don't think CE has Async I/O support and I/O Completion Ports usage like its big brothers.
No overlapping either.

sedwo
Tuesday, April 06, 2004

I'd guess that Method #1 has less total CPU utilisation, because there are fewer threads and therefore fewer thread-switches.

Whereas Method #2 could result in your threads' servicing the serial port more regularly, if the cost of writing to your FIFO Q is less than the cost of writing to the log file.

> Method #2 is more complex, but is it overkill?

I haven't programmed for WinCE. For NT, some hints would be:

- Pass big buffers to your read and write serial port routines: by passing larger buffers, you need call these routines less often: "big" compared to the speed of the serial port (e.g. a 15KB buffer will keep a 115kbps serial port busy for an entire second).

- If the receive data doesn't obey the serial port's flow control so that there can't be any gaps in your reading, then pass 2 read buffers down simultaneously (by having two simultaneous calls to ReadFile): then when the serial port fills your 1st buffer and returns, when your ReadFile returns and you are processing received data, the serial port still has the buffer from your 2nd ReadFile ... and hopefully you can finish processing the results of your first ReadFile and call ReadFile for a 3rd time, before the 2nd call to ReadFile returns. Calling ReadFile twice simultaneously on an O/S with no asynch I/O would require two read threads, not one, and in theory there could be a pathological case where both buffers are filled simultaneously and return out of sequence, so you don't know which ReadFile completed first, but in practice (assuming large read buffer sizes) I wouldn't expect that to happen.

Your mileage may vary: I don't know whether NT implementation details such as this apply to WinCE.

Christopher Wells
Tuesday, April 06, 2004

>which would be more efficient?

It depends,
in case (A) when the same threads are logging over and over again, you might get a problem with lock contention (one of the thread might have to wait to get the lock)

in case (B) you have overhead of keeping another thread + memory overhead of keeping the FIFO (memory is not for free on CE devices).
Another unrelated problem - if this is a log for debugging purposes, you might get a crash before data is logged.

I guess you would have to measure which one is faster in the general case (though my guess is case B).

Another thing you might try (if log is for debug purposes only), is to keep log file per thread, and merge output with a script, when you need it.

Michael Moser
Tuesday, April 06, 2004

This is not for logging debug info, but for logging the actual Rx/Tx data session.

>in case (A) when the same threads are logging over and
>over again, you might get a problem with lock contention
>(one of the thread might have to wait to get the lock)

That's what the CriticalSections are for, since the app. is sharing the single file.  At some point in time one thread will be waiting for the other to finish.  But I'm hoping that the disk i/o Write will be quick enough to not stall for too long and miss out on some Rx data for example.


>in case (B) you have overhead of keeping another thread
> + memory overhead of keeping the FIFO (memory is not
> for free on CE devices).

This overhead and complexity is what seems to be hindering me in pursuing the second method.  Besides, with a single processor, the Log_Thread: would still have to interrupt and halt the other two threads at some point to do the disk Write.  In which case, the overhead now seems overkill and would be quicker to just have the original Rx/Tx thread do the write themselves.


>I guess you would have to measure which one is faster in
> the general case (though my guess is case B).

Trial-and-Error will most likely prove its worth.  If during beta testing the Method #1 becomes an issue, then I'll try something else.

sedwo
Tuesday, April 06, 2004

> Besides, with a single processor, the Log_Thread: would still have to interrupt and halt the other two threads at some point to do the disk Write.

Instead of ...

for (;;)
{
SuspendThread();
If( something in _FIFO_Q[x] )
  Write__FIFO_Q[x] to file;
}

... you'd do ...

for (;;)
{
SuspendThread();
//dequeue from the shared queue
string queue_item;
EnterCriticalSection
queue_item = shared_queue.dequeue();
LeaveCriticalSection
WriteFile(queue_item);
}

... so that the queue is only locked for the short time that it takes to dequeue, not for the long time that it takes to write the dequeued item to disk.

Christopher Wells
Tuesday, April 06, 2004

*  Recent Topics

*  Fog Creek Home