gpuBenchReport

PURPOSE ^

gpuBenchReport create an HTML report based on some GPU performance data

SYNOPSIS ^

function out = gpuBenchReport( varargin )

DESCRIPTION ^

gpuBenchReport  create an HTML report based on some GPU performance data

   gpuBenchReport(data) creates a new HTML report based on the supplied
   PerformanceData and opens it in the system browser.

   gpuBenchReport() creates an HTML report based only on the pre-stored
   performance data.

   filename = gpuBenchReport(...) returns the location of the main page
   for the report instead of viewing it immediately.

   Examples:
   >> gpuBenchReport
   >> data = gpuBench;
   >> gpuBenchReport( data )

   See also: gpuBench

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SUBFUNCTIONS ^

SOURCE CODE ^

0001 function out = gpuBenchReport( varargin )
0002 %gpuBenchReport  create an HTML report based on some GPU performance data
0003 %
0004 %   gpuBenchReport(data) creates a new HTML report based on the supplied
0005 %   PerformanceData and opens it in the system browser.
0006 %
0007 %   gpuBenchReport() creates an HTML report based only on the pre-stored
0008 %   performance data.
0009 %
0010 %   filename = gpuBenchReport(...) returns the location of the main page
0011 %   for the report instead of viewing it immediately.
0012 %
0013 %   Examples:
0014 %   >> gpuBenchReport
0015 %   >> data = gpuBench;
0016 %   >> gpuBenchReport( data )
0017 %
0018 %   See also: gpuBench
0019 
0020 %   Copyright 2011 The MathWorks, Inc.
0021 
0022 narginchk( 0, 2 );
0023 nargoutchk( 0, 1 );
0024 
0025 % Try to get some comparison data
0026 if nargin>0
0027     assert( all( cellfun( 'isclass', varargin, 'gpubench.PerformanceData' ) ) );
0028     [allData,allDataSummary] = getComparisonData( [varargin{:}]' );
0029 else
0030     [allData,allDataSummary] = getComparisonData();
0031 end
0032 if isempty(allData)
0033     error( 'gpuBenchReport:NoData', 'No data to report' );
0034 end
0035 
0036 N = numel( allData );
0037 gpubench.multiWaitbar( 'Creating GPUBench report...', 0 );
0038 
0039 % Ensure the output folder exists
0040 reportDir = fullfile( tempdir(), 'GPUBenchReport' );
0041 if ~exist( reportDir, 'dir' )
0042     mkdir( reportDir );
0043 end
0044 copyFiles( reportDir );
0045 
0046 % Create the summary page for this device
0047 makeSummaryBarChart( reportDir, allDataSummary );
0048 reportname = makeSummaryPage( reportDir, allDataSummary );
0049 gpubench.multiWaitbar( 'Creating GPUBench report...', 'Increment', 1/(N+1) );
0050 
0051 % Now create the detail pages for all devices
0052 for ii=1:numel( allData )
0053     makePerformancePlots( reportDir, allData, ii );
0054     makeDetailPage( reportDir, allData, allDataSummary, ii );
0055     gpubench.multiWaitbar( 'Creating GPUBench report...', 'Increment', 1/(N+1) );
0056 end
0057 
0058 gpubench.multiWaitbar( 'Creating GPUBench report...', 'close' );
0059 
0060 if nargout
0061     out = reportname;
0062 else
0063     web( reportname );
0064 end
0065 
0066 end % gpuBenchReport
0067 
0068 %-------------------------------------------------------------------------%
0069 function [allData,allDataSummary] = getComparisonData( data )
0070 
0071 if nargin<1 || isempty(data)
0072     data = [];
0073     % No user data, so use the current release
0074     thisRelease = regexp( version, 'R\d*[ab]', 'match' );
0075     thisRelease = thisRelease{1};
0076 else
0077     % Work out which data-file to use from the release
0078     thisRelease = data(1).MATLABRelease;
0079     % Flag the user's data so that we can highlight it
0080     for ii=1:numel(data)
0081         data(ii).IsSelected = true;
0082     end
0083 end
0084 
0085 % Try to load the data for this release
0086 otherData = loadDataForRelease( thisRelease, data );
0087 
0088 % If no data was found, warn and try an older release
0089 if isempty(otherData)
0090     allDataFiles = dir( fullfile( 'data', 'R*.mat' ) );
0091     allDataFiles = {allDataFiles.name};
0092    
0093     % Try each file in turn, starting from the most recent
0094     for ii=numel(allDataFiles):-1:1
0095         release = allDataFiles{ii}(1:6);
0096         otherData = loadDataForRelease( release, data );
0097         if ~isempty(otherData)
0098             break;
0099         end
0100     end
0101     % If we reach here without any data then no file had what we wanted.
0102     % Give up!
0103     if isempty( otherData )
0104         error( 'GPUBenchReport:NoData', ...
0105         ['No pre-stored data was found. Please re-download and install ', ...
0106         'gpuBench from the File Exchange.'] );
0107     else
0108         % Indicate that we're using an older release
0109         warning( 'GPUBenchReport:NoDataForRelease', ...
0110             ['There is no pre-stored data for this MATLAB release (%s). ' ...
0111             'Using data for %s instead. ', ...
0112             'Check the File Exchange for an updated version of gpuBench ', ...
0113             'that has data for this release.'], ...
0114             thisRelease, release );
0115     end
0116 end
0117 
0118 % Construct the summary statistics from all the results and then sort the
0119 % original data using the summary score.
0120 if nargin>0
0121     allData = [data;otherData];
0122 else
0123     allData = otherData;
0124 end
0125 
0126 if isempty( allData )
0127     % No data at all, so warn and show something older
0128     error( 'GPUBenchReport:NoData', ...
0129         ['There is no pre-stored data for this MATLAB release (%s). ' ...
0130         'Check the File Exchange for an updated version or use gpuBench ', ...
0131         'to view the results for your system.'], release );
0132 elseif isempty( otherData )
0133 end
0134 
0135 allDataSummary = gpubench.SummaryData( allData );
0136 allData = allData(allDataSummary.SortOrder);
0137 end % getComparisonData
0138 
0139 %-------------------------------------------------------------------------%
0140 function otherData = loadDataForRelease( release, existingData )
0141 datafilename = fullfile( gpubench.getDataDir(), [release, '.mat'] );
0142 if exist( datafilename, 'file' ) == 2
0143     otherData = load( datafilename );
0144     if ~isfield( otherData, 'results' ) || ~isa( otherData.results, 'gpubench.PerformanceData' )
0145         error( 'GPUBench:BadDataFile', 'Data file was corrupted, please delete it: %s', datafilename );
0146     end
0147     otherData = otherData.results;
0148     
0149     % We don't want to see a result for the exact same card
0150     if ~isempty(existingData)
0151         % If the existing data contains some host data, don't keep any from the
0152         % loaded data
0153         if any([existingData.IsHostData])
0154             otherData([otherData.IsHostData]) = [];
0155         end
0156     
0157         keep = true( size( otherData ) );
0158         for ii=1:numel( otherData )
0159             for jj=1:numel(existingData)
0160                 if ~existingData(jj).IsHostData
0161                     keep(ii) = keep(ii) ...
0162                         && ~strcmpi( otherData(ii).GPUInfo.Name, existingData(jj).GPUInfo.Name );
0163                 end
0164             end
0165             otherData(ii).IsSelected = false;
0166         end
0167         otherData = otherData(keep);
0168     end
0169 else
0170     otherData = gpubench.PerformanceData.empty(0,1);
0171 end
0172 end % loadDataForRelease
0173 
0174 %-------------------------------------------------------------------------%
0175 function makeSummaryBarChart( outDir, summaryData )
0176 
0177 assert( isa( summaryData, 'gpubench.SummaryData' ) );
0178 
0179 N = numel( summaryData.DeviceName );
0180 deviceNames = summaryData.DeviceName;
0181 functionNames = summaryData.LongName;
0182 flopsResults = summaryData.PeakFLOPS;
0183 isSelected = summaryData.IsSelectedDevice;
0184 
0185 % Sort by data-type
0186 types = unique( summaryData.Datatype );
0187 numOfType = zeros(size(types));
0188 colsForType = cell(size(types));
0189 for ii=1:numel(types)
0190     match = strcmp( summaryData.Datatype, types{ii} );
0191     numOfType(ii) = sum( match );
0192     colsForType{ii} = find( match );
0193 end
0194 colOrder = [colsForType{:}];
0195 functionNames = functionNames(colOrder);
0196 flopsResults = flopsResults(:,colOrder);
0197 
0198 
0199 figh = gpubench.makeFigure( 'Summary' );
0200 set(figh,'Position',[10 10 650 650])
0201 bars = barh( flopsResults'/1e9 );
0202 set( gca, 'YTickLabel', functionNames, 'YDir', 'Reverse' )
0203 xlabel( sprintf('GFLOPS\n(higher is better)') )
0204 
0205 % Highlight the selected entry
0206 for ii=1:N
0207     if isSelected(ii)
0208         deviceNames{ii} = ['{\bf',deviceNames{ii},' (yours)}'];
0209     end
0210 end
0211 
0212 gpubench.legend( deviceNames{:}, 'Location', 'SouthEast' );
0213 grid on
0214 title( 'Performance Summary' )
0215 
0216 % Set colors to fade from blue to red
0217 colors = [
0218     linspace(0,0.6,N)
0219     zeros(1,N)
0220     linspace(0.75,0,N)
0221     ]';
0222 for ii=1:N
0223     set( bars(ii), 'FaceColor', colors(ii,:), 'EdgeColor', 0.5*colors(ii,:) );
0224 end
0225 
0226 % highlight the user's result
0227 highlightColor = [0 1 0];
0228 selectedResults = find(summaryData.IsSelectedDevice);
0229 N = numel(selectedResults);
0230 for ii=1:N
0231     ratio = (N-ii+2)/(N+1);
0232     set( bars(selectedResults(ii)), ...
0233         'FaceColor', highlightColor*ratio, ...
0234         'EdgeColor', [0 0 0], ...
0235         'Linewidth', 2 );
0236 end
0237 
0238 % Add dividers between types
0239 hold on
0240 x = get(gca,'XLim');
0241 for ii=1:numel(numOfType)-1
0242     plot( x, (numOfType(ii)+0.5)*[1 1], 'k-' )
0243 end
0244 
0245 gpubench.addAreaShadows( gca() );
0246 gpubench.addGradientToAxes( gca() );
0247 
0248 % Save the image to file for the HTML to pick up
0249 filename = 'summarychart.png';
0250 gpubench.captureFigure( figh, fullfile( outDir, filename ), false );
0251 close( figh );
0252 end % makeSummaryBarChart
0253 
0254 %-------------------------------------------------------------------------%
0255 function makePerformancePlots( outDir, data, thisIdx )
0256 %Create a FLOPS plot for each results in data with the "thisIdx" result
0257 %highlighted.
0258 
0259 fileBase = sprintf('device%d',thisIdx);
0260 
0261 plotNames = cell( size(data) );
0262 for ii=1:numel(data)
0263     plotNames{ii} = data(ii).getDeviceName();
0264 end
0265 plotNames{thisIdx} = ['{\bf',plotNames{thisIdx}, ' (selected)}'];
0266 
0267 results = data(thisIdx).Results;
0268 
0269 for rr=1:numel( results )
0270     name = [results(rr).FunctionName, ' (', results(rr).DataType, ')'];
0271     figh = gpubench.makeFigure( name );
0272     color = get( gca(), 'ColorOrder' );
0273     
0274     % Plot each device's curve, if they have the relevant data
0275     for ii=1:numel(data)
0276         colorIdx = mod(ii-1,size(color,1))+1;
0277         % Find the corresponding result
0278         if hasResult( data(ii), name )
0279             linewidth = 1+2*(ii==thisIdx);
0280             gpubench.plotFLOPS( getResult( data(ii), name ), color(colorIdx,:), linewidth )
0281         else
0282             % We still need to plot something for the legend to be correct
0283             plot( nan, nan, 'Color', color(colorIdx,:), 'LineWidth', 1 );
0284         end
0285     end
0286     % Add a highlight around the peak-flops point
0287     colorIdx = mod(thisIdx-1,size(color,1))+1;
0288     thisResult = getResult( data(thisIdx), name );
0289     [maxVal,maxIdx] = max( 1e-9 * thisResult.NumOps ./ thisResult.Times);
0290     plot( thisResult.Sizes(maxIdx), maxVal, ...
0291         'Color', color(colorIdx,:), ...
0292         'Marker', 'o', ...
0293         'MarkerSize', 16, ...
0294         'Linewidth', 2 );
0295     
0296     title( name );
0297     gpubench.legend( plotNames{:}, 'Location', 'NorthWest' );
0298     gpubench.addGradientToAxes( gca() );
0299     
0300     % Due to a bug in ZBuffer renderer, the log-lines disappear when we add
0301     % a patch to the plot. Stick them back in now.
0302     xtick = str2num( get( gca, 'XTickLabel' ) ); %#ok<ST2NM>
0303     ylim = get( gca, 'YLim' );
0304     for ii=1:numel(xtick)-1
0305         xminortick = (2:9) * power(10,xtick(ii));
0306         xdata = [xminortick;xminortick;nan(1,numel(xminortick))];
0307         ydata = [repmat(ylim',1,numel(xminortick)); nan(1,numel(xminortick))];
0308         line( 'XData', xdata(:), 'YData', ydata(:), ...
0309             'LineStyle', ':', ...
0310             'Color', 0.6*[1 1 1] );
0311     end
0312     
0313     % Save the image to file for the HTML to pick up
0314     filename = [fileBase,'-',results(rr).FunctionName,'-',results(rr).DataType,'.png'];
0315     gpubench.captureFigure( figh, fullfile( outDir, filename ) );
0316     close( figh );
0317 end
0318 end % makePerformancePlots
0319 
0320 %-------------------------------------------------------------------------%
0321 function copyFiles( outDir )
0322 % Copy the required stylesheet and other files into the report folder
0323 
0324 dataDir = gpubench.getDataDir();
0325 
0326 files = {
0327     'gpubench.css'
0328     'title.png'
0329     'background.png'
0330     'warning.png'
0331     };
0332 for ii=1:numel(files)
0333     copyfile( fullfile( dataDir, files{ii} ), outDir, 'f' );
0334 end
0335 end % copyFiles
0336 
0337 %-------------------------------------------------------------------------%
0338 function reportname = makeSummaryPage( outDir, summaryData )
0339 % Create the summary page for this device
0340 
0341 assert( isa( summaryData, 'gpubench.SummaryData' ) );
0342 
0343 % Find the user's data
0344 userIdx = find( summaryData.IsSelectedDevice, 1, 'first' );
0345 if isempty( userIdx )
0346     % No user data, so use the first one
0347     pageName = '';
0348 else
0349     pageName = [': ', summaryData.DeviceName{userIdx}];
0350 end
0351 reportname = fullfile( outDir, 'index.html' );
0352 
0353 fid = fopen( reportname, 'wt' );
0354 fprintf( fid, '<html><head>\n' );
0355 fprintf( fid, '  <title>GPU Comparison Report%s</title>\n', pageName );
0356 fprintf( fid, '  <link rel="stylesheet" type="text/css" href="gpubench.css"/>\n' );
0357 fprintf( fid, '  <meta author="Generated by GPUBench."/>\n' );
0358 fprintf( fid, '</head>\n' );
0359 fprintf( fid, '<body>\n' );
0360 
0361 % All of the content goes in a giant table to control the width
0362 fprintf( fid, '  <center><table class="mainlayout"><tr><td>\n' );
0363 fprintf( fid, '  <img src="title.png"/>\n' );
0364 
0365 % Main title
0366 fprintf( fid, '  <h1>GPU Comparison Report%s</h1>\n', pageName );
0367 
0368 % Summary section
0369 fprintf( fid, '  <h2>Summary of results</h2>\n' );
0370 % Add the description
0371 writeBoilerPlate( fid, 'summary_intro.html' )
0372 
0373 names = summaryData.FunctionName;
0374 numCols = numel( names );
0375 
0376 % Print the table header
0377 fprintf( fid, '  <table border="0" width="100%%"><tr><td align="center">\n' );
0378 fprintf( fid, '  <table class="summarytable" cellspacing="0">\n' );
0379 
0380 % Titles for types
0381 types = getDatatypes( summaryData );
0382 colsForType = getColsForType( summaryData, types );
0383 numOfType = cellfun( 'length', colsForType );
0384 colOrder = [colsForType{:}];
0385 
0386 fprintf( fid, '    <tr><th class="summarytable"></th>' );
0387 for ii=1:numel(types)
0388     fprintf( fid, '      <th class="summarytable" colspan="%d">\n', ...
0389         numOfType(ii) );
0390     fprintf( fid, '        Results for data-type ''%s''<br/>(In GFLOPS)</th>\n', ...
0391         types{ii} );
0392 end
0393 
0394 % Titles for each result
0395 fprintf( fid, '    <tr><th class="summarytable"></th>' );
0396 for cc=1:numCols
0397     fprintf( fid, '<th class="summarytable">%s</th>', names{colOrder(cc)} );
0398 end
0399 fprintf( fid, '</tr>\n' );
0400 % Now the body
0401 for rr=1:numel(summaryData.DeviceName)
0402     fprintf( fid, '    <tr>' );
0403     if (summaryData.IsSelectedDevice(rr))
0404         nameStr = ['<b>',summaryData.DeviceName{rr},'</b>'];
0405         cellformat = '<td class="summarytable" align="right"><a href="device%d.html#result%u"><b>%1.2f</b></a></td>';
0406     else
0407         nameStr = summaryData.DeviceName{rr};
0408         cellformat = '<td class="summarytable" align="right"><a href="device%d.html#result%u">%1.2f</a></td>';
0409     end
0410     fprintf( fid, '<td class="summarytable" align="left"><a href="device%d.html">%s</a></td>', rr, nameStr );
0411     for cc=1:numCols
0412         colIdx = colOrder(cc);
0413         fprintf( fid, cellformat, rr, colIdx, summaryData.PeakFLOPS(rr,colIdx) / 1e9 );
0414     end
0415     fprintf( fid, '</tr>\n' );
0416 end
0417 fprintf( fid, '</table>\n\n' );
0418 fprintf( fid, '<small><b>(click any device name or result to see the detailed data)</b></small><br/>\n\n' );
0419 
0420 
0421 % Add the summary image
0422 fprintf( fid, '<img src="summarychart.png"/>\n\n' );
0423 fprintf( fid, '  </td></tr></table>\n' );
0424 
0425 % Footer
0426 fprintf( fid, '  <hr/>\n' );
0427 writeGeneratedBy( fid );
0428 
0429 % Close the main layout table
0430 fprintf( fid, '  </td></tr></table></center>\n' );
0431 fprintf( fid, '</body>\n' );
0432 fprintf( fid, '</html>\n' );
0433 fclose( fid );
0434 end % makeSummaryPage
0435 
0436 %-------------------------------------------------------------------------%
0437 function makeDetailPage( outDir, data, summaryData, thisIdx )
0438 % Create page of detailed information for one device
0439 name = summaryData.DeviceName{thisIdx};
0440 fileBase = sprintf('device%d',thisIdx);
0441 
0442 reportname = fullfile( outDir, [fileBase,'.html'] );
0443 
0444 fid = fopen( reportname, 'wt' );
0445 fprintf( fid, '<html><head>\n' );
0446 fprintf( fid, '  <title>GPU Performance Details: %s</title>\n', name );
0447 fprintf( fid, '  <link rel="stylesheet" type="text/css" href="gpubench.css"/>\n' );
0448 fprintf( fid, '  <meta author="Generated by GPUBench."/>\n' );
0449 fprintf( fid, '</head>\n' );
0450 fprintf( fid, '<body>\n' );
0451 
0452 % All of the content goes in a giant table to control the width
0453 fprintf( fid, '  <center><table class="mainlayout"><tr><td>\n' );
0454 fprintf( fid, '  <a href="index.html"><img src="title.png" border="0"/></a>\n' );
0455 
0456 % Main title
0457 fprintf( fid, '  <h1>GPU Performance Details: %s</h1>\n', name );
0458 
0459 % Contents section
0460 fprintf( fid, '  <table cellspacing="10"><tr><td valign="top">\n' );
0461 fprintf( fid, '    <b>Contents:</b>\n' );
0462 fprintf( fid, '  </td><td valign="top">\n' );
0463 names = summaryData.LongName;
0464 fprintf( fid, '    <ul>\n' );
0465 fprintf( fid, '      <li><a href="#config">System Configuration</a></li>\n' );
0466 
0467 % Sort the contents by type
0468 types = getDatatypes( summaryData );
0469 
0470 for tt=1:numel( types )
0471     fprintf( fid, '      <li>Results for datatype %s</a><ul>\n', types{tt} );
0472     colsForType = getColsForType( summaryData, types{tt} );
0473     for nn=1:numel( colsForType )
0474         myCol = colsForType(nn);
0475         fprintf( fid, '        <li><a href="#result%u">%s</a></li>\n', myCol, names{myCol} );
0476     end
0477     fprintf( fid, '      </ul></li>\n' );
0478 end
0479 fprintf( fid, '    </ul>\n' );
0480 fprintf( fid, '  </tr></table>\n' );
0481 fprintf( fid, '  <br/>\n' );
0482 
0483 
0484 % Add a section showing the operating environment
0485 fprintf( fid, '  <a name="config"><h2>System Configuration</h2></a>\n' );
0486 
0487 % If this isn't the user's system, make that clear
0488 if ~data(thisIdx).IsSelected
0489     fprintf( fid, '  <table border="0"><tr>\n' );
0490     fprintf( fid, '    <td valign="middle"><img src="warning.png"/></td>\n' );
0491     fprintf( fid, '    <td valign="baseline"><b><font color="#660000">Note that this is previously stored data and does not reflect your system configuration.</font></b></td>\n' );
0492     fprintf( fid, '  </tr></table>\n' );
0493 end
0494 
0495 fprintf( fid, '  <p><b>MATLAB Release:</b> %s</p>\n', data(thisIdx).MATLABRelease );
0496 fprintf( fid, '  <table cellspacing="10"><tr><td valign="top">\n' );
0497 fprintf( fid, '    <p><b>Host</b></p>\n' );
0498 writeStructTable( fid, data(thisIdx).CPUInfo );
0499 % - only add the GPU if we ran on it
0500 if ~data(thisIdx).IsHostData
0501     fprintf( fid, '  </td><td valign="top">\n' );
0502     fprintf( fid, '    <p><b>GPU</b></p>\n' );
0503     writeStructTable( fid, data(thisIdx).GPUInfo );
0504 end
0505 fprintf( fid, '  </td></tr></table>\n' );
0506 fprintf( fid, '  <br/>\n' );
0507 
0508 % Add one section per result
0509 names = summaryData.LongName;
0510 for nn=1:numel( names )
0511     fprintf( fid, '  <a name="result%u"><h2>Results for %s</h2></a>\n', nn, names{nn} );
0512     if ~hasResult( data(thisIdx), names{nn} )
0513         % No results for this function
0514         fprintf( fid, '  <p>No results found for %s.</p>\n', names{nn} );
0515         continue;
0516     end
0517     myResult = getResult( data(thisIdx), names{nn} );
0518     % See if there's a description for this function
0519     writeBoilerPlate( fid, [myResult.FunctionName,'.html'] )
0520     
0521     fprintf( fid, '  <table cellspacing="0"><tr><td valign="top">\n' );
0522     
0523     fprintf( fid, '    <table><tr><td>\n');
0524     fprintf( fid, '      <b>Raw data for %s - %s</b>\n', name, names{nn} );
0525     fprintf( fid, '    </td></tr><tr><td>\n');
0526     
0527     % Print the table header
0528     fprintf( fid, '    <table class="summarytable" cellspacing="0">\n' );
0529     fprintf( fid, '      <tr>' );
0530     fprintf( fid, '<th class="summarytable">Array size<br/>(elements)</th>' );
0531     fprintf( fid, '<th class="summarytable">Num<br/>Operations</th>' );
0532     fprintf( fid, '<th class="summarytable">Time<br/>(ms)</th>' );
0533     fprintf( fid, '<th class="summarytable">GigaFLOPS</th>' );
0534     fprintf( fid, '</tr>\n' );
0535     % Now one row per size
0536     sizes = myResult.Sizes;
0537     flops = myResult.NumOps;
0538     times = myResult.Times;
0539     [~,peakIdx] = max( flops ./ times );
0540     baseFormatStr1 = '<td class="summarytable" align="right">';
0541     baseFormatStr2 = '</td>';
0542     for ss=1:numel(sizes)
0543         % Highlight the peak FLOPS row
0544         if ss==peakIdx
0545             formatStr1 = [baseFormatStr1,'<font color="#0000dd">'];
0546             formatStr2 = ['</font>',baseFormatStr2];
0547         else
0548             formatStr1 = baseFormatStr1;
0549             formatStr2 = baseFormatStr2;
0550         end
0551         fprintf( fid, '      <tr>' );
0552         fprintf( fid, [formatStr1,'%s',formatStr2], num2strWithCommas(sizes(ss)) );
0553         fprintf( fid, [formatStr1,'%s',formatStr2], num2strWithCommas(flops(ss)) );
0554         fprintf( fid, [formatStr1,'%2.2f',formatStr2], times(ss)*1000 );
0555         fprintf( fid, [formatStr1,'%2.2f',formatStr2], flops(ss)/times(ss)/1e9 );
0556         fprintf( fid, '</tr>\n' );
0557     end
0558     fprintf( fid, '    </table>\n' );
0559     fprintf( fid, '<center><small>(<code>N</code> gigaflops = <code>Nx10<sup>9</sup></code> operations per second)</small></center><br/>\n\n' );
0560     
0561     fprintf( fid, '    </td></tr></table>\n');
0562     
0563     % Add the image
0564     fprintf( fid, '  </td><td valign="top">\n' );
0565     fprintf( fid, '    <img src="%s-%s-%s.png"/>\n', ...
0566         fileBase, myResult.FunctionName, myResult.DataType );
0567     fprintf( fid, '  </td></tr></table>\n' );
0568 end
0569 
0570 % Footer
0571 fprintf( fid, '  <hr/>\n' );
0572 
0573 fprintf( fid, '  <table width="100%%"><tr><td align="left">\n' );
0574 writeGeneratedBy( fid );
0575 fprintf( fid, '  </td><td align="right">\n' );
0576 fprintf( fid, '    <small><a href="index.html"><i>Back to summary</i></a></small>\n' );
0577 fprintf( fid, '  </td></tr></table>\n' );
0578 
0579 % Close the main layout table
0580 fprintf( fid, '  </td></tr></table></center>\n' );
0581 fprintf( fid, '</body>\n' );
0582 fprintf( fid, '</html>\n' );
0583 fclose( fid );
0584 end % makeDetailPage
0585 
0586 %-------------------------------------------------------------------------%
0587 function writeStructTable( fid, data )
0588 assert( isstruct( data ) && isscalar( data ) );
0589 fprintf( fid, '    <table class="summarytable" cellspacing="0">\n' );
0590 fields = fieldnames( data );
0591 for ff=1:numel( fields )
0592     fprintf( fid, '      <tr><th class="summarytable" align="left">%s</th>', fields{ff} );
0593     fprintf( fid, '<td class="summarytable">' );
0594     x = data.(fields{ff});
0595     if ischar( x )
0596         fprintf( fid, '%s', x );
0597     elseif isinteger( x )
0598         fprintf( fid, '%d', x );
0599     else
0600         % Try to let MATLAB do the right thing
0601         fprintf( fid, '%g', x );
0602     end
0603     fprintf( fid, '</td></tr>\n' );
0604 end
0605 fprintf( fid, '    </table>\n' );
0606 end % writeStructTable
0607 
0608 %-------------------------------------------------------------------------%
0609 function writeBoilerPlate( outFid, filename )
0610 %Read some boiler-plate HTML and paste it into the supplied output file
0611 filename = fullfile( gpubench.getDataDir(), filename );
0612 inFid = fopen( filename, 'rt' );
0613 if inFid<=0
0614     warning( 'gpuBenchReport:MissingBoilerPlateFile', ...
0615         'Input file could not be opened: %s', filename );
0616     return;
0617 end
0618 txt = fread( inFid );
0619 fwrite( outFid, txt );
0620 fclose( inFid );
0621 end % writeBoilerPlate
0622 
0623 %-------------------------------------------------------------------------%
0624 function writeGeneratedBy( outFid )
0625 %Write the "generated by" string into the footer
0626 
0627 fprintf( outFid, '<small><i>Generated by gpuBench v%s: %s</i></small>\n', ...
0628     gpubench.version(), datestr( now(), 'yyyy-mm-dd HH:MM:SS' ) );
0629 end % writeGeneratedBy
0630 
0631 %-------------------------------------------------------------------------%
0632 function str = num2strWithCommas( num )
0633 %Convert an integer into a string with commas separating sets of 3 digits
0634 %
0635 %  e.g. num2StrWithCommas(12345678) = '12,345,678'
0636 
0637 % First convert using the standard method
0638 baseStr = num2str( abs(num) );
0639 % now insert some commas.
0640 % pad to a multiple of 3
0641 padding = 3 - (mod(length(baseStr)-1,3)+1);
0642 str = [repmat(' ',1,padding), baseStr];
0643 numCols = length(str)/3;
0644 str = [reshape(str,3,numCols);repmat(',',1,numCols)];
0645 str = strtrim( str(1:end-1) );
0646 % Finally, re-insert the sign
0647 if num<0
0648     str = ['-',str];
0649 end
0650 end % num2StrWithCommas

Community support and wiki are available on Redmine. Last update: 18-Apr-2019.