To assist in developing and debugging an SPL application, Streams provides the appTrc function to produce trace messages. For example:

appTrc(Trace.debug,"Tuple is "+(rstring)Data);

It should be no surprise that there’s a cost to output these messages to the log file or the terminal. What may be surprising, though, is that there’s a cost to even having the statement when no output is produced.

That’s because appTrc turns into an ordinary function call in the generated C++ code, and like any function call, the arguments are evaluated before the call is made. Regardless of what the trace level is, in the above example, the code to construct the string described by "Tuple is "+(rstring)Data is executed. If the actual work is small, then this string creation cost can have a significant affect on the the throughput of your application.

Streams 4.0 introduces the isTraceable function. Similar to Java’s isLoggable function, isTraceable takes a trace level, and returns true if messages at that trace level would be produced. This allows you to avoid expensive string construction when the result won’t be used.

We demonstrate the use of isTraceable in the following examples. First, consider a program without it:

composite Main {
  param expression<int32> $iterations: (int32)getSubmissionTimeValue("iterations","1000000");
    
  graph
    stream<float32 myFloat, int32 myInt, rstring myString> Data = Beacon() {
        param iterations: $iterations;
        output Data:
            myFloat=3.14159w,
            myInt=24601,
            myString="24601 stole a loaf of bread, not a pi";
    }

    stream<Data> Filtered = Filter(Data) {
        logic onTuple Data: {
            appTrc(Trace.debug,"Tuple is: "+(rstring)Data);
        }
        param filter: false;
    }
}

In this application, the Filter operator does almost nothing, so the time taken by appTrc calls is going to be a relatively large fraction of the total tuple processing time. Without the debug line, run at error level, this application can process 12,800,000 tuples per second. With the appTrc call, the operator processes only 180,000 tuples per second, almost a factor of ten slower. (When outputting the messages, it processed about 4,200 tuples per second.) This is an extreme example because the tuple processing cost is so very low, but we did see a factor of about five slowdown from an appTrc call in a real application.

This left developers with two bad choices: either they had to edit SPL code and recompile to change between a fast version and a version with debug messages, or they had to tolerate a potentially substantial slowdown even when running at error level.

The isTraceable function solves this problem. We’ve taken the example above and guarded the appTrc call with an isTraceable call.

   stream<Data> Filtered = Filter(Data) {
        logic onTuple Data: {
            if (isTraceable(Trace.debug)) {
                appTrc(Trace.debug,"Tuple is: "+(rstring)Data);
            }
        }
        param filter: false;
    }

The tuple rate at the debug level is the same 4,200 tuples per second as before, but the tuple rate at error is 11.5 million tuples per second, much better than the 180,000 tuples per second we got before we guarded the statement, and almost as much as the 12.8 million tuples per second without any appTrc statement at all. Remember that this example has an unrealistically low work-per-tuple; when the work-per-tuple is higher, the rate difference between no appTrc statement an an guarded appTrc statement would be negligible.

By guarding your appTrc messages with isTraceable, you can keep those messages in your code, even for production environments. You can turn on and off the tracing via the normal mechanisms (even dynamically, at runtime, via Eclipse) without worrying that the appTrc messages will significantly affect your overall application performance.

Using isTraceable as a filter

The previous example shows how to use isTraceable to prevent unnecessary string construction in trace statements, which is a common use case. Here we introduce a different use case.

In some Streams applications, debugging information may take the form of tuples written to files. A common way to do this would be to add in FileSink operators at key points to debug specific issues, and then take them out again the issue is resolved. Developers will often add and remove the same FileSink operator during the debugging process. Using mixed-mode can make this easier, but that still requires recompiling to make changes, and mixed-mode can be cumbersome.

Using isTraceable provides an alternative: it allows you to write tuples to a file only when the debug level is a certain value by using the isTraceable call in a Filter operator in front of the FileSink. An example is below:

composite FileTest {
  param
    expression<int32> $iterations: (int32)getSubmissionTimeValue("iterations","1000000");
  graph
    stream<float32 myFloat, int32 myInt, rstring myString> Data = Beacon() {
        param iterations: $iterations;
        output Data:
            myFloat=3.14159w,
            myInt=24601,
            myString="24601 stole a loaf of bread, not a pi";
            config placement: partitionColocation("DataSink");
    }
 
    stream<Data> Filtered = Filter(Data) {
        param filter: isTraceable(Trace.debug);
        config placement: partitionColocation("DataSink");
    }

    () as sink = FileSink(Filtered) {
        param file: "tmp.csv";
    }  
}

When run with trace set to error, nothing is written to the file. When the trace level is debug or trace, tuples pass the filter and are written to the file. There is a performance cost to doing this; in this case, the application that includes the Filter and FileSink runs about 25% slower than one without. However, that cost is much less than the cost of actually writing the tuples to the file (about 50 times lower throughput).

Thanks to Mike Bhola who suggested this feature and the FileSink pattern.

2 comments on"Using isTraceable with appTrc"

  1. A very smart code pattern for isTraceable! Cool!

  2. Cool feature!
    By the way, it almost factor of hundred, not ten!

Join The Discussion