fpspreadsheet: Use TBufStream as general-purpose stream if woBufStream is set in Worksheet.WritingOptions. woBufStream replaces woSaveMemory. Results in a significant speed enhancement for biff2 (64000x100 cells -> 31 sec without, 1.7 sec with woBufStream).

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3337 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-07-19 13:23:12 +00:00
parent 42f55e22d7
commit 08987b52c8
8 changed files with 105 additions and 39 deletions

View File

@ -65,11 +65,12 @@ begin
{ These are the essential commands to activate virtual mode: } { These are the essential commands to activate virtual mode: }
// workbook.WritingOptions := [woVirtualMode, woSaveMemory]; workbook.WritingOptions := [woVirtualMode, woBufStream];
workbook.WritingOptions := [woVirtualMode]; // workbook.WritingOptions := [woVirtualMode];
{ woSaveMemory can be omitted, but is essential for large files: it causes { woBufStream can be omitted, but is important for large files: it causes
writing temporaray data to a file stream instead of a memory stream. writing temporary data to a buffered file stream instead of a pure
woSaveMemory, however, considerably slows down writing of biff files. } memory stream which can overflow memory. The option can slow down the
writing process a bit. }
{ Next two numbers define the size of virtual spreadsheet. { Next two numbers define the size of virtual spreadsheet.
In case of a database, VirtualRowCount is the RecordCount, VirtualColCount In case of a database, VirtualRowCount is the RecordCount, VirtualColCount

View File

@ -2239,7 +2239,7 @@ procedure TsSpreadOpenDocWriter.CreateStreams;
var var
dir: String; dir: String;
begin begin
if (woSaveMemory in Workbook.WritingOptions) then begin if (woBufStream in Workbook.WritingOptions) then begin
dir := IncludeTrailingPathDelimiter(GetTempDir); dir := IncludeTrailingPathDelimiter(GetTempDir);
FSMeta := TFileStream.Create(GetTempFileName(dir, 'fpsM'), fmCreate+fmOpenRead); FSMeta := TFileStream.Create(GetTempFileName(dir, 'fpsM'), fmCreate+fmOpenRead);
FSSettings := TFileStream.Create(GetTempFileName(dir, 'fpsS'), fmCreate+fmOpenRead); FSSettings := TFileStream.Create(GetTempFileName(dir, 'fpsS'), fmCreate+fmOpenRead);

View File

@ -704,10 +704,9 @@ type
@param woVirtualMode If in virtual mode date are not taken from cells @param woVirtualMode If in virtual mode date are not taken from cells
when a spreadsheet is written to file, but are when a spreadsheet is written to file, but are
provided by means of the event OnNeedCellData. provided by means of the event OnNeedCellData.
@param woSaveMemory When this option is set temporary files are not @param woBufStream When this option is set a buffered stream is used
written to memory streams but to file streams using for writing (a memory stream swapping to disk) }
temporary files. } TsWorkbookWritingOption = (woVirtualMode, woBufStream);
TsWorkbookWritingOption = (woVirtualMode, woSaveMemory);
{@@ {@@
Options considered when writing a workbook } Options considered when writing a workbook }
@ -1076,7 +1075,7 @@ function SameCellBorders(ACell1, ACell2: PCell): Boolean;
implementation implementation
uses uses
Math, StrUtils, TypInfo, fpsUtils, fpsNumFormatParser, fpsFunc; Math, StrUtils, TypInfo, fpsStreams, fpsUtils, fpsNumFormatParser, fpsFunc;
{ Translatable strings } { Translatable strings }
resourcestring resourcestring
@ -5702,13 +5701,16 @@ end;
procedure TsCustomSpreadWriter.WriteToFile(const AFileName: string; procedure TsCustomSpreadWriter.WriteToFile(const AFileName: string;
const AOverwriteExisting: Boolean = False); const AOverwriteExisting: Boolean = False);
var var
OutputFile: TFileStream; OutputFile: TStream;
lMode: Word; lMode: Word;
begin begin
if AOverwriteExisting then lMode := fmCreate or fmOpenWrite if AOverwriteExisting then lMode := fmCreate or fmOpenWrite
else lMode := fmCreate; else lMode := fmCreate;
OutputFile := TFileStream.Create(AFileName, lMode); if (woBufStream in Workbook.WritingOptions) then
OutputFile := TBufStream.Create(AFileName, lMode)
else
OutputFile := TFileStream.Create(AFileName, lMode);
try try
WriteToStream(OutputFile); WriteToStream(OutputFile);
finally finally

View File

