Skip to content

Commit c645cdf

Browse files
committed
Publish updates to first post, and second post.
1 parent 04fbb1b commit c645cdf

File tree

2 files changed

+87
-2
lines changed

2 files changed

+87
-2
lines changed

_posts/2015-08-14-Logging-Considered-Harmful.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Race conditions are embarrassingly easy to create. Here's some C# code that has
2323
.Range (0, workerCount)
2424
.Select ((x) => {
2525
return Task.Run (() => {
26-
result += x % 2 == 0 -x : x;
26+
result += x % 2 == 0 ? -x : x;
2727
});
2828
})
2929
.ToList ();
@@ -60,7 +60,7 @@ However unlikely it may seem, it's actually true. I *can* fix the race condition
6060
.Select ((x) => {
6161
return Task.Run (() => {
6262
Console.WriteLine("worker {0}", x); // blink and you'll miss it!
63-
result += x % 2 == 0 ? -i : i;
63+
result += x % 2 == 0 ? -x : x;
6464
});
6565
})
6666
.ToList ();
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
layout: post
3+
title: Logging Reconsidered
4+
---
5+
Hi, I'm David and in this post I'm going to re-examine my [previous post]({% post_url 2015-08-14-Logging-Considered-Harmful %}) in which I concluded that logging applied carelessly can disturb the natural operation of a program to the point where it could even hide race conditions.
6+
7+
As a reminder, here's the code in question:
8+
9+
```csharp
10+
var result = 100;
11+
var expected = 93;
12+
var workerCount = 15;
13+
var workers = Enumerable
14+
.Range (0, workerCount)
15+
.Select ((x) => {
16+
return Task.Run (() => {
17+
Console.WriteLine("worker {0}", x); // blink and you'll miss it!
18+
result += x % 2 == 0 ? -x : x;
19+
});
20+
})
21+
.ToList ();
22+
await Task.WhenAll (workers)
23+
.ContinueWith ((t) => {
24+
if (result != expected) {
25+
// race detected!
26+
}
27+
});
28+
```
29+
30+
I'm fortunate to have some extremely smart friends who are also very good programmers. After I advertised my previous post to them and asked for comments, one came back with the very good observation that the working part of my racing code consumed much less CPU time than the log statements used to record information about it.
31+
32+
I have to agree. The line
33+
34+
```csharp
35+
result += x % 2 == 0 ? -x : x;
36+
```
37+
38+
is over very quickly.
39+
40+
The line
41+
42+
```csharp
43+
Console.WriteLine("worker {0}", x); // blink and you'll miss it!
44+
```
45+
46+
takes a good bit longer to execute, largely because there are several subroutine calls involved all the way down to the depths of the .NET console, where the system then does [this](https://github.com/dotnet/corefx/blob/master/src/System.Console/src/System/IO/SyncTextWriter.cs#L287):
47+
48+
```csharp
49+
public override void WriteLine(String format, Object[] arg)
50+
{
51+
lock (_methodLock)
52+
{
53+
_out.WriteLine(format, arg);
54+
}
55+
}
56+
```
57+
58+
Taking the lock and composing the output string are clearly expensive activities (in terms of CPU time) compared to adding or subtracting one number from another.
59+
60+
My friend then quite reasonably asked: "What happens if you run the add/subtract calculation lots of times during the `Task`?" The implication being that the task may then take long enough to balance the effect of taking a lock during logging, and the lock will no longer hide the race.
61+
62+
So of course in the name of Scientific Research I had to try it. I modified my program to loop around the add/subtract calculation, and increased the number of iterations until the system began to race again while logging was still enabled. If you're inclined to try for yourself, your results will inevitably be different than the following, because everything about threads and multi-tasking is heavily dependent on the hardware the program is running on. But here's what I found for the PC-based command line version:
63+
64+
| Iterations | Result |
65+
| -------------------: | ------ |
66+
| 1 | No race |
67+
| 10 | No race |
68+
| 1000 | No race |
69+
| 10000 | Race |
70+
71+
So on this test system (desktop PC with 8 cores and lots of RAM) I had to have a `Task` whose overall duration was on average about 10000 times that of the original `Task` to prevent the logs from hiding the race.
72+
73+
On the face of it this looks reasonable. In normal applications, multi-threaded code is usually quite long-running - that's often the driver for putting it into another thread in the first place, after all. Think of downloading a an image, updating a database, calculating the new position of some object in 3-D space - these are all tasks that will on the whole take much longer than adding an entry in a log.
74+
75+
Should I be concerned, then? I think the answer to that question, as in so much of computing, is "it depends". If my threaded tasks are relatively long-running, I'll probably get away with simple logging to debug them.
76+
77+
But logs have a way of growing - once you start, there's much seemingly-useful information to be extracted from a program while it's running - and there's a very real danger that the CPU time involved in logging will begin to outweigh the CPU time used by working code, like barnacles on a ship. At that point, I'm once again in danger of masking out the races in my code by all the logging that's going on.
78+
79+
To my mind, the safest way to do logging in a program is to use a non-blocking logger. This offers the best guarantee that logging code won't interfere with working code.
80+
81+
Of course, there's no such thing as a free lunch - and in the world of logging, a non-blocking logger can mean the runtime has to create at least one extra thread for the logger to run in. This in and of itself can affect the operation of an application, especially if the hardware has fewer cores than the number of threads needed to run it. So while it's possible that the C# `async`/`await` construct could eliminate the requirement for an extra thread to manage the logging, the risk still exists and must be accounted for.
82+
83+
Happily, in C# there are several nice ways to create a non-blocking logger that all work well in a [Xamarin](www.xamarin.com) app.
84+
85+
I'll explore one of them in my next post.

0 commit comments

Comments
 (0)