Perl Tk应用程序在超过4GB的内存使用量后有时会崩溃。

15
我有一个Perl Tk GUI应用程序,有时会在超过4GB的RAM使用量后崩溃。使用Perl Tk,在某些情况下,我可以超过4GB的RAM使用量,并且在控制台应用程序中运行测试时,我没有超过4GB的问题。
  • 操作系统:Microsoft Windows [Version 10.0.19044.2006]
  • Perl版本:v5.30.3
  • Tk版本:804.036(CPAN上最新可用版本)

Perl几乎每次崩溃都会输出此错误,但是有时候它会崩溃而没有错误:

Free to wrong pool 678ea0 not e228dd0 at .\common\GUI_TESTS\test_memory_hog_gui.pl line 41.

搜索此错误时,我发现所有结果都与多线程有关,而我们的应用程序不使用多线程

我认为这可能是因为我们将某些内容配置为32位而不是64位,因此我按照此问题中的说明进行了操作,并发现一切都已配置为64位

perl -V:ivsize          # ivsize='8';
perl -V:ptrsize         # ptrsize='8';
perl -V:archname        # archname='MSWin32-x64-multi-thread';

下面是一个示例GUI应用程序,当内存超过4GB时崩溃。我从我们的应用程序中精简出来,并且崩溃的行为是相同的。我们使用的数据结构显然要大得多,因此我将我们的简化版本克隆多次以通过4GB阈值。

use strict;
use warnings;

use Tk;
use Tk::LabFrame;

use Clone;

my $MAIN_WINDOW = MainWindow->new;

$MAIN_WINDOW->minsize(400, 400);

my @dataStructureClones = ();
my $textBox;
my $button_frame = $MAIN_WINDOW->LabFrame(-label => "Test", -relief => 'groove', -borderwidth => 2)->pack();

$button_frame->Button(
    -text    => 'Run Crashing Operation',
    -command => sub {

        my $dataStructureThatCrashes = {
            NETLIST_INFO => {
                EXTRA_PROPERTIES => {
                    C_SIGNAL => {},
                    NET      => {},
                },
                NET_LIST => [
                    # omitting this call will allow the program to exceed 4GB until after it finishes the loop
                    { NL_INDEX => 0, }
                ]
            },
        };

        my $lastUpdate = time();

        push @dataStructureClones, $dataStructureThatCrashes;
        for (1 .. 5000000) {
            if (time() - $lastUpdate > 1) {
                # omitting this call will allow the program to exceed 4GB
                $textBox->insert("end", "Cloning hash ($_)...\n");
                $MAIN_WINDOW->update();
                $lastUpdate = time();
            }
            push @dataStructureClones, Clone::clone($dataStructureThatCrashes);
        }
    }
)->grid(-row => 0, -column => 0);

$textBox = $MAIN_WINDOW->Scrolled(
    'Text',
    -relief     => 'groove',
    -background => 'light grey',
    -foreground => 'black',
    -wrap       => 'char',
    -scrollbars => 'osoe',
    -width      => 110,
    -height     => 24,
)->pack(-side => 'top', -fill => 'both', -expand => 1);

MainLoop;

注意事项:

  1. 将第28行注释掉可以使程序正常运行
  2. 将第38行注释掉会使克隆循环完成执行,但在克隆结束大约15秒后会出现类似错误导致程序崩溃。

类似的错误:

Free to wrong pool 1008ea0 not fcedf7a8 at C:/Strawberry/perl/site/lib/Tk.pm line 424.

我在我们拥有的Linux虚拟机(CentOS 7)上尝试过这个程序,问题没有发生。