@ -6,7 +6,7 @@ uses
SysUtils, Classes; SysUtils, Classes;
const const
DEFAULT_STREAM_BUFFER_SIZE = 1024; // * 1024; DEFAULT_STREAM_BUFFER_SIZE = 1024 * 1024;
type type
{ A buffered stream } { A buffered stream }
@ -18,11 +18,14 @@ type
FBufSize: Int64; FBufSize: Int64;
FKeepTmpFile: Boolean; FKeepTmpFile: Boolean;
FFileName: String; FFileName: String;
FFileMode: Word;
protected protected
procedure CreateFileStream; procedure CreateFileStream;
function GetPosition: Int64; override; function GetPosition: Int64; override;
function GetSize: Int64; override; function GetSize: Int64; override;
public public
constructor Create(AFileName: String; AMode: Word;
ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); overload;
constructor Create(ATempFile: String; AKeepFile: Boolean = false; constructor Create(ATempFile: String; AKeepFile: Boolean = false;
ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); overload; ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); overload;
constructor Create(ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); overload; constructor Create(ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); overload;
@ -46,7 +49,18 @@ begin
AStream.Position := 0; AStream.Position := 0;
end; end;
{@@
Constructor of the TBufStream. Creates a memory stream and prepares everything
to create also a file stream if the streamsize exceeds ABufSize bytes.
@param ATempFile File name for the file stream. If an empty string is
used a temporary file name is created by calling GetTempFileName.
@param AKeepFile If true the stream is flushed to file when the stream is
destroyed. If false the file is deleted when the stream
is destroyed.
@param ABufSize Maximum size of the memory stream before swapping to file
starts. Value is given in bytes.
}
constructor TBufStream.Create(ATempFile: String; AKeepFile: Boolean = false; constructor TBufStream.Create(ATempFile: String; AKeepFile: Boolean = false;
ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE);
begin begin
@ -60,17 +74,45 @@ begin
// The file stream is only created when needed because of possible conflicts // The file stream is only created when needed because of possible conflicts
// of random file names. // of random file names.
FBufSize := ABufSize; FBufSize := ABufSize;
FFileMode := fmCreate + fmOpenRead;
end; end;
{@@
Constructor of the TBufStream. Creates a memory stream and prepares everything
to create also a file stream if the streamsize exceeds ABufSize bytes. The
stream created by this constructor is mainly intended to serve a temporary
purpose, it is not stored permanently to file.
@param ABufSize Maximum size of the memory stream before swapping to file
starts. Value is given in bytes.
}
constructor TBufStream.Create(ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE); constructor TBufStream.Create(ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE);
begin begin
Create('', false, ABufSize); Create('', false, ABufSize);
end; end;
{@@
Constructor of the TBufStream. When swapping to file it will create a file
stream using the given file mode. This kind of BufStream is considered as a
fast replacement of TFileStream.
@param AFileName File name for the file stream. If an empty string is
used a temporary file name is created by calling GetTempFileName.
@param AMode FileMode for the file stream (fmCreate, fmOpenRead etc.)
@param ABufSize Maximum size of the memory stream before swapping to file
starts. Value is given in bytes.
}
constructor TBufStream.Create(AFileName: String; AMode: Word;
ABufSize: Cardinal = DEFAULT_STREAM_BUFFER_SIZE);
begin
Create(AFileName, true, ABufSize);
FFileMode := AMode;
end;
destructor TBufStream.Destroy; destructor TBufStream.Destroy;
begin begin
// Write current buffer content to file // Write current buffer content to file
FlushBuffer; if FKeepTmpFile then FlushBuffer;
// Free streams and delete temporary file, if requested // Free streams and delete temporary file, if requested
FreeAndNil(FMemoryStream); FreeAndNil(FMemoryStream);
@ -87,7 +129,7 @@ procedure TBufStream.CreateFileStream;
begin begin
if FFileStream = nil then begin if FFileStream = nil then begin
if FFileName = '' then FFileName := ChangeFileExt(GetTempFileName, '.~abc'); if FFileName = '' then FFileName := ChangeFileExt(GetTempFileName, '.~abc');
FFileStream := TFileStream.Create(FFileName, fmCreate + fmOpenRead); FFileStream := TFileStream.Create(FFileName, FFileMode);
end; end;
end; end;
@ -113,6 +155,8 @@ begin
Result := FFileStream.Position + FMemoryStream.Position; Result := FFileStream.Position + FMemoryStream.Position;
end; end;
{ Returns the size of the stream. Both memory and file streams are considered
if needed. }
function TBufStream.GetSize: Int64; function TBufStream.GetSize: Int64;
var var
n: Int64; n: Int64;
@ -125,6 +169,15 @@ begin
Result := Max(n, GetPosition); Result := Max(n, GetPosition);
end; end;
{@@
Reads a given number of bytes into a buffer and return the number of bytes
read. If the bytes are not in the memory stream they are read from the file
stream.
@param Buffer Buffer into which the bytes are read. Sufficient space must
have been allocated for Count bytes
@param Count Number of bytes to read from the stream
@return Number of bytes that were read from the stream.}
function TBufStream.Read(var Buffer; Count: Longint): Longint; function TBufStream.Read(var Buffer; Count: Longint): Longint;
begin begin
// Case 1: All "Count" bytes are contained in memory stream // Case 1: All "Count" bytes are contained in memory stream

View File

@ -34,7 +34,7 @@ type
// Set up expected values: // Set up expected values:
procedure SetUp; override; procedure SetUp; override;
procedure TearDown; override; procedure TearDown; override;
procedure TestVirtualMode(AFormat: TsSpreadsheetFormat; SaveMemoryMode: Boolean); procedure TestVirtualMode(AFormat: TsSpreadsheetFormat; ABufStreamMode: Boolean);
published published
// Tests getting Excel style A1 cell locations from row/column based locations. // Tests getting Excel style A1 cell locations from row/column based locations.
@ -59,10 +59,10 @@ type
procedure TestVirtualMode_BIFF8; procedure TestVirtualMode_BIFF8;
procedure TestVirtualMode_OOXML; procedure TestVirtualMode_OOXML;
procedure TestVirtualMode_BIFF2_SaveMemory; procedure TestVirtualMode_BIFF2_BufStream;
procedure TestVirtualMode_BIFF5_SaveMemory; procedure TestVirtualMode_BIFF5_BufStream;
procedure TestVirtualMode_BIFF8_SaveMemory; procedure TestVirtualMode_BIFF8_BufStream;
procedure TestVirtualMode_OOXML_SaveMemory; procedure TestVirtualMode_OOXML_BufStream;
end; end;
implementation implementation
@ -295,7 +295,7 @@ begin
end; end;
procedure TSpreadInternalTests.TestVirtualMode(AFormat: TsSpreadsheetFormat; procedure TSpreadInternalTests.TestVirtualMode(AFormat: TsSpreadsheetFormat;
SaveMemoryMode: Boolean); ABufStreamMode: Boolean);
var var
tempFile: String; tempFile: String;
workbook: TsWorkbook; workbook: TsWorkbook;
@ -308,8 +308,8 @@ begin
try try
worksheet := workbook.AddWorksheet('VirtualMode'); worksheet := workbook.AddWorksheet('VirtualMode');
workbook.WritingOptions := workbook.WritingOptions + [woVirtualMode]; workbook.WritingOptions := workbook.WritingOptions + [woVirtualMode];
if SaveMemoryMode then if ABufStreamMode then
workbook.WritingOptions := workbook.WritingOptions + [woSaveMemory]; workbook.WritingOptions := workbook.WritingOptions + [woBufStream];
workbook.VirtualColCount := 1; workbook.VirtualColCount := 1;
workbook.VirtualRowCount := Length(SollNumbers) + 4; workbook.VirtualRowCount := Length(SollNumbers) + 4;
// We'll use only the first 4 SollStrings, the others cause trouble due to utf8 and formatting. // We'll use only the first 4 SollStrings, the others cause trouble due to utf8 and formatting.
@ -368,22 +368,22 @@ begin
TestVirtualMode(sfOOXML, false); TestVirtualMode(sfOOXML, false);
end; end;
procedure TSpreadInternalTests.TestVirtualMode_BIFF2_SaveMemory; procedure TSpreadInternalTests.TestVirtualMode_BIFF2_BufStream;
begin begin
TestVirtualMode(sfExcel2, True); TestVirtualMode(sfExcel2, True);
end; end;
procedure TSpreadInternalTests.TestVirtualMode_BIFF5_SaveMemory; procedure TSpreadInternalTests.TestVirtualMode_BIFF5_BufStream;
begin begin
TestVirtualMode(sfExcel5, true); TestVirtualMode(sfExcel5, true);
end; end;
procedure TSpreadInternalTests.TestVirtualMode_BIFF8_SaveMemory; procedure TSpreadInternalTests.TestVirtualMode_BIFF8_BufStream;
begin begin
TestVirtualMode(sfExcel8, true); TestVirtualMode(sfExcel8, true);
end; end;
procedure TSpreadInternalTests.TestVirtualMode_OOXML_SaveMemory; procedure TSpreadInternalTests.TestVirtualMode_OOXML_BufStream;
begin begin
TestVirtualMode(sfOOXML, true); TestVirtualMode(sfOOXML, true);
end; end;

View File

@ -219,6 +219,9 @@ var
implementation implementation
uses
fpsStreams;
const const
{ Excel record IDs } { Excel record IDs }
// see: in xlscommon // see: in xlscommon
@ -330,21 +333,25 @@ end;
procedure TsSpreadBIFF5Writer.WriteToFile(const AFileName: string; procedure TsSpreadBIFF5Writer.WriteToFile(const AFileName: string;
const AOverwriteExisting: Boolean); const AOverwriteExisting: Boolean);
var var
MemStream: TMemoryStream; Stream: TStream;
OutputStorage: TOLEStorage; OutputStorage: TOLEStorage;
OLEDocument: TOLEDocument; OLEDocument: TOLEDocument;
begin begin
MemStream := TMemoryStream.Create; if (woBufStream in Workbook.WritingOptions) then begin
Stream := TBufStream.Create
end else
Stream := TMemoryStream.Create;
OutputStorage := TOLEStorage.Create; OutputStorage := TOLEStorage.Create;
try try
WriteToStream(MemStream); WriteToStream(Stream);
// Only one stream is necessary for any number of worksheets // Only one stream is necessary for any number of worksheets
OLEDocument.Stream := MemStream; OLEDocument.Stream := Stream;
OutputStorage.WriteOLEFile(AFileName, OLEDocument, AOverwriteExisting); OutputStorage.WriteOLEFile(AFileName, OLEDocument, AOverwriteExisting);
finally finally
MemStream.Free; Stream.Free;
OutputStorage.Free; OutputStorage.Free;
end; end;
end; end;

View File

@ -365,7 +365,7 @@ var
OutputStorage: TOLEStorage; OutputStorage: TOLEStorage;
OLEDocument: TOLEDocument; OLEDocument: TOLEDocument;
begin begin
if (woSaveMemory in Workbook.WritingOptions) then begin if (woBufStream in Workbook.WritingOptions) then begin
Stream := TBufStream.Create Stream := TBufStream.Create
end else end else
Stream := TMemoryStream.Create; Stream := TMemoryStream.Create;

View File

@ -883,7 +883,7 @@ begin
h0 := Workbook.GetDefaultFontSize; // Point size of default font h0 := Workbook.GetDefaultFontSize; // Point size of default font
// Create the stream // Create the stream
if (woSaveMemory in Workbook.WritingOptions) then if (woBufStream in Workbook.WritingOptions) then
FSSheets[FCurSheetNum] := TBufStream.Create(GetTempFileName('', Format('fpsSH%d', [FCurSheetNum]))) FSSheets[FCurSheetNum] := TBufStream.Create(GetTempFileName('', Format('fpsSH%d', [FCurSheetNum])))
else else
FSSheets[FCurSheetNum] := TMemoryStream.Create; FSSheets[FCurSheetNum] := TMemoryStream.Create;
@ -1013,7 +1013,7 @@ end;
single xlsx file. } single xlsx file. }
procedure TsSpreadOOXMLWriter.CreateStreams; procedure TsSpreadOOXMLWriter.CreateStreams;
begin begin
if (woSaveMemory in Workbook.WritingOptions) then begin if (woBufStream in Workbook.WritingOptions) then begin
FSContentTypes := TBufStream.Create(GetTempFileName('', 'fpsCT')); FSContentTypes := TBufStream.Create(GetTempFileName('', 'fpsCT'));
FSRelsRels := TBufStream.Create(GetTempFileName('', 'fpsRR')); FSRelsRels := TBufStream.Create(GetTempFileName('', 'fpsRR'));
FSWorkbookRels := TBufStream.Create(GetTempFileName('', 'fpsWBR')); FSWorkbookRels := TBufStream.Create(GetTempFileName('', 'fpsWBR'));
@ -1106,14 +1106,17 @@ end;
procedure TsSpreadOOXMLWriter.WriteToFile(const AFileName: string; procedure TsSpreadOOXMLWriter.WriteToFile(const AFileName: string;
const AOverwriteExisting: Boolean); const AOverwriteExisting: Boolean);
var var
lStream: TFileStream; lStream: TStream;
lMode: word; lMode: word;
begin begin
if AOverwriteExisting if AOverwriteExisting
then lMode := fmCreate or fmOpenWrite then lMode := fmCreate or fmOpenWrite
else lMode := fmCreate; else lMode := fmCreate;
lStream:=TFileStream.Create(AFileName, lMode); if (woBufStream in Workbook.WritingOptions) then
lStream := TBufStream.Create(AFileName, lMode)
else
lStream := TFileStream.Create(AFileName, lMode);
try try
WriteToStream(lStream); WriteToStream(lStream);
finally finally
@ -1139,7 +1142,7 @@ begin
WriteGlobalFiles; WriteGlobalFiles;
WriteContent; WriteContent;
// Stream position must be at beginning, it was moved to end during adding of xml strings. // Stream positions must be at beginning, they were moved to end during adding of xml strings.
ResetStreams; ResetStreams;
{ Now compress the files } { Now compress the files }