【发布时间】:2018-04-05 07:52:46
【问题描述】:
我有一个系统,它加载一些压缩到“.log”文件中的文本文件,然后使用多个线程解析成信息类,每个线程处理不同的文件并将解析的对象添加到列表中。 该文件是使用 TStringList 加载的,因为它是我测试过的最快的方法。
文本文件的数量是可变的,但通常我必须在一次入侵中处理 5 到 8 个文件,范围从 50Mb 到 120Mb。
我的问题:用户可以根据需要多次加载 .log 文件,并且在尝试使用 TStringList.LoadFromFile 时,在其中一些进程之后,我收到了 EOutOfMemory 异常。当然,任何使用过 StringList 的人首先想到的是在处理大文本文件时不应该使用它,但是这个异常是随机发生的,并且在进程已经成功完成至少一次之后(对象在新的解析开始之前被销毁,因此除了一些轻微的泄漏之外,内存被正确检索)
我尝试过使用 Textile 和 TStreamReader,但它不如 TStringList 快,并且该过程的持续时间是此功能最关心的问题。
我使用的是 10.1 Berlin,解析过程是一个简单的迭代,通过不同长度的线条列表和基于线条信息的对象构造。
基本上,我的问题是,这是什么原因造成的,我该如何解决。我可能会使用其他方式来加载文件并读取其内容,但它必须与 TStringList 方法一样快(或更好)。
加载线程执行代码:
TThreadFactory= class(TThread)
protected
// Class that holds the list of Commands already parsed, is owned outside of the thread
_logFile: TLogFile;
_criticalSection: TCriticalSection;
_error: string;
procedure Execute; override;
destructor Destroy; override;
public
constructor Create(AFile: TLogFile; ASection: TCriticalSection); overload;
property Error: string read _error;
end;
implementation
{ TThreadFactory}
constructor TThreadFactory.Create(AFile: TLogFile; ASection: TCriticalSection);
begin
inherited Create(True);
_logFile := AFile;
_criticalSection := ASection;
end;
procedure TThreadFactory.Execute;
var
tmpLogFile: TStringList;
tmpConvertedList: TList<TLogCommand>;
tmpCommand: TLogCommand;
tmpLine: string;
i: Integer;
begin
try
try
tmpConvertedList:= TList<TLogCommand>.Create;
if (_path <> '') and not(Terminated) then
begin
try
logFile:= TStringList.Create;
logFile.LoadFromFile(tmpCaminho);
for tmpLine in logFile do
begin
if Terminated then
Break;
if (tmpLine <> '') then
begin
// the logic here was simplified that's just that
tmpConvertedList.Add(TLogCommand.Create(tmpLine));
end;
end;
finally
logFile.Free;
end;
end;
_cricticalSection.Acquire;
_logFile.AddCommands(tmpConvertedList);
finally
_cricticalSection.Release;
FreeAndNil(tmpConvertedList);
end;
Except
on e: Exception do
_error := e.Message;
end;
end;
end.
补充:感谢您的所有反馈。我将解决一些已经讨论过但我在最初的问题中没有提及的问题。
.log 文件内部有多个 .txt 文件实例,但也可以有多个 .log 文件,每个文件代表一天的日志记录或用户选择的时间段,因为解压需要很多时间每次找到 .txt 时都会启动一个线程,这样我就可以立即开始解析,这缩短了用户的明显等待时间
ReportMemoryLeaksOnShutdown 和其他方法(如 TStreamReader)不显示“轻微泄漏”,从而避免了此问题
命令列表由 TLogFile 保存。任何时候都只有一个此类的实例,并且在用户想要加载 .log 文件时被销毁。 所有线程都向同一个对象添加命令,这就是临界区的原因。
无法详细说明解析过程,因为它会泄露一些敏感信息,但这是从字符串和 TCommand 中收集的简单信息
从一开始我就知道碎片,但我从未找到具体证据证明 TStringList 仅通过多次加载才会导致碎片,如果可以确认这一点,我将非常高兴
感谢您的关注。我最终使用了一个外部库,它能够以与TStringList 相同的速度读取行和加载文件,而无需将整个文件加载到内存中
【问题讨论】:
-
解决方案很明显。不要将整个文件读入内存。
-
我怀疑文件本身不是问题,而是该文件做了什么......特别是“将 tmpConvertedList 插入命令列表”但是没有足够的信息可以确定。上面的代码没有提供正确的minimal reproducible example
-
另外,虽然有人声称对象被正确销毁,但我们无法确认......并且“除了一些轻微的泄漏”会导致内存碎片并阻止对大量数据的进一步处理。
-
TStreamReader性能不是很好,IIRC。使用更好的行阅读器实现。令人失望的是,您在帖子中没有提到任何这些。闻起来很像碎片。但是,如果您的上级要求您继续使用TSrtingList,我认为您可能需要花一些时间处理您的简历。 -
抛开你的问题,我建议你回顾一下 try-finally 的正确使用...
标签: delphi text-files delphi-10.1-berlin tstringlist