如何使用File :: Map正确写入文件?

Tho*_*ing 8 perl memory-mapped-files

我经常使用File :: Map将特别小的文本文件映射到内存中,例如在那些上处理一些只读的正则表达式.现在我有一个用例,我需要在文件中替换一些文本并认为我仍然可以使用File::Map,因为它记录了以下内容:

文件被映射到一个可以像任何其他变量一样被读取的变量,并且可以使用标准的Perl技术(例如regexps和substr)来编写它.

虽然我有兴趣替换的数据在文件中被正确替换,但我丢失了数据,因为文件保持原始大小,最后数据被截断.新数据比旧数据略大.使用以下句子记录两件事情:

不建议直接写入内存映射文件

将新值截断为内存映射的大小

对两个警告的解释都不应该写任何东西File::Map,但它可能适用于一个人可以使用截断文件或整个文件大小根本没有改变的情况.但是第一个引用明确提到写入是支持的,没有任何例外.

那么,是否有一些特殊的方法可以安全地使用File::Map,例如增加底层文件等等?第一个警告使用了措辞directly,我觉得还有一些其他更好的支持写作方式?

我只是=~ s///在目前的映射视图上使用,这似乎是错误的方法.我甚至找不到任何人试图写File::Map任何东西,只有正式的测试完全按照我的方式进行,并期待我得到的警告.另外,看一下代码,似乎只有一个用例,其中写入根本不会产生警告,但我不明白我是如何触发的:

static int mmap_write(pTHX_ SV* var, MAGIC* magic) {
        struct mmap_info* info = (struct mmap_info*) magic->mg_ptr;
        if (!SvOK(var))
                mmap_fixup(aTHX_ var, info, NULL, 0);
        else if (!SvPOK(var)) {
                STRLEN len;
                const char* string = SvPV(var, len);
                mmap_fixup(aTHX_ var, info, string, len);
        }
        else if (SvPVX(var) != info->fake_address)
                mmap_fixup(aTHX_ var, info, SvPVX(var), SvCUR(var));
        else
                SvPOK_only_UTF8(var);
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

https://metacpan.org/source/LEONT/File-Map-0.55/lib/File/Map.xs#L240

毕竟,如果应该完全避免写作,为什么文档明确提到支持?如果它在所有情况下至少在一个警告中产生,那么对我来说看起来不受支持.

ike*_*ami 8

mmap是文件的一部分到内存的固定大小的映射.

各种映射函数将提供的标量的字符串缓冲区设置为映射的内存页面.如果请求,操作系统会将该缓冲区的任何更改反映到文件中,反之亦然.

使用mmap的正确方法是修改字符串缓冲区,而不是替换它.

  • 任何改变字符串缓冲区而不改变其大小的东西都是合适的.

    $ perl -e'print "\0"x16' >scratch
    
    $ perl -MFile::Map=map_file -we'
       map_file my $map, "scratch", "+<";
       $map =~ s/\x00/\xFF/g;             # ok
       substr($map, 6, 2, "00");          # ok
       substr($map, 8, 2) = "11";         # ok
       substr($map, 7, 2) =~ s/../22/;    # ok
    '
    
    $ hexdump -C scratch
    00000000  ff ff ff ff ff ff 30 32  32 31 ff ff ff ff ff ff  |......0221......|
    00000010
    
    Run Code Online (Sandbox Code Playgroud)
  • 任何替换字符串缓冲区的东西(例如分配给标量)都不行.

    ...有点儿.模块注意到你已经替换了标量的缓冲区.它继续将新缓冲区的内容复制到映射内存,然后用指向映射内存的指针替换标量缓冲区.

    $ perl -e'print "\0"x16' >scratch
    
    $ perl -MFile::Map=map_file -we'
       map_file my $map, "scratch", "+<";
       $map = "4" x 16;  # Effectively: substr($map, 0, 16, "4" x 16)
    '
    Writing directly to a memory mapped file is not recommended at -e line 3.
    
    $ hexdump -C scratch
    00000000  34 34 34 34 34 34 34 34  34 34 34 34 34 34 34 34  |4444444444444444|
    00000010
    
    Run Code Online (Sandbox Code Playgroud)

    除了警告可以使用静音no warnings qw( substr );,[1]唯一的缺点是这样做需要使用memcpy复制length($map)字节,而使用substr($map, $pos, length($repl), $repl)只需要复制length($repl)字节.

  • 任何改变字符串缓冲区大小的东西都不行.

    $ perl -MFile::Map=map_file -we'
       map_file my $map, "scratch", "+<";
       $map = "5" x 32;  # Effectively: substr($map, 0, 16, "5" x 16)
    '
    Writing directly to a memory mapped file is not recommended at -e line 3.
    Truncating new value to size of the memory map at -e line 3.
    
    $ hexdump -C scratch
    00000000  35 35 35 35 35 35 35 35  35 35 35 35 35 35 35 35  |5555555555555555|
    00000010
    
    Run Code Online (Sandbox Code Playgroud)

警告:如果缩小缓冲区,模块不会发出警告,即使除了使用NUL破坏其中一个字节之外没有任何效果.

$ perl -e'print "\0"x16' >scratch

$ perl -MFile::Map=map_file -we'
   map_file my $map, "scratch", "+<";
   substr($map, 0, 16, "6" x 16);
   substr($map, 14, 2, "");
'

$ hexdump -C scratch
00000000  36 36 36 36 36 36 36 36  36 36 36 36 36 36 00 36  |66666666666666.6|
00000010
Run Code Online (Sandbox Code Playgroud)

我已经提交了一张票.


  1. 这有点讽刺,因为它在不使用时会或多或少地发出警告substr,但我想它在使用substr"错误" 时也会发出警告.


mel*_*ene 6

第一次报价,

文件被映射到一个可以像任何其他变量一样读取的变量,并且可以使用标准的Perl技术(如regexps和)来编写substr.

在"简单"标题下.

确实如此:您可以简单地编写操纵字符串的Perl代码,数据将最终存储在文件中.

但是,在警告部分我们有:

不建议直接写入内存映射文件

由于perl在内部工作的方式,不可能编写允许直接分配但表现良好的映射实现.作为妥协,如果你这样做,File :: Map能够修复这些混乱,但它会警告你,你正在做一些你不应该做的事情.此警告仅use warnings 'substr'在有效时给出.

也就是说,写入mmap'd变量是没有效率的,除非可以在适当的位置完成字符串缓冲区的修改(字符串必须先组装并存储在内存中,然后才复制到文件中).如果您对此感到满意,可以使用以下命令禁用警告no warnings 'substr'.

另外,看一下代码,似乎只有一个用例,其中写入根本不会产生警告,但我不明白我是如何触发它的.

这就是你试图为自己写一个缓冲区的情况.当标量实际被修改到位时会发生这种情况.其他情况是更换字符串缓冲区时的解决方法(例如,因为它被覆盖:) $foo = $bar.对于真正的就地修改,不需要额外的工作,您也不会收到警告.

但是这对你没有帮助,因为增长一个字符串不能用固定大小的映射缓冲区就地完成.

无法更改文件的大小.这不是因为File :: Map,而是因为底层mmap系统调用适用于固定大小的映射,并且不提供任何自动调整文件大小的选项.

如果你需要编辑文件(特别是小文件),我建议改用editPath :: Tiny.

  • 重新"*我不知道如何触发它(可能是自我赋值,`$ foo = $ foo`?)*",Perl字符串是可变的.修改字符串缓冲区而不是替换字符串时会采用该路径,例如使用`substr($ s,2,1,'!')`时.这是修改映射内存的正确方法. (2认同)