Ticket #6746: alex.patch
File alex.patch, 16.5 KB (added by jmoore, 12 years ago) |
---|
-
new file components/blitz/src/ome/services/blitz/impl/BackgroundTiffWriter.java
From 5354b5bf77f16dc360c70fa4fd58b4643b72ad58 Mon Sep 17 00:00:00 2001 From: Alex Herbert <a.herbert@sussex.ac.uk> Date: Mon, 26 Sep 2011 11:51:10 +0100 Subject: [PATCH] Added code to perform a multi-threaded Tiff export Created a new wrapper class for the TiffWriter that operates in a separate thread. The plane bytes are passed using a thread-safe BlockingQueue. Added code to the ExporterI class to use the background thread worker if the file to be written is above a certain size. Added timings for the methods that open the image plane from OMERO, save the plane using BioFormats and the time that the master thread has to wait for the background writer to finish. Overall timings for exporting different images are: MB Single Multi Ratio 0.5 46.03 44.42 0.97 1 79.78 79.11 0.99 2 112.59 106.21 0.94 4 217.53 210.07 0.97 8 436.03 421.24 0.97 16 864.59 840.96 0.97 32 1721.95 1675.20 0.97 64 3441.70 3361.51 0.98 128 6879.39 6727.62 0.98 256 13650.14 13358.87 0.98 The background writing therefore adds a 2-3% increase in performance. Given the low level of performance increase I suggested that the patch is not worth pursueing further: http://www.openmicroscopy.org/community/viewtopic.php?f=6&t=814&sid=a870884013f0657bd3a079d1ca16e41d#p3107 --- .../services/blitz/impl/BackgroundTiffWriter.java | 189 ++++++++++++++++++++ .../src/ome/services/blitz/impl/ExporterI.java | 102 +++++++++-- 2 files changed, 277 insertions(+), 14 deletions(-) create mode 100644 components/blitz/src/ome/services/blitz/impl/BackgroundTiffWriter.java diff --git a/components/blitz/src/ome/services/blitz/impl/BackgroundTiffWriter.java b/components/blitz/src/ome/services/blitz/impl/BackgroundTiffWriter.java new file mode 100644 index 0000000..5cf5d31
- + 1 package ome.services.blitz.impl; 2 3 import java.io.IOException; 4 import java.util.concurrent.ArrayBlockingQueue; 5 import java.util.concurrent.BlockingQueue; 6 7 import org.apache.commons.logging.Log; 8 import org.apache.commons.logging.LogFactory; 9 10 import loci.formats.FormatException; 11 import loci.formats.out.TiffWriter; 12 13 /** 14 * Simple wrapper object for the BioFormats TiffWriter class that allows the 15 * {@link loci.formats.out.TiffWriter#saveBytes(int, byte[])} method to be called 16 * in a background thread. This allows a producer of image bytes to operate 17 * simultaneously with the consumer (BioFormats): 18 * 19 * <pre> 20 TiffWriter writer = new TiffWriter() 21 22 // TODO: Initialise writer here 23 24 BackgroundTiffWriter BackgroundTiffWriter = new BackgroundTiffWriter(writer); 25 Thread thread = new Thread(BackgroundTiffWriter); 26 thread.start(); 27 28 try { 29 for ( <get planes loop> ) { 30 byte[] plane = new byte[planeSize]; 31 32 // TODO: fill byte[] array 33 34 BackgroundTiffWriter.saveBytes(plane); 35 } 36 BackgroundTiffWriter.finalise(); 37 thread.join(); 38 } 39 catch (Exception e) { 40 BackgroundTiffWriter.shutdown(); 41 } 42 finally { 43 // The original writer is not closed by the BackgroundTiffWriter 44 writer.close(); 45 } 46 </pre> 47 * 48 * <p> 49 * The class uses a BlockingQueue to store successive byte arrays passed to 50 * the {@link #saveBytes(byte[])} method. These are sequentially dequeued and 51 * passed to the TiffWriter. The size of the blocking queue can be set in the 52 * constructor. Consequently the producer of the bytes should not get too far 53 * ahead of the consumer TiffWriter class. 54 * 55 * <p> 56 * The BackgroundTiffWriter expects the {@link loci.formats.TiffWriter } class 57 * to be correctly initialised to accept bytes to the 58 * {@link loci.formats.TiffWriter#saveBytes(int, byte[])} method. 59 * 60 * <p> 61 * Note: The TiffWriter is not closed by this class. 62 * 63 * @author Alex Herbert, GDSC 64 */ 65 public class BackgroundTiffWriter implements Runnable { 66 67 private final static Log log = LogFactory.getLog(BackgroundTiffWriter.class); 68 69 private TiffWriter writer; 70 private BlockingQueue<byte[]> queue; 71 private volatile boolean closed = false; 72 73 private final Object lock = new Object(); 74 75 /** 76 * Default constructor 77 * @param writer An opened and initialised TiffWriter 78 */ 79 public BackgroundTiffWriter(TiffWriter writer) { 80 this.writer = writer; 81 init(5); 82 } 83 84 /** 85 * Constructor 86 * @param writer An opened and initialised TiffWriter 87 * @param queueSize The size of the blocking queue 88 */ 89 public BackgroundTiffWriter(TiffWriter writer, int queueSize) { 90 this.writer = writer; 91 init(queueSize); 92 } 93 94 /** 95 * Initialise the blocking queue 96 * @param queueSize 97 */ 98 private void init(int queueSize) { 99 queue = new ArrayBlockingQueue<byte[]>(queueSize); 100 } 101 102 /* (non-Javadoc) 103 * @see java.lang.Runnable#run() 104 */ 105 @Override 106 public void run() { 107 Thread.currentThread(); 108 try { 109 int no = 0; 110 byte[] bytes; 111 while (!closed) { 112 bytes = queue.take(); 113 if (bytes == null || bytes.length == 0 || closed) 114 break; 115 synchronized (lock) { 116 writer.saveBytes(no, bytes); 117 } 118 no++; 119 } 120 } 121 catch (InterruptedException e) { 122 log.info(e.toString()); 123 throw new RuntimeException(e); 124 } catch (FormatException e) { 125 log.error(e.toString()); 126 throw new RuntimeException(e); 127 } catch (IOException e) { 128 log.error(e.toString()); 129 throw new RuntimeException(e); 130 } finally { 131 closed = true; 132 133 // Free remaining memory 134 queue.clear(); 135 queue = null; 136 writer = null; 137 138 notifyAll(); 139 } 140 } 141 142 /** 143 * Adds the bytes to the processing queue. A zero length byte array indicates 144 * that no more input is expected. 145 * @param bytes The bytes 146 * @throws RuntimeException If the writer is closed or the thread is interrupted 147 * adding to the queue. 148 */ 149 public void saveBytes(byte[] bytes) throws RuntimeException { 150 if (closed) 151 throw new RuntimeException("The writer has been closed"); 152 try { 153 queue.put(bytes); 154 } catch (InterruptedException iex) { 155 Thread.currentThread().interrupt(); 156 throw new RuntimeException("Unexpected interruption", iex); 157 } 158 } 159 160 /** 161 * Send a signal to the queue to indicate no more input is expected. 162 */ 163 public void finalise() { 164 saveBytes(new byte[0]); 165 } 166 167 /** 168 * Finish the current write process and stop further writing. 169 * Use this method to end the processing in the event of error. 170 */ 171 public void shutdown() { 172 if (closed) 173 return; 174 175 // Ensure that the current write process is over. This allows the 176 // TiffWriter to be closed after shutting down the BackgroundTiffWriter. 177 synchronized (lock) { 178 closed = true; 179 log.info("Terminating ..."); 180 } 181 } 182 183 /** 184 * @return True if no further writes are possible 185 */ 186 public boolean isClosed() { 187 return closed; 188 } 189 } -
components/blitz/src/ome/services/blitz/impl/ExporterI.java
diff --git a/components/blitz/src/ome/services/blitz/impl/ExporterI.java b/components/blitz/src/ome/services/blitz/impl/ExporterI.java index 1bdf0e2..db70f8d 100644
a b public class ExporterI extends AbstractAmdServant implements 98 98 * @see ticket:6520 99 99 */ 100 100 private final static long BIG_TIFF_SIZE = 2L * Integer.MAX_VALUE; 101 102 /** 103 * The size above which a tiff file should be written using a background 104 * thread. This is to avoid the thread overhead when small files are 105 * exported. 106 */ 107 private final static long MULTI_THREAD_EXPORT_SIZE = 1024L * 1024; // 1MB 101 108 102 109 103 110 /** … … public class ExporterI extends AbstractAmdServant implements 357 364 RawPixelsStore raw = null; 358 365 OmeroReader reader = null; 359 366 TiffWriter writer = null; 367 BackgroundTiffWriter backgroundWriter = null; 360 368 try { 361 369 362 370 Image image = retrieve.getImage(0); … … public class ExporterI extends AbstractAmdServant implements 387 395 long dSize = getDataBytes(reader); 388 396 final boolean bigtiff = 389 397 ( ( mSize + dSize ) > BIG_TIFF_SIZE ); 398 final boolean multiThread = ( dSize > MULTI_THREAD_EXPORT_SIZE ); 390 399 if (bigtiff) { 391 400 writer.setBigTiff(true); 392 401 } … … public class ExporterI extends AbstractAmdServant implements 397 406 log.info(String.format( 398 407 "Using big TIFF? %s mSize=%d " + 399 408 "dSize=%d planeCount=%d " + 400 "planeSize=%d ",409 "planeSize=%d multiThread=%s", 401 410 bigtiff, mSize, dSize, 402 planeCount, planeSize)); 403 byte[] plane = new byte[planeSize]; 404 for (int i = 0; i < planeCount; i++) { 405 int[] zct = FormatTools.getZCTCoords( 406 retrieve.getPixelsDimensionOrder(0).getValue(), 407 reader.getSizeZ(), reader.getSizeC(), reader.getSizeT(), 408 planeCount, i); 409 int readerIndex = reader.getIndex(zct[0], zct[1], zct[2]); 410 reader.openBytes(readerIndex, plane); 411 writer.saveBytes(i, plane); 411 planeCount, planeSize, 412 multiThread)); 413 414 // Perform some debug timing for the export 415 long start = System.nanoTime(); 416 long checkpoint1, checkpoint2; 417 long openTime = 0, saveTime = 0, waitTime = 0; 418 419 if (multiThread) 420 { 421 // Save the image using a different thread 422 backgroundWriter = new BackgroundTiffWriter(writer, 10); 423 Thread thread = new Thread(backgroundWriter); 424 thread.start(); 425 426 for (int i = 0; i < planeCount; i++) { 427 int[] zct = FormatTools.getZCTCoords( 428 retrieve.getPixelsDimensionOrder(0).getValue(), 429 reader.getSizeZ(), reader.getSizeC(), reader.getSizeT(), 430 planeCount, i); 431 int readerIndex = reader.getIndex(zct[0], zct[1], zct[2]); 432 byte[] plane = new byte[planeSize]; 433 checkpoint1 = System.nanoTime(); 434 reader.openBytes(readerIndex, plane); 435 checkpoint2 = System.nanoTime(); 436 openTime += checkpoint2 - checkpoint1; 437 backgroundWriter.saveBytes(plane); 438 checkpoint1 = System.nanoTime(); 439 saveTime += checkpoint1 - checkpoint2; 440 } 441 442 // Wait for background writer 443 checkpoint1 = System.nanoTime(); 444 backgroundWriter.finalise(); 445 thread.join(); 446 waitTime = System.nanoTime() - checkpoint1; 447 } 448 else 449 { 450 byte[] plane = new byte[planeSize]; 451 452 for (int i = 0; i < planeCount; i++) { 453 int[] zct = FormatTools.getZCTCoords( 454 retrieve.getPixelsDimensionOrder(0).getValue(), 455 reader.getSizeZ(), reader.getSizeC(), reader.getSizeT(), 456 planeCount, i); 457 int readerIndex = reader.getIndex(zct[0], zct[1], zct[2]); 458 checkpoint1 = System.nanoTime(); 459 reader.openBytes(readerIndex, plane); 460 checkpoint2 = System.nanoTime(); 461 openTime += checkpoint2 - checkpoint1; 462 writer.saveBytes(i, plane); 463 checkpoint1 = System.nanoTime(); 464 saveTime += checkpoint1 - checkpoint2; 465 } 412 466 } 467 468 long end = System.nanoTime(); 469 String msg = String.format("Saved image %s in %f ms. open = %f ms. save = %f ms. wait = %f ms\n", 470 retrieve.getImageName(0), 471 (end-start) / 1000000.0, 472 openTime / 1000000.0, 473 saveTime / 1000000.0, 474 waitTime / 1000000.0); 475 476 log.info(msg); 477 413 478 retrieve = null; 414 479 415 480 try { … … public class ExporterI extends AbstractAmdServant implements 418 483 // Nulling to prevent another exception 419 484 writer = null; 420 485 } 421 486 422 487 __cb.ice_response(file.length()); 423 488 } catch (Exception e) { 424 489 omero.InternalException ie = new omero.InternalException( … … public class ExporterI extends AbstractAmdServant implements 427 492 IceMapper.fillServerError(ie, e); 428 493 __cb.ice_exception(ie); 429 494 } finally { 430 cleanup(raw, reader, writer );495 cleanup(raw, reader, writer, backgroundWriter); 431 496 } 432 497 433 498 return null; // see calls to __cb above 434 499 } 435 500 436 501 private void cleanup(RawPixelsStore raw, 437 OmeroReader reader, IFormatWriter writer) { 502 OmeroReader reader, IFormatWriter writer, 503 BackgroundTiffWriter backgroundWriter) { 438 504 try { 439 505 if (raw != null) { 440 506 raw.close(); … … public class ExporterI extends AbstractAmdServant implements 450 516 log.error("Error closing reader", e); 451 517 } 452 518 try { 519 if (backgroundWriter != null) { 520 backgroundWriter.shutdown(); 521 backgroundWriter = null; 522 } 523 } catch (Exception e) { 524 log.error("Error closing background writer", e); 525 } 526 try { 453 527 if (writer != null) { 454 528 writer.close(); 455 529 }