Details
-
Improvement
-
Status: Closed
-
Major
-
Resolution: Fixed
-
None
-
None
-
None
Description
Testing reveals that streamlining the DataReaderStrip.java operations for 8 and 24 bit-per-pixel TIFF images reduces the TIFF file load time by a factor of 5.
For each pixel in images of these types, the interpretStrip() method of DataReaderStrip makes calls to a generic bit extractor using its getSamplesAsBytes() method. Internally, this method simply copies the requisite number of bytes (8 or 24), but it executes a lot of conditional statements to do so. Under most architectures, conditionals tend to take 2 to 3 times as long to execute as simple arithmetic statements, so this approach is expensive (especially since an image may contain millions of pixels). While the implementation is very generic, the majority of TIFF files out there appear to fall into two simple categories. By implementing specialized code for these two cases, the loading time for TIFF images is dramatically reduced.
The following snippet shows the code I used for testing. It was added right at the beginning of the interpretStrip() method.
// Oct 2011 changes.
// The general case decoder is based on the idea of using a
// generic bit-reader to unpack the number of bytes that are
// needed. Although it is efficiently implemented, it does
// require performing at least three conditional branches per sample
// extracted (and often more). This change attempts to bypass that
// overhead by implementing specialized blocks of extraction code
// for commonly used 8 bitsPerPixel and 24 bitsPerPixel cases.
// In other cases, it will simply fall through to the original code.
// note that when promoting a byte to an integer, it is necessary
// to mask it with 0xff because the Java byte type is signed
// an this implementation requires an unsigned value
if(x>=width)
if(y>=height)
{ // we check it once before starting, so that we don't have // to check it redundantly for each pixel return; } if(predictor==-1 && this.bitsPerPixel==8)
{
int [] samples = new int[1];
for(int i=0; i<pixels_per_strip; i++)
{
samples[0] = bytes[i]&0x000000ff;
photometricInterpreter.interpretPixel(bi, samples, x, y);
x++;
if(x>=width)
}
return;
}
else if(predictor==-1 && this.bitsPerPixel==24)
{
int [] samples = new int[3];
int k = 0;
for(int i=0; i<pixels_per_strip; i++)
{
samples[0] = bytes[k++]&0x000000ff;
samples[1] = bytes[k++]&0x000000ff;
samples[2] = bytes[k++]&0x000000ff;
photometricInterpreter.interpretPixel(bi, samples, x, y);
x++;
if(x>=width)
{ x = 0; y++; if(y>=height) return; // any remaining bytes are not needed }
}
return;
}
// original code before Oct 2011 modification
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
BitInputStream bis = new BitInputStream(bais);
etc.