1
我明白了。你也考虑了这个吗?(https://learn.microsoft.com/en-us/windows/win32/memory/memory-limits-for-windows-releases) - Thingamabobs
1
在我为你的问题设置赏金之前,你能先告诉我你的操作系统是否也安装了64位吗? - Thingamabobs
3
我在Windows 11操作系统上,使用Strawberry Perl版本5.32.1测试了样例程序。我的笔记本电脑有8GB的内存。我打开任务管理器,可以确认该程序的内存占用一直在增加,直到达到4GB时,程序崩溃,并没有打印任何错误信息。 - Håkon Hægland
5
在Ubuntu 22.10上使用默认的 perl + perltk 无法复现该 bug。因此问题可能是由于 windows 上的 perl/perltk 引起的。我不知道 perl 如何工作,但是 perltk 是否可能是32位的,即使 perl 本身是64位的?我认为这可能会导致相同的问题。 - TheLizzard
2
看起来Clone的代码使用I32来表示某些对象的大小。请参见https://metacpan.org/release/GARU/Clone-0.46/source/Clone.xs#L68 如果这是问题的原因,您可以尝试修改代码并提交错误报告。 - stark
显示剩余18条评论
1个回答

3
第一个问题是底层的Tk库具有内部内存分配器,只能处理大约2GB以下的内存分配(由于ABI不当引起的记忆对象大小参数必须为int;修复这一点会很麻烦并且会破坏许多事情)。内部数据结构意味着您不一定会在超过2GB时立即出现问题,但您正在玩火,或者使用硝化甘油可能是更好的比喻。
第二个问题,你目前还没有明确遇到,但很快就会遇到,那就是4GB的文本数据实际上在GUI中很难处理!用户一下子就会被压垮所有细节。
解决方法是利用这么多的数据永远不会一次显示在屏幕上这一事实。相反,您需要滚动数据……但是您可以连接滚动机制以滚动窗口通过数据本身而不是将小部件视图移入数据。当发生滚动事件时,删除窗口的内容并从底层数据源替换它。(这只是一个概述,因为我不知道如何在Perl/Tk中执行此操作……即使我更了解Tcl/Tk也需要查找详细信息。)我已经阅读过人们在可视化非常大的DB查询结果集时这样做,并认为类似的东西对您也很有用。
当然,如果你选择把所有数据放在一个DB中(为了它们的筛选支持),那么你可能会找到更好的查询和更新方式,而不涉及一次查看大量数据...

这是一个社区维基答案,因为我认为它只是一个部分回答,可能更多地是指向完整答案的指针,随意编辑以添加实际代码和/或链接! - Donal Fellows
感谢您的回复。很不幸,第一部分似乎是准确的。 尽管我很感激您在帖子中所做的努力并提供了有价值的信息,但第二部分并不是我们应用程序的实际工作方式。在构建报告(excel/html)并在外部打开之前,我们会进行大量的后台处理。Perl GUI 实际上只是为了让用户配置设置和启动操作。 - Erik Angerstig
@ErikAngerstig 我不是在写 [tag:perl],所以不太确定,但我认为这一行 $textBox->insert("end", "Cloning hash ($_)...\n"); 会将您的数据插入 Text 小部件。不是吗?如果你不需要显示所有这些数据,那么这个问题就相当具有误导性,似乎没有更多的理由在你的 GUI 线程或者进程中执行 后台处理 - Thingamabobs
我曾经用 [tag:python] 和 [tag:tkinter] 做过与 @DonalFellows 在这里提出的类似的事情。--修复方法,我只能在这里概述,就是利用这样一个事实:这么多数据永远不会一次性全部显示在屏幕上。相反,您需要滚动浏览数据...但是您可以将滚动机制连接起来,通过数据本身滚动窗口,而不是将小部件的视图移动到数据中。-- 在一个案例中,我使用了 [tag:generator],在另一种方法中,我使用了内存映射文件对象。 - Thingamabobs
@Thingamabobs 是的,那一行代码是将数据插入到 Text 控件中。如果您运行此程序,您会看到在程序崩溃之前,文本框会接收到 5-15 行(取决于您的机器速度)的打印输出。这些行只是哈希克隆的进度指示器,并且用于说明当有 GUI 事件时,程序在超过内存阈值后崩溃的情况。我认为我不需要更多帮助了,很明显解决方案超出了可能性范围。谢谢你们的所有帮助。 - Erik Angerstig

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接